Fossil

Changes On Branch no-joy
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch no-joy Excluding Merge-Ins

This is equivalent to a diff from 421d6570 to 7c3cf7a1

2020-12-27
06:48
chat: next round of Safari-friendly baby steps, developed in conjunction with Safari user mgagnon via chat session. ... (check-in: a1161fa9 user: stephan tags: trunk)
04:50
chat: reintegrated partial changes from [b0ab6cbd3] and [670732a6]: remove skin-induced div.content margins in chat-only mode and cap image preview size to avoid screen overflow. Moved #dbgMessage element out of the way at app startup to avoid it potentially influencing our flexbox layout, and include that element in the to-hide list for chat-only mode. Edit: Martin G. reports that this one also causes Grief with Safari, but in different ways that before. ... (Closed-Leaf check-in: 7c3cf7a1 user: stephan tags: no-joy)
04:30
chat: re-integrated JS-based div.content resizer to do approximately what the preferred 'vh' CSS units would, but upon which Safari apparently chokes. Message area now gets a scrollbar. This works reasonably well on FF/Chrome on both Linux and Android. The jury is still out on Safari. Edit: Martin G. confirmed this one also suffers from the "collapsing messages" problem on Safari. ... (check-in: d488f5c6 user: stephan tags: no-joy)
03:39
Eliminated top-down chat mode altogether in an attempt to eliminate some complexity and cruft. Re-added the toast-on-new-invisible-message from [0a00a103]. ... (check-in: 421d6570 user: stephan tags: trunk)
2020-12-26
22:09
Disabled automatic scrolling when a new chat message arrives, as it is unnecessary when the user input fields are not sticky. To revisit later with sticky input fields. ... (check-in: b75ce865 user: stephan tags: trunk)

Changes to src/chat.js.

1
2
3
4
5
6
7
8
9
10
11










12
13
14
15
16
17
18
19
20

























