Fossil SCM

Several internal cleanups in chat app, e.g. replace document.createXYZ with simpler fossil.dom API. Eliminate assignment to innerHTML, which is widely considered unsafe.

stephan 2020-12-23 23:27 trunk
Commit 68da24594ff5cb7aa9ad3e9e2c4ffd593a7d90c2100efe05923a57ea370913a1
3 files changed +4 -3 +40 -34 +4 -4
+4 -3
--- src/chat.c
+++ src/chat.c
@@ -177,14 +177,15 @@
177177
@ <span id='message-inject-point'></span>
178178
179179
builtin_fossil_js_bundle_or("popupwidget", NULL);
180180
/* Always in-line the javascript for the chat page */
181181
@ <script nonce="%h(style_nonce())">/* chat.c:%d(__LINE__) */
182
- @ window.fossilChatInitSize = %d(db_get_int("chat-initial-history",50));
182
+ /* We need an onload handler to ensure that window.fossil is
183
+ initialized before the chat init code runs. */
183184
@ window.addEventListener('load', function(){
184
- /* We need an onload handler to ensure that window.fossil is
185
- loaded first. */
185
+ @ window.fossil.config.chatInitSize =
186
+ @ %d(db_get_int("chat-initial-history",50));
186187
cgi_append_content(builtin_text("chat.js"),-1);
187188
@ }, false);
188189
@ </script>
189190
190191
style_finish_page();
191192
--- src/chat.c
+++ src/chat.c
@@ -177,14 +177,15 @@
177 @ <span id='message-inject-point'></span>
178
179 builtin_fossil_js_bundle_or("popupwidget", NULL);
180 /* Always in-line the javascript for the chat page */
181 @ <script nonce="%h(style_nonce())">/* chat.c:%d(__LINE__) */
182 @ window.fossilChatInitSize = %d(db_get_int("chat-initial-history",50));
 
