Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | 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. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
593d3a3a1e5455a40663ce45dc50915d |
| User & Date: | stephan 2021-09-22 11:15:21.064 |
Context
|
2021-09-22
| ||
| 12:22 | Micro-adjustments to /chat CSS to squeeze a tiny bit more space from the bottom of the screen. ... (check-in: 62deb8f794 user: stephan tags: trunk) | |
| 11:15 | 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. ... (check-in: 593d3a3a1e user: stephan tags: trunk) | |
| 08:43 | Revert part of the previous commit so that only buttons, not textareas an input fields, are affected. ... (check-in: 15d58775a7 user: stephan tags: trunk) | |
Changes
Changes to src/chat.c.
| ︙ | ︙ | |||
179 180 181 182 183 184 185 | @ your environment. @ </div> @ <input type="file" name="file" id="chat-input-file"> @ </div> @ <div id="chat-drop-details"></div> @ </div> @ </div> | | | | | 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 |
@ your environment.
@ </div>
@ <input type="file" name="file" id="chat-input-file">
@ </div>
@ <div id="chat-drop-details"></div>
@ </div>
@ </div>
@ <div id='chat-preview' class='hidden chat-view'>
@ <header>Preview: (<a href='%R/md_rules' target='_blank'>markdown reference</a>)</header>
@ <div id='chat-preview-content' class='message-widget-content'></div>
@ <div id='chat-preview-buttons'><button id='chat-preview-close'>Close Preview</button></div>
@ </div>
@ <div id='chat-config' class='hidden chat-view'>
@ <div id='chat-config-options'></div>
/* ^^^populated client-side */
@ <button>Close Settings</button>
@ </div>
@ <div id='chat-messages-wrapper' class='chat-view'>
/* New chat messages get inserted immediately after this element */
@ <span id='message-inject-point'></span>
@ </div>
fossil_free(zProjectName);
builtin_fossil_js_bundle_or("popupwidget", "storage", "fetch",
"pikchr", "confirmer", NULL);
/* Always in-line the javascript for the chat page */
|
| ︙ | ︙ |
Changes to src/chat.js.
| ︙ | ︙ | |||
103 104 105 106 107 108 109 |
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"),
inputLine: E1('#chat-input-line'),
fileSelectWrapper: E1('#chat-input-file-area'),
| | | | | > | 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
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"),
inputLine: E1('#chat-input-line'),
fileSelectWrapper: E1('#chat-input-file-area'),
viewMessages: E1('#chat-messages-wrapper'),
btnSubmit: E1('#chat-message-submit'),
inputSingle: E1('#chat-input-single'),
inputMulti: E1('#chat-input-multi'),
inputCurrent: undefined/*one of inputSingle or inputMulti*/,
inputFile: E1('#chat-input-file'),
contentDiv: E1('div.content'),
viewConfig: E1('#chat-config'),
viewPreview: E1('#chat-preview'),
previewContent: E1('#chat-preview-content'),
btnPreview: E1('#chat-preview-button'),
views: document.querySelectorAll('.chat-view')
},
me: F.user.name,
mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
pageIsActive: 'visible'===document.visibilityState,
changesSincePageHidden: 0,
notificationBubbleColor: 'white',
|
| ︙ | ︙ | |||
155 156 157 158 159 160 161 |
if(this.e.inputCurrent === this.e.inputSingle){
this.e.inputCurrent = this.e.inputMulti;
this.e.inputLine.classList.remove('single-line');
}else{
this.e.inputCurrent = this.e.inputSingle;
this.e.inputLine.classList.add('single-line');
}
| | | 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
if(this.e.inputCurrent === this.e.inputSingle){
this.e.inputCurrent = this.e.inputMulti;
this.e.inputLine.classList.remove('single-line');
}else{
this.e.inputCurrent = this.e.inputSingle;
this.e.inputLine.classList.add('single-line');
}
const m = this.e.viewMessages,
sTop = m.scrollTop,
mh1 = m.clientHeight;
D.addClass(old, 'hidden');
D.removeClass(this.e.inputCurrent, 'hidden');
const mh2 = m.clientHeight;
m.scrollTo(0, sTop + (mh1-mh2));
this.e.inputCurrent.value = old.value;
|
| ︙ | ︙ | |||
239 240 241 242 243 244 245 |
return this;
},
/* Injects DOM element e as a new row in the chat, at the oldest
end of the list if atEnd is truthy, else at the newest end of
the list. */
injectMessageElem: function f(e, atEnd){
const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
| | | 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
return this;
},
/* Injects DOM element e as a new row in the chat, at the oldest
end of the list if atEnd is truthy, else at the newest end of
the list. */
injectMessageElem: function f(e, atEnd){
const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
holder = this.e.viewMessages,
prevMessage = this.e.newestMessage;
if(atEnd){
const fe = mip.nextElementSibling;
if(fe) mip.parentNode.insertBefore(e, fe);
else D.append(mip.parentNode, e);
}else{
D.append(holder,e);
|
| ︙ | ︙ | |||
335 336 337 338 339 340 341 |
},
/** Tries to scroll the message area to...
<0 = top of the message list, >0 = bottom of the message list,
0 == the newest message (normally the same position as >1).
*/
scrollMessagesTo: function(where){
if(where<0){
| | | | | 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 |
},
/** Tries to scroll the message area to...
<0 = top of the message list, >0 = bottom of the message list,
0 == the newest message (normally the same position as >1).
*/
scrollMessagesTo: function(where){
if(where<0){
Chat.e.viewMessages.scrollTop = 0;
}else if(where>0){
Chat.e.viewMessages.scrollTop = Chat.e.viewMessages.scrollHeight;
}else if(Chat.e.newestMessage){
Chat.e.newestMessage.scrollIntoView(false);
}
},
toggleChatOnlyMode: function(){
return this.chatOnlyMode(!this.isChatOnlyMode());
},
messageIsInView: function(e){
return e ? overlapsElemView(e, this.e.viewMessages) : false;
},
settings:{
get: (k,dflt)=>F.storage.get(k,dflt),
getBool: (k,dflt)=>F.storage.getBool(k,dflt),
set: (k,v)=>F.storage.set(k,v),
/* Toggles the boolean setting specified by k. Returns the
new value.*/
|
| ︙ | ︙ | |||
393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
this.
*/
setNewMessageSound: function f(uri){
delete this.playNewMessageSound.audio;
this.playNewMessageSound.uri = uri;
this.settings.set('audible-alert', uri);
return this;
}
};
F.fetch.beforesend = ()=>cs.ajaxStart();
F.fetch.aftersend = ()=>cs.ajaxEnd();
cs.e.inputCurrent = cs.e.inputSingle;
/* Install default settings... */
Object.keys(cs.settings.defaults).forEach(function(k){
| > > > > > > > > > > > | 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 |
this.
*/
setNewMessageSound: function f(uri){
delete this.playNewMessageSound.audio;
this.playNewMessageSound.uri = uri;
this.settings.set('audible-alert', uri);
return this;
},
/**
Expects e to be one of the elements in this.e.views.
The 'hidden' class is removed from e and added to
all other elements in that list. Returns e.
*/
setCurrentView: function(e){
this.e.views.forEach(function(E){
if(e!==E) D.addClass(E,'hidden');
});
return this.e.currentView = D.removeClass(e,'hidden');
}
};
F.fetch.beforesend = ()=>cs.ajaxStart();
F.fetch.aftersend = ()=>cs.ajaxEnd();
cs.e.inputCurrent = cs.e.inputSingle;
/* Install default settings... */
Object.keys(cs.settings.defaults).forEach(function(k){
|
| ︙ | ︙ | |||
609 610 611 612 613 614 615 616 617 618 619 620 621 622 |
};
document.addEventListener('visibilitychange', function(ev){
cs.pageIsActive = ('visible' === document.visibilityState);
if(cs.pageIsActive){
cs.e.pageTitle.innerText = cs.pageTitleOrig;
}
}, true);
return cs;
})()/*Chat initialization*/;
/**
Custom widget type for rendering messages (one message per
instance). These are modelled after FIELDSET elements but we
don't use FIELDSET because of cross-browser inconsistencies in
| > | 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 |
};
document.addEventListener('visibilitychange', function(ev){
cs.pageIsActive = ('visible' === document.visibilityState);
if(cs.pageIsActive){
cs.e.pageTitle.innerText = cs.pageTitleOrig;
}
}, true);
cs.setCurrentView(cs.e.viewMessages);
return cs;
})()/*Chat initialization*/;
/**
Custom widget type for rendering messages (one message per
instance). These are modelled after FIELDSET elements but we
don't use FIELDSET because of cross-browser inconsistencies in
|
| ︙ | ︙ | |||
959 960 961 962 963 964 965 |
and/or the file attachment field to the server. If both are
empty, this is a no-op.
*/
Chat.submitMessage = function f(){
if(!f.spaces){
f.spaces = /\s+$/;
}
| | | 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 |
and/or the file attachment field to the server. If both are
empty, this is a no-op.
*/
Chat.submitMessage = function f(){
if(!f.spaces){
f.spaces = /\s+$/;
}
this.setCurrentView(this.e.viewMessages);
const fd = new FormData();
var msg = this.inputValue().trim();
if(msg && (msg.indexOf('\n')>0 || f.spaces.test(msg))){
/* Cosmetic: trim whitespace from the ends of lines to try to
keep copy/paste from terminals, especially wide ones, from
forcing a horizontal scrollbar on all clients. */
const xmsg = msg.split('\n');
|
| ︙ | ︙ | |||
1031 1032 1033 1034 1035 1036 1037 |
(function(){/*Set up #chat-settings-button */
const settingsButton = document.querySelector('#chat-settings-button');
const optionsMenu = E1('#chat-config-options');
const cbToggle = function(ev){
ev.preventDefault();
ev.stopPropagation();
| < | < < | < < | | 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 |
(function(){/*Set up #chat-settings-button */
const settingsButton = document.querySelector('#chat-settings-button');
const optionsMenu = E1('#chat-config-options');
const cbToggle = function(ev){
ev.preventDefault();
ev.stopPropagation();
Chat.setCurrentView(Chat.e.currentView===Chat.e.viewConfig
? Chat.e.viewMessages : Chat.e.viewConfig);
return false;
};
D.attr(settingsButton, 'role', 'button').addEventListener('click', cbToggle, false);
Chat.e.viewConfig.querySelector('button').addEventListener('click', cbToggle, false);
/* Settings menu entries... */
const settingsOps = [{
label: "Multi-line input",
boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
persistentSetting: 'edit-multiline',
callback: function(){
Chat.inputToggleSingleMulti();
|
| ︙ | ︙ | |||
1153 1154 1155 1156 1157 1158 1159 |
}
//settingsButton.click()/*for for development*/;
})()/*#chat-settings-button setup*/;
(function(){/*set up message preview*/
const btnPreview = Chat.e.btnPreview;
Chat.setPreviewText = function(t){
| > | | < < < < < < < < < < < < < < < | | | 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 |
}
//settingsButton.click()/*for for development*/;
})()/*#chat-settings-button setup*/;
(function(){/*set up message preview*/
const btnPreview = Chat.e.btnPreview;
Chat.setPreviewText = function(t){
this.setCurrentView(this.e.viewPreview);
this.e.previewContent.innerHTML = t;
this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
this.e.inputCurrent.focus();
};
Chat.e.viewPreview.querySelector('#chat-preview-close').
addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
let previewPending = false;
const elemsToEnable = [
btnPreview, Chat.e.btnSubmit,
Chat.e.inputSingle, Chat.e.inputMulti];
Chat.disableDuringAjax.push(btnPreview);
const submit = function(ev){
ev.preventDefault();
|
| ︙ | ︙ | |||
1275 1276 1277 1278 1279 1280 1281 |
const loadLegend = D.legend("Load...");
const toolbar = Chat.e.loadOlderToolbar = D.attr(
D.fieldset(loadLegend), "id", "load-msg-toolbar"
);
Chat.disableDuringAjax.push(toolbar);
/* Loads the next n oldest messages, or all previous history if n is negative. */
const loadOldMessages = function(n){
| | | | | 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 |
const loadLegend = D.legend("Load...");
const toolbar = Chat.e.loadOlderToolbar = D.attr(
D.fieldset(loadLegend), "id", "load-msg-toolbar"
);
Chat.disableDuringAjax.push(toolbar);
/* Loads the next n oldest messages, or all previous history if n is negative. */
const loadOldMessages = function(n){
Chat.e.viewMessages.classList.add('loading');
Chat._isBatchLoading = true;
const scrollHt = Chat.e.viewMessages.scrollHeight,
scrollTop = Chat.e.viewMessages.scrollTop;
F.fetch("chat-poll",{
urlParams:{
before: Chat.mnMsg,
n: n
},
responseType: 'json',
onerror:function(err){
|
| ︙ | ︙ | |||
1316 1317 1318 1319 1320 1321 1322 |
if(ndx>=0) Chat.disableDuringAjax.splice(ndx,1);
Chat.e.loadOlderToolbar.disabled = true;
}
if(gotMessages > 0){
F.toast.message("Loaded "+gotMessages+" older messages.");
/* Return scroll position to where it was when the history load
was requested, per user request */
| | | | | | | 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 |
if(ndx>=0) Chat.disableDuringAjax.splice(ndx,1);
Chat.e.loadOlderToolbar.disabled = true;
}
if(gotMessages > 0){
F.toast.message("Loaded "+gotMessages+" older messages.");
/* Return scroll position to where it was when the history load
was requested, per user request */
Chat.e.viewMessages.scrollTo(
0, Chat.e.viewMessages.scrollHeight - scrollHt + scrollTop
);
}
},
aftersend:function(){
Chat.e.viewMessages.classList.remove('loading');
Chat.ajaxEnd();
}
});
};
const wrapper = D.div(); /* browsers don't all properly handle >1 child in a fieldset */;
D.append(toolbar, wrapper);
var btn = D.button("Previous "+Chat.loadMessageCount+" messages");
D.append(wrapper, btn);
btn.addEventListener('click',()=>loadOldMessages(Chat.loadMessageCount));
btn = D.button("All previous messages");
D.append(wrapper, btn);
btn.addEventListener('click',()=>loadOldMessages(-1));
D.append(Chat.e.viewMessages, toolbar);
toolbar.disabled = true /*will be enabled when msg load finishes */;
})()/*end history loading widget setup*/;
const afterFetch = function f(){
if(true===f.isFirstCall){
f.isFirstCall = false;
Chat.ajaxEnd();
Chat.e.viewMessages.classList.remove('loading');
setTimeout(function(){
Chat.scrollMessagesTo(1);
}, 250);
}
if(Chat._gotServerError && Chat.intervalTimer){
clearInterval(Chat.intervalTimer);
Chat.reportErrorAsMessage(
|
| ︙ | ︙ | |||
1365 1366 1367 1368 1369 1370 1371 |
const poll = async function f(){
if(f.running) return;
f.running = true;
Chat._isBatchLoading = f.isFirstCall;
if(true===f.isFirstCall){
f.isFirstCall = false;
Chat.ajaxStart();
| | | 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 |
const poll = async function f(){
if(f.running) return;
f.running = true;
Chat._isBatchLoading = f.isFirstCall;
if(true===f.isFirstCall){
f.isFirstCall = false;
Chat.ajaxStart();
Chat.e.viewMessages.classList.add('loading');
}
F.fetch("chat-poll",{
timeout: 420 * 1000/*FIXME: get the value from the server*/,
urlParams:{
name: Chat.mxMsg
},
responseType: "json",
|
| ︙ | ︙ |