Fossil

Check-in [68da24594f]
Login

Check-in [68da24594f]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment: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.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 68da24594ff5cb7aa9ad3e9e2c4ffd593a7d90c2100efe05923a57ea370913a1
User & Date: stephan 2020-12-23 23:27:35.577
Context
2020-12-24
03:34
The /chat page now redirects to the login page if needed. ... (check-in: 77d3058600 user: drh tags: trunk)
01:04
Bungled merge. Was: The /chat page redirects to the login page if the user does not have appropriate permissions to use chat. ... (Closed-Leaf check-in: fee74ce5e7 user: drh tags: mistake)
2020-12-23
23:27
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. ... (check-in: 68da24594f user: stephan tags: trunk)
22:58
chat: minor UI refinements. ... (check-in: 8eb01c314b user: stephan tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/chat.c.
175
176
177
178
179
180
181
182

183
184
185

186
187
188
189
190
191
192

  /* New chat messages get inserted immediately after this element */
  @ <span id='message-inject-point'></span>

  builtin_fossil_js_bundle_or("popupwidget", NULL);
  /* Always in-line the javascript for the chat page */
  @ <script nonce="%h(style_nonce())">/* chat.c:%d(__LINE__) */
  @ window.fossilChatInitSize = %d(db_get_int("chat-initial-history",50));

  @ window.addEventListener('load', function(){
  /* We need an onload handler to ensure that window.fossil is
     loaded first. */

  cgi_append_content(builtin_text("chat.js"),-1);
  @ }, false);
  @ </script>

  style_finish_page();
}








|
>

|
<
>







175
176
177
178
179
180
181
182
183
184
185

186
187
188
189
190
191
192
193

  /* New chat messages get inserted immediately after this element */
  @ <span id='message-inject-point'></span>

  builtin_fossil_js_bundle_or("popupwidget", NULL);
  /* Always in-line the javascript for the chat page */
  @ <script nonce="%h(style_nonce())">/* chat.c:%d(__LINE__) */
  /* We need an onload handler to ensure that window.fossil is
     initialized before the chat init code runs. */
  @ window.addEventListener('load', function(){
  @ window.fossil.config.chatInitSize =

  @   %d(db_get_int("chat-initial-history",50));
  cgi_append_content(builtin_text("chat.js"),-1);
  @ }, false);
  @ </script>

  style_finish_page();
}

Changes to src/chat.js.
1
2
3
4
5


6












