Fossil SCM
Rescoped the chat timestamp popup widget into a deeper scope (less visible/leaky). Moved the duplicated click-somewhere-to-close-popup handlers into PopupWidget.installClickToHide() method.
Commit
6bccbc20ea208283e55adeec8f3a07abd4d6424c41d07add6e69e5d21ecba1d4
Parent
2fe8d7c4b1d271c…
2 files changed
+26
-28
+18
-9
+26
-28
| --- src/chat.js | ||
| +++ src/chat.js | ||
| @@ -69,46 +69,44 @@ | ||
| 69 | 69 | /* Returns an almost-ISO8601 form of Date object d. */ |
| 70 | 70 | const iso8601ish = function(d){ |
| 71 | 71 | return d.toISOString() |
| 72 | 72 | .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' GMT'); |
| 73 | 73 | }; |
| 74 | - /* Timestampt popup widget */ | |
| 75 | - const tsPopup = new F.PopupWidget({ | |
| 76 | - cssClass: ['fossil-tooltip', 'chat-timestamp'], | |
| 77 | - refresh:function(){ | |
| 78 | - const D = F.dom; | |
| 79 | - D.clearElement(this.e); | |
| 80 | - const d = new Date(this._timestamp+"Z"); | |
| 81 | - if(d.getMinutes().toString()!=="NaN"){ | |
| 82 | - // Date works, render informative timestamps | |
| 83 | - D.append(this.e, localTimeString(d)," client-local", D.br(), | |
| 84 | - iso8601ish(d)); | |
| 85 | - }else{ | |
| 86 | - // Date doesn't work, so dumb it down... | |
| 87 | - D.append(this.e, this._timestamp," GMT"); | |
| 88 | - } | |
| 89 | - } | |
| 90 | - }); | |
| 91 | - const hidePopup = ()=>tsPopup.hide(); | |
| 92 | - tsPopup.e.addEventListener('click', hidePopup, false); | |
| 93 | - document.body.addEventListener('click', hidePopup, true); | |
| 94 | - document.body.addEventListener('keydown', function(ev){ | |
| 95 | - if(tsPopup.isShown() && 27===ev.which) tsPopup.hide(); | |
| 96 | - }, true); | |
| 97 | 74 | /* Event handler for clicking .message-user elements to show their |
| 98 | 75 | timestamps. */ |
| 99 | - const handleLegendClicked = function(ev){ | |
| 76 | + const handleLegendClicked = function f(ev){ | |
| 77 | + if(!f.popup){ | |
| 78 | + /* Timestamp popup widget */ | |
| 79 | + f.popup = new F.PopupWidget({ | |
| 80 | + cssClass: ['fossil-tooltip', 'chat-timestamp'], | |
| 81 | + refresh:function(){ | |
| 82 | + const D = F.dom; | |
| 83 | + D.clearElement(this.e); | |
| 84 | + const d = new Date(this._timestamp+"Z"); | |
| 85 | + if(d.getMinutes().toString()!=="NaN"){ | |
| 86 | + // Date works, render informative timestamps | |
| 87 | + D.append(this.e, localTimeString(d)," client-local", D.br(), | |
| 88 | + iso8601ish(d)); | |
| 89 | + }else{ | |
| 90 | + // Date doesn't work, so dumb it down... | |
| 91 | + D.append(this.e, this._timestamp," GMT"); | |
| 92 | + } | |
| 93 | + } | |
| 94 | + }); | |
| 95 | + const hidePopup = ()=>f.popup.hide(); | |
| 96 | + f.popup.installClickToHide(); | |
| 97 | + } | |
| 100 | 98 | const rect = ev.target.getBoundingClientRect(); |
| 101 | - tsPopup._timestamp = ev.target.dataset.timestamp; | |
| 99 | + f.popup._timestamp = ev.target.dataset.timestamp; | |
| 102 | 100 | let x = rect.left, y = rect.top - 10; |
| 103 | - tsPopup.show(ev.target)/*so we can get its computed size*/; | |
| 101 | + f.popup.show(ev.target)/*so we can get its computed size*/; | |
| 104 | 102 | // Shift to the left for right-aligned messages |
| 105 | 103 | if('right'===ev.target.getAttribute('align')){ |
| 106 | - const pRect = tsPopup.e.getBoundingClientRect(); | |
| 104 | + const pRect = f.popup.e.getBoundingClientRect(); | |
| 107 | 105 | x -= pRect.width/3*2; |
| 108 | 106 | } |
| 109 | - tsPopup.show(x, y); | |
| 107 | + f.popup.show(x, y); | |
| 110 | 108 | }; |
| 111 | 109 | |
| 112 | 110 | function newcontent(jx){ |
| 113 | 111 | var i; |
| 114 | 112 | for(i=0; i<jx.msgs.length; ++i){ |
| 115 | 113 |
| --- src/chat.js | |
| +++ src/chat.js | |
| @@ -69,46 +69,44 @@ | |
| 69 | /* Returns an almost-ISO8601 form of Date object d. */ |
| 70 | const iso8601ish = function(d){ |
| 71 | return d.toISOString() |
| 72 | .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' GMT'); |
| 73 | }; |
| 74 | /* Timestampt popup widget */ |
| 75 | const tsPopup = new F.PopupWidget({ |
| 76 | cssClass: ['fossil-tooltip', 'chat-timestamp'], |
| 77 | refresh:function(){ |
| 78 | const D = F.dom; |
| 79 | D.clearElement(this.e); |
| 80 | const d = new Date(this._timestamp+"Z"); |
| 81 | if(d.getMinutes().toString()!=="NaN"){ |
| 82 | // Date works, render informative timestamps |
| 83 | D.append(this.e, localTimeString(d)," client-local", D.br(), |
| 84 | iso8601ish(d)); |
| 85 | }else{ |
| 86 | // Date doesn't work, so dumb it down... |
| 87 | D.append(this.e, this._timestamp," GMT"); |
| 88 | } |
| 89 | } |
| 90 | }); |
| 91 | const hidePopup = ()=>tsPopup.hide(); |
| 92 | tsPopup.e.addEventListener('click', hidePopup, false); |
| 93 | document.body.addEventListener('click', hidePopup, true); |
| 94 | document.body.addEventListener('keydown', function(ev){ |
| 95 | if(tsPopup.isShown() && 27===ev.which) tsPopup.hide(); |
| 96 | }, true); |
| 97 | /* Event handler for clicking .message-user elements to show their |
| 98 | timestamps. */ |
| 99 | const handleLegendClicked = function(ev){ |
| 100 | const rect = ev.target.getBoundingClientRect(); |
| 101 | tsPopup._timestamp = ev.target.dataset.timestamp; |
| 102 | let x = rect.left, y = rect.top - 10; |
| 103 | tsPopup.show(ev.target)/*so we can get its computed size*/; |
| 104 | // Shift to the left for right-aligned messages |
| 105 | if('right'===ev.target.getAttribute('align')){ |
| 106 | const pRect = tsPopup.e.getBoundingClientRect(); |
| 107 | x -= pRect.width/3*2; |
| 108 | } |
| 109 | tsPopup.show(x, y); |
| 110 | }; |
| 111 | |
| 112 | function newcontent(jx){ |
| 113 | var i; |
| 114 | for(i=0; i<jx.msgs.length; ++i){ |
| 115 |
| --- src/chat.js | |
| +++ src/chat.js | |
| @@ -69,46 +69,44 @@ | |
| 69 | /* Returns an almost-ISO8601 form of Date object d. */ |
| 70 | const iso8601ish = function(d){ |
| 71 | return d.toISOString() |
| 72 | .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' GMT'); |
| 73 | }; |
| 74 | /* Event handler for clicking .message-user elements to show their |
| 75 | timestamps. */ |
| 76 | const handleLegendClicked = function f(ev){ |
| 77 | if(!f.popup){ |
| 78 | /* Timestamp popup widget */ |
| 79 | f.popup = new F.PopupWidget({ |
| 80 | cssClass: ['fossil-tooltip', 'chat-timestamp'], |
| 81 | refresh:function(){ |
| 82 | const D = F.dom; |
| 83 | D.clearElement(this.e); |
| 84 | const d = new Date(this._timestamp+"Z"); |
| 85 | if(d.getMinutes().toString()!=="NaN"){ |
| 86 | // Date works, render informative timestamps |
| 87 | D.append(this.e, localTimeString(d)," client-local", D.br(), |
| 88 | iso8601ish(d)); |
| 89 | }else{ |
| 90 | // Date doesn't work, so dumb it down... |
| 91 | D.append(this.e, this._timestamp," GMT"); |
| 92 | } |
| 93 | } |
| 94 | }); |
| 95 | const hidePopup = ()=>f.popup.hide(); |
| 96 | f.popup.installClickToHide(); |
| 97 | } |
| 98 | const rect = ev.target.getBoundingClientRect(); |
| 99 | f.popup._timestamp = ev.target.dataset.timestamp; |
| 100 | let x = rect.left, y = rect.top - 10; |
| 101 | f.popup.show(ev.target)/*so we can get its computed size*/; |
| 102 | // Shift to the left for right-aligned messages |
| 103 | if('right'===ev.target.getAttribute('align')){ |
| 104 | const pRect = f.popup.e.getBoundingClientRect(); |
| 105 | x -= pRect.width/3*2; |
| 106 | } |
| 107 | f.popup.show(x, y); |
| 108 | }; |
| 109 | |
| 110 | function newcontent(jx){ |
| 111 | var i; |
| 112 | for(i=0; i<jx.msgs.length; ++i){ |
| 113 |
+18
-9
| --- src/fossil.popupwidget.js | ||
| +++ src/fossil.popupwidget.js | ||
| @@ -182,11 +182,27 @@ | ||
| 182 | 182 | this.e.style.removeProperty('top'); |
| 183 | 183 | } |
| 184 | 184 | return this; |
| 185 | 185 | }, |
| 186 | 186 | |
| 187 | - hide: function(){return this.show(false)} | |
| 187 | + hide: function(){return this.show(false)}, | |
| 188 | + | |
| 189 | + /** | |
| 190 | + A convenience method which adds click handlers to this popup's | |
| 191 | + main element and document.body to hide the popup when either | |
| 192 | + element is clicked or the ESC key is pressed. Only call this | |
| 193 | + once per instance, if at all. Returns this; | |
| 194 | + */ | |
| 195 | + installClickToHide: function f(){ | |
| 196 | + this.e.addEventListener('click', ()=>this.show(false), false); | |
| 197 | + document.body.addEventListener('click', ()=>this.show(false), true); | |
| 198 | + const self = this; | |
| 199 | + document.body.addEventListener('keydown', function(ev){ | |
| 200 | + if(self.isShown() && 27===ev.which) self.show(false); | |
| 201 | + }, true); | |
| 202 | + return this; | |
| 203 | + } | |
| 188 | 204 | }/*F.PopupWidget.prototype*/; |
| 189 | 205 | |
| 190 | 206 | /** |
| 191 | 207 | Internal impl for F.toast() and friends. |
| 192 | 208 | |
| @@ -297,18 +313,11 @@ | ||
| 297 | 313 | cssClass: ['fossil-tooltip', 'help-buttonlet-content'], |
| 298 | 314 | refresh: function(){ |
| 299 | 315 | } |
| 300 | 316 | }); |
| 301 | 317 | fch.popup.e.style.maxWidth = '80%'/*of body*/; |
| 302 | - const hide = ()=>fch.popup.hide(); | |
| 303 | - fch.popup.e.addEventListener('click', hide, false); | |
| 304 | - document.body.addEventListener('click', hide, true); | |
| 305 | - document.body.addEventListener('keydown', function(ev){ | |
| 306 | - if(fch.popup.isShown() && 27===ev.which){ | |
| 307 | - fch.popup.hide(); | |
| 308 | - } | |
| 309 | - }, true); | |
| 318 | + fch.popup.installClickToHide(); | |
| 310 | 319 | } |
| 311 | 320 | D.append(D.clearElement(fch.popup.e), ev.target.$helpContent); |
| 312 | 321 | var popupRect = ev.target.getClientRects()[0]; |
| 313 | 322 | var x = popupRect.left, y = popupRect.top; |
| 314 | 323 | if(x<0) x = 0; |
| 315 | 324 |
| --- src/fossil.popupwidget.js | |
| +++ src/fossil.popupwidget.js | |
| @@ -182,11 +182,27 @@ | |
| 182 | this.e.style.removeProperty('top'); |
| 183 | } |
| 184 | return this; |
| 185 | }, |
| 186 | |
| 187 | hide: function(){return this.show(false)} |
| 188 | }/*F.PopupWidget.prototype*/; |
| 189 | |
| 190 | /** |
| 191 | Internal impl for F.toast() and friends. |
| 192 | |
| @@ -297,18 +313,11 @@ | |
| 297 | cssClass: ['fossil-tooltip', 'help-buttonlet-content'], |
| 298 | refresh: function(){ |
| 299 | } |
| 300 | }); |
| 301 | fch.popup.e.style.maxWidth = '80%'/*of body*/; |
| 302 | const hide = ()=>fch.popup.hide(); |
| 303 | fch.popup.e.addEventListener('click', hide, false); |
| 304 | document.body.addEventListener('click', hide, true); |
| 305 | document.body.addEventListener('keydown', function(ev){ |
| 306 | if(fch.popup.isShown() && 27===ev.which){ |
| 307 | fch.popup.hide(); |
| 308 | } |
| 309 | }, true); |
| 310 | } |
| 311 | D.append(D.clearElement(fch.popup.e), ev.target.$helpContent); |
| 312 | var popupRect = ev.target.getClientRects()[0]; |
| 313 | var x = popupRect.left, y = popupRect.top; |
| 314 | if(x<0) x = 0; |
| 315 |
| --- src/fossil.popupwidget.js | |
| +++ src/fossil.popupwidget.js | |
| @@ -182,11 +182,27 @@ | |
| 182 | this.e.style.removeProperty('top'); |
| 183 | } |
| 184 | return this; |
| 185 | }, |
| 186 | |
| 187 | hide: function(){return this.show(false)}, |
| 188 | |
| 189 | /** |
| 190 | A convenience method which adds click handlers to this popup's |
| 191 | main element and document.body to hide the popup when either |
| 192 | element is clicked or the ESC key is pressed. Only call this |
| 193 | once per instance, if at all. Returns this; |
| 194 | */ |
| 195 | installClickToHide: function f(){ |
| 196 | this.e.addEventListener('click', ()=>this.show(false), false); |
| 197 | document.body.addEventListener('click', ()=>this.show(false), true); |
| 198 | const self = this; |
| 199 | document.body.addEventListener('keydown', function(ev){ |
| 200 | if(self.isShown() && 27===ev.which) self.show(false); |
| 201 | }, true); |
| 202 | return this; |
| 203 | } |
| 204 | }/*F.PopupWidget.prototype*/; |
| 205 | |
| 206 | /** |
| 207 | Internal impl for F.toast() and friends. |
| 208 | |
| @@ -297,18 +313,11 @@ | |
| 313 | cssClass: ['fossil-tooltip', 'help-buttonlet-content'], |
| 314 | refresh: function(){ |
| 315 | } |
| 316 | }); |
| 317 | fch.popup.e.style.maxWidth = '80%'/*of body*/; |
| 318 | fch.popup.installClickToHide(); |
| 319 | } |
| 320 | D.append(D.clearElement(fch.popup.e), ev.target.$helpContent); |
| 321 | var popupRect = ev.target.getClientRects()[0]; |
| 322 | var x = popupRect.left, y = popupRect.top; |
| 323 | if(x<0) x = 0; |
| 324 |