Fossil SCM

chat: next round of Safari-friendly baby steps, developed in conjunction with Safari user mgagnon via chat session.

stephan 2020-12-27 06:48 trunk
Commit a1161fa9bd6587f81e30dd4f0b0170eb83396f8519550e5a2367230b4b62efa3
2 files changed +49 -2 +3
+49 -2
--- src/chat.js
+++ src/chat.js
@@ -16,10 +16,35 @@
1616
rect.left >= 0 &&
1717
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
1818
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
1919
);
2020
};
21
+
22
+ const ForceResizeKludge = 0 ? function(){} : (function(){
23
+ /* Workaround for Safari mayhem regarding use of vh CSS units....
24
+ We tried to use vh units to set the content area size for the
25
+ chat layout, but Safari chokes on that, so we calculate that
26
+ height here: 85% when in "normal" mode and 95% in chat-only
27
+ mode. Larger than ~95% is too big for Firefox on Android,
28
+ causing the input area to move off-screen. */
29
+ const contentArea = E1('div.content'),
30
+ bcl = document.body.classList;
31
+ const resized = function(){
32
+ const wh = window.innerHeight,
33
+ mult = bcl.contains('chat-only-mode') ? 0.95 : 0.85;
34
+ contentArea.style.height = contentArea.style.maxHeight = (wh * mult)+"px";
35
+ //console.debug("resized.",wh, mult, window.getComputedStyle(contentArea).maxHeight);
36
+ };
37
+ var doit;
38
+ window.addEventListener('resize',function(ev){
39
+ clearTimeout(doit);
40
+ doit = setTimeout(resized, 100);
41
+ }, false);
42
+ resized();
43
+ return resized;
44
+ })();
45
+
2146
const Chat = (function(){
2247
const cs = {
2348
e:{/*map of certain DOM elements.*/
2449
messageInjectPoint: E1('#message-inject-point'),
2550
pageTitle: E1('head title'),
@@ -128,18 +153,21 @@
128153
const fe = mip.nextElementSibling;
129154
if(fe) mip.parentNode.insertBefore(e, fe);
130155
else D.append(mip.parentNode, e);
131156
}else{
132157
D.append(holder,e);
158
+ Chat.newestMessageElem = e;
133159
}
134160
if(!atEnd && !this.isMassLoading
135161
&& e.dataset.xfrom!==Chat.me && !isInViewport(e)){
136162
/* If a new non-history message arrives while the user is
137163
scrolled elsewhere, do not scroll to the latest
138164
message, but gently alert the user that a new message
139165
has arrived. */
140166
F.toast.message("New message has arrived.");
167
+ }else if(e.dataset.xfrom===Chat.me){
168
+ e.scrollIntoView();
141169
}
142170
},
143171
/** Returns true if chat-only mode is enabled. */
144172
isChatOnlyMode: ()=>document.body.classList.contains('chat-only-mode'),
145173
/**
@@ -165,10 +193,11 @@
165193
D.removeClass(f.elemsToToggle, 'hidden');
166194
D.removeClass(document.body, 'chat-only-mode');
167195
}
168196
const msg = document.querySelector('.message-widget');
169197
if(msg) setTimeout(()=>msg.scrollIntoView(),0);
198
+ ForceResizeKludge();
170199
return this;
171200
},
172201
toggleChatOnlyMode: function(){
173202
return this.chatOnlyMode(!this.isChatOnlyMode());
174203
},
@@ -229,10 +258,18 @@
229258
};
230259
231260
cs.getMessageElemById = function(id){
232261
return qs('[data-msgid="'+id+'"]');
233262
};
263
+
264
+ /** Finds the last .message-widget element and returns it or
265
+ the undefined value if none are found. */
266
+ cs.fetchLastMessageElem = function(){
267
+ const msgs = document.querySelectorAll('.message-widget');
268
+ return msgs.length ? msgs[msgs.length-1] : undefined;
269
+ };
270
+
234271
/**
235272
LOCALLY deletes a message element by the message ID or passing
236273
the .message-row element. Returns true if it removes an element,
237274
else false.
238275
*/
@@ -244,10 +281,13 @@
244281
}else{
245282
e = this.getMessageElemById(id);
246283
}
247284
if(e && id){
248285
D.remove(e);
286
+ if(e===this.newestMessageElem){
287
+ Chat.newestMessageElem = Chat.fetchLastMessageElem();
288
+ }
249289
F.toast.message("Deleted message "+id+".");
250290
}
251291
return !!e;
252292
};
253293
@@ -819,19 +859,26 @@
819859
.then(y=>newcontent(y))
820860
.catch(e=>console.error(e))
821861
/* ^^^ we don't use Chat.reportError(e) here b/c the polling
822862
fails exepectedly when it times out, but is then immediately
823863
resumed, and reportError() produces a loud error message. */
824
- .finally(function(x){
864
+ .finally(function(){
825865
if(isFirstCall){
826866
Chat.isMassLoading = false;
827867
Chat.ajaxEnd();
828
- Chat.e.inputWrapper.scrollIntoView();
868
+ setTimeout(function(){
869
+ const m = Chat.newestMessageElem;
870
+ if(m){
871
+ m.scrollIntoView();
872
+ //console.debug("Scrolling into view...",msgs[msgs.length-1]);
873
+ }
874
+ Chat.e.inputWrapper.scrollIntoView()
875
+ }, 0);
829876
}
830877
poll.running=false;
831878
});
832879
}
833880
poll.running = false;
834881
poll(true);
835882
setInterval(poll, 1000);
836883
F.page.chat = Chat/* enables testing the APIs via the dev tools */;
837884
})();
838885
--- src/chat.js
+++ src/chat.js
@@ -16,10 +16,35 @@
16 rect.left >= 0 &&
17 rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
18 rect.right <= (window.innerWidth || document.documentElement.clientWidth)
19 );
20 };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21 const Chat = (function(){
22 const cs = {
23 e:{/*map of certain DOM elements.*/
24 messageInjectPoint: E1('#message-inject-point'),
25 pageTitle: E1('head title'),
@@ -128,18 +153,21 @@
128 const fe = mip.nextElementSibling;
129 if(fe) mip.parentNode.insertBefore(e, fe);
130 else D.append(mip.parentNode, e);
131 }else{
132 D.append(holder,e);
 
133 }
134 if(!atEnd && !this.isMassLoading
135 && e.dataset.xfrom!==Chat.me && !isInViewport(e)){
136 /* If a new non-history message arrives while the user is
137 scrolled elsewhere, do not scroll to the latest
138 message, but gently alert the user that a new message
139 has arrived. */
140 F.toast.message("New message has arrived.");
 
 
141 }
142 },
143 /** Returns true if chat-only mode is enabled. */
144 isChatOnlyMode: ()=>document.body.classList.contains('chat-only-mode'),
145 /**
@@ -165,10 +193,11 @@
165 D.removeClass(f.elemsToToggle, 'hidden');
166 D.removeClass(document.body, 'chat-only-mode');
167 }
168 const msg = document.querySelector('.message-widget');
169 if(msg) setTimeout(()=>msg.scrollIntoView(),0);
 
170 return this;
171 },
172 toggleChatOnlyMode: function(){
173 return this.chatOnlyMode(!this.isChatOnlyMode());
174 },
@@ -229,10 +258,18 @@
229 };
230
231 cs.getMessageElemById = function(id){
232 return qs('[data-msgid="'+id+'"]');
233 };
 
 
 
 
 
 
 
 
234 /**
235 LOCALLY deletes a message element by the message ID or passing
236 the .message-row element. Returns true if it removes an element,
237 else false.
238 */
@@ -244,10 +281,13 @@
244 }else{
245 e = this.getMessageElemById(id);
246 }
247 if(e && id){
248 D.remove(e);
 
 
 
249 F.toast.message("Deleted message "+id+".");
250 }
251 return !!e;
252 };
253
@@ -819,19 +859,26 @@
819 .then(y=>newcontent(y))
820 .catch(e=>console.error(e))
821 /* ^^^ we don't use Chat.reportError(e) here b/c the polling
822 fails exepectedly when it times out, but is then immediately
823 resumed, and reportError() produces a loud error message. */
824 .finally(function(x){
825 if(isFirstCall){
826 Chat.isMassLoading = false;
827 Chat.ajaxEnd();
828 Chat.e.inputWrapper.scrollIntoView();
 
 
 
 
 
 
 
829 }
830 poll.running=false;
831 });
832 }
833 poll.running = false;
834 poll(true);
835 setInterval(poll, 1000);
836 F.page.chat = Chat/* enables testing the APIs via the dev tools */;
837 })();
838
--- src/chat.js
+++ src/chat.js
@@ -16,10 +16,35 @@
16 rect.left >= 0 &&
17 rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
18 rect.right <= (window.innerWidth || document.documentElement.clientWidth)
19 );
20 };
21
22 const ForceResizeKludge = 0 ? function(){} : (function(){
23 /* Workaround for Safari mayhem regarding use of vh CSS units....
24 We tried to use vh units to set the content area size for the
25 chat layout, but Safari chokes on that, so we calculate that
26 height here: 85% when in "normal" mode and 95% in chat-only
27 mode. Larger than ~95% is too big for Firefox on Android,
28 causing the input area to move off-screen. */
29 const contentArea = E1('div.content'),
30 bcl = document.body.classList;
31 const resized = function(){
32 const wh = window.innerHeight,
33 mult = bcl.contains('chat-only-mode') ? 0.95 : 0.85;
34 contentArea.style.height = contentArea.style.maxHeight = (wh * mult)+"px";
35 //console.debug("resized.",wh, mult, window.getComputedStyle(contentArea).maxHeight);
36 };
37 var doit;
38 window.addEventListener('resize',function(ev){
39 clearTimeout(doit);
40 doit = setTimeout(resized, 100);
41 }, false);
42 resized();
43 return resized;
44 })();
45
46 const Chat = (function(){
47 const cs = {
48 e:{/*map of certain DOM elements.*/
49 messageInjectPoint: E1('#message-inject-point'),
50 pageTitle: E1('head title'),
@@ -128,18 +153,21 @@
153 const fe = mip.nextElementSibling;
154 if(fe) mip.parentNode.insertBefore(e, fe);
155 else D.append(mip.parentNode, e);
156 }else{
157 D.append(holder,e);
158 Chat.newestMessageElem = e;
159 }
160 if(!atEnd && !this.isMassLoading
161 && e.dataset.xfrom!==Chat.me && !isInViewport(e)){
162 /* If a new non-history message arrives while the user is
163 scrolled elsewhere, do not scroll to the latest
164 message, but gently alert the user that a new message
165 has arrived. */
166 F.toast.message("New message has arrived.");
167 }else if(e.dataset.xfrom===Chat.me){
168 e.scrollIntoView();
169 }
170 },
171 /** Returns true if chat-only mode is enabled. */
172 isChatOnlyMode: ()=>document.body.classList.contains('chat-only-mode'),
173 /**
@@ -165,10 +193,11 @@
193 D.removeClass(f.elemsToToggle, 'hidden');
194 D.removeClass(document.body, 'chat-only-mode');
195 }
196 const msg = document.querySelector('.message-widget');
197 if(msg) setTimeout(()=>msg.scrollIntoView(),0);
198 ForceResizeKludge();
199 return this;
200 },
201 toggleChatOnlyMode: function(){
202 return this.chatOnlyMode(!this.isChatOnlyMode());
203 },
@@ -229,10 +258,18 @@
258 };
259
260 cs.getMessageElemById = function(id){
261 return qs('[data-msgid="'+id+'"]');
262 };
263
264 /** Finds the last .message-widget element and returns it or
265 the undefined value if none are found. */
266 cs.fetchLastMessageElem = function(){
267 const msgs = document.querySelectorAll('.message-widget');
268 return msgs.length ? msgs[msgs.length-1] : undefined;
269 };
270
271 /**
272 LOCALLY deletes a message element by the message ID or passing
273 the .message-row element. Returns true if it removes an element,
274 else false.
275 */
@@ -244,10 +281,13 @@
281 }else{
282 e = this.getMessageElemById(id);
283 }
284 if(e && id){
285 D.remove(e);
286 if(e===this.newestMessageElem){
287 Chat.newestMessageElem = Chat.fetchLastMessageElem();
288 }
289 F.toast.message("Deleted message "+id+".");
290 }
291 return !!e;
292 };
293
@@ -819,19 +859,26 @@
859 .then(y=>newcontent(y))
860 .catch(e=>console.error(e))
861 /* ^^^ we don't use Chat.reportError(e) here b/c the polling
862 fails exepectedly when it times out, but is then immediately
863 resumed, and reportError() produces a loud error message. */
864 .finally(function(){
865 if(isFirstCall){
866 Chat.isMassLoading = false;
867 Chat.ajaxEnd();
868 setTimeout(function(){
869 const m = Chat.newestMessageElem;
870 if(m){
871 m.scrollIntoView();
872 //console.debug("Scrolling into view...",msgs[msgs.length-1]);
873 }
874 Chat.e.inputWrapper.scrollIntoView()
875 }, 0);
876 }
877 poll.running=false;
878 });
879 }
880 poll.running = false;
881 poll(true);
882 setInterval(poll, 1000);
883 F.page.chat = Chat/* enables testing the APIs via the dev tools */;
884 })();
885
--- src/default.css
+++ src/default.css
@@ -1636,10 +1636,13 @@
16361636
body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
16371637
cursor: inherit;
16381638
}
16391639
/** Container for the list of /chat messages. */
16401640
body.chat #chat-messages-wrapper {
1641
+ overflow: auto;
1642
+ /*max-height: 800em*//*will be re-calc'd in JS*/;
1643
+ flex: 2 1 auto;
16411644
}
16421645
body.chat div.content {
16431646
margin: 0;
16441647
padding: 0;
16451648
display: flex;
16461649
--- src/default.css
+++ src/default.css
@@ -1636,10 +1636,13 @@
1636 body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
1637 cursor: inherit;
1638 }
1639 /** Container for the list of /chat messages. */
1640 body.chat #chat-messages-wrapper {
 
 
 
1641 }
1642 body.chat div.content {
1643 margin: 0;
1644 padding: 0;
1645 display: flex;
1646
--- src/default.css
+++ src/default.css
@@ -1636,10 +1636,13 @@
1636 body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
1637 cursor: inherit;
1638 }
1639 /** Container for the list of /chat messages. */
1640 body.chat #chat-messages-wrapper {
1641 overflow: auto;
1642 /*max-height: 800em*//*will be re-calc'd in JS*/;
1643 flex: 2 1 auto;
1644 }
1645 body.chat div.content {
1646 margin: 0;
1647 padding: 0;
1648 display: flex;
1649

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button