7
8
9
10
11
12
13
(function(){
  const form = document.querySelector('#chat-form');
  let mxMsg = -50;
  if( window.fossilChatInitSize ) mxMsg = -window.fossilChatInitSize;
  const F = window.fossil, D = F.dom;


  const _me = F.user.name;












  /* State for paste and drag/drop */
  const BlobXferState = {
    dropDetails: document.querySelector('#chat-drop-details'),
    blob: undefined
  };
  /** Updates the paste/drop zone with details of the pasted/dropped
      data. The argument must be a Blob or Blob-like object (File) or


<
<

>
>
|
>
>
>
>
>
>
>
>
>
>
>
>







1
2


3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(function(){
  const form = document.querySelector('#chat-form');


  const F = window.fossil, D = F.dom;
  const Chat = (function(){
    const cs = {
      me: F.user.name,
      mxMsg: F.config.chatInitSize ? -F.config.chatInitSize : -50,
      pageIsActive: !document.hidden,
      onPageActive: function(){console.debug("Page active.")}, //override below
      onPageInactive: function(){console.debug("Page inactive.")} //override below
    };
    document.addEventListener('visibilitychange', function(ev){
      cs.pageIsActive = !document.hidden;
      if(cs.pageIsActive) cs.onPageActive();
      else cs.onPageInactive();
    }, true);
    return cs;
  })();
  /* State for paste and drag/drop */
  const BlobXferState = {
    dropDetails: document.querySelector('#chat-drop-details'),
    blob: undefined
  };
  /** Updates the paste/drop zone with details of the pasted/dropped
      data. The argument must be a Blob or Blob-like object (File) or
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
    if(f.injectPoint.nextSibling){
      f.injectPoint.parentNode.insertBefore(e, f.injectPoint.nextSibling);
    }else{
      f.injectPoint.parentNode.appendChild(e);
    }
  };
  /* Returns a new TEXT node with the given text content. */
  const textNode = (T)=>document.createTextNode(T);
  /** Returns the local time string of Date object d, defaulting
      to the current time. */
  const localTimeString = function ff(d){
    if(!ff.pad){
      ff.pad = (x)=>(''+x).length>1 ? x : '0'+x;
    }
    d || (d = new Date());







<







130
131
132
133
134
135
136

137
138
139
140
141
142
143
    if(f.injectPoint.nextSibling){
      f.injectPoint.parentNode.insertBefore(e, f.injectPoint.nextSibling);
    }else{
      f.injectPoint.parentNode.appendChild(e);
    }
  };
  /* Returns a new TEXT node with the given text content. */

  /** Returns the local time string of Date object d, defaulting
      to the current time. */
  const localTimeString = function ff(d){
    if(!ff.pad){
      ff.pad = (x)=>(''+x).length>1 ? x : '0'+x;
    }
    d || (d = new Date());
177
178
179
180
181
182
183
184
185
186

187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226

227
228
229
230
231
232
233
234
235

236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
    }
    f.popup.show(x, y);
  };
  /** Callback for poll() to inject new content into the page. */
  function newcontent(jx){
    var i;
    for(i=0; i<jx.msgs.length; ++i){
      let m = jx.msgs[i];
      let row = document.createElement("fieldset");
      if( m.msgid>mxMsg ) mxMsg = m.msgid;

      row.classList.add('message-row');
      injectMessage(row);
      const eWho = document.createElement('legend');
      eWho.dataset.timestamp = m.mtime;
      eWho.addEventListener('click', handleLegendClicked, false);
      if( m.xfrom==_me && window.outerWidth<1000 ){
        eWho.setAttribute('align', 'right');
        row.style.justifyContent = "flex-end";
      }else{
        eWho.setAttribute('align', 'left');
      }
      eWho.style.backgroundColor = m.uclr;
      row.appendChild(eWho);
      eWho.classList.add('message-user');
      let whoName = m.xfrom;
      var d = new Date(m.mtime + "Z");
      if( d.getMinutes().toString()!="NaN" ){
        /* Show local time when we can compute it */
        eWho.append(textNode(whoName+' @ '+
          d.getHours()+":"+(d.getMinutes()+100).toString().slice(1,3)
        ))
      }else{
        /* Show UTC on systems where Date() does not work */
        eWho.append(textNode(whoName+' @ '+m.mtime.slice(11,16)))
      }
      let span = document.createElement("div");
      span.classList.add('message-content');
      span.style.backgroundColor = m.uclr;
      row.appendChild(span);
      if( m.fsize>0 ){
        if( m.fmime && m.fmime.startsWith("image/") ){
          let img = document.createElement("img");
          img.src = "chat-download/" + m.msgid;
          span.appendChild(img);
        }else{
          let a = document.createElement("a");
          let txt = "(" + m.fname + " " + m.fsize + " bytes)";
          a.href = window.fossil.rootPath+
            'chat-download/' + m.msgid+'/'+encodeURIComponent(m.fname);
          // ^^^ add m.fname to URL to cause downloaded file to have that name.

          a.appendChild(textNode(txt));
          span.appendChild(a);
        }
        let br = document.createElement("br");
        br.style.clear = "both";
        span.appendChild(br);
      }
      if(m.xmsg){
        span.innerHTML += m.xmsg;

      }
      span.classList.add('chat-message');
    }
  }
  async function poll(){
    if(poll.running) return;
    poll.running = true;
    fetch("chat-poll?name=" + mxMsg)
    .then(x=>x.json())
    .then(y=>newcontent(y))
    .catch(e=>console.error(e))
    .finally(()=>poll.running=false)
  }
  poll();
  setInterval(poll, 1000);
})();







|
<
|
>
|

<


|






<





|




|

<
|
|
|


<
|
<

|
<
|
|
|
>
|
<

|

|


|
>

|





|








188
189
190
191
192
193
194
195

196
197
198
199

200
201
202
203
204
205
206
207
208

209
210
211
212
213
214
215
216
217
218
219
220

221
222
223
224
225

226

227
228

229
230
231
232
233