21
22
23
24
25
26
27
/**
   This file contains the client-side implementation of fossil's /chat
   application. 
*/
(function(){
  const F = window.fossil, D = F.dom;
  const E1 = function(selector){
    const e = document.querySelector(selector);
    if(!e) throw new Error("missing required DOM element: "+selector);
    return e;
  };










  const isInViewport = function(e) {
    const rect = e.getBoundingClientRect();
    return (
      rect.top >= 0 &&
      rect.left >= 0 &&
      rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
      rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
  };

























  const Chat = (function(){
    const cs = {
      e:{/*map of certain DOM elements.*/
        messageInjectPoint: E1('#message-inject-point'),
        pageTitle: E1('head title'),
        loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
        inputWrapper: E1("#chat-input-area"),











>
>
>
>
>
>
>
>
>
>









>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
   This file contains the client-side implementation of fossil's /chat
   application. 
*/
(function(){
  const F = window.fossil, D = F.dom;
  const E1 = function(selector){
    const e = document.querySelector(selector);
    if(!e) throw new Error("missing required DOM element: "+selector);
    return e;
  };

  (function(){
    let dbg = document.querySelector('#debugMsg');
    if(dbg){
      /* This can inadvertently influence our flexbox layouts, so move
         it out of the way. */
      D.append(document.body,dbg);
    }
  })();

  const isInViewport = function(e) {
    const rect = e.getBoundingClientRect();
    return (
      rect.top >= 0 &&
      rect.left >= 0 &&
      rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
      rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
  };

  const ForceResizeKludge = 0 ? function(){} : (function(){
    /* Workaround for Safari mayhem regarding use of vh CSS units....
       We tried to use vh units to set the content area size for the
       chat layout, but Safari chokes on that, so we calculate that
       height here: 85% when in "normal" mode and 95% in chat-only
       mode. Larger than ~95% is too big for Firefox on Android,
       causing the input area to move off-screen. */
    const contentArea = E1('div.content'),
          bcl = document.body.classList;
    const resized = function(){
      const wh = window.innerHeight,
            mult = bcl.contains('chat-only-mode') ? 0.95 : 0.85;
      contentArea.style.maxHeight = (wh * mult)+"px";
      //console.debug("resized.",wh, mult, window.getComputedStyle(contentArea).maxHeight);
    };
    var doit;
    window.addEventListener('resize',function(ev){
      clearTimeout(doit);
      doit = setTimeout(resized, 100);
    }, false);
    resized();
    return resized;
  })();

  const Chat = (function(){
    const cs = {
      e:{/*map of certain DOM elements.*/
        messageInjectPoint: E1('#message-inject-point'),
        pageTitle: E1('head title'),
        loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
        inputWrapper: E1("#chat-input-area"),
118
119
120
121
122
123
124
125

126

127


128
129
130
131
132
133
134
135
136
137
138
139
140


141
142
143
144
145
146
147
148
149
150
151
152
153
154

155


156
157
158
159
160
161
162
163
164
165
166
167
168
169

170
171
172
173
174
175
176
           to avoid various race conditions in the UI and long-running
           network requests. */
      ],
      /* Injects element e as a new row in the chat, at the top of the
         list if atEnd is falsy, else at the end of the list, before
         the load-history widget. */
      injectMessageElem: function f(e, atEnd){
        const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,

              holder = this.e.messagesWrapper;

        if(atEnd){


          const fe = mip.nextElementSibling;
          if(fe) mip.parentNode.insertBefore(e, fe);
          else D.append(mip.parentNode, e);
        }else{
          D.append(holder,e);
        }
        if(!atEnd && !this.isMassLoading
           && e.dataset.xfrom!==Chat.me && !isInViewport(e)){
          /* If a new non-history message arrives while the user is
             scrolled elsewhere, do not scroll to the latest
             message, but gently alert the user that a new message
             has arrived. */
          F.toast.message("New message has arrived.");


        }
      },
      /** Returns true if chat-only mode is enabled. */
      isChatOnlyMode: ()=>document.body.classList.contains('chat-only-mode'),
      /**
         Enters (if passed a truthy value or no arguments) or leaves
         "chat-only" mode. That mode hides the page's header and
         footer, leaving only the chat application visible to the
         user.
      */
      chatOnlyMode: function f(yes){
        if(undefined === f.elemsToToggle){
          f.elemsToToggle = [];
          document.querySelectorAll(

            "body > div.header, body > div.mainmenu, body > div.footer"


          ).forEach((e)=>f.elemsToToggle.push(e));
        }
        if(!arguments.length) yes = true;
        if(yes === this.isChatOnlyMode()) return this;
        if(yes){
          D.addClass(f.elemsToToggle, 'hidden');
          D.addClass(document.body, 'chat-only-mode');
          document.body.scroll(0,document.body.height);
        }else{
          D.removeClass(f.elemsToToggle, 'hidden');
          D.removeClass(document.body, 'chat-only-mode');
        }
        const msg = document.querySelector('.message-widget');
        if(msg) setTimeout(()=>msg.scrollIntoView(),0);

        return this;
      },
      toggleChatOnlyMode: function(){
        return this.chatOnlyMode(!this.isChatOnlyMode());
      },
      settings:{
        get: (k,dflt)=>F.storage.get(k,dflt),







|
>
|
>

>
>



<
<








>
>














>
|
>
>














>







153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169


170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
           to avoid various race conditions in the UI and long-running
           network requests. */
      ],
      /* Injects element e as a new row in the chat, at the top of the
         list if atEnd is falsy, else at the end of the list, before
         the load-history widget. */
      injectMessageElem: function f(e, atEnd){
        const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint;
        /* Reminder: this placement is kinda odd because of the
           flex-direction:column-reverse in this.e.messagesWrapper,
           which reverses our directions. */
        if(atEnd){
          mip.parentNode.insertBefore(e, mip);
        }else{
          const fe = mip.nextElementSibling;
          if(fe) mip.parentNode.insertBefore(e, fe);
          else D.append(mip.parentNode, e);


        }
        if(!atEnd && !this.isMassLoading
           && e.dataset.xfrom!==Chat.me && !isInViewport(e)){
          /* If a new non-history message arrives while the user is
             scrolled elsewhere, do not scroll to the latest
             message, but gently alert the user that a new message
             has arrived. */
          F.toast.message("New message has arrived.");
        }else if(e.dataset.xfrom===Chat.me){
          e.scrollIntoView();
        }
      },
      /** Returns true if chat-only mode is enabled. */
      isChatOnlyMode: ()=>document.body.classList.contains('chat-only-mode'),
      /**
         Enters (if passed a truthy value or no arguments) or leaves
         "chat-only" mode. That mode hides the page's header and
         footer, leaving only the chat application visible to the
         user.
      */
      chatOnlyMode: function f(yes){
        if(undefined === f.elemsToToggle){
          f.elemsToToggle = [];
          document.querySelectorAll(
            ["body > div.header",
             "body > div.mainmenu",
             "body > div.footer",
             "#debugMsg"].join(',')
          ).forEach((e)=>f.elemsToToggle.push(e));
        }
        if(!arguments.length) yes = true;
        if(yes === this.isChatOnlyMode()) return this;
        if(yes){
          D.addClass(f.elemsToToggle, 'hidden');
          D.addClass(document.body, 'chat-only-mode');
          document.body.scroll(0,document.body.height);
        }else{
          D.removeClass(f.elemsToToggle, 'hidden');
          D.removeClass(document.body, 'chat-only-mode');
        }
        const msg = document.querySelector('.message-widget');
        if(msg) setTimeout(()=>msg.scrollIntoView(),0);
        ForceResizeKludge();
        return this;
      },
      toggleChatOnlyMode: function(){
        return this.chatOnlyMode(!this.isChatOnlyMode());
      },
      settings:{
        get: (k,dflt)=>F.storage.get(k,dflt),

Changes to src/default.css.

1634
1635
1636
1637
1638
1639
1640




1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654








1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
  flex: 1 1 auto/*eliminates dead no-click zones on the right*/;
}
body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
  cursor: inherit;
}
/** Container for the list of /chat messages. */
body.chat #chat-messages-wrapper {




}
body.chat div.content {
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column-reverse;
  /* ^^^^ In order to get good automatic scrolling of new messages on
     the BOTTOM in bottom-up chat mode, such that they scroll up
     instead of down, we have to use column-reverse layout, which
     changes #chat-messages-wrapper's "gravity" for purposes of
     scrolling! If we instead use flex-direction:column then each new
     message pushes #chat-input-area down further off the screen!
 */
  align-items: stretch;








}
/* Wrapper for /chat user input controls */
body.chat #chat-input-area {
  display: flex;
  flex-direction: column;
  padding: 0.5em 1em;
  border-bottom: none;
  border-top: 1px solid black;
  margin-bottom: 0;
  margin-top: 0.5em;
  position: initial /*sticky currently disabled due to scrolling-related issues*/;
  bottom: 0;
}
/* Widget holding the chat message input field, send button, and
   settings button. */
body.chat #chat-input-line {
  display: flex;
  flex-direction: row;
  margin-bottom: 0.25em;







>
>
>
>














>
>
>
>
>
>
>
>










<
<







1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676


1677
1678
1679
1680
1681
1682
1683
  flex: 1 1 auto/*eliminates dead no-click zones on the right*/;
}
body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
  cursor: inherit;
}
/** Container for the list of /chat messages. */
body.chat #chat-messages-wrapper {
  overflow: auto;
  max-height: 200em/*we *really* want approx. 85vh*/;
  display: flex;
  flex-direction: column-reverse/*necessary for scrolling gravity!*/;
}
body.chat div.content {
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column-reverse;
  /* ^^^^ In order to get good automatic scrolling of new messages on
     the BOTTOM in bottom-up chat mode, such that they scroll up
     instead of down, we have to use column-reverse layout, which
     changes #chat-messages-wrapper's "gravity" for purposes of
     scrolling! If we instead use flex-direction:column then each new
     message pushes #chat-input-area down further off the screen!
 */
  align-items: stretch;
}
body.chat.chat-only-mode div.content {
  /*max-height: 95vh*//*larger than approx. this is too big for Firefox on Android*/;
  /* Some skins set margins and a max-width on div.content, but we
     needn't(?) honor those in chat-only mode. */
  margin: 0;
  width: 100%;
  max-width: 100%;
}
/* Wrapper for /chat user input controls */
body.chat #chat-input-area {
  display: flex;
  flex-direction: column;
  padding: 0.5em 1em;
  border-bottom: none;
  border-top: 1px solid black;
  margin-bottom: 0;
  margin-top: 0.5em;


}
/* Widget holding the chat message input field, send button, and
   settings button. */
body.chat #chat-input-line {
  display: flex;
  flex-direction: row;
  margin-bottom: 0.25em;
1714
1715
1716
1717
1718
1719
1720
1721




/* Widget holding the details of a selected/dropped file/image. */
body.chat #chat-drop-details {
  flex: 0 1 auto;
  padding: 0.5em 1em;
  margin-left: 0.5em;
  white-space: pre;
  font-family: monospace;
}












>
>
>
>
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
/* Widget holding the details of a selected/dropped file/image. */
body.chat #chat-drop-details {
  flex: 0 1 auto;
  padding: 0.5em 1em;
  margin-left: 0.5em;
  white-space: pre;
  font-family: monospace;
}
body.chat #chat-drop-details img {
  max-width: 45%;
  max-height: 45%;
}