Fossil SCM

Simplified and consolidated how /chat internally manages its 3 separate main views, with an eye towards making it easy to add additional views. No user-visible changes.

stephan 2021-09-22 11:15 trunk
Commit 593d3a3a1e5455a40663ce45dc50915d6bcd64f206ffa6f803bc2553628c3913
2 files changed +3 -3 +40 -46
+3 -3
--- src/chat.c
+++ src/chat.c
@@ -181,21 +181,21 @@
181181
@ <input type="file" name="file" id="chat-input-file">
182182
@ </div>
183183
@ <div id="chat-drop-details"></div>
184184
@ </div>
185185
@ </div>
186
- @ <div id='chat-preview' class='hidden'>
186
+ @ <div id='chat-preview' class='hidden chat-view'>
187187
@ <header>Preview: (<a href='%R/md_rules' target='_blank'>markdown reference</a>)</header>
188188
@ <div id='chat-preview-content' class='message-widget-content'></div>
189189
@ <div id='chat-preview-buttons'><button id='chat-preview-close'>Close Preview</button></div>
190190
@ </div>
191
- @ <div id='chat-config' class='hidden'>
191
+ @ <div id='chat-config' class='hidden chat-view'>
192192
@ <div id='chat-config-options'></div>
193193
/* ^^^populated client-side */
194194
@ <button>Close Settings</button>
195195
@ </div>
196
- @ <div id='chat-messages-wrapper'>
196
+ @ <div id='chat-messages-wrapper' class='chat-view'>
197197
/* New chat messages get inserted immediately after this element */
198198
@ <span id='message-inject-point'></span>
199199
@ </div>
200200
fossil_free(zProjectName);
201201
builtin_fossil_js_bundle_or("popupwidget", "storage", "fetch",
202202
--- src/chat.c
+++ src/chat.c
@@ -181,21 +181,21 @@
181 @ <input type="file" name="file" id="chat-input-file">
182 @ </div>
183 @ <div id="chat-drop-details"></div>
184 @ </div>
185 @ </div>
186 @ <div id='chat-preview' class='hidden'>
187 @ <header>Preview: (<a href='%R/md_rules' target='_blank'>markdown reference</a>)</header>
188 @ <div id='chat-preview-content' class='message-widget-content'></div>
189 @ <div id='chat-preview-buttons'><button id='chat-preview-close'>Close Preview</button></div>
190 @ </div>
191 @ <div id='chat-config' class='hidden'>
192 @ <div id='chat-config-options'></div>
193 /* ^^^populated client-side */
194 @ <button>Close Settings</button>
195 @ </div>
196 @ <div id='chat-messages-wrapper'>
197 /* New chat messages get inserted immediately after this element */
198 @ <span id='message-inject-point'></span>
199 @ </div>
200 fossil_free(zProjectName);
201 builtin_fossil_js_bundle_or("popupwidget", "storage", "fetch",
202
--- src/chat.c
+++ src/chat.c
@@ -181,21 +181,21 @@
181 @ <input type="file" name="file" id="chat-input-file">
182 @ </div>
183 @ <div id="chat-drop-details"></div>
184 @ </div>
185 @ </div>
186 @ <div id='chat-preview' class='hidden chat-view'>
187 @ <header>Preview: (<a href='%R/md_rules' target='_blank'>markdown reference</a>)</header>
188 @ <div id='chat-preview-content' class='message-widget-content'></div>
189 @ <div id='chat-preview-buttons'><button id='chat-preview-close'>Close Preview</button></div>
190 @ </div>
191 @ <div id='chat-config' class='hidden chat-view'>
192 @ <div id='chat-config-options'></div>
193 /* ^^^populated client-side */
194 @ <button>Close Settings</button>
195 @ </div>
196 @ <div id='chat-messages-wrapper' class='chat-view'>
197 /* New chat messages get inserted immediately after this element */
198 @ <span id='message-inject-point'></span>
199 @ </div>
200 fossil_free(zProjectName);
201 builtin_fossil_js_bundle_or("popupwidget", "storage", "fetch",
202
+40 -46
--- src/chat.js
+++ src/chat.js
@@ -105,21 +105,22 @@
105105
pageTitle: E1('head title'),
106106
loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
107107
inputWrapper: E1("#chat-input-area"),
108108
inputLine: E1('#chat-input-line'),
109109
fileSelectWrapper: E1('#chat-input-file-area'),
110
- messagesWrapper: E1('#chat-messages-wrapper'),
110
+ viewMessages: E1('#chat-messages-wrapper'),
111111
btnSubmit: E1('#chat-message-submit'),
112112
inputSingle: E1('#chat-input-single'),
113113
inputMulti: E1('#chat-input-multi'),
114114
inputCurrent: undefined/*one of inputSingle or inputMulti*/,
115115
inputFile: E1('#chat-input-file'),
116116
contentDiv: E1('div.content'),
117
- configArea: E1('#chat-config'),
118
- previewArea: E1('#chat-preview'),
117
+ viewConfig: E1('#chat-config'),
118
+ viewPreview: E1('#chat-preview'),
119119
previewContent: E1('#chat-preview-content'),
120
- btnPreview: E1('#chat-preview-button')
120
+ btnPreview: E1('#chat-preview-button'),
121
+ views: document.querySelectorAll('.chat-view')
121122
},
122123
me: F.user.name,
123124
mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
124125
mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
125126
pageIsActive: 'visible'===document.visibilityState,
@@ -157,11 +158,11 @@
157158
this.e.inputLine.classList.remove('single-line');
158159
}else{
159160
this.e.inputCurrent = this.e.inputSingle;
160161
this.e.inputLine.classList.add('single-line');
161162
}
162
- const m = this.e.messagesWrapper,
163
+ const m = this.e.viewMessages,
163164
sTop = m.scrollTop,
164165
mh1 = m.clientHeight;
165166
D.addClass(old, 'hidden');
166167
D.removeClass(this.e.inputCurrent, 'hidden');
167168
const mh2 = m.clientHeight;
@@ -241,11 +242,11 @@
241242
/* Injects DOM element e as a new row in the chat, at the oldest
242243
end of the list if atEnd is truthy, else at the newest end of
243244
the list. */
244245
injectMessageElem: function f(e, atEnd){
245246
const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
246
- holder = this.e.messagesWrapper,
247
+ holder = this.e.viewMessages,
247248
prevMessage = this.e.newestMessage;
248249
if(atEnd){
249250
const fe = mip.nextElementSibling;
250251
if(fe) mip.parentNode.insertBefore(e, fe);
251252
else D.append(mip.parentNode, e);
@@ -337,22 +338,22 @@
337338
<0 = top of the message list, >0 = bottom of the message list,
338339
0 == the newest message (normally the same position as >1).
339340
*/
340341
scrollMessagesTo: function(where){
341342
if(where<0){
342
- Chat.e.messagesWrapper.scrollTop = 0;
343
+ Chat.e.viewMessages.scrollTop = 0;
343344
}else if(where>0){
344
- Chat.e.messagesWrapper.scrollTop = Chat.e.messagesWrapper.scrollHeight;
345
+ Chat.e.viewMessages.scrollTop = Chat.e.viewMessages.scrollHeight;
345346
}else if(Chat.e.newestMessage){
346347
Chat.e.newestMessage.scrollIntoView(false);
347348
}
348349
},
349350
toggleChatOnlyMode: function(){
350351
return this.chatOnlyMode(!this.isChatOnlyMode());
351352
},
352353
messageIsInView: function(e){
353
- return e ? overlapsElemView(e, this.e.messagesWrapper) : false;
354
+ return e ? overlapsElemView(e, this.e.viewMessages) : false;
354355
},
355356
settings:{
356357
get: (k,dflt)=>F.storage.get(k,dflt),
357358
getBool: (k,dflt)=>F.storage.getBool(k,dflt),
358359
set: (k,v)=>F.storage.set(k,v),
@@ -395,10 +396,21 @@
395396
setNewMessageSound: function f(uri){
396397
delete this.playNewMessageSound.audio;
397398
this.playNewMessageSound.uri = uri;
398399
this.settings.set('audible-alert', uri);
399400
return this;
401
+ },
402
+ /**
403
+ Expects e to be one of the elements in this.e.views.
404
+ The 'hidden' class is removed from e and added to
405
+ all other elements in that list. Returns e.
406
+ */
407
+ setCurrentView: function(e){
408
+ this.e.views.forEach(function(E){
409
+ if(e!==E) D.addClass(E,'hidden');
410
+ });
411
+ return this.e.currentView = D.removeClass(e,'hidden');
400412
}
401413
};
402414
F.fetch.beforesend = ()=>cs.ajaxStart();
403415
F.fetch.aftersend = ()=>cs.ajaxEnd();
404416
cs.e.inputCurrent = cs.e.inputSingle;
@@ -611,10 +623,11 @@
611623
cs.pageIsActive = ('visible' === document.visibilityState);
612624
if(cs.pageIsActive){
613625
cs.e.pageTitle.innerText = cs.pageTitleOrig;
614626
}
615627
}, true);
628
+ cs.setCurrentView(cs.e.viewMessages);
616629
return cs;
617630
})()/*Chat initialization*/;
618631
619632
/**
620633
Custom widget type for rendering messages (one message per
@@ -961,11 +974,11 @@
961974
*/
962975
Chat.submitMessage = function f(){
963976
if(!f.spaces){
964977
f.spaces = /\s+$/;
965978
}
966
- this.revealPreview(false);
979
+ this.setCurrentView(this.e.viewMessages);
967980
const fd = new FormData();
968981
var msg = this.inputValue().trim();
969982
if(msg && (msg.indexOf('\n')>0 || f.spaces.test(msg))){
970983
/* Cosmetic: trim whitespace from the ends of lines to try to
971984
keep copy/paste from terminals, especially wide ones, from
@@ -1033,21 +1046,16 @@
10331046
const settingsButton = document.querySelector('#chat-settings-button');
10341047
const optionsMenu = E1('#chat-config-options');
10351048
const cbToggle = function(ev){
10361049
ev.preventDefault();
10371050
ev.stopPropagation();
1038
- if(Chat.e.configArea.classList.contains('hidden')){
1039
- D.removeClass(Chat.e.configArea, 'hidden');
1040
- D.addClass([Chat.e.messagesWrapper, Chat.e.previewArea], 'hidden');
1041
- }else{
1042
- D.addClass(Chat.e.configArea, 'hidden');
1043
- D.removeClass(Chat.e.messagesWrapper, 'hidden');
1044
- }
1051
+ Chat.setCurrentView(Chat.e.currentView===Chat.e.viewConfig
1052
+ ? Chat.e.viewMessages : Chat.e.viewConfig);
10451053
return false;
10461054
};
10471055
D.attr(settingsButton, 'role', 'button').addEventListener('click', cbToggle, false);
1048
- Chat.e.configArea.querySelector('button').addEventListener('click', cbToggle, false);
1056
+ Chat.e.viewConfig.querySelector('button').addEventListener('click', cbToggle, false);
10491057
/* Settings menu entries... */
10501058
const settingsOps = [{
10511059
label: "Multi-line input",
10521060
boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
10531061
persistentSetting: 'edit-multiline',
@@ -1155,31 +1163,17 @@
11551163
})()/*#chat-settings-button setup*/;
11561164
11571165
(function(){/*set up message preview*/
11581166
const btnPreview = Chat.e.btnPreview;
11591167
Chat.setPreviewText = function(t){
1160
- this.revealPreview(true).e.previewContent.innerHTML = t;
1161
- this.e.previewArea.querySelectorAll('a').forEach(addAnchorTargetBlank);
1168
+ this.setCurrentView(this.e.viewPreview);
1169
+ this.e.previewContent.innerHTML = t;
1170
+ this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
11621171
this.e.inputCurrent.focus();
11631172
};
1164
- /**
1165
- Reveals preview area if showIt is true, else hides it.
1166
- This also shows/hides other elements, "as appropriate."
1167
- */
1168
- Chat.revealPreview = function(showIt){
1169
- if(showIt){
1170
- D.removeClass(Chat.e.previewArea, 'hidden');
1171
- D.addClass([Chat.e.messagesWrapper, Chat.e.configArea],
1172
- 'hidden');
1173
- }else{
1174
- D.addClass([Chat.e.configArea, Chat.e.previewArea], 'hidden');
1175
- D.removeClass(Chat.e.messagesWrapper, 'hidden');
1176
- }
1177
- return this;
1178
- };
1179
- Chat.e.previewArea.querySelector('#chat-preview-close').
1180
- addEventListener('click', ()=>Chat.revealPreview(false), false);
1173
+ Chat.e.viewPreview.querySelector('#chat-preview-close').
1174
+ addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
11811175
let previewPending = false;
11821176
const elemsToEnable = [
11831177
btnPreview, Chat.e.btnSubmit,
11841178
Chat.e.inputSingle, Chat.e.inputMulti];
11851179
Chat.disableDuringAjax.push(btnPreview);
@@ -1277,14 +1271,14 @@
12771271
D.fieldset(loadLegend), "id", "load-msg-toolbar"
12781272
);
12791273
Chat.disableDuringAjax.push(toolbar);
12801274
/* Loads the next n oldest messages, or all previous history if n is negative. */
12811275
const loadOldMessages = function(n){
1282
- Chat.e.messagesWrapper.classList.add('loading');
1276
+ Chat.e.viewMessages.classList.add('loading');
12831277
Chat._isBatchLoading = true;
1284
- const scrollHt = Chat.e.messagesWrapper.scrollHeight,
1285
- scrollTop = Chat.e.messagesWrapper.scrollTop;
1278
+ const scrollHt = Chat.e.viewMessages.scrollHeight,
1279
+ scrollTop = Chat.e.viewMessages.scrollTop;
12861280
F.fetch("chat-poll",{
12871281
urlParams:{
12881282
before: Chat.mnMsg,
12891283
n: n
12901284
},
@@ -1318,17 +1312,17 @@
13181312
}
13191313
if(gotMessages > 0){
13201314
F.toast.message("Loaded "+gotMessages+" older messages.");
13211315
/* Return scroll position to where it was when the history load
13221316
was requested, per user request */
1323
- Chat.e.messagesWrapper.scrollTo(
1324
- 0, Chat.e.messagesWrapper.scrollHeight - scrollHt + scrollTop
1317
+ Chat.e.viewMessages.scrollTo(
1318
+ 0, Chat.e.viewMessages.scrollHeight - scrollHt + scrollTop
13251319
);
13261320
}
13271321
},
13281322
aftersend:function(){
1329
- Chat.e.messagesWrapper.classList.remove('loading');
1323
+ Chat.e.viewMessages.classList.remove('loading');
13301324
Chat.ajaxEnd();
13311325
}
13321326
});
13331327
};
13341328
const wrapper = D.div(); /* browsers don't all properly handle >1 child in a fieldset */;
@@ -1337,19 +1331,19 @@
13371331
D.append(wrapper, btn);
13381332
btn.addEventListener('click',()=>loadOldMessages(Chat.loadMessageCount));
13391333
btn = D.button("All previous messages");
13401334
D.append(wrapper, btn);
13411335
btn.addEventListener('click',()=>loadOldMessages(-1));
1342
- D.append(Chat.e.messagesWrapper, toolbar);
1336
+ D.append(Chat.e.viewMessages, toolbar);
13431337
toolbar.disabled = true /*will be enabled when msg load finishes */;
13441338
})()/*end history loading widget setup*/;
13451339
13461340
const afterFetch = function f(){
13471341
if(true===f.isFirstCall){
13481342
f.isFirstCall = false;
13491343
Chat.ajaxEnd();
1350
- Chat.e.messagesWrapper.classList.remove('loading');
1344
+ Chat.e.viewMessages.classList.remove('loading');
13511345
setTimeout(function(){
13521346
Chat.scrollMessagesTo(1);
13531347
}, 250);
13541348
}
13551349
if(Chat._gotServerError && Chat.intervalTimer){
@@ -1367,11 +1361,11 @@
13671361
f.running = true;
13681362
Chat._isBatchLoading = f.isFirstCall;
13691363
if(true===f.isFirstCall){
13701364
f.isFirstCall = false;
13711365
Chat.ajaxStart();
1372
- Chat.e.messagesWrapper.classList.add('loading');
1366
+ Chat.e.viewMessages.classList.add('loading');
13731367
}
13741368
F.fetch("chat-poll",{
13751369
timeout: 420 * 1000/*FIXME: get the value from the server*/,
13761370
urlParams:{
13771371
name: Chat.mxMsg
13781372
--- src/chat.js
+++ src/chat.js
@@ -105,21 +105,22 @@
105 pageTitle: E1('head title'),
106 loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
107 inputWrapper: E1("#chat-input-area"),
108 inputLine: E1('#chat-input-line'),
109 fileSelectWrapper: E1('#chat-input-file-area'),
110 messagesWrapper: E1('#chat-messages-wrapper'),
111 btnSubmit: E1('#chat-message-submit'),
112 inputSingle: E1('#chat-input-single'),
113 inputMulti: E1('#chat-input-multi'),
114 inputCurrent: undefined/*one of inputSingle or inputMulti*/,
115 inputFile: E1('#chat-input-file'),
116 contentDiv: E1('div.content'),
117 configArea: E1('#chat-config'),
118 previewArea: E1('#chat-preview'),
119 previewContent: E1('#chat-preview-content'),
120 btnPreview: E1('#chat-preview-button')
 
121 },
122 me: F.user.name,
123 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
124 mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
125 pageIsActive: 'visible'===document.visibilityState,
@@ -157,11 +158,11 @@
157 this.e.inputLine.classList.remove('single-line');
158 }else{
159 this.e.inputCurrent = this.e.inputSingle;
160 this.e.inputLine.classList.add('single-line');
161 }
162 const m = this.e.messagesWrapper,
163 sTop = m.scrollTop,
164 mh1 = m.clientHeight;
165 D.addClass(old, 'hidden');
166 D.removeClass(this.e.inputCurrent, 'hidden');
167 const mh2 = m.clientHeight;
@@ -241,11 +242,11 @@
241 /* Injects DOM element e as a new row in the chat, at the oldest
242 end of the list if atEnd is truthy, else at the newest end of
243 the list. */
244 injectMessageElem: function f(e, atEnd){
245 const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
246 holder = this.e.messagesWrapper,
247 prevMessage = this.e.newestMessage;
248 if(atEnd){
249 const fe = mip.nextElementSibling;
250 if(fe) mip.parentNode.insertBefore(e, fe);
251 else D.append(mip.parentNode, e);
@@ -337,22 +338,22 @@
337 <0 = top of the message list, >0 = bottom of the message list,
338 0 == the newest message (normally the same position as >1).
339 */
340 scrollMessagesTo: function(where){
341 if(where<0){
342 Chat.e.messagesWrapper.scrollTop = 0;
343 }else if(where>0){
344 Chat.e.messagesWrapper.scrollTop = Chat.e.messagesWrapper.scrollHeight;
345 }else if(Chat.e.newestMessage){
346 Chat.e.newestMessage.scrollIntoView(false);
347 }
348 },
349 toggleChatOnlyMode: function(){
350 return this.chatOnlyMode(!this.isChatOnlyMode());
351 },
352 messageIsInView: function(e){
353 return e ? overlapsElemView(e, this.e.messagesWrapper) : false;
354 },
355 settings:{
356 get: (k,dflt)=>F.storage.get(k,dflt),
357 getBool: (k,dflt)=>F.storage.getBool(k,dflt),
358 set: (k,v)=>F.storage.set(k,v),
@@ -395,10 +396,21 @@
395 setNewMessageSound: function f(uri){
396 delete this.playNewMessageSound.audio;
397 this.playNewMessageSound.uri = uri;
398 this.settings.set('audible-alert', uri);
399 return this;
 
 
 
 
 
 
 
 
 
 
 
400 }
401 };
402 F.fetch.beforesend = ()=>cs.ajaxStart();
403 F.fetch.aftersend = ()=>cs.ajaxEnd();
404 cs.e.inputCurrent = cs.e.inputSingle;
@@ -611,10 +623,11 @@
611 cs.pageIsActive = ('visible' === document.visibilityState);
612 if(cs.pageIsActive){
613 cs.e.pageTitle.innerText = cs.pageTitleOrig;
614 }
615 }, true);
 
616 return cs;
617 })()/*Chat initialization*/;
618
619 /**
620 Custom widget type for rendering messages (one message per
@@ -961,11 +974,11 @@
961 */
962 Chat.submitMessage = function f(){
963 if(!f.spaces){
964 f.spaces = /\s+$/;
965 }
966 this.revealPreview(false);
967 const fd = new FormData();
968 var msg = this.inputValue().trim();
969 if(msg && (msg.indexOf('\n')>0 || f.spaces.test(msg))){
970 /* Cosmetic: trim whitespace from the ends of lines to try to
971 keep copy/paste from terminals, especially wide ones, from
@@ -1033,21 +1046,16 @@
1033 const settingsButton = document.querySelector('#chat-settings-button');
1034 const optionsMenu = E1('#chat-config-options');
1035 const cbToggle = function(ev){
1036 ev.preventDefault();
1037 ev.stopPropagation();
1038 if(Chat.e.configArea.classList.contains('hidden')){
1039 D.removeClass(Chat.e.configArea, 'hidden');
1040 D.addClass([Chat.e.messagesWrapper, Chat.e.previewArea], 'hidden');
1041 }else{
1042 D.addClass(Chat.e.configArea, 'hidden');
1043 D.removeClass(Chat.e.messagesWrapper, 'hidden');
1044 }
1045 return false;
1046 };
1047 D.attr(settingsButton, 'role', 'button').addEventListener('click', cbToggle, false);
1048 Chat.e.configArea.querySelector('button').addEventListener('click', cbToggle, false);
1049 /* Settings menu entries... */
1050 const settingsOps = [{
1051 label: "Multi-line input",
1052 boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
1053 persistentSetting: 'edit-multiline',
@@ -1155,31 +1163,17 @@
1155 })()/*#chat-settings-button setup*/;
1156
1157 (function(){/*set up message preview*/
1158 const btnPreview = Chat.e.btnPreview;
1159 Chat.setPreviewText = function(t){
1160 this.revealPreview(true).e.previewContent.innerHTML = t;
1161 this.e.previewArea.querySelectorAll('a').forEach(addAnchorTargetBlank);
 
1162 this.e.inputCurrent.focus();
1163 };
1164 /**
1165 Reveals preview area if showIt is true, else hides it.
1166 This also shows/hides other elements, "as appropriate."
1167 */
1168 Chat.revealPreview = function(showIt){
1169 if(showIt){
1170 D.removeClass(Chat.e.previewArea, 'hidden');
1171 D.addClass([Chat.e.messagesWrapper, Chat.e.configArea],
1172 'hidden');
1173 }else{
1174 D.addClass([Chat.e.configArea, Chat.e.previewArea], 'hidden');
1175 D.removeClass(Chat.e.messagesWrapper, 'hidden');
1176 }
1177 return this;
1178 };
1179 Chat.e.previewArea.querySelector('#chat-preview-close').
1180 addEventListener('click', ()=>Chat.revealPreview(false), false);
1181 let previewPending = false;
1182 const elemsToEnable = [
1183 btnPreview, Chat.e.btnSubmit,
1184 Chat.e.inputSingle, Chat.e.inputMulti];
1185 Chat.disableDuringAjax.push(btnPreview);
@@ -1277,14 +1271,14 @@
1277 D.fieldset(loadLegend), "id", "load-msg-toolbar"
1278 );
1279 Chat.disableDuringAjax.push(toolbar);
1280 /* Loads the next n oldest messages, or all previous history if n is negative. */
1281 const loadOldMessages = function(n){
1282 Chat.e.messagesWrapper.classList.add('loading');
1283 Chat._isBatchLoading = true;
1284 const scrollHt = Chat.e.messagesWrapper.scrollHeight,
1285 scrollTop = Chat.e.messagesWrapper.scrollTop;
1286 F.fetch("chat-poll",{
1287 urlParams:{
1288 before: Chat.mnMsg,
1289 n: n
1290 },
@@ -1318,17 +1312,17 @@
1318 }
1319 if(gotMessages > 0){
1320 F.toast.message("Loaded "+gotMessages+" older messages.");
1321 /* Return scroll position to where it was when the history load
1322 was requested, per user request */
1323 Chat.e.messagesWrapper.scrollTo(
1324 0, Chat.e.messagesWrapper.scrollHeight - scrollHt + scrollTop
1325 );
1326 }
1327 },
1328 aftersend:function(){
1329 Chat.e.messagesWrapper.classList.remove('loading');
1330 Chat.ajaxEnd();
1331 }
1332 });
1333 };
1334 const wrapper = D.div(); /* browsers don't all properly handle >1 child in a fieldset */;
@@ -1337,19 +1331,19 @@
1337 D.append(wrapper, btn);
1338 btn.addEventListener('click',()=>loadOldMessages(Chat.loadMessageCount));
1339 btn = D.button("All previous messages");
1340 D.append(wrapper, btn);
1341 btn.addEventListener('click',()=>loadOldMessages(-1));
1342 D.append(Chat.e.messagesWrapper, toolbar);
1343 toolbar.disabled = true /*will be enabled when msg load finishes */;
1344 })()/*end history loading widget setup*/;
1345
1346 const afterFetch = function f(){
1347 if(true===f.isFirstCall){
1348 f.isFirstCall = false;
1349 Chat.ajaxEnd();
1350 Chat.e.messagesWrapper.classList.remove('loading');
1351 setTimeout(function(){
1352 Chat.scrollMessagesTo(1);
1353 }, 250);
1354 }
1355 if(Chat._gotServerError && Chat.intervalTimer){
@@ -1367,11 +1361,11 @@
1367 f.running = true;
1368 Chat._isBatchLoading = f.isFirstCall;
1369 if(true===f.isFirstCall){
1370 f.isFirstCall = false;
1371 Chat.ajaxStart();
1372 Chat.e.messagesWrapper.classList.add('loading');
1373 }
1374 F.fetch("chat-poll",{
1375 timeout: 420 * 1000/*FIXME: get the value from the server*/,
1376 urlParams:{
1377 name: Chat.mxMsg
1378
--- src/chat.js
+++ src/chat.js
@@ -105,21 +105,22 @@
105 pageTitle: E1('head title'),
106 loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
107 inputWrapper: E1("#chat-input-area"),
108 inputLine: E1('#chat-input-line'),
109 fileSelectWrapper: E1('#chat-input-file-area'),
110 viewMessages: E1('#chat-messages-wrapper'),
111 btnSubmit: E1('#chat-message-submit'),
112 inputSingle: E1('#chat-input-single'),
113 inputMulti: E1('#chat-input-multi'),
114 inputCurrent: undefined/*one of inputSingle or inputMulti*/,
115 inputFile: E1('#chat-input-file'),
116 contentDiv: E1('div.content'),
117 viewConfig: E1('#chat-config'),
118 viewPreview: E1('#chat-preview'),
119 previewContent: E1('#chat-preview-content'),
120 btnPreview: E1('#chat-preview-button'),
121 views: document.querySelectorAll('.chat-view')
122 },
123 me: F.user.name,
124 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
125 mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
126 pageIsActive: 'visible'===document.visibilityState,
@@ -157,11 +158,11 @@
158 this.e.inputLine.classList.remove('single-line');
159 }else{
160 this.e.inputCurrent = this.e.inputSingle;
161 this.e.inputLine.classList.add('single-line');
162 }
163 const m = this.e.viewMessages,
164 sTop = m.scrollTop,
165 mh1 = m.clientHeight;
166 D.addClass(old, 'hidden');
167 D.removeClass(this.e.inputCurrent, 'hidden');
168 const mh2 = m.clientHeight;
@@ -241,11 +242,11 @@
242 /* Injects DOM element e as a new row in the chat, at the oldest
243 end of the list if atEnd is truthy, else at the newest end of
244 the list. */
245 injectMessageElem: function f(e, atEnd){
246 const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
247 holder = this.e.viewMessages,
248 prevMessage = this.e.newestMessage;
249 if(atEnd){
250 const fe = mip.nextElementSibling;
251 if(fe) mip.parentNode.insertBefore(e, fe);
252 else D.append(mip.parentNode, e);
@@ -337,22 +338,22 @@
338 <0 = top of the message list, >0 = bottom of the message list,
339 0 == the newest message (normally the same position as >1).
340 */
341 scrollMessagesTo: function(where){
342 if(where<0){
343 Chat.e.viewMessages.scrollTop = 0;
344 }else if(where>0){
345 Chat.e.viewMessages.scrollTop = Chat.e.viewMessages.scrollHeight;
346 }else if(Chat.e.newestMessage){
347 Chat.e.newestMessage.scrollIntoView(false);
348 }
349 },
350 toggleChatOnlyMode: function(){
351 return this.chatOnlyMode(!this.isChatOnlyMode());
352 },
353 messageIsInView: function(e){
354 return e ? overlapsElemView(e, this.e.viewMessages) : false;
355 },
356 settings:{
357 get: (k,dflt)=>F.storage.get(k,dflt),
358 getBool: (k,dflt)=>F.storage.getBool(k,dflt),
359 set: (k,v)=>F.storage.set(k,v),
@@ -395,10 +396,21 @@
396 setNewMessageSound: function f(uri){
397 delete this.playNewMessageSound.audio;
398 this.playNewMessageSound.uri = uri;
399 this.settings.set('audible-alert', uri);
400 return this;
401 },
402 /**
403 Expects e to be one of the elements in this.e.views.
404 The 'hidden' class is removed from e and added to
405 all other elements in that list. Returns e.
406 */
407 setCurrentView: function(e){
408 this.e.views.forEach(function(E){
409 if(e!==E) D.addClass(E,'hidden');
410 });
411 return this.e.currentView = D.removeClass(e,'hidden');
412 }
413 };
414 F.fetch.beforesend = ()=>cs.ajaxStart();
415 F.fetch.aftersend = ()=>cs.ajaxEnd();
416 cs.e.inputCurrent = cs.e.inputSingle;
@@ -611,10 +623,11 @@
623 cs.pageIsActive = ('visible' === document.visibilityState);
624 if(cs.pageIsActive){
625 cs.e.pageTitle.innerText = cs.pageTitleOrig;
626 }
627 }, true);
628 cs.setCurrentView(cs.e.viewMessages);
629 return cs;
630 })()/*Chat initialization*/;
631
632 /**
633 Custom widget type for rendering messages (one message per
@@ -961,11 +974,11 @@
974 */
975 Chat.submitMessage = function f(){
976 if(!f.spaces){
977 f.spaces = /\s+$/;
978 }
979 this.setCurrentView(this.e.viewMessages);
980 const fd = new FormData();
981 var msg = this.inputValue().trim();
982 if(msg && (msg.indexOf('\n')>0 || f.spaces.test(msg))){
983 /* Cosmetic: trim whitespace from the ends of lines to try to
984 keep copy/paste from terminals, especially wide ones, from
@@ -1033,21 +1046,16 @@
1046 const settingsButton = document.querySelector('#chat-settings-button');
1047 const optionsMenu = E1('#chat-config-options');
1048 const cbToggle = function(ev){
1049 ev.preventDefault();
1050 ev.stopPropagation();
1051 Chat.setCurrentView(Chat.e.currentView===Chat.e.viewConfig
1052 ? Chat.e.viewMessages : Chat.e.viewConfig);
 
 
 
 
 
1053 return false;
1054 };
1055 D.attr(settingsButton, 'role', 'button').addEventListener('click', cbToggle, false);
1056 Chat.e.viewConfig.querySelector('button').addEventListener('click', cbToggle, false);
1057 /* Settings menu entries... */
1058 const settingsOps = [{
1059 label: "Multi-line input",
1060 boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
1061 persistentSetting: 'edit-multiline',
@@ -1155,31 +1163,17 @@
1163 })()/*#chat-settings-button setup*/;
1164
1165 (function(){/*set up message preview*/
1166 const btnPreview = Chat.e.btnPreview;
1167 Chat.setPreviewText = function(t){
1168 this.setCurrentView(this.e.viewPreview);
1169 this.e.previewContent.innerHTML = t;
1170 this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
1171 this.e.inputCurrent.focus();
1172 };
1173 Chat.e.viewPreview.querySelector('#chat-preview-close').
1174 addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1175 let previewPending = false;
1176 const elemsToEnable = [
1177 btnPreview, Chat.e.btnSubmit,
1178 Chat.e.inputSingle, Chat.e.inputMulti];
1179 Chat.disableDuringAjax.push(btnPreview);
@@ -1277,14 +1271,14 @@
1271 D.fieldset(loadLegend), "id", "load-msg-toolbar"
1272 );
1273 Chat.disableDuringAjax.push(toolbar);
1274 /* Loads the next n oldest messages, or all previous history if n is negative. */
1275 const loadOldMessages = function(n){
1276 Chat.e.viewMessages.classList.add('loading');
1277 Chat._isBatchLoading = true;
1278 const scrollHt = Chat.e.viewMessages.scrollHeight,
1279 scrollTop = Chat.e.viewMessages.scrollTop;
1280 F.fetch("chat-poll",{
1281 urlParams:{
1282 before: Chat.mnMsg,
1283 n: n
1284 },
@@ -1318,17 +1312,17 @@
1312 }
1313 if(gotMessages > 0){
1314 F.toast.message("Loaded "+gotMessages+" older messages.");
1315 /* Return scroll position to where it was when the history load
1316 was requested, per user request */
1317 Chat.e.viewMessages.scrollTo(
1318 0, Chat.e.viewMessages.scrollHeight - scrollHt + scrollTop
1319 );
1320 }
1321 },
1322 aftersend:function(){
1323 Chat.e.viewMessages.classList.remove('loading');
1324 Chat.ajaxEnd();
1325 }
1326 });
1327 };
1328 const wrapper = D.div(); /* browsers don't all properly handle >1 child in a fieldset */;
@@ -1337,19 +1331,19 @@
1331 D.append(wrapper, btn);
1332 btn.addEventListener('click',()=>loadOldMessages(Chat.loadMessageCount));
1333 btn = D.button("All previous messages");
1334 D.append(wrapper, btn);
1335 btn.addEventListener('click',()=>loadOldMessages(-1));
1336 D.append(Chat.e.viewMessages, toolbar);
1337 toolbar.disabled = true /*will be enabled when msg load finishes */;
1338 })()/*end history loading widget setup*/;
1339
1340 const afterFetch = function f(){
1341 if(true===f.isFirstCall){
1342 f.isFirstCall = false;
1343 Chat.ajaxEnd();
1344 Chat.e.viewMessages.classList.remove('loading');
1345 setTimeout(function(){
1346 Chat.scrollMessagesTo(1);
1347 }, 250);
1348 }
1349 if(Chat._gotServerError && Chat.intervalTimer){
@@ -1367,11 +1361,11 @@
1361 f.running = true;
1362 Chat._isBatchLoading = f.isFirstCall;
1363 if(true===f.isFirstCall){
1364 f.isFirstCall = false;
1365 Chat.ajaxStart();
1366 Chat.e.viewMessages.classList.add('loading');
1367 }
1368 F.fetch("chat-poll",{
1369 timeout: 420 * 1000/*FIXME: get the value from the server*/,
1370 urlParams:{
1371 name: Chat.mxMsg
1372

Keyboard Shortcuts

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