Fossil SCM
In /chat-generated notifications (typically error messages), add a button to the drop-down options to delete all notifications. That replaces the 'delete all poller notifications' button which previously only showed up only on those message types. Add a mention of the backoff timer to chat.md.
Commit
da1c351b6ef56f36a6c43fd7f0bd8d32ae69e94e974635aef5ba2956401ec8d7
Parent
1bfb06c7524b132…
2 files changed
+10
-7
+18
+10
-7
| --- src/fossil.page.chat.js | ||
| +++ src/fossil.page.chat.js | ||
| @@ -1326,17 +1326,18 @@ | ||
| 1326 | 1326 | const self = this; |
| 1327 | 1327 | btnDeleteLocal.addEventListener('click', function(){ |
| 1328 | 1328 | self.hide(); |
| 1329 | 1329 | Chat.deleteMessageElem(eMsg) |
| 1330 | 1330 | }); |
| 1331 | - if( eMsg.classList.contains('poller-connection') ){ | |
| 1332 | - const btnDeletePoll = D.button("Delete poller messages?"); | |
| 1331 | + if( eMsg.classList.contains('notification') ){ | |
| 1332 | + const btnDeletePoll = D.button("Delete /chat notifications?"); | |
| 1333 | 1333 | D.append(toolbar, btnDeletePoll); |
| 1334 | 1334 | btnDeletePoll.addEventListener('click', function(){ |
| 1335 | 1335 | self.hide(); |
| 1336 | - Chat.e.viewMessages.querySelectorAll('.message-widget.poller-connection') | |
| 1337 | - .forEach(e=>Chat.deleteMessageElem(e, true)); | |
| 1336 | + Chat.e.viewMessages.querySelectorAll( | |
| 1337 | + '.message-widget.notification:not(.resend-message)' | |
| 1338 | + ).forEach(e=>Chat.deleteMessageElem(e, true)); | |
| 1338 | 1339 | }); |
| 1339 | 1340 | } |
| 1340 | 1341 | if(Chat.userMayDelete(eMsg)){ |
| 1341 | 1342 | const btnDeleteGlobal = D.button("Delete globally"); |
| 1342 | 1343 | D.append(toolbar, btnDeleteGlobal); |
| @@ -1646,11 +1647,11 @@ | ||
| 1646 | 1647 | reader.onload = (e)=>img.setAttribute('src', e.target.result); |
| 1647 | 1648 | reader.readAsDataURL(blob); |
| 1648 | 1649 | } |
| 1649 | 1650 | }; |
| 1650 | 1651 | Chat.e.inputFile.addEventListener('change', function(ev){ |
| 1651 | - updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined) | |
| 1652 | + updateDropZoneContent(this?.files[0]) | |
| 1652 | 1653 | }); |
| 1653 | 1654 | /* Handle image paste from clipboard. TODO: figure out how we can |
| 1654 | 1655 | paste non-image binary data as if it had been selected via the |
| 1655 | 1656 | file selection element. */ |
| 1656 | 1657 | const pasteListener = function(event){ |
| @@ -1726,10 +1727,11 @@ | ||
| 1726 | 1727 | D.span(),"This message was not successfully sent to the server:" |
| 1727 | 1728 | )); |
| 1728 | 1729 | if(state.msg){ |
| 1729 | 1730 | const ta = D.textarea(); |
| 1730 | 1731 | ta.value = state.msg; |
| 1732 | + ta.setAttribute('readonly','true'); | |
| 1731 | 1733 | D.append(w,ta); |
| 1732 | 1734 | } |
| 1733 | 1735 | if(state.blob){ |
| 1734 | 1736 | D.append(w,D.append(D.span(),"Attachment: ",(state.blob.name||"unnamed"))); |
| 1735 | 1737 | //console.debug("blob = ",state.blob); |
| @@ -1744,11 +1746,11 @@ | ||
| 1744 | 1746 | if(state.msg) Chat.inputValue(state.msg); |
| 1745 | 1747 | if(state.blob) BlobXferState.updateDropZoneContent(state.blob); |
| 1746 | 1748 | const theMsg = findMessageWidgetParent(w); |
| 1747 | 1749 | if(theMsg) Chat.deleteMessageElem(theMsg); |
| 1748 | 1750 | })); |
| 1749 | - Chat.reportErrorAsMessage(w); | |
| 1751 | + D.addClass(Chat.reportErrorAsMessage(w).e.body, "resend-message"); | |
| 1750 | 1752 | }; |
| 1751 | 1753 | |
| 1752 | 1754 | /* Assume the connection has been established, reset the |
| 1753 | 1755 | Chat.timer.tidReconnect, and (if showMsg and |
| 1754 | 1756 | !!Chat.e.eMsgPollError) alert the user that the outage appears to |
| @@ -1755,11 +1757,12 @@ | ||
| 1755 | 1757 | be over. Also schedule Chat.poll() to run in the very near |
| 1756 | 1758 | future. */ |
| 1757 | 1759 | const reportConnectionOkay = function(dbgContext, showMsg = true){ |
| 1758 | 1760 | if(Chat.beVerbose){ |
| 1759 | 1761 | console.warn('reportConnectionOkay', dbgContext, |
| 1760 | - 'Chat.e.pollErrorMarker =',Chat.e.pollErrorMarker, | |
| 1762 | + 'Chat.e.pollErrorMarker classes =', | |
| 1763 | + Chat.e.pollErrorMarker.classList, | |
| 1761 | 1764 | 'Chat.timer.tidReconnect =',Chat.timer.tidReconnect, |
| 1762 | 1765 | 'Chat.timer =',Chat.timer); |
| 1763 | 1766 | } |
| 1764 | 1767 | setTimeout( Chat.poll, Chat.timer.resetDelay() ); |
| 1765 | 1768 | if( Chat.timer.errCount ){ |
| 1766 | 1769 |
| --- src/fossil.page.chat.js | |
| +++ src/fossil.page.chat.js | |
| @@ -1326,17 +1326,18 @@ | |
| 1326 | const self = this; |
| 1327 | btnDeleteLocal.addEventListener('click', function(){ |
| 1328 | self.hide(); |
| 1329 | Chat.deleteMessageElem(eMsg) |
| 1330 | }); |
| 1331 | if( eMsg.classList.contains('poller-connection') ){ |
| 1332 | const btnDeletePoll = D.button("Delete poller messages?"); |
| 1333 | D.append(toolbar, btnDeletePoll); |
| 1334 | btnDeletePoll.addEventListener('click', function(){ |
| 1335 | self.hide(); |
| 1336 | Chat.e.viewMessages.querySelectorAll('.message-widget.poller-connection') |
| 1337 | .forEach(e=>Chat.deleteMessageElem(e, true)); |
| 1338 | }); |
| 1339 | } |
| 1340 | if(Chat.userMayDelete(eMsg)){ |
| 1341 | const btnDeleteGlobal = D.button("Delete globally"); |
| 1342 | D.append(toolbar, btnDeleteGlobal); |
| @@ -1646,11 +1647,11 @@ | |
| 1646 | reader.onload = (e)=>img.setAttribute('src', e.target.result); |
| 1647 | reader.readAsDataURL(blob); |
| 1648 | } |
| 1649 | }; |
| 1650 | Chat.e.inputFile.addEventListener('change', function(ev){ |
| 1651 | updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined) |
| 1652 | }); |
| 1653 | /* Handle image paste from clipboard. TODO: figure out how we can |
| 1654 | paste non-image binary data as if it had been selected via the |
| 1655 | file selection element. */ |
| 1656 | const pasteListener = function(event){ |
| @@ -1726,10 +1727,11 @@ | |
| 1726 | D.span(),"This message was not successfully sent to the server:" |
| 1727 | )); |
| 1728 | if(state.msg){ |
| 1729 | const ta = D.textarea(); |
| 1730 | ta.value = state.msg; |
| 1731 | D.append(w,ta); |
| 1732 | } |
| 1733 | if(state.blob){ |
| 1734 | D.append(w,D.append(D.span(),"Attachment: ",(state.blob.name||"unnamed"))); |
| 1735 | //console.debug("blob = ",state.blob); |
| @@ -1744,11 +1746,11 @@ | |
| 1744 | if(state.msg) Chat.inputValue(state.msg); |
| 1745 | if(state.blob) BlobXferState.updateDropZoneContent(state.blob); |
| 1746 | const theMsg = findMessageWidgetParent(w); |
| 1747 | if(theMsg) Chat.deleteMessageElem(theMsg); |
| 1748 | })); |
| 1749 | Chat.reportErrorAsMessage(w); |
| 1750 | }; |
| 1751 | |
| 1752 | /* Assume the connection has been established, reset the |
| 1753 | Chat.timer.tidReconnect, and (if showMsg and |
| 1754 | !!Chat.e.eMsgPollError) alert the user that the outage appears to |
| @@ -1755,11 +1757,12 @@ | |
| 1755 | be over. Also schedule Chat.poll() to run in the very near |
| 1756 | future. */ |
| 1757 | const reportConnectionOkay = function(dbgContext, showMsg = true){ |
| 1758 | if(Chat.beVerbose){ |
| 1759 | console.warn('reportConnectionOkay', dbgContext, |
| 1760 | 'Chat.e.pollErrorMarker =',Chat.e.pollErrorMarker, |
| 1761 | 'Chat.timer.tidReconnect =',Chat.timer.tidReconnect, |
| 1762 | 'Chat.timer =',Chat.timer); |
| 1763 | } |
| 1764 | setTimeout( Chat.poll, Chat.timer.resetDelay() ); |
| 1765 | if( Chat.timer.errCount ){ |
| 1766 |
| --- src/fossil.page.chat.js | |
| +++ src/fossil.page.chat.js | |
| @@ -1326,17 +1326,18 @@ | |
| 1326 | const self = this; |
| 1327 | btnDeleteLocal.addEventListener('click', function(){ |
| 1328 | self.hide(); |
| 1329 | Chat.deleteMessageElem(eMsg) |
| 1330 | }); |
| 1331 | if( eMsg.classList.contains('notification') ){ |
| 1332 | const btnDeletePoll = D.button("Delete /chat notifications?"); |
| 1333 | D.append(toolbar, btnDeletePoll); |
| 1334 | btnDeletePoll.addEventListener('click', function(){ |
| 1335 | self.hide(); |
| 1336 | Chat.e.viewMessages.querySelectorAll( |
| 1337 | '.message-widget.notification:not(.resend-message)' |
| 1338 | ).forEach(e=>Chat.deleteMessageElem(e, true)); |
| 1339 | }); |
| 1340 | } |
| 1341 | if(Chat.userMayDelete(eMsg)){ |
| 1342 | const btnDeleteGlobal = D.button("Delete globally"); |
| 1343 | D.append(toolbar, btnDeleteGlobal); |
| @@ -1646,11 +1647,11 @@ | |
| 1647 | reader.onload = (e)=>img.setAttribute('src', e.target.result); |
| 1648 | reader.readAsDataURL(blob); |
| 1649 | } |
| 1650 | }; |
| 1651 | Chat.e.inputFile.addEventListener('change', function(ev){ |
| 1652 | updateDropZoneContent(this?.files[0]) |
| 1653 | }); |
| 1654 | /* Handle image paste from clipboard. TODO: figure out how we can |
| 1655 | paste non-image binary data as if it had been selected via the |
| 1656 | file selection element. */ |
| 1657 | const pasteListener = function(event){ |
| @@ -1726,10 +1727,11 @@ | |
| 1727 | D.span(),"This message was not successfully sent to the server:" |
| 1728 | )); |
| 1729 | if(state.msg){ |
| 1730 | const ta = D.textarea(); |
| 1731 | ta.value = state.msg; |
| 1732 | ta.setAttribute('readonly','true'); |
| 1733 | D.append(w,ta); |
| 1734 | } |
| 1735 | if(state.blob){ |
| 1736 | D.append(w,D.append(D.span(),"Attachment: ",(state.blob.name||"unnamed"))); |
| 1737 | //console.debug("blob = ",state.blob); |
| @@ -1744,11 +1746,11 @@ | |
| 1746 | if(state.msg) Chat.inputValue(state.msg); |
| 1747 | if(state.blob) BlobXferState.updateDropZoneContent(state.blob); |
| 1748 | const theMsg = findMessageWidgetParent(w); |
| 1749 | if(theMsg) Chat.deleteMessageElem(theMsg); |
| 1750 | })); |
| 1751 | D.addClass(Chat.reportErrorAsMessage(w).e.body, "resend-message"); |
| 1752 | }; |
| 1753 | |
| 1754 | /* Assume the connection has been established, reset the |
| 1755 | Chat.timer.tidReconnect, and (if showMsg and |
| 1756 | !!Chat.e.eMsgPollError) alert the user that the outage appears to |
| @@ -1755,11 +1757,12 @@ | |
| 1757 | be over. Also schedule Chat.poll() to run in the very near |
| 1758 | future. */ |
| 1759 | const reportConnectionOkay = function(dbgContext, showMsg = true){ |
| 1760 | if(Chat.beVerbose){ |
| 1761 | console.warn('reportConnectionOkay', dbgContext, |
| 1762 | 'Chat.e.pollErrorMarker classes =', |
| 1763 | Chat.e.pollErrorMarker.classList, |
| 1764 | 'Chat.timer.tidReconnect =',Chat.timer.tidReconnect, |
| 1765 | 'Chat.timer =',Chat.timer); |
| 1766 | } |
| 1767 | setTimeout( Chat.poll, Chat.timer.resetDelay() ); |
| 1768 | if( Chat.timer.errCount ){ |
| 1769 |
+18
| --- www/chat.md | ||
| +++ www/chat.md | ||
| @@ -164,10 +164,28 @@ | ||
| 164 | 164 | setting. |
| 165 | 165 | |
| 166 | 166 | This mechanism is similar to [email notification](./alerts.md) except that |
| 167 | 167 | the notification is sent via chat instead of via email. |
| 168 | 168 | |
| 169 | +## Quirks | |
| 170 | + | |
| 171 | + - There is no message-editing capability. This is by design and | |
| 172 | + desire of `/chat`'s developers. | |
| 173 | + | |
| 174 | + - When `/chat` has problems connecting to the message poller (see | |
| 175 | + the next section) it will provide a subtle visual indicator of the | |
| 176 | + connection problem - a dotted line along the top of the input | |
| 177 | + field. If the connection recovers within a short time, that | |
| 178 | + indicator will go away, otherwise it will pop up a loud message | |
| 179 | + signifying that the connection to the poller is down and how long | |
| 180 | + it will wait to retry (progressively longer, up to some | |
| 181 | + maximum). `/chat` will recover automatically when the server is | |
| 182 | + reachable. Trying to send messages while the poller connection is | |
| 183 | + down is permitted, and the poller will attempt to recover | |
| 184 | + immediately if sending of a message succeeds. That applies to any | |
| 185 | + operations which send traffic, e.g. if the per-message "toggle | |
| 186 | + text mode" button is activated or a message is globally deleted. | |
| 169 | 187 | |
| 170 | 188 | ## Implementation Details |
| 171 | 189 | |
| 172 | 190 | *You do not need to understand how Fossil chat works in order to use it. |
| 173 | 191 | But many developers prefer to know how their tools work. |
| 174 | 192 |
| --- www/chat.md | |
| +++ www/chat.md | |
| @@ -164,10 +164,28 @@ | |
| 164 | setting. |
| 165 | |
| 166 | This mechanism is similar to [email notification](./alerts.md) except that |
| 167 | the notification is sent via chat instead of via email. |
| 168 | |
| 169 | |
| 170 | ## Implementation Details |
| 171 | |
| 172 | *You do not need to understand how Fossil chat works in order to use it. |
| 173 | But many developers prefer to know how their tools work. |
| 174 |
| --- www/chat.md | |
| +++ www/chat.md | |
| @@ -164,10 +164,28 @@ | |
| 164 | setting. |
| 165 | |
| 166 | This mechanism is similar to [email notification](./alerts.md) except that |
| 167 | the notification is sent via chat instead of via email. |
| 168 | |
| 169 | ## Quirks |
| 170 | |
| 171 | - There is no message-editing capability. This is by design and |
| 172 | desire of `/chat`'s developers. |
| 173 | |
| 174 | - When `/chat` has problems connecting to the message poller (see |
| 175 | the next section) it will provide a subtle visual indicator of the |
| 176 | connection problem - a dotted line along the top of the input |
| 177 | field. If the connection recovers within a short time, that |
| 178 | indicator will go away, otherwise it will pop up a loud message |
| 179 | signifying that the connection to the poller is down and how long |
| 180 | it will wait to retry (progressively longer, up to some |
| 181 | maximum). `/chat` will recover automatically when the server is |
| 182 | reachable. Trying to send messages while the poller connection is |
| 183 | down is permitted, and the poller will attempt to recover |
| 184 | immediately if sending of a message succeeds. That applies to any |
| 185 | operations which send traffic, e.g. if the per-message "toggle |
| 186 | text mode" button is activated or a message is globally deleted. |
| 187 | |
| 188 | ## Implementation Details |
| 189 | |
| 190 | *You do not need to understand how Fossil chat works in order to use it. |
| 191 | But many developers prefer to know how their tools work. |
| 192 |