Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Proof of concept /chat "active user list" which keeps track only of users who have posted messages in the client's current list and allows filtering on those messages by tapping a user. Widget is hidden by default and can be toggled in the config area. There are still cases to figure out (e.g. new messages do not apply the current filter). |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | chat-user-filter |
| Files: | files | file ages | folders |
| SHA3-256: |
dafd5497118ed02401e3ce7d871f5fb5 |
| User & Date: | stephan 2021-09-23 09:41:44.563 |
Context
|
2021-09-23
| ||
| 11:44 | UI refinement of the chat user activity list. ... (check-in: 7aea432a47 user: stephan tags: chat-user-filter) | |
| 09:41 | Proof of concept /chat "active user list" which keeps track only of users who have posted messages in the client's current list and allows filtering on those messages by tapping a user. Widget is hidden by default and can be toggled in the config area. There are still cases to figure out (e.g. new messages do not apply the current filter). ... (check-in: dafd549711 user: stephan tags: chat-user-filter) | |
| 04:53 | In /ci_edit, add a footnote recommending against setting fixed color values. That feature predates skins by years and does not play well with arbitrary skins. ... (check-in: 9956fa6dde user: stephan tags: trunk) | |
Changes
Changes to src/chat.c.
| ︙ | ︙ | |||
179 180 181 182 183 184 185 186 187 188 189 190 191 192 | @ 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> | > | 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | @ your environment. @ </div> @ <input type="file" name="file" id="chat-input-file"> @ </div> @ <div id="chat-drop-details"></div> @ </div> @ </div> @ <div id='chat-user-list' class='hidden'></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> |
| ︙ | ︙ |
Changes to src/chat.js.
| ︙ | ︙ | |||
114 115 116 117 118 119 120 |
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'),
| | > > > > > > > > | 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
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'),
activeUserList: D.append(E1('#chat-user-list'), "user list placeholder")
},
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',
totalMessageCount: 0, // total # of inbound messages
//! Number of messages to load for the history buttons
loadMessageCount: Math.abs(F.config.chat.initSize || 20),
ajaxInflight: 0,
usersLastSeen:{
/* Map of user names to their most recent message time
(JS Date object). Only messages received by the chat client
are considered. */
/* Reminder: to convert a Julian time J to JS:
new Date((J - 2440587.5) * 86400000) */
},
/** Gets (no args) or sets (1 arg) the current input text field value,
taking into account single- vs multi-line input. The getter returns
a string and the setter returns this object. */
inputValue: function(){
const e = this.inputElement();
if(arguments.length){
e.value = arguments[0];
|
| ︙ | ︙ | |||
365 366 367 368 369 370 371 |
return !v;
},
defaults:{
"images-inline": !!F.config.chat.imagesInline,
"edit-multiline": false,
"monospace-messages": false,
"chat-only-mode": false,
| | > | 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
return !v;
},
defaults:{
"images-inline": !!F.config.chat.imagesInline,
"edit-multiline": false,
"monospace-messages": false,
"chat-only-mode": false,
"audible-alert": true,
"active-user-list": false
}
},
/** Plays a new-message notification sound IF the audible-alert
setting is true, else this is a no-op. Returns this.
*/
playNewMessageSound: function f(){
if(f.uri){
|
| ︙ | ︙ | |||
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
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){
const v = cs.settings.get(k,cs);
if(cs===v) cs.settings.set(k,cs.settings.defaults[k]);
});
if(window.innerWidth<window.innerHeight){
/* Alignment of 'my' messages: right alignment is conventional
for mobile chat apps but can be difficult to read in wide
windows (desktop/tablet landscape mode), so we default to a
layout based on the apparent "orientation" of the window:
tall vs wide. Can be toggled via settings popup. */
document.body.classList.add('my-messages-right');
}
if(cs.settings.getBool('monospace-messages',false)){
document.body.classList.add('monospace-messages');
}
cs.inputMultilineMode(cs.settings.getBool('edit-multiline',false));
cs.chatOnlyMode(cs.settings.getBool('chat-only-mode'));
cs.pageTitleOrig = cs.e.pageTitle.innerText;
const qs = (e)=>document.querySelector(e);
const argsToArray = function(args){
return Array.prototype.slice.call(args,0);
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 |
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');
},
/**
Updates the "active user list" view.
*/
updateActiveUserList: function callee(){
if(!callee.sortUsersSeen){
/** Array.sort() callback. Expects an array of user names and
sorts them in last-received message order (newest first). */
const usersLastSeen = this.usersLastSeen;
callee.sortUsersSeen = function(l,r){
l = usersLastSeen[l];
r = usersLastSeen[r];
if(l && r) return r - l;
else if(l) return -1;
else if(r) return 1;
else return 0;
};
}
const self = this,
users = Object.keys(this.usersLastSeen).sort(callee.sortUsersSeen);
if(!users.length) return this;
const ael = this.e.activeUserList;
D.clearElement(ael);
users.forEach(function(u){
const uSpan = D.addClass(D.span(), 'chat-user');
const uDate = self.usersLastSeen[u];
D.append(uSpan, u);
if(uDate.$uColor){
uSpan.style.backgroundColor = uDate.$uColor;
}
D.append(ael, uSpan);
});
}
};
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){
const v = cs.settings.get(k,cs);
if(cs===v) cs.settings.set(k,cs.settings.defaults[k]);
});
if(window.innerWidth<window.innerHeight){
/* Alignment of 'my' messages: right alignment is conventional
for mobile chat apps but can be difficult to read in wide
windows (desktop/tablet landscape mode), so we default to a
layout based on the apparent "orientation" of the window:
tall vs wide. Can be toggled via settings popup. */
document.body.classList.add('my-messages-right');
}
if(cs.settings.getBool('monospace-messages',false)){
document.body.classList.add('monospace-messages');
}
if(cs.settings.getBool('active-user-list',false)){
cs.e.activeUserList.classList.remove('hidden');
}
cs.inputMultilineMode(cs.settings.getBool('edit-multiline',false));
cs.chatOnlyMode(cs.settings.getBool('chat-only-mode'));
cs.pageTitleOrig = cs.e.pageTitle.innerText;
const qs = (e)=>document.querySelector(e);
const argsToArray = function(args){
return Array.prototype.slice.call(args,0);
|
| ︙ | ︙ | |||
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
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 |
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);
cs.e.activeUserList.addEventListener('click', function f(ev){
/* Filter messages on a user clicked in activeUserList */
ev.stopPropagation();
ev.preventDefault();
if(!ev.target.classList.contains('chat-user')) return false;
const eUser = ev.target;
const uname = eUser.innerText;
let eLast;
cs.setCurrentView(cs.e.viewMessages);
if(eUser.classList.contains('selected')){
eUser.classList.remove('selected');
cs.e.viewMessages.querySelectorAll(
'.message-widget.hidden'
).forEach(function(e){
e.classList.remove('hidden');
eLast = e;
});
delete f.$eSelected;
}else{
if(f.$eSelected) f.$eSelected.classList.remove('selected');
f.$eSelected = eUser;
eUser.classList.add('selected');
cs.e.viewMessages.querySelectorAll(
'.message-widget'
).forEach(function(e){
if(e.dataset.xfrom===uname){
e.classList.remove('hidden');
eLast = e;
}
else e.classList.add('hidden');
});
}
if(eLast) eLast.scrollIntoView(false);
return false;
}, false);
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
|
| ︙ | ︙ | |||
1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 |
const settingsOps = [{
label: "Multi-line input",
boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
persistentSetting: 'edit-multiline',
callback: function(){
Chat.inputToggleSingleMulti();
}
},{
label: "Monospace message font",
boolValue: ()=>document.body.classList.contains('monospace-messages'),
persistentSetting: 'monospace-messages',
callback: function(){
document.body.classList.toggle('monospace-messages');
}
| > > > > > > > > > > > > > > > | 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 |
const settingsOps = [{
label: "Multi-line input",
boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
persistentSetting: 'edit-multiline',
callback: function(){
Chat.inputToggleSingleMulti();
}
},{
label: "Show recent user list",
boolValue: ()=>!Chat.e.activeUserList.classList.contains('hidden'),
persistentSetting: 'active-user-list',
callback: function(){
D.toggleClass(Chat.e.activeUserList,'hidden');
if(Chat.e.activeUserList.classList.contains('hidden')){
/* When hiding this element, undo all filtering */
D.removeClass(Chat.e.viewMessages.querySelectorAll('.message-widget.hidden'), 'hidden');
/*Ideally we'd scroll the final message into view
now, but because viewMessages is currently hidden behind
viewConfig, scrolling is a no-op. */
Chat.scrollMessagesTo(1);
}
}
},{
label: "Monospace message font",
boolValue: ()=>document.body.classList.contains('monospace-messages'),
persistentSetting: 'monospace-messages',
callback: function(){
document.body.classList.toggle('monospace-messages');
}
|
| ︙ | ︙ | |||
1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 |
/** Processes chat message m, placing it either the start (if atEnd
is falsy) or end (if atEnd is truthy) of the chat history. atEnd
should only be true when loading older messages. */
f.processPost = function(m,atEnd){
++Chat.totalMessageCount;
if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid;
if( !Chat.mnMsg || m.msgid<Chat.mnMsg) Chat.mnMsg = m.msgid;
if( m.mdel ){
/* A record deletion notice. */
Chat.deleteMessageElem(m.mdel);
return;
}
if(!Chat._isBatchLoading /*&& Chat.me!==m.xfrom*/ && Chat.playNewMessageSound){
Chat.playNewMessageSound();
}
const row = new Chat.MessageWidget(m);
Chat.injectMessageElem(row.e.body,atEnd);
if(m.isError){
Chat._gotServerError = m;
}
}/*processPost()*/;
}/*end static init*/
jx.msgs.forEach((m)=>f.processPost(m,atEnd));
if('visible'===document.visibilityState){
if(Chat.changesSincePageHidden){
Chat.changesSincePageHidden = 0;
| > > > > > > > > > > | 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 1352 1353 1354 1355 1356 |
/** Processes chat message m, placing it either the start (if atEnd
is falsy) or end (if atEnd is truthy) of the chat history. atEnd
should only be true when loading older messages. */
f.processPost = function(m,atEnd){
++Chat.totalMessageCount;
if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid;
if( !Chat.mnMsg || m.msgid<Chat.mnMsg) Chat.mnMsg = m.msgid;
if(m.xfrom && m.mtime){
const d = new Date(m.mtime);
const uls = Chat.usersLastSeen[m.xfrom];
if(!uls || uls<d){
d.$uColor = m.uclr;
Chat.usersLastSeen[m.xfrom] = d;
}
}
if( m.mdel ){
/* A record deletion notice. */
Chat.deleteMessageElem(m.mdel);
return;
}
if(!Chat._isBatchLoading /*&& Chat.me!==m.xfrom*/ && Chat.playNewMessageSound){
Chat.playNewMessageSound();
}
const row = new Chat.MessageWidget(m);
Chat.injectMessageElem(row.e.body,atEnd);
if(m.isError){
Chat._gotServerError = m;
}else{
Chat.updateActiveUserList();
}
}/*processPost()*/;
}/*end static init*/
jx.msgs.forEach((m)=>f.processPost(m,atEnd));
if('visible'===document.visibilityState){
if(Chat.changesSincePageHidden){
Chat.changesSincePageHidden = 0;
|
| ︙ | ︙ |
Changes to src/style.chat.css.
| ︙ | ︙ | |||
355 356 357 358 359 360 361 |
}
body.chat #chat-config > button,
body.chat #chat-preview #chat-preview-buttons > button {
padding: 0.5em;
flex: 0 1 auto;
margin: 0.25em 0;
}
| > > > > > > > > > > > > > > > > > > > > > > > > > > > | 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
}
body.chat #chat-config > button,
body.chat #chat-preview #chat-preview-buttons > button {
padding: 0.5em;
flex: 0 1 auto;
margin: 0.25em 0;
}
body.chat #chat-user-list {
border: 1px inset;
padding: 0.1em 0.2em;
border-radius: 0.25em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
font-size: 85%;
margin: 0.5em 0 0 0;
border-radius: 0.5em;
padding: 0.5em;
}
body.chat #chat-user-list::before {
content: "Most recently active:";
}
body.chat #chat-user-list .chat-user {
margin: 0.2em;
padding: 0.1em 0.5em;
border-radius: 0.5em;
cursor: pointer;
}
body.chat #chat-user-list .chat-user.selected {
font-weight: bold;
text-decoration: underline;
}
|