183 @ window.addEventListener('load', function(){
184 /* We need an onload handler to ensure that window.fossil is
185 loaded first. */
186 cgi_append_content(builtin_text("chat.js"),-1);
187 @ }, false);
188 @ </script>
189
190 style_finish_page();
191
--- src/chat.c
+++ src/chat.c
@@ -177,14 +177,15 @@
177 @ <span id='message-inject-point'></span>
178
179 builtin_fossil_js_bundle_or("popupwidget", NULL);
180 /* Always in-line the javascript for the chat page */
181 @ <script nonce="%h(style_nonce())">/* chat.c:%d(__LINE__) */
182 /* We need an onload handler to ensure that window.fossil is
183 initialized before the chat init code runs. */
184 @ window.addEventListener('load', function(){
185 @ window.fossil.config.chatInitSize =
186 @ %d(db_get_int("chat-initial-history",50));
187 cgi_append_content(builtin_text("chat.js"),-1);
188 @ }, false);
189 @ </script>
190
191 style_finish_page();
192
+40 -34
--- src/chat.js
+++ src/chat.js
@@ -1,11 +1,23 @@
11
(function(){
22
const form = document.querySelector('#chat-form');
3
- let mxMsg = -50;
4
- if( window.fossilChatInitSize ) mxMsg = -window.fossilChatInitSize;
53
const F = window.fossil, D = F.dom;
6
- const _me = F.user.name;
4
+ const Chat = (function(){
5
+ const cs = {
6
+ me: F.user.name,
7
+ mxMsg: F.config.chatInitSize ? -F.config.chatInitSize : -50,
8
+ pageIsActive: !document.hidden,
9
+ onPageActive: function(){console.debug("Page active.")}, //override below
10
+ onPageInactive: function(){console.debug("Page inactive.")} //override below
11
+ };
12
+ document.addEventListener('visibilitychange', function(ev){
13
+ cs.pageIsActive = !document.hidden;
14
+ if(cs.pageIsActive) cs.onPageActive();
15
+ else cs.onPageInactive();
16
+ }, true);
17
+ return cs;
18
+ })();
719
/* State for paste and drag/drop */
820
const BlobXferState = {
921
dropDetails: document.querySelector('#chat-drop-details'),
1022
blob: undefined
1123
};
@@ -120,11 +132,10 @@
120132
}else{
121133
f.injectPoint.parentNode.appendChild(e);
122134
}
123135
};
124136
/* Returns a new TEXT node with the given text content. */
125
- const textNode = (T)=>document.createTextNode(T);
126137
/** Returns the local time string of Date object d, defaulting
127138
to the current time. */
128139
const localTimeString = function ff(d){
129140
if(!ff.pad){
130141
ff.pad = (x)=>(''+x).length>1 ? x : '0'+x;
@@ -179,73 +190,68 @@
179190
};
180191
/** Callback for poll() to inject new content into the page. */
181192
function newcontent(jx){
182193
var i;
183194
for(i=0; i<jx.msgs.length; ++i){
184
- let m = jx.msgs[i];
185
- let row = document.createElement("fieldset");
186
- if( m.msgid>mxMsg ) mxMsg = m.msgid;
187
- row.classList.add('message-row');
195
+ const m = jx.msgs[i];
196
+ if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid;
197
+ const eWho = D.create('legend'),
198
+ row = D.addClass(D.fieldset(eWho), 'message-row');
188199
injectMessage(row);
189
- const eWho = document.createElement('legend');
190200
eWho.dataset.timestamp = m.mtime;
191201
eWho.addEventListener('click', handleLegendClicked, false);
192
- if( m.xfrom==_me && window.outerWidth<1000 ){
202
+ if( m.xfrom==Chat.me && window.outerWidth<1000 ){
193203
eWho.setAttribute('align', 'right');
194204
row.style.justifyContent = "flex-end";
195205
}else{
196206
eWho.setAttribute('align', 'left');
197207
}
198208
eWho.style.backgroundColor = m.uclr;
199
- row.appendChild(eWho);
200209
eWho.classList.add('message-user');
201210
let whoName = m.xfrom;
202211
var d = new Date(m.mtime + "Z");
203212
if( d.getMinutes().toString()!="NaN" ){
204213
/* Show local time when we can compute it */
205
- eWho.append(textNode(whoName+' @ '+
214
+ eWho.append(D.text(whoName+' @ '+
206215
d.getHours()+":"+(d.getMinutes()+100).toString().slice(1,3)
207216
))
208217
}else{
209218
/* Show UTC on systems where Date() does not work */
210
- eWho.append(textNode(whoName+' @ '+m.mtime.slice(11,16)))
219
+ eWho.append(D.text(whoName+' @ '+m.mtime.slice(11,16)))
211220
}
212
- let span = document.createElement("div");
213
- span.classList.add('message-content');
214
- span.style.backgroundColor = m.uclr;
215
- row.appendChild(span);
221
+ let eContent = D.addClass(D.div(),'message-content','chat-message');
222
+ eContent.style.backgroundColor = m.uclr;
223
+ row.appendChild(eContent);
216224
if( m.fsize>0 ){
217225
if( m.fmime && m.fmime.startsWith("image/") ){
218
- let img = document.createElement("img");
219
- img.src = "chat-download/" + m.msgid;
220
- span.appendChild(img);
226
+ eContent.appendChild(D.img("chat-download/" + m.msgid));
221227
}else{
222
- let a = document.createElement("a");
223
- let txt = "(" + m.fname + " " + m.fsize + " bytes)";
224
- a.href = window.fossil.rootPath+
225
- 'chat-download/' + m.msgid+'/'+encodeURIComponent(m.fname);
226
- // ^^^ add m.fname to URL to cause downloaded file to have that name.
227
- a.appendChild(textNode(txt));
228
- span.appendChild(a);
229
- }
230
- let br = document.createElement("br");
231
- br.style.clear = "both";
232
- span.appendChild(br);
228
+ eContent.appendChild(D.a(
229
+ window.fossil.rootPath+
230
+ 'chat-download/' + m.msgid+'/'+encodeURIComponent(m.fname),
231
+ // ^^^ add m.fname to URL to cause downloaded file to have that name.
232
+ "(" + m.fname + " " + m.fsize + " bytes)"
233
+ ));
234
+ }
235
+ const br = D.br();
236
+ br.style.clear = "both";
237
+ eContent.appendChild(br);
233238
}
234239
if(m.xmsg){
235
- span.innerHTML += m.xmsg;
240
+ try{D.moveChildrenTo(eContent, D.parseHtml(m.xmsg))}
241
+ catch(e){console.error(e)}
236242
}
237
- span.classList.add('chat-message');
243
+ eContent.classList.add('chat-message');
238244
}
239245
}
240246
async function poll(){
241247
if(poll.running) return;
242248
poll.running = true;
243
- fetch("chat-poll?name=" + mxMsg)
249
+ fetch("chat-poll?name=" + Chat.mxMsg)
244250
.then(x=>x.json())
245251
.then(y=>newcontent(y))
246252
.catch(e=>console.error(e))
247253
.finally(()=>poll.running=false)
248254
}
249255
poll();
250256
setInterval(poll, 1000);
251257
})();
252258
--- src/chat.js
+++ src/chat.js
@@ -1,11 +1,23 @@
1 (function(){
2 const form = document.querySelector('#chat-form');
3 let mxMsg = -50;
4 if( window.fossilChatInitSize ) mxMsg = -window.fossilChatInitSize;
5 const F = window.fossil, D = F.dom;
6 const _me = F.user.name;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7 /* State for paste and drag/drop */
8 const BlobXferState = {
9 dropDetails: document.querySelector('#chat-drop-details'),
10 blob: undefined
11 };
@@ -120,11 +132,10 @@
120 }else{
121 f.injectPoint.parentNode.appendChild(e);
122 }
123 };
124 /* Returns a new TEXT node with the given text content. */
125 const textNode = (T)=>document.createTextNode(T);
126 /** Returns the local time string of Date object d, defaulting
127 to the current time. */
128 const localTimeString = function ff(d){
129 if(!ff.pad){
130 ff.pad = (x)=>(''+x).length>1 ? x : '0'+x;
@@ -179,73 +190,68 @@
179 };
180 /** Callback for poll() to inject new content into the page. */
181 function newcontent(jx){
182 var i;
183 for(i=0; i<jx.msgs.length; ++i){
184 let m = jx.msgs[i];
185 let row = document.createElement("fieldset");
186 if( m.msgid>mxMsg ) mxMsg = m.msgid;
187 row.classList.add('message-row');
188 injectMessage(row);
189 const eWho = document.createElement('legend');
190 eWho.dataset.timestamp = m.mtime;
191 eWho.addEventListener('click', handleLegendClicked, false);
192 if( m.xfrom==_me && window.outerWidth<1000 ){
193 eWho.setAttribute('align', 'right');
194 row.style.justifyContent = "flex-end";
195 }else{
196 eWho.setAttribute('align', 'left');
197 }
198 eWho.style.backgroundColor = m.uclr;
199 row.appendChild(eWho);
200 eWho.classList.add('message-user');
201 let whoName = m.xfrom;
202 var d = new Date(m.mtime + "Z");
203 if( d.getMinutes().toString()!="NaN" ){
204 /* Show local time when we can compute it */
205 eWho.append(textNode(whoName+' @ '+
206 d.getHours()+":"+(d.getMinutes()+100).toString().slice(1,3)
207 ))
208 }else{
209 /* Show UTC on systems where Date() does not work */
210 eWho.append(textNode(whoName+' @ '+m.mtime.slice(11,16)))
211 }
212 let span = document.createElement("div");
213 span.classList.add('message-content');
214 span.style.backgroundColor = m.uclr;
215 row.appendChild(span);
216 if( m.fsize>0 ){
217 if( m.fmime && m.fmime.startsWith("image/") ){
218 let img = document.createElement("img");
219 img.src = "chat-download/" + m.msgid;
220 span.appendChild(img);
221 }else{
222 let a = document.createElement("a");
223 let txt = "(" + m.fname + " " + m.fsize + " bytes)";
224 a.href = window.fossil.rootPath+
225 'chat-download/' + m.msgid+'/'+encodeURIComponent(m.fname);
226 // ^^^ add m.fname to URL to cause downloaded file to have that name.
227 a.appendChild(textNode(txt));
228 span.appendChild(a);
229 }
230 let br = document.createElement("br");
231 br.style.clear = "both";
232 span.appendChild(br);
233 }
234 if(m.xmsg){
235 span.innerHTML += m.xmsg;
 
236 }
237 span.classList.add('chat-message');
238 }
239 }
240 async function poll(){
241 if(poll.running) return;
242 poll.running = true;
243 fetch("chat-poll?name=" + mxMsg)
244 .then(x=>x.json())
245 .then(y=>newcontent(y))
246 .catch(e=>console.error(e))
247 .finally(()=>poll.running=false)
248 }
249 poll();
250 setInterval(poll, 1000);
251 })();
252
--- src/chat.js
+++ src/chat.js
@@ -1,11 +1,23 @@
1 (function(){
2 const form = document.querySelector('#chat-form');
 
 
3 const F = window.fossil, D = F.dom;
4 const Chat = (function(){
5 const cs = {
6 me: F.user.name,
7 mxMsg: F.config.chatInitSize ? -F.config.chatInitSize : -50,
8 pageIsActive: !document.hidden,
9 onPageActive: function(){console.debug("Page active.")}, //override below
10 onPageInactive: function(){console.debug("Page inactive.")} //override below
11 };
12 document.addEventListener('visibilitychange', function(ev){
13 cs.pageIsActive = !document.hidden;
14 if(cs.pageIsActive) cs.onPageActive();
15 else cs.onPageInactive();
16 }, true);
17 return cs;
18 })();
19 /* State for paste and drag/drop */
20 const BlobXferState = {
21 dropDetails: document.querySelector('#chat-drop-details'),
22 blob: undefined
23 };
@@ -120,11 +132,10 @@
132 }else{
133 f.injectPoint.parentNode.appendChild(e);
134 }
135 };
136 /* Returns a new TEXT node with the given text content. */
 
