Fossil SCM

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].

stephan 2020-12-27 03:39 trunk
Commit 421d6570785de527afc88ea7a164303fe1f8fc9e91f61af705d7ba9e9e34fb29
3 files changed +1 -2 +34 -84 +8 -26
+1 -2
--- src/chat.c
+++ src/chat.c
@@ -135,13 +135,12 @@
135135
@ </div>
136136
@ <div id="chat-drop-details"></div>
137137
@ </div>
138138
@ </form>
139139
@ </div>
140
-
141
- /* New chat messages get inserted immediately after this element */
142140
@ <div id='chat-messages-wrapper'>
141
+ /* New chat messages get inserted immediately after this element */
143142
@ <span id='message-inject-point'></span>
144143
@ </div>
145144
146145
builtin_fossil_js_bundle_or("popupwidget", "storage", NULL);
147146
/* Always in-line the javascript for the chat page */
148147
--- src/chat.c
+++ src/chat.c
@@ -135,13 +135,12 @@
135 @ </div>
136 @ <div id="chat-drop-details"></div>
137 @ </div>
138 @ </form>
139 @ </div>
140
141 /* New chat messages get inserted immediately after this element */
142 @ <div id='chat-messages-wrapper'>
 
143 @ <span id='message-inject-point'></span>
144 @ </div>
145
146 builtin_fossil_js_bundle_or("popupwidget", "storage", NULL);
147 /* Always in-line the javascript for the chat page */
148
--- src/chat.c
+++ src/chat.c
@@ -135,13 +135,12 @@
135 @ </div>
136 @ <div id="chat-drop-details"></div>
137 @ </div>
138 @ </form>
139 @ </div>
 
 
140 @ <div id='chat-messages-wrapper'>
141 /* New chat messages get inserted immediately after this element */
142 @ <span id='message-inject-point'></span>
143 @ </div>
144
145 builtin_fossil_js_bundle_or("popupwidget", "storage", NULL);
146 /* Always in-line the javascript for the chat page */
147
+34 -84
--- src/chat.js
+++ src/chat.js
@@ -7,17 +7,25 @@
77
const E1 = function(selector){
88
const e = document.querySelector(selector);
99
if(!e) throw new Error("missing required DOM element: "+selector);
1010
return e;
1111
};
12
- //document.body.classList.add('chat-only-mode');
12
+ const isInViewport = function(e) {
13
+ const rect = e.getBoundingClientRect();
14
+ return (
15
+ rect.top >= 0 &&
16
+ rect.left >= 0 &&
17
+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
18
+ rect.right <= (window.innerWidth || document.documentElement.clientWidth)
19
+ );
20
+ };
1321
const Chat = (function(){
1422
const cs = {
1523
e:{/*map of certain DOM elements.*/
1624
messageInjectPoint: E1('#message-inject-point'),
1725
pageTitle: E1('head title'),
18
- loadToolbar: undefined /* the load-posts toolbar (dynamically created) */,
26
+ loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
1927
inputWrapper: E1("#chat-input-area"),
2028
fileSelectWrapper: E1('#chat-input-file-area'),
2129
messagesWrapper: E1('#chat-messages-wrapper'),
2230
inputForm: E1('#chat-form'),
2331
btnSubmit: E1('#chat-message-submit'),
@@ -112,55 +120,30 @@
112120
],
113121
/* Injects element e as a new row in the chat, at the top of the
114122
list if atEnd is falsy, else at the end of the list, before
115123
the load-history widget. */
116124
injectMessageElem: function f(e, atEnd){
117
- const mip = atEnd ? this.e.loadToolbar : this.e.messageInjectPoint;
125
+ const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
126
+ holder = this.e.messagesWrapper;
118127
if(atEnd){
119
- mip.parentNode.insertBefore(e, mip);
128
+ const fe = mip.nextElementSibling;
129
+ if(fe) mip.parentNode.insertBefore(e, fe);
130
+ else D.append(mip.parentNode, e);
120131
}else{
121
- const self = this;
122
- if(false && this.isUiFlipped()){
123
- /* When UI is flipped, new messages start out under the
124
- text input area because of its position:sticky
125
- style. We have to scroll them up. When the page footer
126
- is not hidden but is not on-screen, this causes a
127
- slight amount of UI jarring as the footer is *also*
128
- scrolled into view (for whatever reason).
129
-
130
- The remaining problem here is messages with IMG tags.
131
- At this point in the process their IMG.src has not yet
132
- been loaded - that's async. We scroll the message into
133
- view, but then the downstream loading of IMG.src pushes
134
- the message content back down, sliding the message
135
- behind the input field. This can be verified by delaying the
136
- message scroll by a second or so to give the image time
137
- to load (from a local server instance).
138
- */
139
- D.addClass(self.e.inputWrapper,'unsticky');
140
- }
141
- if(mip.nextSibling) mip.parentNode.insertBefore(e, mip.nextSibling);
142
- else mip.parentNode.appendChild(e);
143
- if(false && this.isUiFlipped()){
144
- //e.scrollIntoView();
145
- setTimeout(function(){
146
- //self.e.inputWrapper.scrollIntoView();
147
- //self.e.fileSelectWrapper.scrollIntoView();
148
- //e.scrollIntoView();
149
- //D.removeClass(self.e.inputWrapper,'unsticky');
150
- self.e.inputWrapper.scrollIntoView();
151
- },0);
152
- }
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.");
153141
}
154142
},
155143
/** Returns true if chat-only mode is enabled. */
156144
isChatOnlyMode: ()=>document.body.classList.contains('chat-only-mode'),
157
- /** Returns true if the UI seems to be in "bottom-up" mode. */
158
- isUiFlipped: function(){
159
- const style = window.getComputedStyle(this.e.contentDiv);
160
- return style.flexDirection.indexOf("-reverse")>0;
161
- },
162145
/**
163146
Enters (if passed a truthy value or no arguments) or leaves
164147
"chat-only" mode. That mode hides the page's header and
165148
footer, leaving only the chat application visible to the
166149
user.
@@ -167,11 +150,11 @@
167150
*/
168151
chatOnlyMode: function f(yes){
169152
if(undefined === f.elemsToToggle){
170153
f.elemsToToggle = [];
171154
document.querySelectorAll(
172
- "body > div.header, body > div.footer"
155
+ "body > div.header, body > div.mainmenu, body > div.footer"
173156
).forEach((e)=>f.elemsToToggle.push(e));
174157
}
175158
if(!arguments.length) yes = true;
176159
if(yes === this.isChatOnlyMode()) return this;
177160
if(yes){
@@ -179,17 +162,10 @@
179162
D.addClass(document.body, 'chat-only-mode');
180163
document.body.scroll(0,document.body.height);
181164
}else{
182165
D.removeClass(f.elemsToToggle, 'hidden');
183166
D.removeClass(document.body, 'chat-only-mode');
184
- if(false){
185
- setTimeout(()=>document.body.scrollIntoView(
186
- /*moves to (0,0), whereas scrollTo(0,0) does not!
187
- setTimeout() is unfortunately necessary to get the scroll
188
- placement correct.*/
189
- ), 0);
190
- }
191167
}
192168
const msg = document.querySelector('.message-widget');
193169
if(msg) setTimeout(()=>msg.scrollIntoView(),0);
194170
return this;
195171
},
@@ -200,12 +176,11 @@
200176
get: (k,dflt)=>F.storage.get(k,dflt),
201177
getBool: (k,dflt)=>F.storage.getBool(k,dflt),
202178
set: (k,v)=>F.storage.set(k,v),
203179
defaults:{
204180
"images-inline": !!F.config.chat.imagesInline,
205
- "monospace-messages": false,
206
- "bottom-up": true
181
+ "monospace-messages": false
207182
}
208183
}
209184
};
210185
/* Install default settings... */
211186
Object.keys(cs.settings.defaults).forEach(function(k){
@@ -218,13 +193,10 @@
218193
windows (desktop/tablet landscape mode), so we default to a
219194
layout based on the apparently "orientation" of the window:
220195
tall vs wide. Can be toggled via settings popup. */
221196
document.body.classList.add('my-messages-right');
222197
}
223
- if(cs.settings.getBool("bottom-up")){
224
- document.body.classList.add('chat-bottom-up');
225
- }
226198
if(cs.settings.getBool('monospace-messages',false)){
227199
document.body.classList.add('monospace-messages');
228200
}
229201
cs.e.inputCurrent = cs.e.inputSingle;
230202
cs.pageTitleOrig = cs.e.pageTitle.innerText;
@@ -671,30 +643,10 @@
671643
label: "Left-align my posts",
672644
boolValue: ()=>!document.body.classList.contains('my-messages-right'),
673645
callback: function f(){
674646
document.body.classList.toggle('my-messages-right');
675647
}
676
- },{
677
- label: "Bottom-up chat",
678
- boolValue: ()=>document.body.classList.contains('chat-bottom-up'),
679
- callback: function(){
680
- document.body.classList.toggle('chat-bottom-up');
681
- Chat.settings.set('bottom-up',
682
- document.body.classList.contains('chat-bottom-up'));
683
- if(false){
684
- /* Reminder: in order to get a good scrolling effect when
685
- sticky mode is enabled for Chat.e.inputWrapper, BOTH of
686
- these scrollIntoView() calls are needed. */
687
- const e = document.querySelector(
688
- '.message-widget'/*this is always the most recent message,
689
- even if flexbox placed it at the end of
690
- the page!*/
691
- );
692
- if(e) e.scrollIntoView();
693
- }
694
- setTimeout(()=>Chat.e.inputWrapper.scrollIntoView(), 0);
695
- }
696648
},{
697649
label: "Images inline",
698650
boolValue: ()=>Chat.settings.getBool('images-inline'),
699651
callback: function(){
700652
const v = Chat.settings.getBool('images-inline',true);
@@ -753,15 +705,11 @@
753705
const rect = settingsButton.getBoundingClientRect();
754706
return rect.right - popupSize.width;
755707
};
756708
settingsPopup.options.adjustY = function(y){
757709
const rect = settingsButton.getBoundingClientRect();
758
- if(Chat.isUiFlipped()){
759
- return rect.top - popupSize.height -2;
760
- }else{
761
- return rect.bottom + 2;
762
- }
710
+ return rect.top - popupSize.height -2;
763711
};
764712
})()/*#chat-settings-button setup*/;
765713
766714
767715
/** Callback for poll() to inject new content into the page. jx ==
@@ -810,11 +758,11 @@
810758
/** Add toolbar for loading older messages. We use a FIELDSET here
811759
because a fieldset is the only parent element type which can
812760
automatically enable/disable its children by
813761
enabling/disabling the parent element. */
814762
const loadLegend = D.legend("Load...");
815
- const toolbar = Chat.e.loadToolbar = D.attr(
763
+ const toolbar = Chat.e.loadOlderToolbar = D.attr(
816764
D.fieldset(loadLegend), "id", "load-msg-toolbar"
817765
);
818766
Chat.disableDuringAjax.push(toolbar);
819767
/* Loads the next n oldest messages, or all previous history if n is negative. */
820768
const loadOldMessages = function(n){
@@ -834,16 +782,16 @@
834782
|| (false!==gotMessages && n<0 && gotMessages<Chat.loadMessageCount
835783
/*we asked for default amount and got fewer than that.*/)){
836784
/* We've loaded all history. Permanently disable the
837785
history-load toolbar and keep it from being re-enabled
838786
via the ajaxStart()/ajaxEnd() mechanism... */
839
- const div = Chat.e.loadToolbar.querySelector('div');
787
+ const div = Chat.e.loadOlderToolbar.querySelector('div');
840788
D.append(D.clearElement(div), "All history has been loaded.");
841
- D.addClass(Chat.e.loadToolbar, 'all-done');
842
- const ndx = Chat.disableDuringAjax.indexOf(Chat.e.loadToolbar);
789
+ D.addClass(Chat.e.loadOlderToolbar, 'all-done');
790
+ const ndx = Chat.disableDuringAjax.indexOf(Chat.e.loadOlderToolbar);
843791
if(ndx>=0) Chat.disableDuringAjax.splice(ndx,1);
844
- Chat.e.loadToolbar.disabled = true;
792
+ Chat.e.loadOlderToolbar.disabled = true;
845793
}
846794
if(gotMessages > 0){
847795
F.toast.message("Loaded "+gotMessages+" older messages.");
848796
}
849797
Chat.ajaxEnd();
@@ -863,19 +811,21 @@
863811
864812
async function poll(isFirstCall){
865813
if(poll.running) return;
866814
poll.running = true;
867815
if(isFirstCall) Chat.ajaxStart();
816
+ Chat.isMassLoading = isFirstCall;
868817
var p = fetch("chat-poll?name=" + Chat.mxMsg);
869818
p.then(x=>x.json())
870819
.then(y=>newcontent(y))
871820
.catch(e=>console.error(e))
872821
/* ^^^ we don't use Chat.reportError(e) here b/c the polling
873822
fails exepectedly when it times out, but is then immediately
874823
resumed, and reportError() produces a loud error message. */
875824
.finally(function(x){
876825
if(isFirstCall){
826
+ Chat.isMassLoading = false;
877827
Chat.ajaxEnd();
878828
Chat.e.inputWrapper.scrollIntoView();
879829
}
880830
poll.running=false;
881831
});
882832
--- src/chat.js
+++ src/chat.js
@@ -7,17 +7,25 @@
7 const E1 = function(selector){
8 const e = document.querySelector(selector);
9 if(!e) throw new Error("missing required DOM element: "+selector);
10 return e;
11 };
12 //document.body.classList.add('chat-only-mode');
 
 
 
 
 
 
 
 
13 const Chat = (function(){
14 const cs = {
15 e:{/*map of certain DOM elements.*/
16 messageInjectPoint: E1('#message-inject-point'),
17 pageTitle: E1('head title'),
18 loadToolbar: undefined /* the load-posts toolbar (dynamically created) */,
19 inputWrapper: E1("#chat-input-area"),
20 fileSelectWrapper: E1('#chat-input-file-area'),
21 messagesWrapper: E1('#chat-messages-wrapper'),
22 inputForm: E1('#chat-form'),
23 btnSubmit: E1('#chat-message-submit'),
@@ -112,55 +120,30 @@
112 ],
113 /* Injects element e as a new row in the chat, at the top of the
114 list if atEnd is falsy, else at the end of the list, before
115 the load-history widget. */
116 injectMessageElem: function f(e, atEnd){
117 const mip = atEnd ? this.e.loadToolbar : this.e.messageInjectPoint;
 
118 if(atEnd){
119 mip.parentNode.insertBefore(e, mip);
 
 
120 }else{
121 const self = this;
122 if(false && this.isUiFlipped()){
123 /* When UI is flipped, new messages start out under the
124 text input area because of its position:sticky
125 style. We have to scroll them up. When the page footer
126 is not hidden but is not on-screen, this causes a
127 slight amount of UI jarring as the footer is *also*
128 scrolled into view (for whatever reason).
129
130 The remaining problem here is messages with IMG tags.
131 At this point in the process their IMG.src has not yet
132 been loaded - that's async. We scroll the message into
133 view, but then the downstream loading of IMG.src pushes
134 the message content back down, sliding the message
135 behind the input field. This can be verified by delaying the
136 message scroll by a second or so to give the image time
137 to load (from a local server instance).
138 */
139 D.addClass(self.e.inputWrapper,'unsticky');
140 }
141 if(mip.nextSibling) mip.parentNode.insertBefore(e, mip.nextSibling);
142 else mip.parentNode.appendChild(e);
143 if(false && this.isUiFlipped()){
144 //e.scrollIntoView();
145 setTimeout(function(){
146 //self.e.inputWrapper.scrollIntoView();
147 //self.e.fileSelectWrapper.scrollIntoView();
148 //e.scrollIntoView();
149 //D.removeClass(self.e.inputWrapper,'unsticky');
150 self.e.inputWrapper.scrollIntoView();
151 },0);
152 }
153 }
154 },
155 /** Returns true if chat-only mode is enabled. */
156 isChatOnlyMode: ()=>document.body.classList.contains('chat-only-mode'),
157 /** Returns true if the UI seems to be in "bottom-up" mode. */
158 isUiFlipped: function(){
159 const style = window.getComputedStyle(this.e.contentDiv);
160 return style.flexDirection.indexOf("-reverse")>0;
161 },
162 /**
163 Enters (if passed a truthy value or no arguments) or leaves
164 "chat-only" mode. That mode hides the page's header and
165 footer, leaving only the chat application visible to the
166 user.
@@ -167,11 +150,11 @@
167 */
168 chatOnlyMode: function f(yes){
169 if(undefined === f.elemsToToggle){
170 f.elemsToToggle = [];
171 document.querySelectorAll(
172 "body > div.header, body > div.footer"
173 ).forEach((e)=>f.elemsToToggle.push(e));
174 }
175 if(!arguments.length) yes = true;
176 if(yes === this.isChatOnlyMode()) return this;
177 if(yes){
@@ -179,17 +162,10 @@
179 D.addClass(document.body, 'chat-only-mode');
180 document.body.scroll(0,document.body.height);
181 }else{
182 D.removeClass(f.elemsToToggle, 'hidden');
183 D.removeClass(document.body, 'chat-only-mode');
184 if(false){
185 setTimeout(()=>document.body.scrollIntoView(
186 /*moves to (0,0), whereas scrollTo(0,0) does not!
187 setTimeout() is unfortunately necessary to get the scroll
188 placement correct.*/
189 ), 0);
190 }
191 }
192 const msg = document.querySelector('.message-widget');
193 if(msg) setTimeout(()=>msg.scrollIntoView(),0);
194 return this;
195 },
@@ -200,12 +176,11 @@
200 get: (k,dflt)=>F.storage.get(k,dflt),
201 getBool: (k,dflt)=>F.storage.getBool(k,dflt),
202 set: (k,v)=>F.storage.set(k,v),
203 defaults:{
204 "images-inline": !!F.config.chat.imagesInline,
205 "monospace-messages": false,
206 "bottom-up": true
207 }
208 }
209 };
210 /* Install default settings... */
211 Object.keys(cs.settings.defaults).forEach(function(k){
@@ -218,13 +193,10 @@
218 windows (desktop/tablet landscape mode), so we default to a
219 layout based on the apparently "orientation" of the window:
220 tall vs wide. Can be toggled via settings popup. */
221 document.body.classList.add('my-messages-right');
222 }
223 if(cs.settings.getBool("bottom-up")){
224 document.body.classList.add('chat-bottom-up');
225 }
226 if(cs.settings.getBool('monospace-messages',false)){
227 document.body.classList.add('monospace-messages');
228 }
229 cs.e.inputCurrent = cs.e.inputSingle;
230 cs.pageTitleOrig = cs.e.pageTitle.innerText;
@@ -671,30 +643,10 @@
671 label: "Left-align my posts",
672 boolValue: ()=>!document.body.classList.contains('my-messages-right'),
673 callback: function f(){
674 document.body.classList.toggle('my-messages-right');
675 }
676 },{
677 label: "Bottom-up chat",
678 boolValue: ()=>document.body.classList.contains('chat-bottom-up'),
679 callback: function(){
680 document.body.classList.toggle('chat-bottom-up');
681 Chat.settings.set('bottom-up',
682 document.body.classList.contains('chat-bottom-up'));
683 if(false){
684 /* Reminder: in order to get a good scrolling effect when
685 sticky mode is enabled for Chat.e.inputWrapper, BOTH of
686 these scrollIntoView() calls are needed. */
687 const e = document.querySelector(
688 '.message-widget'/*this is always the most recent message,
689 even if flexbox placed it at the end of
690 the page!*/
691 );
692 if(e) e.scrollIntoView();
693 }
694 setTimeout(()=>Chat.e.inputWrapper.scrollIntoView(), 0);
695 }
696 },{
697 label: "Images inline",
698 boolValue: ()=>Chat.settings.getBool('images-inline'),
699 callback: function(){
700 const v = Chat.settings.getBool('images-inline',true);
@@ -753,15 +705,11 @@
753 const rect = settingsButton.getBoundingClientRect();
754 return rect.right - popupSize.width;
755 };
756 settingsPopup.options.adjustY = function(y){
757 const rect = settingsButton.getBoundingClientRect();
758 if(Chat.isUiFlipped()){
759 return rect.top - popupSize.height -2;
760 }else{
761 return rect.bottom + 2;
762 }
763 };
764 })()/*#chat-settings-button setup*/;
765
766
767 /** Callback for poll() to inject new content into the page. jx ==
@@ -810,11 +758,11 @@
810 /** Add toolbar for loading older messages. We use a FIELDSET here
811 because a fieldset is the only parent element type which can
812 automatically enable/disable its children by
813 enabling/disabling the parent element. */
814 const loadLegend = D.legend("Load...");
815 const toolbar = Chat.e.loadToolbar = D.attr(
816 D.fieldset(loadLegend), "id", "load-msg-toolbar"
817 );
818 Chat.disableDuringAjax.push(toolbar);
819 /* Loads the next n oldest messages, or all previous history if n is negative. */
820 const loadOldMessages = function(n){
@@ -834,16 +782,16 @@
834 || (false!==gotMessages && n<0 && gotMessages<Chat.loadMessageCount
835 /*we asked for default amount and got fewer than that.*/)){
836 /* We've loaded all history. Permanently disable the
837 history-load toolbar and keep it from being re-enabled
838 via the ajaxStart()/ajaxEnd() mechanism... */
839 const div = Chat.e.loadToolbar.querySelector('div');
840 D.append(D.clearElement(div), "All history has been loaded.");
841 D.addClass(Chat.e.loadToolbar, 'all-done');
842 const ndx = Chat.disableDuringAjax.indexOf(Chat.e.loadToolbar);
843 if(ndx>=0) Chat.disableDuringAjax.splice(ndx,1);
844 Chat.e.loadToolbar.disabled = true;
845 }
846 if(gotMessages > 0){
847 F.toast.message("Loaded "+gotMessages+" older messages.");
848 }
849 Chat.ajaxEnd();
@@ -863,19 +811,21 @@
863
864 async function poll(isFirstCall){
865 if(poll.running) return;
866 poll.running = true;
867 if(isFirstCall) Chat.ajaxStart();
 
868 var p = fetch("chat-poll?name=" + Chat.mxMsg);
869 p.then(x=>x.json())
870 .then(y=>newcontent(y))
871 .catch(e=>console.error(e))
872 /* ^^^ we don't use Chat.reportError(e) here b/c the polling
873 fails exepectedly when it times out, but is then immediately
874 resumed, and reportError() produces a loud error message. */
875 .finally(function(x){
876 if(isFirstCall){
 
877 Chat.ajaxEnd();
878 Chat.e.inputWrapper.scrollIntoView();
879 }
880 poll.running=false;
881 });
882
--- src/chat.js
+++ src/chat.js
@@ -7,17 +7,25 @@
7 const E1 = function(selector){
8 const e = document.querySelector(selector);
9 if(!e) throw new Error("missing required DOM element: "+selector);
10 return e;
11 };
12 const isInViewport = function(e) {
13 const rect = e.getBoundingClientRect();
14 return (
15 rect.top >= 0 &&
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'),
26 loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
27 inputWrapper: E1("#chat-input-area"),
28 fileSelectWrapper: E1('#chat-input-file-area'),
29 messagesWrapper: E1('#chat-messages-wrapper'),
30 inputForm: E1('#chat-form'),
31 btnSubmit: E1('#chat-message-submit'),
@@ -112,55 +120,30 @@
120 ],
121 /* Injects element e as a new row in the chat, at the top of the
122 list if atEnd is falsy, else at the end of the list, before
123 the load-history widget. */
124 injectMessageElem: function f(e, atEnd){
125 const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
126 holder = this.e.messagesWrapper;
127 if(atEnd){
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 /**
146 Enters (if passed a truthy value or no arguments) or leaves
147 "chat-only" mode. That mode hides the page's header and
148 footer, leaving only the chat application visible to the
149 user.
@@ -167,11 +150,11 @@
150 */
151 chatOnlyMode: function f(yes){
152 if(undefined === f.elemsToToggle){
153 f.elemsToToggle = [];
154 document.querySelectorAll(
155 "body > div.header, body > div.mainmenu, body > div.footer"
156 ).forEach((e)=>f.elemsToToggle.push(e));
157 }
158 if(!arguments.length) yes = true;
159 if(yes === this.isChatOnlyMode()) return this;
160 if(yes){
@@ -179,17 +162,10 @@
162 D.addClass(document.body, 'chat-only-mode');
163 document.body.scroll(0,document.body.height);
164 }else{
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 },
@@ -200,12 +176,11 @@
176 get: (k,dflt)=>F.storage.get(k,dflt),
177 getBool: (k,dflt)=>F.storage.getBool(k,dflt),
178 set: (k,v)=>F.storage.set(k,v),
179 defaults:{
180 "images-inline": !!F.config.chat.imagesInline,
181 "monospace-messages": false
 
182 }
183 }
184 };
185 /* Install default settings... */
186 Object.keys(cs.settings.defaults).forEach(function(k){
@@ -218,13 +193,10 @@
193 windows (desktop/tablet landscape mode), so we default to a
194 layout based on the apparently "orientation" of the window:
195 tall vs wide. Can be toggled via settings popup. */
196 document.body.classList.add('my-messages-right');
197 }
 
 
 
198 if(cs.settings.getBool('monospace-messages',false)){
199 document.body.classList.add('monospace-messages');
200 }
201 cs.e.inputCurrent = cs.e.inputSingle;
202 cs.pageTitleOrig = cs.e.pageTitle.innerText;
@@ -671,30 +643,10 @@
643 label: "Left-align my posts",
644 boolValue: ()=>!document.body.classList.contains('my-messages-right'),
645 callback: function f(){
646 document.body.classList.toggle('my-messages-right');
647 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
648 },{
649 label: "Images inline",
650 boolValue: ()=>Chat.settings.getBool('images-inline'),
651 callback: function(){
652 const v = Chat.settings.getBool('images-inline',true);
@@ -753,15 +705,11 @@
705 const rect = settingsButton.getBoundingClientRect();
706 return rect.right - popupSize.width;
707 };
708 settingsPopup.options.adjustY = function(y){
709 const rect = settingsButton.getBoundingClientRect();
710 return rect.top - popupSize.height -2;
 
 
 
 
711 };
712 })()/*#chat-settings-button setup*/;
713
714
715 /** Callback for poll() to inject new content into the page. jx ==
@@ -810,11 +758,11 @@
758 /** Add toolbar for loading older messages. We use a FIELDSET here
759 because a fieldset is the only parent element type which can
760 automatically enable/disable its children by
761 enabling/disabling the parent element. */
762 const loadLegend = D.legend("Load...");
763 const toolbar = Chat.e.loadOlderToolbar = D.attr(
764 D.fieldset(loadLegend), "id", "load-msg-toolbar"
765 );
766 Chat.disableDuringAjax.push(toolbar);
767 /* Loads the next n oldest messages, or all previous history if n is negative. */
768 const loadOldMessages = function(n){
@@ -834,16 +782,16 @@
782 || (false!==gotMessages && n<0 && gotMessages<Chat.loadMessageCount
783 /*we asked for default amount and got fewer than that.*/)){
784 /* We've loaded all history. Permanently disable the
785 history-load toolbar and keep it from being re-enabled
786 via the ajaxStart()/ajaxEnd() mechanism... */
787 const div = Chat.e.loadOlderToolbar.querySelector('div');
788 D.append(D.clearElement(div), "All history has been loaded.");
789 D.addClass(Chat.e.loadOlderToolbar, 'all-done');
790 const ndx = Chat.disableDuringAjax.indexOf(Chat.e.loadOlderToolbar);
791 if(ndx>=0) Chat.disableDuringAjax.splice(ndx,1);
792 Chat.e.loadOlderToolbar.disabled = true;
793 }
794 if(gotMessages > 0){
795 F.toast.message("Loaded "+gotMessages+" older messages.");
796 }
797 Chat.ajaxEnd();
@@ -863,19 +811,21 @@
811
812 async function poll(isFirstCall){
813 if(poll.running) return;
814 poll.running = true;
815 if(isFirstCall) Chat.ajaxStart();
816 Chat.isMassLoading = isFirstCall;
817 var p = fetch("chat-poll?name=" + Chat.mxMsg);
818 p.then(x=>x.json())
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
+8 -26
--- src/default.css
+++ src/default.css
@@ -1636,55 +1636,37 @@
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
- display: flex;
1642
- flex-direction: column;
1643
-}
1644
-body.chat.chat-bottom-up #chat-messages-wrapper {
1645
- flex-direction: column-reverse;
1646
- z-index: 99 /* so that it scrolls under input area. If it's
1647
- lower than div.content then mouse events to it
1648
- are blocked!*/;
16491641
}
16501642
body.chat div.content {
16511643
margin: 0;
16521644
padding: 0;
16531645
display: flex;
1654
- flex-direction: column;
1655
- align-items: stretch;
1656
-}
1657
-body.chat.chat-bottom-up div.content {
16581646
flex-direction: column-reverse;
1647
+ /* ^^^^ In order to get good automatic scrolling of new messages on
1648
+ the BOTTOM in bottom-up chat mode, such that they scroll up
1649
+ instead of down, we have to use column-reverse layout, which
1650
+ changes #chat-messages-wrapper's "gravity" for purposes of
1651
+ scrolling! If we instead use flex-direction:column then each new
1652
+ message pushes #chat-input-area down further off the screen!
1653
+ */
1654
+ align-items: stretch;
16591655
}
16601656
/* Wrapper for /chat user input controls */
16611657
body.chat #chat-input-area {
16621658
display: flex;
16631659
flex-direction: column;
1664
- border-bottom: 1px solid black;
16651660
padding: 0.5em 1em;
1666
- margin-bottom: 0.5em;
1667
- /*position: sticky; top: 0; disabled for the time being because of
1668
- scroll-related quirks which are still unresolved. */
1669
- z-index: 100
1670
- /* see notes in #chat-messages-wrapper. The various popups require a
1671
- z-index higher than this one. */;
1672
-}
1673
-body.chat.chat-bottom-up #chat-input-area {
16741661
border-bottom: none;
16751662
border-top: 1px solid black;
16761663
margin-bottom: 0;
16771664
margin-top: 0.5em;
16781665
position: initial /*sticky currently disabled due to scrolling-related issues*/;
16791666
bottom: 0;
16801667
}
1681
-/* An internal hack to try to help resolve a message-scrolling quirk
1682
- when #chat-input-area is sticky on the bottom of the screen. */
1683
-body.chat.chat-bottom-up #chat-input-area.unsticky {
1684
- position: initial;
1685
-}
16861668
/* Widget holding the chat message input field, send button, and
16871669
settings button. */
16881670
body.chat #chat-input-line {
16891671
display: flex;
16901672
flex-direction: row;
16911673
--- src/default.css
+++ src/default.css
@@ -1636,55 +1636,37 @@
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 display: flex;
1642 flex-direction: column;
1643 }
1644 body.chat.chat-bottom-up #chat-messages-wrapper {
1645 flex-direction: column-reverse;
1646 z-index: 99 /* so that it scrolls under input area. If it's
1647 lower than div.content then mouse events to it
1648 are blocked!*/;
1649 }
1650 body.chat div.content {
1651 margin: 0;
1652 padding: 0;
1653 display: flex;
1654 flex-direction: column;
1655 align-items: stretch;
1656 }
1657 body.chat.chat-bottom-up div.content {
1658 flex-direction: column-reverse;
 
 
 
 
 
 
 
 
1659 }
1660 /* Wrapper for /chat user input controls */
1661 body.chat #chat-input-area {
1662 display: flex;
1663 flex-direction: column;
1664 border-bottom: 1px solid black;
1665 padding: 0.5em 1em;
1666 margin-bottom: 0.5em;
1667 /*position: sticky; top: 0; disabled for the time being because of
1668 scroll-related quirks which are still unresolved. */
1669 z-index: 100
1670 /* see notes in #chat-messages-wrapper. The various popups require a
1671 z-index higher than this one. */;
1672 }
1673 body.chat.chat-bottom-up #chat-input-area {
1674 border-bottom: none;
1675 border-top: 1px solid black;
1676 margin-bottom: 0;
1677 margin-top: 0.5em;
1678 position: initial /*sticky currently disabled due to scrolling-related issues*/;
1679 bottom: 0;
1680 }
1681 /* An internal hack to try to help resolve a message-scrolling quirk
1682 when #chat-input-area is sticky on the bottom of the screen. */
1683 body.chat.chat-bottom-up #chat-input-area.unsticky {
1684 position: initial;
1685 }
1686 /* Widget holding the chat message input field, send button, and
1687 settings button. */
1688 body.chat #chat-input-line {
1689 display: flex;
1690 flex-direction: row;
1691
--- src/default.css
+++ src/default.css
@@ -1636,55 +1636,37 @@
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 flex-direction: column-reverse;
1647 /* ^^^^ In order to get good automatic scrolling of new messages on
1648 the BOTTOM in bottom-up chat mode, such that they scroll up
1649 instead of down, we have to use column-reverse layout, which
1650 changes #chat-messages-wrapper's "gravity" for purposes of
1651 scrolling! If we instead use flex-direction:column then each new
1652 message pushes #chat-input-area down further off the screen!
1653 */
1654 align-items: stretch;
1655 }
1656 /* Wrapper for /chat user input controls */
1657 body.chat #chat-input-area {
1658 display: flex;
1659 flex-direction: column;
 
1660 padding: 0.5em 1em;
 
 
 
 
 
 
 
 
1661 border-bottom: none;
1662 border-top: 1px solid black;
1663 margin-bottom: 0;
1664 margin-top: 0.5em;
1665 position: initial /*sticky currently disabled due to scrolling-related issues*/;
1666 bottom: 0;
1667 }
 
 
 
 
 
1668 /* Widget holding the chat message input field, send button, and
1669 settings button. */
1670 body.chat #chat-input-line {
1671 display: flex;
1672 flex-direction: row;
1673

Keyboard Shortcuts

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