Fossil SCM

UI refinement of the chat user activity list.

stephan 2021-09-23 11:44 chat-user-filter
Commit 7aea432a4786882ab3016807fb2c78b69b8881879823d590c9b346f92f4207fe
3 files changed +4 -1 +91 -56 +14 -12
+4 -1
--- src/chat.c
+++ src/chat.c
@@ -181,11 +181,14 @@
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-user-list' class='hidden'></div>
186
+ @ <fieldset id='chat-user-list-wrapper' class='hidden'>
187
+ @ <legend>Recently Active</legend>
188
+ @ <div id='chat-user-list'>(user list goes here)</div>
189
+ @ </fieldset>
187190
@ <div id='chat-preview' class='hidden chat-view'>
188191
@ <header>Preview: (<a href='%R/md_rules' target='_blank'>markdown reference</a>)</header>
189192
@ <div id='chat-preview-content' class='message-widget-content'></div>
190193
@ <div id='chat-preview-buttons'><button id='chat-preview-close'>Close Preview</button></div>
191194
@ </div>
192195
--- src/chat.c
+++ src/chat.c
@@ -181,11 +181,14 @@
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-user-list' class='hidden'></div>
 
 
 
187 @ <div id='chat-preview' class='hidden chat-view'>
188 @ <header>Preview: (<a href='%R/md_rules' target='_blank'>markdown reference</a>)</header>
189 @ <div id='chat-preview-content' class='message-widget-content'></div>
190 @ <div id='chat-preview-buttons'><button id='chat-preview-close'>Close Preview</button></div>
191 @ </div>
192
--- src/chat.c
+++ src/chat.c
@@ -181,11 +181,14 @@
181 @ <input type="file" name="file" id="chat-input-file">
182 @ </div>
183 @ <div id="chat-drop-details"></div>
184 @ </div>
185 @ </div>
186 @ <fieldset id='chat-user-list-wrapper' class='hidden'>
187 @ <legend>Recently Active</legend>
188 @ <div id='chat-user-list'>(user list goes here)</div>
189 @ </fieldset>
190 @ <div id='chat-preview' class='hidden chat-view'>
191 @ <header>Preview: (<a href='%R/md_rules' target='_blank'>markdown reference</a>)</header>
192 @ <div id='chat-preview-content' class='message-widget-content'></div>
193 @ <div id='chat-preview-buttons'><button id='chat-preview-close'>Close Preview</button></div>
194 @ </div>
195
+91 -56
--- src/chat.js
+++ src/chat.js
@@ -34,10 +34,30 @@
3434
else if(r1.bottom<=r2.bottom && r1.bottom>=r2.top) return true;
3535
return false;
3636
};
3737
3838
const addAnchorTargetBlank = (e)=>D.attr(e, 'target','_blank');
39
+
40
+ /**
41
+ Returns an almost-ISO8601 form of Date object d.
42
+ */
43
+ const iso8601ish = function(d){
44
+ return d.toISOString()
45
+ .replace('T',' ').replace(/\.\d+/,'')
46
+ .replace('Z', ' zulu');
47
+ };
48
+ /** Returns the local time string of Date object d, defaulting
49
+ to the current time. */
50
+ const localTimeString = function ff(d){
51
+ d || (d = new Date());
52
+ return [
53
+ d.getFullYear(),'-',pad2(d.getMonth()+1/*sigh*/),
54
+ '-',pad2(d.getDate()),
55
+ ' ',pad2(d.getHours()),':',pad2(d.getMinutes()),
56
+ ':',pad2(d.getSeconds())
57
+ ].join('');
58
+ };
3959
4060
(function(){
4161
let dbg = document.querySelector('#debugMsg');
4262
if(dbg){
4363
/* This can inadvertently influence our flexbox layouts, so move
@@ -117,11 +137,12 @@
117137
viewConfig: E1('#chat-config'),
118138
viewPreview: E1('#chat-preview'),
119139
previewContent: E1('#chat-preview-content'),
120140
btnPreview: E1('#chat-preview-button'),
121141
views: document.querySelectorAll('.chat-view'),
122
- activeUserList: D.append(E1('#chat-user-list'), "user list placeholder")
142
+ activeUserListWrapper: E1('#chat-user-list-wrapper'),
143
+ activeUserList: E1('#chat-user-list')
123144
},
124145
me: F.user.name,
125146
mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
126147
mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
127148
pageIsActive: 'visible'===document.visibilityState,
@@ -135,10 +156,16 @@
135156
/* Map of user names to their most recent message time
136157
(JS Date object). Only messages received by the chat client
137158
are considered. */
138159
/* Reminder: to convert a Julian time J to JS:
139160
new Date((J - 2440587.5) * 86400000) */
161
+ },
162
+ filterState:{
163
+ activeUser: undefined,
164
+ match: function(uname){
165
+ return this.activeUser===uname || !this.activeUser;
166
+ }
140167
},
141168
/** Gets (no args) or sets (1 arg) the current input text field value,
142169
taking into account single- vs multi-line input. The getter returns
143170
a string and the setter returns this object. */
144171
inputValue: function(){
@@ -252,10 +279,13 @@
252279
the list. */
253280
injectMessageElem: function f(e, atEnd){
254281
const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
255282
holder = this.e.viewMessages,
256283
prevMessage = this.e.newestMessage;
284
+ if(!this.filterState.match(e.dataset.xfrom)){
285
+ e.classList.add('hidden');
286
+ }
257287
if(atEnd){
258288
const fe = mip.nextElementSibling;
259289
if(fe) mip.parentNode.insertBefore(e, fe);
260290
else D.append(mip.parentNode, e);
261291
}else{
@@ -425,33 +455,65 @@
425455
updateActiveUserList: function callee(){
426456
if(!callee.sortUsersSeen){
427457
/** Array.sort() callback. Expects an array of user names and
428458
sorts them in last-received message order (newest first). */
429459
const usersLastSeen = this.usersLastSeen;
460
+ const self = this;
430461
callee.sortUsersSeen = function(l,r){
431462
l = usersLastSeen[l];
432463
r = usersLastSeen[r];
433464
if(l && r) return r - l;
434465
else if(l) return -1;
435466
else if(r) return 1;
436467
else return 0;
437468
};
438
- }
439
- const self = this,
440
- users = Object.keys(this.usersLastSeen).sort(callee.sortUsersSeen);
441
- if(!users.length) return this;
442
- const ael = this.e.activeUserList;
443
- D.clearElement(ael);
444
- users.forEach(function(u){
445
- const uSpan = D.addClass(D.span(), 'chat-user');
446
- const uDate = self.usersLastSeen[u];
447
- D.append(uSpan, u);
448
- if(uDate.$uColor){
449
- uSpan.style.backgroundColor = uDate.$uColor;
450
- }
451
- D.append(ael, uSpan);
469
+ callee.addUserElem = function(u){
470
+ const uSpan = D.addClass(D.span(), 'chat-user');
471
+ const uDate = self.usersLastSeen[u];
472
+ if(self.filterState.activeUser===u){
473
+ uSpan.classList.add('selected');
474
+ }
475
+ uSpan.dataset.uname = u;
476
+ D.append(uSpan, u, "\n",
477
+ D.append(
478
+ D.addClass(D.span(),'timestamp'),
479
+ localTimeString(uDate)//.substr(5/*chop off year*/)
480
+ ));
481
+ if(uDate.$uColor){
482
+ uSpan.style.backgroundColor = uDate.$uColor;
483
+ }
484
+ D.append(self.e.activeUserList, uSpan);
485
+ };
486
+ }
487
+ D.clearElement(this.e.activeUserList);
488
+ Object.keys(this.usersLastSeen).sort(
489
+ callee.sortUsersSeen
490
+ ).forEach(callee.addUserElem);
491
+ return this;
492
+ },
493
+ /**
494
+ Applies user name filter to all current messages, or clears
495
+ the filter if uname is falsy.
496
+ */
497
+ setUserFilter: function(uname){
498
+ this.filterState.activeUser = uname;
499
+ const mw = this.e.viewMessages.querySelectorAll('.message-widget');
500
+ const self = this;
501
+ let eLast;
502
+ mw.forEach(function(w){
503
+ if(self.filterState.match(w.dataset.xfrom)){
504
+ w.classList.remove('hidden');
505
+ eLast = w;
506
+ }else{
507
+ w.classList.add('hidden');
508
+ }
509
+ });
510
+ if(eLast) eLast.scrollIntoView(false);
511
+ cs.e.activeUserList.querySelectorAll('.chat-user').forEach(function(e){
512
+ e.classList[uname===e.dataset.uname ? 'add' : 'remove']('selected');
452513
});
514
+ return this;
453515
}
454516
};
455517
F.fetch.beforesend = ()=>cs.ajaxStart();
456518
F.fetch.aftersend = ()=>cs.ajaxEnd();
457519
cs.e.inputCurrent = cs.e.inputSingle;
@@ -470,11 +532,11 @@
470532
}
471533
if(cs.settings.getBool('monospace-messages',false)){
472534
document.body.classList.add('monospace-messages');
473535
}
474536
if(cs.settings.getBool('active-user-list',false)){
475
- cs.e.activeUserList.classList.remove('hidden');
537
+ cs.e.activeUserListWrapper.classList.remove('hidden');
476538
}
477539
cs.inputMultilineMode(cs.settings.getBool('edit-multiline',false));
478540
cs.chatOnlyMode(cs.settings.getBool('chat-only-mode'));
479541
cs.pageTitleOrig = cs.e.pageTitle.innerText;
480542
const qs = (e)=>document.querySelector(e);
@@ -673,39 +735,29 @@
673735
674736
cs.e.activeUserList.addEventListener('click', function f(ev){
675737
/* Filter messages on a user clicked in activeUserList */
676738
ev.stopPropagation();
677739
ev.preventDefault();
678
- if(!ev.target.classList.contains('chat-user')) return false;
679
- const eUser = ev.target;
680
- const uname = eUser.innerText;
740
+ let eUser = ev.target;
741
+ while(eUser!==this && !eUser.classList.contains('chat-user')){
742
+ eUser = eUser.parentNode;
743
+ }
744
+ if(eUser==this || !eUser) return false;
745
+ const uname = eUser.dataset.uname;
681746
let eLast;
682747
cs.setCurrentView(cs.e.viewMessages);
683748
if(eUser.classList.contains('selected')){
749
+ /* If curently selected, toggle filter off */
684750
eUser.classList.remove('selected');
685
- cs.e.viewMessages.querySelectorAll(
686
- '.message-widget.hidden'
687
- ).forEach(function(e){
688
- e.classList.remove('hidden');
689
- eLast = e;
690
- });
751
+ cs.setUserFilter(false);
691752
delete f.$eSelected;
692753
}else{
693754
if(f.$eSelected) f.$eSelected.classList.remove('selected');
694755
f.$eSelected = eUser;
695756
eUser.classList.add('selected');
696
- cs.e.viewMessages.querySelectorAll(
697
- '.message-widget'
698
- ).forEach(function(e){
699
- if(e.dataset.xfrom===uname){
700
- e.classList.remove('hidden');
701
- eLast = e;
702
- }
703
- else e.classList.add('hidden');
704
- });
705
- }
706
- if(eLast) eLast.scrollIntoView(false);
757
+ cs.setUserFilter(uname);
758
+ }
707759
return false;
708760
}, false);
709761
return cs;
710762
})()/*Chat initialization*/;
711763
@@ -751,21 +803,10 @@
751803
d.getHours(),":",
752804
(d.getMinutes()+100).toString().slice(1,3),
753805
' ', dowMap[d.getDay()]
754806
].join('');
755807
};
756
- /** Returns the local time string of Date object d, defaulting
757
- to the current time. */
758
- const localTimeString = function ff(d){
759
- d || (d = new Date());
760
- return [
761
- d.getFullYear(),'-',pad2(d.getMonth()+1/*sigh*/),
762
- '-',pad2(d.getDate()),
763
- ' ',pad2(d.getHours()),':',pad2(d.getMinutes()),
764
- ':',pad2(d.getSeconds())
765
- ].join('');
766
- };
767808
cf.prototype = {
768809
scrollIntoView: function(){
769810
this.e.content.scrollIntoView();
770811
},
771812
setMessage: function(m){
@@ -1114,16 +1155,10 @@
11141155
e.preventDefault();
11151156
Chat.submitMessage();
11161157
return false;
11171158
});
11181159
1119
- /* Returns an almost-ISO8601 form of Date object d. */
1120
- const iso8601ish = function(d){
1121
- return d.toISOString()
1122
- .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' zulu');
1123
- };
1124
-
11251160
(function(){/*Set up #chat-settings-button */
11261161
const settingsButton = document.querySelector('#chat-settings-button');
11271162
const optionsMenu = E1('#chat-config-options');
11281163
const cbToggle = function(ev){
11291164
ev.preventDefault();
@@ -1142,15 +1177,15 @@
11421177
callback: function(){
11431178
Chat.inputToggleSingleMulti();
11441179
}
11451180
},{
11461181
label: "Show recent user list",
1147
- boolValue: ()=>!Chat.e.activeUserList.classList.contains('hidden'),
1182
+ boolValue: ()=>!Chat.e.activeUserListWrapper.classList.contains('hidden'),
11481183
persistentSetting: 'active-user-list',
11491184
callback: function(){
1150
- D.toggleClass(Chat.e.activeUserList,'hidden');
1151
- if(Chat.e.activeUserList.classList.contains('hidden')){
1185
+ D.toggleClass(Chat.e.activeUserListWrapper,'hidden');
1186
+ if(Chat.e.activeUserListWrapper.classList.contains('hidden')){
11521187
/* When hiding this element, undo all filtering */
11531188
D.removeClass(Chat.e.viewMessages.querySelectorAll('.message-widget.hidden'), 'hidden');
11541189
/*Ideally we'd scroll the final message into view
11551190
now, but because viewMessages is currently hidden behind
11561191
viewConfig, scrolling is a no-op. */
11571192
--- src/chat.js
+++ src/chat.js
@@ -34,10 +34,30 @@
34 else if(r1.bottom<=r2.bottom && r1.bottom>=r2.top) return true;
35 return false;
36 };
37
38 const addAnchorTargetBlank = (e)=>D.attr(e, 'target','_blank');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
40 (function(){
41 let dbg = document.querySelector('#debugMsg');
42 if(dbg){
43 /* This can inadvertently influence our flexbox layouts, so move
@@ -117,11 +137,12 @@
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 activeUserList: D.append(E1('#chat-user-list'), "user list placeholder")
 
123 },
124 me: F.user.name,
125 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
126 mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
127 pageIsActive: 'visible'===document.visibilityState,
@@ -135,10 +156,16 @@
135 /* Map of user names to their most recent message time
136 (JS Date object). Only messages received by the chat client
137 are considered. */
138 /* Reminder: to convert a Julian time J to JS:
139 new Date((J - 2440587.5) * 86400000) */
 
 
 
 
 
 
140 },
141 /** Gets (no args) or sets (1 arg) the current input text field value,
142 taking into account single- vs multi-line input. The getter returns
143 a string and the setter returns this object. */
144 inputValue: function(){
@@ -252,10 +279,13 @@
252 the list. */
253 injectMessageElem: function f(e, atEnd){
254 const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
255 holder = this.e.viewMessages,
256 prevMessage = this.e.newestMessage;
 
 
 
257 if(atEnd){
258 const fe = mip.nextElementSibling;
259 if(fe) mip.parentNode.insertBefore(e, fe);
260 else D.append(mip.parentNode, e);
261 }else{
@@ -425,33 +455,65 @@
425 updateActiveUserList: function callee(){
426 if(!callee.sortUsersSeen){
427 /** Array.sort() callback. Expects an array of user names and
428 sorts them in last-received message order (newest first). */
429 const usersLastSeen = this.usersLastSeen;
 
430 callee.sortUsersSeen = function(l,r){
431 l = usersLastSeen[l];
432 r = usersLastSeen[r];
433 if(l && r) return r - l;
434 else if(l) return -1;
435 else if(r) return 1;
436 else return 0;
437 };
438 }
439 const self = this,
440 users = Object.keys(this.usersLastSeen).sort(callee.sortUsersSeen);
441 if(!users.length) return this;
442 const ael = this.e.activeUserList;
443 D.clearElement(ael);
444 users.forEach(function(u){
445 const uSpan = D.addClass(D.span(), 'chat-user');
446 const uDate = self.usersLastSeen[u];
447 D.append(uSpan, u);
448 if(uDate.$uColor){
449 uSpan.style.backgroundColor = uDate.$uColor;
450 }
451 D.append(ael, uSpan);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452 });
 
453 }
454 };
455 F.fetch.beforesend = ()=>cs.ajaxStart();
456 F.fetch.aftersend = ()=>cs.ajaxEnd();
457 cs.e.inputCurrent = cs.e.inputSingle;
@@ -470,11 +532,11 @@
470 }
471 if(cs.settings.getBool('monospace-messages',false)){
472 document.body.classList.add('monospace-messages');
473 }
474 if(cs.settings.getBool('active-user-list',false)){
475 cs.e.activeUserList.classList.remove('hidden');
476 }
477 cs.inputMultilineMode(cs.settings.getBool('edit-multiline',false));
478 cs.chatOnlyMode(cs.settings.getBool('chat-only-mode'));
479 cs.pageTitleOrig = cs.e.pageTitle.innerText;
480 const qs = (e)=>document.querySelector(e);
@@ -673,39 +735,29 @@
673
674 cs.e.activeUserList.addEventListener('click', function f(ev){
675 /* Filter messages on a user clicked in activeUserList */
676 ev.stopPropagation();
677 ev.preventDefault();
678 if(!ev.target.classList.contains('chat-user')) return false;
679 const eUser = ev.target;
680 const uname = eUser.innerText;
 
 
 
681 let eLast;
682 cs.setCurrentView(cs.e.viewMessages);
683 if(eUser.classList.contains('selected')){
 
684 eUser.classList.remove('selected');
685 cs.e.viewMessages.querySelectorAll(
686 '.message-widget.hidden'
687 ).forEach(function(e){
688 e.classList.remove('hidden');
689 eLast = e;
690 });
691 delete f.$eSelected;
692 }else{
693 if(f.$eSelected) f.$eSelected.classList.remove('selected');
694 f.$eSelected = eUser;
695 eUser.classList.add('selected');
696 cs.e.viewMessages.querySelectorAll(
697 '.message-widget'
698 ).forEach(function(e){
699 if(e.dataset.xfrom===uname){
700 e.classList.remove('hidden');
701 eLast = e;
702 }
703 else e.classList.add('hidden');
704 });
705 }
706 if(eLast) eLast.scrollIntoView(false);
707 return false;
708 }, false);
709 return cs;
710 })()/*Chat initialization*/;
711
@@ -751,21 +803,10 @@
751 d.getHours(),":",
752 (d.getMinutes()+100).toString().slice(1,3),
753 ' ', dowMap[d.getDay()]
754 ].join('');
755 };
756 /** Returns the local time string of Date object d, defaulting
757 to the current time. */
758 const localTimeString = function ff(d){
759 d || (d = new Date());
760 return [
761 d.getFullYear(),'-',pad2(d.getMonth()+1/*sigh*/),
762 '-',pad2(d.getDate()),
763 ' ',pad2(d.getHours()),':',pad2(d.getMinutes()),
764 ':',pad2(d.getSeconds())
765 ].join('');
766 };
767 cf.prototype = {
768 scrollIntoView: function(){
769 this.e.content.scrollIntoView();
770 },
771 setMessage: function(m){
@@ -1114,16 +1155,10 @@
1114 e.preventDefault();
1115 Chat.submitMessage();
1116 return false;
1117 });
1118
1119 /* Returns an almost-ISO8601 form of Date object d. */
1120 const iso8601ish = function(d){
1121 return d.toISOString()
1122 .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' zulu');
1123 };
1124
1125 (function(){/*Set up #chat-settings-button */
1126 const settingsButton = document.querySelector('#chat-settings-button');
1127 const optionsMenu = E1('#chat-config-options');
1128 const cbToggle = function(ev){
1129 ev.preventDefault();
@@ -1142,15 +1177,15 @@
1142 callback: function(){
1143 Chat.inputToggleSingleMulti();
1144 }
1145 },{
1146 label: "Show recent user list",
1147 boolValue: ()=>!Chat.e.activeUserList.classList.contains('hidden'),
1148 persistentSetting: 'active-user-list',
1149 callback: function(){
1150 D.toggleClass(Chat.e.activeUserList,'hidden');
1151 if(Chat.e.activeUserList.classList.contains('hidden')){
1152 /* When hiding this element, undo all filtering */
1153 D.removeClass(Chat.e.viewMessages.querySelectorAll('.message-widget.hidden'), 'hidden');
1154 /*Ideally we'd scroll the final message into view
1155 now, but because viewMessages is currently hidden behind
1156 viewConfig, scrolling is a no-op. */
1157
--- src/chat.js
+++ src/chat.js
@@ -34,10 +34,30 @@
34 else if(r1.bottom<=r2.bottom && r1.bottom>=r2.top) return true;
35 return false;
36 };
37
38 const addAnchorTargetBlank = (e)=>D.attr(e, 'target','_blank');
39
40 /**
41 Returns an almost-ISO8601 form of Date object d.
42 */
43 const iso8601ish = function(d){
44 return d.toISOString()
45 .replace('T',' ').replace(/\.\d+/,'')
46 .replace('Z', ' zulu');
47 };
48 /** Returns the local time string of Date object d, defaulting
49 to the current time. */
50 const localTimeString = function ff(d){
51 d || (d = new Date());
52 return [
53 d.getFullYear(),'-',pad2(d.getMonth()+1/*sigh*/),
54 '-',pad2(d.getDate()),
55 ' ',pad2(d.getHours()),':',pad2(d.getMinutes()),
56 ':',pad2(d.getSeconds())
57 ].join('');
58 };
59
60 (function(){
61 let dbg = document.querySelector('#debugMsg');
62 if(dbg){
63 /* This can inadvertently influence our flexbox layouts, so move
@@ -117,11 +137,12 @@
137 viewConfig: E1('#chat-config'),
138 viewPreview: E1('#chat-preview'),
139 previewContent: E1('#chat-preview-content'),
140 btnPreview: E1('#chat-preview-button'),
141 views: document.querySelectorAll('.chat-view'),
142 activeUserListWrapper: E1('#chat-user-list-wrapper'),
143 activeUserList: E1('#chat-user-list')
144 },
145 me: F.user.name,
146 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
147 mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
148 pageIsActive: 'visible'===document.visibilityState,
@@ -135,10 +156,16 @@
156 /* Map of user names to their most recent message time
157 (JS Date object). Only messages received by the chat client
158 are considered. */
159 /* Reminder: to convert a Julian time J to JS:
160 new Date((J - 2440587.5) * 86400000) */
161 },
162 filterState:{
163 activeUser: undefined,
164 match: function(uname){
165 return this.activeUser===uname || !this.activeUser;
166 }
167 },
168 /** Gets (no args) or sets (1 arg) the current input text field value,
169 taking into account single- vs multi-line input. The getter returns
170 a string and the setter returns this object. */
171 inputValue: function(){
@@ -252,10 +279,13 @@
279 the list. */
280 injectMessageElem: function f(e, atEnd){
281 const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
282 holder = this.e.viewMessages,
283 prevMessage = this.e.newestMessage;
284 if(!this.filterState.match(e.dataset.xfrom)){
285 e.classList.add('hidden');
286 }
287 if(atEnd){
288 const fe = mip.nextElementSibling;
289 if(fe) mip.parentNode.insertBefore(e, fe);
290 else D.append(mip.parentNode, e);
291 }else{
@@ -425,33 +455,65 @@
455 updateActiveUserList: function callee(){
456 if(!callee.sortUsersSeen){
457 /** Array.sort() callback. Expects an array of user names and
458 sorts them in last-received message order (newest first). */
459 const usersLastSeen = this.usersLastSeen;
460 const self = this;
461 callee.sortUsersSeen = function(l,r){
462 l = usersLastSeen[l];
463 r = usersLastSeen[r];
464 if(l && r) return r - l;
465 else if(l) return -1;
466 else if(r) return 1;
467 else return 0;
468 };
469 callee.addUserElem = function(u){
470 const uSpan = D.addClass(D.span(), 'chat-user');
471 const uDate = self.usersLastSeen[u];
472 if(self.filterState.activeUser===u){
473 uSpan.classList.add('selected');
474 }
475 uSpan.dataset.uname = u;
476 D.append(uSpan, u, "\n",
477 D.append(
478 D.addClass(D.span(),'timestamp'),
479 localTimeString(uDate)//.substr(5/*chop off year*/)
480 ));
481 if(uDate.$uColor){
482 uSpan.style.backgroundColor = uDate.$uColor;
483 }
484 D.append(self.e.activeUserList, uSpan);
485 };
486 }
487 D.clearElement(this.e.activeUserList);
488 Object.keys(this.usersLastSeen).sort(
489 callee.sortUsersSeen
490 ).forEach(callee.addUserElem);
491 return this;
492 },
493 /**
494 Applies user name filter to all current messages, or clears
495 the filter if uname is falsy.
496 */
497 setUserFilter: function(uname){
498 this.filterState.activeUser = uname;
499 const mw = this.e.viewMessages.querySelectorAll('.message-widget');
500 const self = this;
501 let eLast;
502 mw.forEach(function(w){
503 if(self.filterState.match(w.dataset.xfrom)){
504 w.classList.remove('hidden');
505 eLast = w;
506 }else{
507 w.classList.add('hidden');
508 }
509 });
510 if(eLast) eLast.scrollIntoView(false);
511 cs.e.activeUserList.querySelectorAll('.chat-user').forEach(function(e){
512 e.classList[uname===e.dataset.uname ? 'add' : 'remove']('selected');
513 });
514 return this;
515 }
516 };
517 F.fetch.beforesend = ()=>cs.ajaxStart();
518 F.fetch.aftersend = ()=>cs.ajaxEnd();
519 cs.e.inputCurrent = cs.e.inputSingle;
@@ -470,11 +532,11 @@
532 }
533 if(cs.settings.getBool('monospace-messages',false)){
534 document.body.classList.add('monospace-messages');
535 }
536 if(cs.settings.getBool('active-user-list',false)){
537 cs.e.activeUserListWrapper.classList.remove('hidden');
538 }
539 cs.inputMultilineMode(cs.settings.getBool('edit-multiline',false));
540 cs.chatOnlyMode(cs.settings.getBool('chat-only-mode'));
541 cs.pageTitleOrig = cs.e.pageTitle.innerText;
542 const qs = (e)=>document.querySelector(e);
@@ -673,39 +735,29 @@
735
736 cs.e.activeUserList.addEventListener('click', function f(ev){
737 /* Filter messages on a user clicked in activeUserList */
738 ev.stopPropagation();
739 ev.preventDefault();
740 let eUser = ev.target;
741 while(eUser!==this && !eUser.classList.contains('chat-user')){
742 eUser = eUser.parentNode;
743 }
744 if(eUser==this || !eUser) return false;
745 const uname = eUser.dataset.uname;
746 let eLast;
747 cs.setCurrentView(cs.e.viewMessages);
748 if(eUser.classList.contains('selected')){
749 /* If curently selected, toggle filter off */
750 eUser.classList.remove('selected');
751 cs.setUserFilter(false);
 
 
 
 
 
752 delete f.$eSelected;
753 }else{
754 if(f.$eSelected) f.$eSelected.classList.remove('selected');
755 f.$eSelected = eUser;
756 eUser.classList.add('selected');
757 cs.setUserFilter(uname);
758 }
 
 
 
 
 
 
 
 
 
759 return false;
760 }, false);
761 return cs;
762 })()/*Chat initialization*/;
763
@@ -751,21 +803,10 @@
803 d.getHours(),":",
804 (d.getMinutes()+100).toString().slice(1,3),
805 ' ', dowMap[d.getDay()]
806 ].join('');
807 };
 
 
 
 
 
 
 
 
 
 
 
808 cf.prototype = {
809 scrollIntoView: function(){
810 this.e.content.scrollIntoView();
811 },
812 setMessage: function(m){
@@ -1114,16 +1155,10 @@
1155 e.preventDefault();
1156 Chat.submitMessage();
1157 return false;
1158 });
1159
 
 
 
 
 
 
1160 (function(){/*Set up #chat-settings-button */
1161 const settingsButton = document.querySelector('#chat-settings-button');
1162 const optionsMenu = E1('#chat-config-options');
1163 const cbToggle = function(ev){
1164 ev.preventDefault();
@@ -1142,15 +1177,15 @@
1177 callback: function(){
1178 Chat.inputToggleSingleMulti();
1179 }
1180 },{
1181 label: "Show recent user list",
1182 boolValue: ()=>!Chat.e.activeUserListWrapper.classList.contains('hidden'),
1183 persistentSetting: 'active-user-list',
1184 callback: function(){
1185 D.toggleClass(Chat.e.activeUserListWrapper,'hidden');
1186 if(Chat.e.activeUserListWrapper.classList.contains('hidden')){
1187 /* When hiding this element, undo all filtering */
1188 D.removeClass(Chat.e.viewMessages.querySelectorAll('.message-widget.hidden'), 'hidden');
1189 /*Ideally we'd scroll the final message into view
1190 now, but because viewMessages is currently hidden behind
1191 viewConfig, scrolling is a no-op. */
1192
+14 -12
--- src/style.chat.css
+++ src/style.chat.css
@@ -200,15 +200,13 @@
200200
/* Wrapper for /chat user input controls */
201201
body.chat #chat-input-area {
202202
display: flex;
203203
flex-direction: column;
204204
padding: 0.5em 0 0 0;
205
- border-bottom: none;
206
- border-top: 1px solid black;
207205
margin: 0.5em 0 0 0;
208206
position: initial /*sticky currently disabled due to scrolling-related issues*/;
209
- bottom: 0;
207
+ /*bottom: 0;*/
210208
}
211209
body.chat:not(.chat-only-mode) #chat-input-area{
212210
/* Safari user reports that 2em is necessary to keep the file selection
213211
widget from overlapping the page footer, whereas a margin of 0 is fine
214212
for FF/Chrome (and 2em is a *huge* waste of space for those). */
@@ -358,31 +356,35 @@
358356
padding: 0.5em;
359357
flex: 0 1 auto;
360358
margin: 0.25em 0;
361359
}
362360
361
+body.chat #chat-user-list-wrapper {
362
+ border-radius: 0.5em;
363
+ margin: 0.5em 0 0 0;
364
+ font-size: 85%;
365
+}
363366
body.chat #chat-user-list {
364
- border: 1px inset;
365
- padding: 0.1em 0.2em;
366
- border-radius: 0.25em;
367367
display: flex;
368368
flex-direction: row;
369369
flex-wrap: wrap;
370370
align-items: center;
371
- font-size: 85%;
372
- margin: 0.5em 0 0 0;
373
- border-radius: 0.5em;
374
- padding: 0.5em;
375371
}
376372
body.chat #chat-user-list::before {
377
- content: "Most recently active:";
373
+ /*content: "Recently active: ";*/
378374
}
379375
body.chat #chat-user-list .chat-user {
380376
margin: 0.2em;
381
- padding: 0.1em 0.5em;
377
+ padding: 0.1em 0.5em 0.2em 0.5em;
382378
border-radius: 0.5em;
383379
cursor: pointer;
380
+ text-align: center;
381
+ white-space: pre;
382
+}
383
+body.chat #chat-user-list .chat-user > .timestamp {
384
+ font-size: 80%;
385
+ font-family: monospace;
384386
}
385387
body.chat #chat-user-list .chat-user.selected {
386388
font-weight: bold;
387389
text-decoration: underline;
388390
}
389391
--- src/style.chat.css
+++ src/style.chat.css
@@ -200,15 +200,13 @@
200 /* Wrapper for /chat user input controls */
201 body.chat #chat-input-area {
202 display: flex;
203 flex-direction: column;
204 padding: 0.5em 0 0 0;
205 border-bottom: none;
206 border-top: 1px solid black;
207 margin: 0.5em 0 0 0;
208 position: initial /*sticky currently disabled due to scrolling-related issues*/;
209 bottom: 0;
210 }
211 body.chat:not(.chat-only-mode) #chat-input-area{
212 /* Safari user reports that 2em is necessary to keep the file selection
213 widget from overlapping the page footer, whereas a margin of 0 is fine
214 for FF/Chrome (and 2em is a *huge* waste of space for those). */
@@ -358,31 +356,35 @@
358 padding: 0.5em;
359 flex: 0 1 auto;
360 margin: 0.25em 0;
361 }
362
 
 
 
 
 
363 body.chat #chat-user-list {
364 border: 1px inset;
365 padding: 0.1em 0.2em;
366 border-radius: 0.25em;
367 display: flex;
368 flex-direction: row;
369 flex-wrap: wrap;
370 align-items: center;
371 font-size: 85%;
372 margin: 0.5em 0 0 0;
373 border-radius: 0.5em;
374 padding: 0.5em;
375 }
376 body.chat #chat-user-list::before {
377 content: "Most recently active:";
378 }
379 body.chat #chat-user-list .chat-user {
380 margin: 0.2em;
381 padding: 0.1em 0.5em;
382 border-radius: 0.5em;
383 cursor: pointer;
 
 
 
 
 
 
384 }
385 body.chat #chat-user-list .chat-user.selected {
386 font-weight: bold;
387 text-decoration: underline;
388 }
389
--- src/style.chat.css
+++ src/style.chat.css
@@ -200,15 +200,13 @@
200 /* Wrapper for /chat user input controls */
201 body.chat #chat-input-area {
202 display: flex;
203 flex-direction: column;
204 padding: 0.5em 0 0 0;
 
 
205 margin: 0.5em 0 0 0;
206 position: initial /*sticky currently disabled due to scrolling-related issues*/;
207 /*bottom: 0;*/
208 }
209 body.chat:not(.chat-only-mode) #chat-input-area{
210 /* Safari user reports that 2em is necessary to keep the file selection
211 widget from overlapping the page footer, whereas a margin of 0 is fine
212 for FF/Chrome (and 2em is a *huge* waste of space for those). */
@@ -358,31 +356,35 @@
356 padding: 0.5em;
357 flex: 0 1 auto;
358 margin: 0.25em 0;
359 }
360
361 body.chat #chat-user-list-wrapper {
362 border-radius: 0.5em;
363 margin: 0.5em 0 0 0;
364 font-size: 85%;
365 }
366 body.chat #chat-user-list {
 
 
 
367 display: flex;
368 flex-direction: row;
369 flex-wrap: wrap;
370 align-items: center;
 
 
 
 
371 }
372 body.chat #chat-user-list::before {
373 /*content: "Recently active: ";*/
374 }
375 body.chat #chat-user-list .chat-user {
376 margin: 0.2em;
377 padding: 0.1em 0.5em 0.2em 0.5em;
378 border-radius: 0.5em;
379 cursor: pointer;
380 text-align: center;
381 white-space: pre;
382 }
383 body.chat #chat-user-list .chat-user > .timestamp {
384 font-size: 80%;
385 font-family: monospace;
386 }
387 body.chat #chat-user-list .chat-user.selected {
388 font-weight: bold;
389 text-decoration: underline;
390 }
391

Keyboard Shortcuts

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