Fossil

Check-in [9d24a28490]
Login

Check-in [9d24a28490]

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

Overview
Comment:chat: added toggles for single/multi-line input (non-persistent) and monospace message font (persistent - affects message bodies and text input fields).
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 9d24a2849018c58e735d689121263587f752535a9534eb6fbe12ab98d4ca0da1
User & Date: stephan 2020-12-25 17:52:51.123
Context
2020-12-25
19:12
Improvements to chat documentation. ... (check-in: e525317e63 user: drh tags: trunk)
17:52
chat: added toggles for single/multi-line input (non-persistent) and monospace message font (persistent - affects message bodies and text input fields). ... (check-in: 9d24a28490 user: stephan tags: trunk)
16:09
First attempt at documentation for Fossil chat. ... (check-in: bcfdc1a106 user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/chat.c.
100
101
102
103
104
105
106
107


108
109
110
111
112
113
114
115
  if( iPingTcp ) style_disable_csp();
  style_set_current_feature("chat");
  style_header("Chat");
  @ <div id='chat-input-area'>
  @ <form accept-encoding="utf-8" id="chat-form" autocomplete="off">
  @   <div id='chat-input-line'>
  @     <input type="text" name="msg" id="chat-input-single" \
  @      placeholder="Type message here.">


  @     <input type="submit" value="Send">
  @     <span id="chat-settings-button" class="settings-icon"></span>
  @   </div>
  @   <div id='chat-input-file-area'>
  @     <div class='file-selection-wrapper'>
  @       <div class='help-buttonlet'>
  @        Select a file to upload, drag/drop a file into this spot,
  @        or paste an image from the clipboard if supported by







|
>
>
|







100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
  if( iPingTcp ) style_disable_csp();
  style_set_current_feature("chat");
  style_header("Chat");
  @ <div id='chat-input-area'>
  @ <form accept-encoding="utf-8" id="chat-form" autocomplete="off">
  @   <div id='chat-input-line'>
  @     <input type="text" name="msg" id="chat-input-single" \
  @      placeholder="Type message here." autocomplete="off">
  @     <textarea rows="8" id="chat-input-multi" \
  @      placeholder="Type message here" class="hidden"></textarea>
  @     <input type="submit" value="Send" id="chat-message-submit">
  @     <span id="chat-settings-button" class="settings-icon"></span>
  @   </div>
  @   <div id='chat-input-file-area'>
  @     <div class='file-selection-wrapper'>
  @       <div class='help-buttonlet'>
  @        Select a file to upload, drag/drop a file into this spot,
  @        or paste an image from the clipboard if supported by
Changes to src/chat.js.
14
15
16
17
18
19
20

21


22
23
24
25
26
27
28
      e:{/*map of certain DOM elements.*/
        messageInjectPoint: E1('#message-inject-point'),
        pageTitle: E1('head title'),
        loadToolbar: undefined /* the load-posts toolbar (dynamically created) */,
        inputWrapper: E1("#chat-input-area"),
        messagesWrapper: E1('#chat-messages-wrapper'),
        inputForm: E1('#chat-form'),

        inputSingle: E1('#chat-input-single'),


        inputFile: E1('#chat-input-file')
      },
      me: F.user.name,
      mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
      mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
      pageIsActive: 'visible'===document.visibilityState,
      changesSincePageHidden: 0,







>

>
>







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
      e:{/*map of certain DOM elements.*/
        messageInjectPoint: E1('#message-inject-point'),
        pageTitle: E1('head title'),
        loadToolbar: undefined /* the load-posts toolbar (dynamically created) */,
        inputWrapper: E1("#chat-input-area"),
        messagesWrapper: E1('#chat-messages-wrapper'),
        inputForm: E1('#chat-form'),
        btnSubmit: E1('#chat-message-submit'),
        inputSingle: E1('#chat-input-single'),
        inputMulti: E1('#chat-input-multi'),
        inputCurrent: undefined/*one of inputSingle or inputMulti*/,
        inputFile: E1('#chat-input-file')
      },
      me: F.user.name,
      mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
      mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
      pageIsActive: 'visible'===document.visibilityState,
      changesSincePageHidden: 0,
36
37
38
39
40
41
42
43
44
45
46
47
48
49

50
51
52





53












54
55
56
57
58
59
60
         mode). Can be toggled via settings popup. */
      msgMyAlign: (window.innerWidth<window.innerHeight) ? 'right' : 'left',
      ajaxInflight: 0,
      /** Gets (no args) or sets (1 arg) the current input text field value,
          taking into account single- vs multi-line input. The getter returns
          a string and the setter returns this object. */
      inputValue: function(){
        const e = this.e.inputSingle;
        if(arguments.length){
          e.value = arguments[0];
          return this;
        }else {
          return e.value;
        }

      },
      /** Asks the current user input field to take focus. Returns this. */
      inputFocus: function(){





        this.e.inputSingle.focus();












        return this;
      },
      /** Enables (if yes is truthy) or disables all elements in
       * this.disableDuringAjax. */
      enableAjaxComponents: function(yes){
        D[yes ? 'enable' : 'disable'](this.disableDuringAjax);
        return this;







|



<
<

>



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







39
40
41
42
43
44
45
46
47
48
49


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
         mode). Can be toggled via settings popup. */
      msgMyAlign: (window.innerWidth<window.innerHeight) ? 'right' : 'left',
      ajaxInflight: 0,
      /** Gets (no args) or sets (1 arg) the current input text field value,
          taking into account single- vs multi-line input. The getter returns
          a string and the setter returns this object. */
      inputValue: function(){
        const e = this.inputElement();
        if(arguments.length){
          e.value = arguments[0];
          return this;


        }
        return e.value;
      },
      /** Asks the current user input field to take focus. Returns this. */
      inputFocus: function(){
        this.inputElement().focus();
        return this;
      },
      /** Returns the current message input element. */
      inputElement: function(){
        return this.e.inputCurrent;
      },
      /** Toggles between single- and multi-line edit modes. Returns this. */
      inputToggleSingleMulti: function(){
        const old = this.e.inputCurrent;
        if(this.e.inputCurrent === this.e.inputSingle){
          this.e.inputCurrent = this.e.inputMulti;
        }else{
          this.e.inputCurrent = this.e.inputSingle;
        }
        D.addClass(old, 'hidden');
        D.removeClass(this.e.inputCurrent, 'hidden');
        this.e.inputCurrent.value = old.value;
        return this;
      },
      /** Enables (if yes is truthy) or disables all elements in
       * this.disableDuringAjax. */
      enableAjaxComponents: function(yes){
        D[yes ? 'enable' : 'disable'](this.disableDuringAjax);
        return this;
108
109
110
111
112
113
114
115

116
117
118
119
120
121
122




123
124
125
126
127
128
129
        }
      },
      settings:{
        get: (k,dflt)=>F.storage.get(k,dflt),
        getBool: (k,dflt)=>F.storage.getBool(k,dflt),
        set: (k,v)=>F.storage.set(k,v),
        defaults:{
          "images-inline": !!F.config.chat.imagesInline

        }
      }
    };
    Object.keys(cs.settings.defaults).forEach(function f(k){
      const v = cs.settings.get(k,f);
      if(f===v) cs.settings.set(k,cs.settings.defaults[k]);
    });




    cs.pageTitleOrig = cs.e.pageTitle.innerText;
    const qs = (e)=>document.querySelector(e);
    const argsToArray = function(args){
      return Array.prototype.slice.call(args,0);
    };
    cs.reportError = function(/*msg args*/){
      const args = argsToArray(arguments);







|
>







>
>
>
>







127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
        }
      },
      settings:{
        get: (k,dflt)=>F.storage.get(k,dflt),
        getBool: (k,dflt)=>F.storage.getBool(k,dflt),
        set: (k,v)=>F.storage.set(k,v),
        defaults:{
          "images-inline": !!F.config.chat.imagesInline,
          "monospace-messages": false
        }
      }
    };
    Object.keys(cs.settings.defaults).forEach(function f(k){
      const v = cs.settings.get(k,f);
      if(f===v) cs.settings.set(k,cs.settings.defaults[k]);
    });
    if(cs.settings.getBool('monospace-messages',false)){
      document.body.classList.add('monospace-messages');
    }
    cs.e.inputCurrent = cs.e.inputSingle;
    cs.pageTitleOrig = cs.e.pageTitle.innerText;
    const qs = (e)=>document.querySelector(e);
    const argsToArray = function(args){
      return Array.prototype.slice.call(args,0);
    };
    cs.reportError = function(/*msg args*/){
      const args = argsToArray(arguments);
278
279
280
281
282
283
284
285
286
287

288
289
290
291
292

293


294
295
296
297
298
299
300






















301
302
303
304
305
306
307
    };
    Object.keys(dropEvents).forEach(
      (k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
    );
    return bxs;
  })()/*drag/drop*/;

  Chat.e.inputForm.addEventListener('submit',(e)=>{
    e.preventDefault();
    const fd = new FormData(Chat.e.inputForm);

    if(BlobXferState.blob/*replace file content with this*/){
      fd.set("file", BlobXferState.blob);
    }
    if( !!Chat.inputValue()
        || Chat.e.inputFile.value.length>0

        || BlobXferState.blob ){


      fetch("chat-send",{
        method: 'POST',
        body: fd
      });
    }
    BlobXferState.clear();
    Chat.inputValue("").inputFocus();






















  });

  /* 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){







|
<
|
>
|
|
<
|
<
>
|
>
>







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







302
303
304
305
306
307
308
309

310
311
312
313

314

315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
    };
    Object.keys(dropEvents).forEach(
      (k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
    );
    return bxs;
  })()/*drag/drop*/;

  Chat.submitMessage = function(){

    const fd = new FormData(this.e.inputForm)
    /* ^^^^ we don't really want/need the FORM element, but when
       FormData() is default-constructed here then the server
       segfaults, and i have no clue why! */;

    const msg = this.inputValue();

    if(msg) fd.set('msg',msg);
    const file = BlobXferState.blob || this.e.inputFile.files[0];
    if(file) fd.set("file", file);
    if( msg || file ){
      fetch("chat-send",{
        method: 'POST',
        body: fd
      });
    }
    BlobXferState.clear();
    Chat.inputValue("").inputFocus();
  };

  Chat.e.inputSingle.addEventListener('keydown',function(ev){
    if(13===ev.keyCode/*ENTER*/){
      ev.preventDefault();
      ev.stopPropagation();
      Chat.submitMessage();
      return false;
    }
  }, false);
  Chat.e.inputMulti.addEventListener('keydown',function(ev){
    if(ev.ctrlKey && 13 === ev.keyCode){
      ev.preventDefault();
      ev.stopPropagation();
      Chat.submitMessage();
      return false;
    }
  }, false);
  Chat.e.btnSubmit.addEventListener('click',(e)=>{
    e.preventDefault();
    Chat.submitMessage();
    return false;
  });

  /* 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){
389
390
391
392
393
394
395














396
397
398
399
400
401
402
      adjustY: function(y){
        const rect = settingsButton.getBoundingClientRect();
        return rect.top + rect.height + 2;
      }
    });
    /* Settings menu entries... */
    const settingsOps = [{














      label: "Chat-only mode",
      boolValue: ()=>!!document.body.classList.contains('chat-only-mode'),
      callback: function f(){
        if(undefined === f.isHidden){
          f.isHidden = false;
          f.elemsToToggle = [];
          document.body.childNodes.forEach(function(e){







>
>
>
>
>
>
>
>
>
>
>
>
>
>







436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
      adjustY: function(y){
        const rect = settingsButton.getBoundingClientRect();
        return rect.top + rect.height + 2;
      }
    });
    /* Settings menu entries... */
    const settingsOps = [{
      label: "Multi-line input",
      boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
      callback: function(){
        Chat.inputToggleSingleMulti();
      }
    },{
      label: "Monospace message font",
      boolValue: ()=>document.body.classList.contains('monospace-messages'),
      callback: function(){
        document.body.classList.toggle('monospace-messages');
        Chat.settings.set('monospace-messages',
                          document.body.classList.contains('monospace-messages'));
      }
    },{
      label: "Chat-only mode",
      boolValue: ()=>!!document.body.classList.contains('chat-only-mode'),
      callback: function f(){
        if(undefined === f.isHidden){
          f.isHidden = false;
          f.elemsToToggle = [];
          document.body.childNodes.forEach(function(e){
Changes to src/default.css.
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497

1498






1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
}
/* 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;
}

body.chat .fossil-tooltip.help-buttonlet-content {
  font-size: 80%;
}








|
|

>

>
>
>
>
>
>





|

<







1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512

1513
1514
1515
1516
1517
1518
1519
}
/* 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 0.5em;
  margin-top: -0.75em/*slide it up to the base of the LEGEND*/;
  min-width: 9em /*avoid unsightly "underlap" with the user name label*/;
  white-space: pre-wrap/*needed for multi-line edits*/;
}
body.chat.monospace-messages .message-content,
body.chat.monospace-messages textarea,
body.chat.monospace-messages input[type=text]{
  font-family: monospace;  
}

/* 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.25em 0.4em 0.15em;
  padding: 0 0.5em 0em 0.5em;

  cursor: pointer;
}

body.chat .fossil-tooltip.help-buttonlet-content {
  font-size: 80%;
}

1629
1630
1631
1632
1633
1634
1635

1636
1637
1638
1639
1640
1641
1642
body.chat.chat-only-mode #chat-input-area {
  /* would like to pin this to the top so that it stays in place when
 scrolling, but doing so causes #chat-messages-wrapper to scroll
 behind it visibly, which is really ugly. Only current workaround is
 to force an opaque background color on this element, but that's not
 skin-friendly. */
  position: sticky;

  top: 0;
  padding: 0.5em 1em;
  z-index: 100
    /* see notes in #chat-messages-wrapper. The various popups require a
       z-index higher than this one. */
}
body.chat.chat-only-mode #chat-messages-wrapper {







>







1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
body.chat.chat-only-mode #chat-input-area {
  /* would like to pin this to the top so that it stays in place when
 scrolling, but doing so causes #chat-messages-wrapper to scroll
 behind it visibly, which is really ugly. Only current workaround is
 to force an opaque background color on this element, but that's not
 skin-friendly. */
  position: sticky;
  position: -webkit-sticky /* supposedly some versions of Safari */;
  top: 0;
  padding: 0.5em 1em;
  z-index: 100
    /* see notes in #chat-messages-wrapper. The various popups require a
       z-index higher than this one. */
}
body.chat.chat-only-mode #chat-messages-wrapper {
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667

1668
1669
1670
1671
1672
1673
1674
  border-bottom: 1px solid black;
  margin-bottom: 0.5em;
}
body.chat #chat-input-line {
  display: flex;
  flex-direction: row;
  margin-bottom: 0.25em;
  align-items: center;
}
body.chat #chat-input-line > input[type=submit] {
  flex: 1 5 auto;
  max-width: 6em;
  margin: 0 1em;
}
body.chat #chat-input-line > input[type=text] {

  flex: 5 1 auto;
}
body.chat #chat-input-file-area  {
  display: flex;
  flex-direction: row;
  align-items: center;
  flex-wrap: wrap;







|






|
>







1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
  border-bottom: 1px solid black;
  margin-bottom: 0.5em;
}
body.chat #chat-input-line {
  display: flex;
  flex-direction: row;
  margin-bottom: 0.25em;
  align-items: flex-start;
}
body.chat #chat-input-line > input[type=submit] {
  flex: 1 5 auto;
  max-width: 6em;
  margin: 0 1em;
}
body.chat #chat-input-line > input[type=text],
body.chat #chat-input-line > textarea {
  flex: 5 1 auto;
}
body.chat #chat-input-file-area  {
  display: flex;
  flex-direction: row;
  align-items: center;
  flex-wrap: wrap;