Fossil SCM
/chat: experimentally render a list of users ordered by most recent activity. Until/unless we can find a useful function for the list, though, it's really just a somewhat pretty screen space hog.
Commit
c7ee6f4ef19a3b1ef94ded459b98bd94c91bf249f0d3b5d1bfd71f808ad588e4
Parent
be07b8d1373071d…
2 files changed
+47
-18
+18
+47
-18
| --- src/chat.js | ||
| +++ src/chat.js | ||
| @@ -109,11 +109,12 @@ | ||
| 109 | 109 | btnSubmit: E1('#chat-message-submit'), |
| 110 | 110 | inputSingle: E1('#chat-input-single'), |
| 111 | 111 | inputMulti: E1('#chat-input-multi'), |
| 112 | 112 | inputCurrent: undefined/*one of inputSingle or inputMulti*/, |
| 113 | 113 | inputFile: E1('#chat-input-file'), |
| 114 | - contentDiv: E1('div.content') | |
| 114 | + contentDiv: E1('div.content'), | |
| 115 | + activeUserList: undefined/*active user list (dynamically created later on)*/ | |
| 115 | 116 | }, |
| 116 | 117 | me: F.user.name, |
| 117 | 118 | mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50, |
| 118 | 119 | mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/, |
| 119 | 120 | pageIsActive: 'visible'===document.visibilityState, |
| @@ -394,10 +395,49 @@ | ||
| 394 | 395 | setNewMessageSound: function f(uri){ |
| 395 | 396 | delete this.playNewMessageSound.audio; |
| 396 | 397 | this.playNewMessageSound.uri = uri; |
| 397 | 398 | this.settings.set('audible-alert', !!uri); |
| 398 | 399 | return this; |
| 400 | + }, | |
| 401 | + | |
| 402 | + /** | |
| 403 | + Updates the "active user list" view. | |
| 404 | + */ | |
| 405 | + updateActiveUserList: function callee(){ | |
| 406 | + if(!this.e.activeUserList){ | |
| 407 | + /** Array.sort() callback. Expects an array of user names and | |
| 408 | + sorts them in last-received message order (newest first). */ | |
| 409 | + const usersLastSeen = this.usersLastSeen; | |
| 410 | + callee.sortUsersSeen = function(l,r){ | |
| 411 | + l = usersLastSeen[l]; | |
| 412 | + r = usersLastSeen[r]; | |
| 413 | + if(l && r) return r - l; | |
| 414 | + else if(l) return -1; | |
| 415 | + else if(r) return 1; | |
| 416 | + else return 0; | |
| 417 | + }; | |
| 418 | + const content = document.querySelector('body > div.content'); | |
| 419 | + const ael = this.e.activeUserList = | |
| 420 | + D.attr(D.div(),'id','active-user-list'); | |
| 421 | + D.append(ael, "user list placeholder"); | |
| 422 | + content.insertBefore(ael, content.firstElementChild) | |
| 423 | + /*remember: layout is reversed!*/; | |
| 424 | + } | |
| 425 | + const self = this, | |
| 426 | + users = Object.keys(this.usersLastSeen).sort(callee.sortUsersSeen); | |
| 427 | + if(!users.length) return this; | |
| 428 | + const ael = this.e.activeUserList; | |
| 429 | + D.clearElement(ael); | |
| 430 | + users.forEach(function(u){ | |
| 431 | + const uSpan = D.addClass(D.span(), 'chat-user'); | |
| 432 | + const uDate = self.usersLastSeen[u]; | |
| 433 | + D.append(uSpan, u); | |
| 434 | + if(uDate.$uColor){ | |
| 435 | + uSpan.style.backgroundColor = uDate.$uColor; | |
| 436 | + } | |
| 437 | + D.append(ael, uSpan); | |
| 438 | + }); | |
| 399 | 439 | } |
| 400 | 440 | }; |
| 401 | 441 | F.fetch.beforesend = ()=>cs.ajaxStart(); |
| 402 | 442 | F.fetch.aftersend = ()=>cs.ajaxEnd(); |
| 403 | 443 | cs.e.inputCurrent = cs.e.inputSingle; |
| @@ -1086,20 +1126,10 @@ | ||
| 1086 | 1126 | the response from /chat-poll. If atEnd is true, the message is |
| 1087 | 1127 | appended to the end of the chat list (for loading older |
| 1088 | 1128 | messages), else the beginning (the default). */ |
| 1089 | 1129 | const newcontent = function f(jx,atEnd){ |
| 1090 | 1130 | if(!f.processPost){ |
| 1091 | - /** Array.sort() callback. Expects an array of user names and | |
| 1092 | - sorts them in last-received message order (newest first). */ | |
| 1093 | - f.sortUsersSeen = function(l,r){ | |
| 1094 | - l = Chat.usersLastSeen[l]; | |
| 1095 | - r = Chat.usersLastSeen[r]; | |
| 1096 | - if(l && r) return r - l; | |
| 1097 | - else if(l) return -1; | |
| 1098 | - else if(r) return 1; | |
| 1099 | - else return 0; | |
| 1100 | - }; | |
| 1101 | 1131 | /** Processes chat message m, placing it either at the start (if |
| 1102 | 1132 | atEnd is falsy) or end (if atEnd is truthy) of the chat |
| 1103 | 1133 | history. atEnd should only be true when loading older |
| 1104 | 1134 | messages. */ |
| 1105 | 1135 | f.processPost = function(m,atEnd){ |
| @@ -1107,11 +1137,14 @@ | ||
| 1107 | 1137 | if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid; |
| 1108 | 1138 | if( !Chat.mnMsg || m.msgid<Chat.mnMsg) Chat.mnMsg = m.msgid; |
| 1109 | 1139 | if(m.xfrom && m.mtime){ |
| 1110 | 1140 | const d = new Date(m.mtime); |
| 1111 | 1141 | const uls = Chat.usersLastSeen[m.xfrom]; |
| 1112 | - if(!uls || uls<d) Chat.usersLastSeen[m.xfrom] = d; | |
| 1142 | + if(!uls || uls<d){ | |
| 1143 | + d.$uColor = m.uclr; | |
| 1144 | + Chat.usersLastSeen[m.xfrom] = d; | |
| 1145 | + } | |
| 1113 | 1146 | } |
| 1114 | 1147 | if( m.mdel ){ |
| 1115 | 1148 | /* A record deletion notice. */ |
| 1116 | 1149 | Chat.deleteMessageElem(m.mdel); |
| 1117 | 1150 | return; |
| @@ -1121,16 +1154,12 @@ | ||
| 1121 | 1154 | } |
| 1122 | 1155 | const row = new Chat.MessageWidget(m); |
| 1123 | 1156 | Chat.injectMessageElem(row.e.body,atEnd); |
| 1124 | 1157 | if(m.isError){ |
| 1125 | 1158 | Chat._gotServerError = m; |
| 1126 | - }else if(false){ | |
| 1127 | - const users = Object.keys(Chat.usersLastSeen).sort(f.sortUsersSeen); | |
| 1128 | - console.debug("Users sorted by most recent activity (newest first):", users); | |
| 1129 | - users.forEach(function(u){ | |
| 1130 | - console.debug(u, Chat.usersLastSeen[u].toISOString()); | |
| 1131 | - }); | |
| 1159 | + }else{ | |
| 1160 | + Chat.updateActiveUserList(); | |
| 1132 | 1161 | } |
| 1133 | 1162 | }/*processPost()*/; |
| 1134 | 1163 | }/*end static init*/ |
| 1135 | 1164 | jx.msgs.forEach((m)=>f.processPost(m,atEnd)); |
| 1136 | 1165 | if('visible'===document.visibilityState){ |
| 1137 | 1166 |
| --- src/chat.js | |
| +++ src/chat.js | |
| @@ -109,11 +109,12 @@ | |
| 109 | btnSubmit: E1('#chat-message-submit'), |
| 110 | inputSingle: E1('#chat-input-single'), |
| 111 | inputMulti: E1('#chat-input-multi'), |
| 112 | inputCurrent: undefined/*one of inputSingle or inputMulti*/, |
| 113 | inputFile: E1('#chat-input-file'), |
| 114 | contentDiv: E1('div.content') |
| 115 | }, |
| 116 | me: F.user.name, |
| 117 | mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50, |
| 118 | mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/, |
| 119 | pageIsActive: 'visible'===document.visibilityState, |
| @@ -394,10 +395,49 @@ | |
| 394 | setNewMessageSound: function f(uri){ |
| 395 | delete this.playNewMessageSound.audio; |
| 396 | this.playNewMessageSound.uri = uri; |
| 397 | this.settings.set('audible-alert', !!uri); |
| 398 | return this; |
| 399 | } |
| 400 | }; |
| 401 | F.fetch.beforesend = ()=>cs.ajaxStart(); |
| 402 | F.fetch.aftersend = ()=>cs.ajaxEnd(); |
| 403 | cs.e.inputCurrent = cs.e.inputSingle; |
| @@ -1086,20 +1126,10 @@ | |
| 1086 | the response from /chat-poll. If atEnd is true, the message is |
| 1087 | appended to the end of the chat list (for loading older |
| 1088 | messages), else the beginning (the default). */ |
| 1089 | const newcontent = function f(jx,atEnd){ |
| 1090 | if(!f.processPost){ |
| 1091 | /** Array.sort() callback. Expects an array of user names and |
| 1092 | sorts them in last-received message order (newest first). */ |
| 1093 | f.sortUsersSeen = function(l,r){ |
| 1094 | l = Chat.usersLastSeen[l]; |
| 1095 | r = Chat.usersLastSeen[r]; |
| 1096 | if(l && r) return r - l; |
| 1097 | else if(l) return -1; |
| 1098 | else if(r) return 1; |
| 1099 | else return 0; |
| 1100 | }; |
| 1101 | /** Processes chat message m, placing it either at the start (if |
| 1102 | atEnd is falsy) or end (if atEnd is truthy) of the chat |
| 1103 | history. atEnd should only be true when loading older |
| 1104 | messages. */ |
| 1105 | f.processPost = function(m,atEnd){ |
| @@ -1107,11 +1137,14 @@ | |
| 1107 | if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid; |
| 1108 | if( !Chat.mnMsg || m.msgid<Chat.mnMsg) Chat.mnMsg = m.msgid; |
| 1109 | if(m.xfrom && m.mtime){ |
| 1110 | const d = new Date(m.mtime); |
| 1111 | const uls = Chat.usersLastSeen[m.xfrom]; |
| 1112 | if(!uls || uls<d) Chat.usersLastSeen[m.xfrom] = d; |
| 1113 | } |
| 1114 | if( m.mdel ){ |
| 1115 | /* A record deletion notice. */ |
| 1116 | Chat.deleteMessageElem(m.mdel); |
| 1117 | return; |
| @@ -1121,16 +1154,12 @@ | |
| 1121 | } |
| 1122 | const row = new Chat.MessageWidget(m); |
| 1123 | Chat.injectMessageElem(row.e.body,atEnd); |
| 1124 | if(m.isError){ |
| 1125 | Chat._gotServerError = m; |
| 1126 | }else if(false){ |
| 1127 | const users = Object.keys(Chat.usersLastSeen).sort(f.sortUsersSeen); |
| 1128 | console.debug("Users sorted by most recent activity (newest first):", users); |
| 1129 | users.forEach(function(u){ |
| 1130 | console.debug(u, Chat.usersLastSeen[u].toISOString()); |
| 1131 | }); |
| 1132 | } |
| 1133 | }/*processPost()*/; |
| 1134 | }/*end static init*/ |
| 1135 | jx.msgs.forEach((m)=>f.processPost(m,atEnd)); |
| 1136 | if('visible'===document.visibilityState){ |
| 1137 |
| --- src/chat.js | |
| +++ src/chat.js | |
| @@ -109,11 +109,12 @@ | |
| 109 | btnSubmit: E1('#chat-message-submit'), |
| 110 | inputSingle: E1('#chat-input-single'), |
| 111 | inputMulti: E1('#chat-input-multi'), |
| 112 | inputCurrent: undefined/*one of inputSingle or inputMulti*/, |
| 113 | inputFile: E1('#chat-input-file'), |
| 114 | contentDiv: E1('div.content'), |
| 115 | activeUserList: undefined/*active user list (dynamically created later on)*/ |
| 116 | }, |
| 117 | me: F.user.name, |
| 118 | mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50, |
| 119 | mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/, |
| 120 | pageIsActive: 'visible'===document.visibilityState, |
| @@ -394,10 +395,49 @@ | |
| 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 | /** |
| 403 | Updates the "active user list" view. |
| 404 | */ |
| 405 | updateActiveUserList: function callee(){ |
| 406 | if(!this.e.activeUserList){ |
| 407 | /** Array.sort() callback. Expects an array of user names and |
| 408 | sorts them in last-received message order (newest first). */ |
| 409 | const usersLastSeen = this.usersLastSeen; |
| 410 | callee.sortUsersSeen = function(l,r){ |
| 411 | l = usersLastSeen[l]; |
| 412 | r = usersLastSeen[r]; |
| 413 | if(l && r) return r - l; |
| 414 | else if(l) return -1; |
| 415 | else if(r) return 1; |
| 416 | else return 0; |
| 417 | }; |
| 418 | const content = document.querySelector('body > div.content'); |
| 419 | const ael = this.e.activeUserList = |
| 420 | D.attr(D.div(),'id','active-user-list'); |
| 421 | D.append(ael, "user list placeholder"); |
| 422 | content.insertBefore(ael, content.firstElementChild) |
| 423 | /*remember: layout is reversed!*/; |
| 424 | } |
| 425 | const self = this, |
| 426 | users = Object.keys(this.usersLastSeen).sort(callee.sortUsersSeen); |
| 427 | if(!users.length) return this; |
| 428 | const ael = this.e.activeUserList; |
| 429 | D.clearElement(ael); |
| 430 | users.forEach(function(u){ |
| 431 | const uSpan = D.addClass(D.span(), 'chat-user'); |
| 432 | const uDate = self.usersLastSeen[u]; |
| 433 | D.append(uSpan, u); |
| 434 | if(uDate.$uColor){ |
| 435 | uSpan.style.backgroundColor = uDate.$uColor; |
| 436 | } |
| 437 | D.append(ael, uSpan); |
| 438 | }); |
| 439 | } |
| 440 | }; |
| 441 | F.fetch.beforesend = ()=>cs.ajaxStart(); |
| 442 | F.fetch.aftersend = ()=>cs.ajaxEnd(); |
| 443 | cs.e.inputCurrent = cs.e.inputSingle; |
| @@ -1086,20 +1126,10 @@ | |
| 1126 | the response from /chat-poll. If atEnd is true, the message is |
| 1127 | appended to the end of the chat list (for loading older |
| 1128 | messages), else the beginning (the default). */ |
| 1129 | const newcontent = function f(jx,atEnd){ |
| 1130 | if(!f.processPost){ |
| 1131 | /** Processes chat message m, placing it either at the start (if |
| 1132 | atEnd is falsy) or end (if atEnd is truthy) of the chat |
| 1133 | history. atEnd should only be true when loading older |
| 1134 | messages. */ |
| 1135 | f.processPost = function(m,atEnd){ |
| @@ -1107,11 +1137,14 @@ | |
| 1137 | if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid; |
| 1138 | if( !Chat.mnMsg || m.msgid<Chat.mnMsg) Chat.mnMsg = m.msgid; |
| 1139 | if(m.xfrom && m.mtime){ |
| 1140 | const d = new Date(m.mtime); |
| 1141 | const uls = Chat.usersLastSeen[m.xfrom]; |
| 1142 | if(!uls || uls<d){ |
| 1143 | d.$uColor = m.uclr; |
| 1144 | Chat.usersLastSeen[m.xfrom] = d; |
| 1145 | } |
| 1146 | } |
| 1147 | if( m.mdel ){ |
| 1148 | /* A record deletion notice. */ |
| 1149 | Chat.deleteMessageElem(m.mdel); |
| 1150 | return; |
| @@ -1121,16 +1154,12 @@ | |
| 1154 | } |
| 1155 | const row = new Chat.MessageWidget(m); |
| 1156 | Chat.injectMessageElem(row.e.body,atEnd); |
| 1157 | if(m.isError){ |
| 1158 | Chat._gotServerError = m; |
| 1159 | }else{ |
| 1160 | Chat.updateActiveUserList(); |
| 1161 | } |
| 1162 | }/*processPost()*/; |
| 1163 | }/*end static init*/ |
| 1164 | jx.msgs.forEach((m)=>f.processPost(m,atEnd)); |
| 1165 | if('visible'===document.visibilityState){ |
| 1166 |
+18
| --- src/default.css | ||
| +++ src/default.css | ||
| @@ -1767,10 +1767,28 @@ | ||
| 1767 | 1767 | } |
| 1768 | 1768 | |
| 1769 | 1769 | body.chat #chat-drop-details img { |
| 1770 | 1770 | max-width: 45%; |
| 1771 | 1771 | max-height: 45%; |
| 1772 | +} | |
| 1773 | +body.chat #active-user-list { | |
| 1774 | + border: 1px inset; | |
| 1775 | + padding: 0.1em 0.2em; | |
| 1776 | + border-radius: 0.25em; | |
| 1777 | + display: flex; | |
| 1778 | + flex-direction: row; | |
| 1779 | + flex-wrap: wrap; | |
| 1780 | + align-items: center; | |
| 1781 | + font-size: 80%; | |
| 1782 | +} | |
| 1783 | +body.chat #active-user-list::before { | |
| 1784 | + content: "Most recently active:"; | |
| 1785 | +} | |
| 1786 | +body.chat #active-user-list span.chat-user { | |
| 1787 | + margin: 0.2em; | |
| 1788 | + padding: 0.15em; | |
| 1789 | + border-radius: 0.25em; | |
| 1772 | 1790 | } |
| 1773 | 1791 | |
| 1774 | 1792 | input[type="checkbox"].diff-toggle { |
| 1775 | 1793 | float: right; |
| 1776 | 1794 | } |
| 1777 | 1795 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -1767,10 +1767,28 @@ | |
| 1767 | } |
| 1768 | |
| 1769 | body.chat #chat-drop-details img { |
| 1770 | max-width: 45%; |
| 1771 | max-height: 45%; |
| 1772 | } |
| 1773 | |
| 1774 | input[type="checkbox"].diff-toggle { |
| 1775 | float: right; |
| 1776 | } |
| 1777 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -1767,10 +1767,28 @@ | |
| 1767 | } |
| 1768 | |
| 1769 | body.chat #chat-drop-details img { |
| 1770 | max-width: 45%; |
| 1771 | max-height: 45%; |
| 1772 | } |
| 1773 | body.chat #active-user-list { |
| 1774 | border: 1px inset; |
| 1775 | padding: 0.1em 0.2em; |
| 1776 | border-radius: 0.25em; |
| 1777 | display: flex; |
| 1778 | flex-direction: row; |
| 1779 | flex-wrap: wrap; |
| 1780 | align-items: center; |
| 1781 | font-size: 80%; |
| 1782 | } |
| 1783 | body.chat #active-user-list::before { |
| 1784 | content: "Most recently active:"; |
| 1785 | } |
| 1786 | body.chat #active-user-list span.chat-user { |
| 1787 | margin: 0.2em; |
| 1788 | padding: 0.15em; |
| 1789 | border-radius: 0.25em; |
| 1790 | } |
| 1791 | |
| 1792 | input[type="checkbox"].diff-toggle { |
| 1793 | float: right; |
| 1794 | } |
| 1795 |