137 /** Returns the local time string of Date object d, defaulting
138 to the current time. */
139 const localTimeString = function ff(d){
140 if(!ff.pad){
141 ff.pad = (x)=>(''+x).length>1 ? x : '0'+x;
@@ -179,73 +190,68 @@
190 };
191 /** Callback for poll() to inject new content into the page. */
192 function newcontent(jx){
193 var i;
194 for(i=0; i<jx.msgs.length; ++i){
195 const m = jx.msgs[i];
196 if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid;
197 const eWho = D.create('legend'),
198 row = D.addClass(D.fieldset(eWho), 'message-row');
199 injectMessage(row);
 
200 eWho.dataset.timestamp = m.mtime;
201 eWho.addEventListener('click', handleLegendClicked, false);
202 if( m.xfrom==Chat.me && window.outerWidth<1000 ){
203 eWho.setAttribute('align', 'right');
204 row.style.justifyContent = "flex-end";
205 }else{
206 eWho.setAttribute('align', 'left');
207 }
208 eWho.style.backgroundColor = m.uclr;
 
209 eWho.classList.add('message-user');
210 let whoName = m.xfrom;
211 var d = new Date(m.mtime + "Z");
212 if( d.getMinutes().toString()!="NaN" ){
213 /* Show local time when we can compute it */
214 eWho.append(D.text(whoName+' @ '+
215 d.getHours()+":"+(d.getMinutes()+100).toString().slice(1,3)
216 ))
217 }else{
218 /* Show UTC on systems where Date() does not work */
219 eWho.append(D.text(whoName+' @ '+m.mtime.slice(11,16)))
220 }
221 let eContent = D.addClass(D.div(),'message-content','chat-message');
222 eContent.style.backgroundColor = m.uclr;
223 row.appendChild(eContent);
 
224 if( m.fsize>0 ){
225 if( m.fmime && m.fmime.startsWith("image/") ){
226 eContent.appendChild(D.img("chat-download/" + m.msgid));
 
 
227 }else{
228 eContent.appendChild(D.a(
229 window.fossil.rootPath+
230 'chat-download/' + m.msgid+'/'+encodeURIComponent(m.fname),
231 // ^^^ add m.fname to URL to cause downloaded file to have that name.
232 "(" + m.fname + " " + m.fsize + " bytes)"
233 ));
234 }
235 const br = D.br();
236 br.style.clear = "both";
237 eContent.appendChild(br);
 
238 }
239 if(m.xmsg){
240 try{D.moveChildrenTo(eContent, D.parseHtml(m.xmsg))}
241 catch(e){console.error(e)}
242 }
243 eContent.classList.add('chat-message');
244 }
245 }
246 async function poll(){
247 if(poll.running) return;
248 poll.running = true;
249 fetch("chat-poll?name=" + Chat.mxMsg)
250 .then(x=>x.json())
251 .then(y=>newcontent(y))
252 .catch(e=>console.error(e))
253 .finally(()=>poll.running=false)
254 }
255 poll();
256 setInterval(poll, 1000);
257 })();
258
+4 -4
--- src/default.css
+++ src/default.css
@@ -1463,16 +1463,16 @@
14631463
pointer-events: none !important;
14641464
display: none !important;
14651465
}
14661466
14671467
/* Chat-related */
1468
-span.at-name { /* for @USERNAME references */
1468
+body.chat span.at-name { /* for @USERNAME references */
14691469
text-decoration: underline;
14701470
font-weight: bold;
14711471
}
14721472
/* A wrapper for a single single message (one row of the UI) */
1473
-.message-row {
1473
+body.chat .message-row {
14741474
margin-bottom: 0.5em;
14751475
border: none;
14761476
display: flex;
14771477
flex-direction: row;
14781478
justify-content: flex-start;
@@ -1480,21 +1480,21 @@
14801480
border-radius: 0.25em;
14811481
box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);*/
14821482
border: none;
14831483
}
14841484
/* The content area of a message (the body element of a FIELDSET) */
1485
-.message-content {
1485
+body.chat .message-content {
14861486
display: inline-block;
14871487
border-radius: 0.25em;
14881488
border: 1px solid rgba(0,0,0,0.2);
14891489
box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);
14901490
padding: 0.25em 1em;
14911491
margin-top: -0.75em;
14921492
min-width: 9em /*avoid unsightly "underlap" with the user name label*/;
14931493
}
14941494
/* User name for the post (a LEGEND element) */
1495
-.message-row .message-user {
1495
+body.chat .message-row .message-user {
14961496
border-radius: 0.25em 0.25em 0 0;
14971497
padding: 0 0.5em;
14981498
/*text-align: left; Firefox requires the 'align' attribute */
14991499
margin: 0 0.15em;
15001500
padding: 0 0.5em 0em 0.5em;
15011501
--- src/default.css
+++ src/default.css
@@ -1463,16 +1463,16 @@
1463 pointer-events: none !important;
1464 display: none !important;
1465 }
1466
1467 /* Chat-related */
1468 span.at-name { /* for @USERNAME references */
1469 text-decoration: underline;
1470 font-weight: bold;
1471 }
1472 /* A wrapper for a single single message (one row of the UI) */
1473 .message-row {
1474 margin-bottom: 0.5em;
1475 border: none;
1476 display: flex;
1477 flex-direction: row;
1478 justify-content: flex-start;
@@ -1480,21 +1480,21 @@
1480 border-radius: 0.25em;
1481 box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);*/
1482 border: none;
1483 }
1484 /* The content area of a message (the body element of a FIELDSET) */
1485 .message-content {
1486 display: inline-block;
1487 border-radius: 0.25em;
1488 border: 1px solid rgba(0,0,0,0.2);
1489 box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);
1490 padding: 0.25em 1em;
1491 margin-top: -0.75em;
1492 min-width: 9em /*avoid unsightly "underlap" with the user name label*/;
1493 }
1494 /* User name for the post (a LEGEND element) */
1495 .message-row .message-user {
1496 border-radius: 0.25em 0.25em 0 0;
1497 padding: 0 0.5em;
1498 /*text-align: left; Firefox requires the 'align' attribute */
1499 margin: 0 0.15em;
1500 padding: 0 0.5em 0em 0.5em;
1501
--- src/default.css
+++ src/default.css
@@ -1463,16 +1463,16 @@
1463 pointer-events: none !important;
1464 display: none !important;
1465 }
1466
1467 /* Chat-related */
1468 body.chat span.at-name { /* for @USERNAME references */
1469 text-decoration: underline;
1470 font-weight: bold;
1471 }
1472 /* A wrapper for a single single message (one row of the UI) */
1473 body.chat .message-row {
1474 margin-bottom: 0.5em;
1475 border: none;
1476 display: flex;
1477 flex-direction: row;
1478 justify-content: flex-start;
@@ -1480,21 +1480,21 @@
1480 border-radius: 0.25em;
1481 box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);*/
1482 border: none;
1483 }
1484 /* The content area of a message (the body element of a FIELDSET) */
1485 body.chat .message-content {
1486 display: inline-block;
1487 border-radius: 0.25em;
1488 border: 1px solid rgba(0,0,0,0.2);
1489 box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);
1490 padding: 0.25em 1em;
1491 margin-top: -0.75em;
1492 min-width: 9em /*avoid unsightly "underlap" with the user name label*/;
1493 }
1494 /* User name for the post (a LEGEND element) */
1495 body.chat .message-row .message-user {
1496 border-radius: 0.25em 0.25em 0 0;
1497 padding: 0 0.5em;
1498 /*text-align: left; Firefox requires the 'align' attribute */
1499 margin: 0 0.15em;
1500 padding: 0 0.5em 0em 0.5em;
1501

Keyboard Shortcuts

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