Fossil SCM
/chat experiment, per chat discussion: when a given user posts multiple messages in a row, indent the 2nd and subsequent messages.
Commit
609bcd32c85fd10f5492a515ff7835c7f67c6d96ab6426983656e84660302da2
Parent
69135e4f61bddb4…
2 files changed
+43
-4
+17
+43
-4
| --- src/chat.js | ||
| +++ src/chat.js | ||
| @@ -226,17 +226,54 @@ | ||
| 226 | 226 | }else{ |
| 227 | 227 | eMsg.scrollIntoView(false); |
| 228 | 228 | } |
| 229 | 229 | return this; |
| 230 | 230 | }, |
| 231 | - /* Injects element e as a new row in the chat, at the top of the | |
| 232 | - list if atEnd is falsy, else at the end of the list, before | |
| 233 | - the load-history widget. */ | |
| 231 | + /** | |
| 232 | + Recalculates the ".subsequent" CSS class tag for all | |
| 233 | + messages. When a user posts multiple messages in a row, the | |
| 234 | + 2nd and subsequent ones get the "subsequent" tag added to | |
| 235 | + them for additional styling. We cannot do this easily as | |
| 236 | + messages arrive in batch mode because they can arrive out of | |
| 237 | + order (they arrive, and are processed, in reverse order for | |
| 238 | + the "load older messages" buttons). We do this handling in | |
| 239 | + injectMessageElem() for "interactive" use but have to | |
| 240 | + recalculate them en mass after receiving a batch of messages | |
| 241 | + right after this app loads or via the "load older messages" | |
| 242 | + buttons. | |
| 243 | + */ | |
| 244 | + recalcMessageIndents: function(){ | |
| 245 | + var prevXFrom; | |
| 246 | + this.e.messagesWrapper.querySelectorAll('.message-widget').forEach(function(e,ndx){ | |
| 247 | + if(e.classList.contains('notification')){ | |
| 248 | + // Obligatory special case. | |
| 249 | + prevXFrom = undefined; | |
| 250 | + D.removeClass(e, 'subsequent'); | |
| 251 | + return; | |
| 252 | + } | |
| 253 | + const xfrom = e.dataset.xfrom; | |
| 254 | + if(ndx && xfrom === prevXFrom){ | |
| 255 | + D.addClass(e,'subsequent'); | |
| 256 | + }else{ | |
| 257 | + D.removeClass(e, 'subsequent'); | |
| 258 | + } | |
| 259 | + prevXFrom = xfrom; | |
| 260 | + }); | |
| 261 | + }, | |
| 262 | + /* Injects element e as a new row in the chat, at the oldest end | |
| 263 | + of the list if atEnd is truthy, else at the newest end of the | |
| 264 | + list. */ | |
| 234 | 265 | injectMessageElem: function f(e, atEnd){ |
| 235 | 266 | const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint, |
| 236 | 267 | holder = this.e.messagesWrapper, |
| 237 | 268 | prevMessage = this.e.newestMessage; |
| 269 | + if(!atEnd && !this._isBatchLoading | |
| 270 | + && e.dataset.xfrom && !e.classList.contains('notification')){ | |
| 271 | + if(prevMessage && prevMessage.dataset.xfrom===e.dataset.xfrom){ | |
| 272 | + D.addClass(e, 'subsequent'); | |
| 273 | + } | |
| 274 | + } | |
| 238 | 275 | if(atEnd){ |
| 239 | 276 | const fe = mip.nextElementSibling; |
| 240 | 277 | if(fe) mip.parentNode.insertBefore(e, fe); |
| 241 | 278 | else D.append(mip.parentNode, e); |
| 242 | 279 | }else{ |
| @@ -1147,10 +1184,11 @@ | ||
| 1147 | 1184 | Chat._isBatchLoading = false; |
| 1148 | 1185 | if(Chat._gotServerError){ |
| 1149 | 1186 | Chat._gotServerError = false; |
| 1150 | 1187 | return; |
| 1151 | 1188 | } |
| 1189 | + Chat.recalcMessageIndents(); | |
| 1152 | 1190 | if(n<0/*we asked for all history*/ |
| 1153 | 1191 | || 0===gotMessages/*we found no history*/ |
| 1154 | 1192 | || (n>0 && gotMessages<n /*we got fewer history entries than requested*/) |
| 1155 | 1193 | || (n===0 && gotMessages<Chat.loadMessageCount |
| 1156 | 1194 | /*we asked for default amount and got fewer than that.*/)){ |
| @@ -1194,10 +1232,11 @@ | ||
| 1194 | 1232 | const afterFetch = function f(){ |
| 1195 | 1233 | if(true===f.isFirstCall){ |
| 1196 | 1234 | f.isFirstCall = false; |
| 1197 | 1235 | Chat.ajaxEnd(); |
| 1198 | 1236 | Chat.e.messagesWrapper.classList.remove('loading'); |
| 1237 | + Chat.recalcMessageIndents(); | |
| 1199 | 1238 | setTimeout(function(){ |
| 1200 | 1239 | Chat.scrollMessagesTo(1); |
| 1201 | 1240 | }, 250); |
| 1202 | 1241 | } |
| 1203 | 1242 | if(Chat._gotServerError && Chat.intervalTimer){ |
| @@ -1235,11 +1274,11 @@ | ||
| 1235 | 1274 | fails exepectedly when it times out, but is then immediately |
| 1236 | 1275 | resumed, and reportError() produces a loud error message. */ |
| 1237 | 1276 | afterFetch(); |
| 1238 | 1277 | }, |
| 1239 | 1278 | onload:function(y){ |
| 1240 | - newcontent(y); | |
| 1279 | + newcontent(y); | |
| 1241 | 1280 | Chat._isBatchLoading = false; |
| 1242 | 1281 | afterFetch(); |
| 1243 | 1282 | } |
| 1244 | 1283 | }); |
| 1245 | 1284 | }; |
| 1246 | 1285 |
| --- src/chat.js | |
| +++ src/chat.js | |
| @@ -226,17 +226,54 @@ | |
| 226 | }else{ |
| 227 | eMsg.scrollIntoView(false); |
| 228 | } |
| 229 | return this; |
| 230 | }, |
| 231 | /* Injects element e as a new row in the chat, at the top of the |
| 232 | list if atEnd is falsy, else at the end of the list, before |
| 233 | the load-history widget. */ |
| 234 | injectMessageElem: function f(e, atEnd){ |
| 235 | const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint, |
| 236 | holder = this.e.messagesWrapper, |
| 237 | prevMessage = this.e.newestMessage; |
| 238 | if(atEnd){ |
| 239 | const fe = mip.nextElementSibling; |
| 240 | if(fe) mip.parentNode.insertBefore(e, fe); |
| 241 | else D.append(mip.parentNode, e); |
| 242 | }else{ |
| @@ -1147,10 +1184,11 @@ | |
| 1147 | Chat._isBatchLoading = false; |
| 1148 | if(Chat._gotServerError){ |
| 1149 | Chat._gotServerError = false; |
| 1150 | return; |
| 1151 | } |
| 1152 | if(n<0/*we asked for all history*/ |
| 1153 | || 0===gotMessages/*we found no history*/ |
| 1154 | || (n>0 && gotMessages<n /*we got fewer history entries than requested*/) |
| 1155 | || (n===0 && gotMessages<Chat.loadMessageCount |
| 1156 | /*we asked for default amount and got fewer than that.*/)){ |
| @@ -1194,10 +1232,11 @@ | |
| 1194 | const afterFetch = function f(){ |
| 1195 | if(true===f.isFirstCall){ |
| 1196 | f.isFirstCall = false; |
| 1197 | Chat.ajaxEnd(); |
| 1198 | Chat.e.messagesWrapper.classList.remove('loading'); |
| 1199 | setTimeout(function(){ |
| 1200 | Chat.scrollMessagesTo(1); |
| 1201 | }, 250); |
| 1202 | } |
| 1203 | if(Chat._gotServerError && Chat.intervalTimer){ |
| @@ -1235,11 +1274,11 @@ | |
| 1235 | fails exepectedly when it times out, but is then immediately |
| 1236 | resumed, and reportError() produces a loud error message. */ |
| 1237 | afterFetch(); |
| 1238 | }, |
| 1239 | onload:function(y){ |
| 1240 | newcontent(y); |
| 1241 | Chat._isBatchLoading = false; |
| 1242 | afterFetch(); |
| 1243 | } |
| 1244 | }); |
| 1245 | }; |
| 1246 |
| --- src/chat.js | |
| +++ src/chat.js | |
| @@ -226,17 +226,54 @@ | |
| 226 | }else{ |
| 227 | eMsg.scrollIntoView(false); |
| 228 | } |
| 229 | return this; |
| 230 | }, |
| 231 | /** |
| 232 | Recalculates the ".subsequent" CSS class tag for all |
| 233 | messages. When a user posts multiple messages in a row, the |
| 234 | 2nd and subsequent ones get the "subsequent" tag added to |
| 235 | them for additional styling. We cannot do this easily as |
| 236 | messages arrive in batch mode because they can arrive out of |
| 237 | order (they arrive, and are processed, in reverse order for |
| 238 | the "load older messages" buttons). We do this handling in |
| 239 | injectMessageElem() for "interactive" use but have to |
| 240 | recalculate them en mass after receiving a batch of messages |
| 241 | right after this app loads or via the "load older messages" |
| 242 | buttons. |
| 243 | */ |
| 244 | recalcMessageIndents: function(){ |
| 245 | var prevXFrom; |
| 246 | this.e.messagesWrapper.querySelectorAll('.message-widget').forEach(function(e,ndx){ |
| 247 | if(e.classList.contains('notification')){ |
| 248 | // Obligatory special case. |
| 249 | prevXFrom = undefined; |
| 250 | D.removeClass(e, 'subsequent'); |
| 251 | return; |
| 252 | } |
| 253 | const xfrom = e.dataset.xfrom; |
| 254 | if(ndx && xfrom === prevXFrom){ |
| 255 | D.addClass(e,'subsequent'); |
| 256 | }else{ |
| 257 | D.removeClass(e, 'subsequent'); |
| 258 | } |
| 259 | prevXFrom = xfrom; |
| 260 | }); |
| 261 | }, |
| 262 | /* Injects element e as a new row in the chat, at the oldest end |
| 263 | of the list if atEnd is truthy, else at the newest end of the |
| 264 | list. */ |
| 265 | injectMessageElem: function f(e, atEnd){ |
| 266 | const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint, |
| 267 | holder = this.e.messagesWrapper, |
| 268 | prevMessage = this.e.newestMessage; |
| 269 | if(!atEnd && !this._isBatchLoading |
| 270 | && e.dataset.xfrom && !e.classList.contains('notification')){ |
| 271 | if(prevMessage && prevMessage.dataset.xfrom===e.dataset.xfrom){ |
| 272 | D.addClass(e, 'subsequent'); |
| 273 | } |
| 274 | } |
| 275 | if(atEnd){ |
| 276 | const fe = mip.nextElementSibling; |
| 277 | if(fe) mip.parentNode.insertBefore(e, fe); |
| 278 | else D.append(mip.parentNode, e); |
| 279 | }else{ |
| @@ -1147,10 +1184,11 @@ | |
| 1184 | Chat._isBatchLoading = false; |
| 1185 | if(Chat._gotServerError){ |
| 1186 | Chat._gotServerError = false; |
| 1187 | return; |
| 1188 | } |
| 1189 | Chat.recalcMessageIndents(); |
| 1190 | if(n<0/*we asked for all history*/ |
| 1191 | || 0===gotMessages/*we found no history*/ |
| 1192 | || (n>0 && gotMessages<n /*we got fewer history entries than requested*/) |
| 1193 | || (n===0 && gotMessages<Chat.loadMessageCount |
| 1194 | /*we asked for default amount and got fewer than that.*/)){ |
| @@ -1194,10 +1232,11 @@ | |
| 1232 | const afterFetch = function f(){ |
| 1233 | if(true===f.isFirstCall){ |
| 1234 | f.isFirstCall = false; |
| 1235 | Chat.ajaxEnd(); |
| 1236 | Chat.e.messagesWrapper.classList.remove('loading'); |
| 1237 | Chat.recalcMessageIndents(); |
| 1238 | setTimeout(function(){ |
| 1239 | Chat.scrollMessagesTo(1); |
| 1240 | }, 250); |
| 1241 | } |
| 1242 | if(Chat._gotServerError && Chat.intervalTimer){ |
| @@ -1235,11 +1274,11 @@ | |
| 1274 | fails exepectedly when it times out, but is then immediately |
| 1275 | resumed, and reportError() produces a loud error message. */ |
| 1276 | afterFetch(); |
| 1277 | }, |
| 1278 | onload:function(y){ |
| 1279 | newcontent(y); |
| 1280 | Chat._isBatchLoading = false; |
| 1281 | afterFetch(); |
| 1282 | } |
| 1283 | }); |
| 1284 | }; |
| 1285 |
+17
| --- src/default.css | ||
| +++ src/default.css | ||
| @@ -1518,10 +1518,27 @@ | ||
| 1518 | 1518 | white-space: nowrap; |
| 1519 | 1519 | } |
| 1520 | 1520 | body.chat .fossil-tooltip.help-buttonlet-content { |
| 1521 | 1521 | font-size: 80%; |
| 1522 | 1522 | } |
| 1523 | +body.chat .message-widget.subsequent { | |
| 1524 | + /* When a single user posts multiple messages in a row, | |
| 1525 | + the 2nd and subsequent ones get the 'subsequent' class | |
| 1526 | + added to them. */ | |
| 1527 | + margin-left: 2.5em; | |
| 1528 | + margin-right: 2.5em; | |
| 1529 | +} | |
| 1530 | +body.chat .message-widget.subsequent .message-widget-tab:before { | |
| 1531 | + content: "↳ "; | |
| 1532 | +} | |
| 1533 | +body.chat.my-messages-right .message-widget.subsequent.mine .message-widget-tab:before { | |
| 1534 | + content: revert; | |
| 1535 | +} | |
| 1536 | +body.chat.my-messages-right .message-widget.subsequent.mine .message-widget-tab:after { | |
| 1537 | + content: " ↲"; | |
| 1538 | +} | |
| 1539 | + | |
| 1523 | 1540 | /* The popup element for displaying message timestamps |
| 1524 | 1541 | and deletion controls. */ |
| 1525 | 1542 | body.chat .chat-message-popup { |
| 1526 | 1543 | font-family: monospace; |
| 1527 | 1544 | font-size: 0.8em; |
| 1528 | 1545 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -1518,10 +1518,27 @@ | |
| 1518 | white-space: nowrap; |
| 1519 | } |
| 1520 | body.chat .fossil-tooltip.help-buttonlet-content { |
| 1521 | font-size: 80%; |
| 1522 | } |
| 1523 | /* The popup element for displaying message timestamps |
| 1524 | and deletion controls. */ |
| 1525 | body.chat .chat-message-popup { |
| 1526 | font-family: monospace; |
| 1527 | font-size: 0.8em; |
| 1528 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -1518,10 +1518,27 @@ | |
| 1518 | white-space: nowrap; |
| 1519 | } |
| 1520 | body.chat .fossil-tooltip.help-buttonlet-content { |
| 1521 | font-size: 80%; |
| 1522 | } |
| 1523 | body.chat .message-widget.subsequent { |
| 1524 | /* When a single user posts multiple messages in a row, |
| 1525 | the 2nd and subsequent ones get the 'subsequent' class |
| 1526 | added to them. */ |
| 1527 | margin-left: 2.5em; |
| 1528 | margin-right: 2.5em; |
| 1529 | } |
| 1530 | body.chat .message-widget.subsequent .message-widget-tab:before { |
| 1531 | content: "↳ "; |
| 1532 | } |
| 1533 | body.chat.my-messages-right .message-widget.subsequent.mine .message-widget-tab:before { |
| 1534 | content: revert; |
| 1535 | } |
| 1536 | body.chat.my-messages-right .message-widget.subsequent.mine .message-widget-tab:after { |
| 1537 | content: " ↲"; |
| 1538 | } |
| 1539 | |
| 1540 | /* The popup element for displaying message timestamps |
| 1541 | and deletion controls. */ |
| 1542 | body.chat .chat-message-popup { |
| 1543 | font-family: monospace; |
| 1544 | font-size: 0.8em; |
| 1545 |