234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
    }
    f.popup.show(x, y);
  };
  /** Callback for poll() to inject new content into the page. */
  function newcontent(jx){
    var i;
    for(i=0; i<jx.msgs.length; ++i){
      const m = jx.msgs[i];

      if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid;
      const eWho = D.create('legend'),
            row = D.addClass(D.fieldset(eWho), 'message-row');
      injectMessage(row);

      eWho.dataset.timestamp = m.mtime;
      eWho.addEventListener('click', handleLegendClicked, false);
      if( m.xfrom==Chat.me && window.outerWidth<1000 ){
        eWho.setAttribute('align', 'right');
        row.style.justifyContent = "flex-end";
      }else{
        eWho.setAttribute('align', 'left');
      }
      eWho.style.backgroundColor = m.uclr;

      eWho.classList.add('message-user');
      let whoName = m.xfrom;
      var d = new Date(m.mtime + "Z");
      if( d.getMinutes().toString()!="NaN" ){
        /* Show local time when we can compute it */
        eWho.append(D.text(whoName+' @ '+
          d.getHours()+":"+(d.getMinutes()+100).toString().slice(1,3)
        ))
      }else{
        /* Show UTC on systems where Date() does not work */
        eWho.append(D.text(whoName+' @ '+m.mtime.slice(11,16)))
      }

      let eContent = D.addClass(D.div(),'message-content','chat-message');
      eContent.style.backgroundColor = m.uclr;
      row.appendChild(eContent);
      if( m.fsize>0 ){
        if( m.fmime && m.fmime.startsWith("image/") ){

          eContent.appendChild(D.img("chat-download/" + m.msgid));

        }else{
          eContent.appendChild(D.a(

            window.fossil.rootPath+
              'chat-download/' + m.msgid+'/'+encodeURIComponent(m.fname),
            // ^^^ add m.fname to URL to cause downloaded file to have that name.
            "(" + m.fname + " " + m.fsize + " bytes)"
          ));

        }
        const br = D.br();
        br.style.clear = "both";
        eContent.appendChild(br);
      }
      if(m.xmsg){
        try{D.moveChildrenTo(eContent, D.parseHtml(m.xmsg))}
        catch(e){console.error(e)}
      }
      eContent.classList.add('chat-message');
    }
  }
  async function poll(){
    if(poll.running) return;
    poll.running = true;
    fetch("chat-poll?name=" + Chat.mxMsg)
    .then(x=>x.json())
    .then(y=>newcontent(y))
    .catch(e=>console.error(e))
    .finally(()=>poll.running=false)
  }
  poll();
  setInterval(poll, 1000);
})();
Changes to src/default.css.
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
  position: absolute !important;
  opacity: 0 !important;
  pointer-events: none !important;
  display: none !important;
}

/* Chat-related */
span.at-name { /* for @USERNAME references */
  text-decoration: underline;
  font-weight: bold;
}
/* A wrapper for a single single message (one row of the UI) */
.message-row {
  margin-bottom: 0.5em;
  border: none;
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  /*border: 1px solid rgba(0,0,0,0.2);
  border-radius: 0.25em;
  box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);*/
  border: none;
}
/* The content area of a message (the body element of a FIELDSET) */
.message-content {
  display: inline-block;
  border-radius: 0.25em;
  border: 1px solid rgba(0,0,0,0.2);
  box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);
  padding: 0.25em 1em;
  margin-top: -0.75em;
  min-width: 9em /*avoid unsightly "underlap" with the user name label*/;
}
/* User name for the post (a LEGEND element) */
.message-row .message-user {
  border-radius: 0.25em 0.25em 0 0;
  padding: 0 0.5em;
  /*text-align: left; Firefox requires the 'align' attribute */
  margin: 0 0.15em;
  padding: 0 0.5em 0em 0.5em;
  margin-bottom: 0.4em;
  cursor: pointer;







|




|











|









|







1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
  position: absolute !important;
  opacity: 0 !important;
  pointer-events: none !important;
  display: none !important;
}

/* Chat-related */
body.chat span.at-name { /* for @USERNAME references */
  text-decoration: underline;
  font-weight: bold;
}
/* A wrapper for a single single message (one row of the UI) */
body.chat .message-row {
  margin-bottom: 0.5em;
  border: none;
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  /*border: 1px solid rgba(0,0,0,0.2);
  border-radius: 0.25em;
  box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);*/
  border: none;
}
/* The content area of a message (the body element of a FIELDSET) */
body.chat .message-content {
  display: inline-block;
  border-radius: 0.25em;
  border: 1px solid rgba(0,0,0,0.2);
  box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);
  padding: 0.25em 1em;
  margin-top: -0.75em;
  min-width: 9em /*avoid unsightly "underlap" with the user name label*/;
}
/* User name for the post (a LEGEND element) */
body.chat .message-row .message-user {
  border-radius: 0.25em 0.25em 0 0;
  padding: 0 0.5em;
  /*text-align: left; Firefox requires the 'align' attribute */
  margin: 0 0.15em;
  padding: 0 0.5em 0em 0.5em;
  margin-bottom: 0.4em;
  cursor: pointer;