Fossil

Check-in [2b07b66d59]
Login

Check-in [2b07b66d59]

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

Overview
Comment:Removed the cumbersome and platform-dependent file selection widget from view and now proxy its activation via a new toolbar button. Saves space and looks nicer.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | chat-input-rework
Files: files | file ages | folders
SHA3-256: 2b07b66d5942c69844c43710abc960f77dd8f0bc7e2dc51be76bc0bc33851624
User & Date: stephan 2021-10-01 17:40:13.881
Context
2021-10-01
18:01
Added the chat input area resize option to compact mode so there is a recovery strategy if someone manages to paste a whole book into that field. ... (check-in: 797e33ba6b user: stephan tags: chat-input-rework)
17:40
Removed the cumbersome and platform-dependent file selection widget from view and now proxy its activation via a new toolbar button. Saves space and looks nicer. ... (check-in: 2b07b66d59 user: stephan tags: chat-input-rework)
17:14
In compact mode, move the buttons below the input field in order to stop truncation and button layout shifting as the input field automatically resizes during editing. Takes up more a bit more space but provides better UX. ... (check-in: fe0760c95d user: stephan tags: chat-input-rework)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/chat.c.
162
163
164
165
166
167
168


169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
  @     <div contenteditable id="chat-input-field" \
  @      data-placeholder0="%h(zInputPlaceholder0)" \
  @      data-placeholder="%h(zInputPlaceholder0)" \
  @      class=""></div>
  @     <div id='chat-edit-buttons'>
  @       <button id="chat-preview-button" \
  @         title="Preview message (Shift-Enter)">&#128065;</button>


  @       <button id="chat-settings-button" \
  @         title="Configure chat">&#9881;</button>
  @       <button id="chat-message-submit" \
  @         title="Send message (Ctrl-Enter)">&#128228;</button>
  @     </div>
  @   </div>
  @   <div id='chat-input-file-area' class='hidden'>
  @     <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
  @        your environment.
  @       </div>
  @       <input type="file" name="file" id="chat-input-file">
  @     </div>
  @     <div id="chat-drop-details"></div>
  @   </div>
  @ </div>
  @ <div id='chat-user-list-wrapper' class='hidden'>
  @   <div class='legend'>







>
>






|
|
<
<
<
<
<







162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178





179
180
181
182
183
184
185
  @     <div contenteditable id="chat-input-field" \
  @      data-placeholder0="%h(zInputPlaceholder0)" \
  @      data-placeholder="%h(zInputPlaceholder0)" \
  @      class=""></div>
  @     <div id='chat-edit-buttons'>
  @       <button id="chat-preview-button" \
  @         title="Preview message (Shift-Enter)">&#128065;</button>
  @       <button id="chat-message-attach" \
  @         title="Attach file to message">&#128206;</button>
  @       <button id="chat-settings-button" \
  @         title="Configure chat">&#9881;</button>
  @       <button id="chat-message-submit" \
  @         title="Send message (Ctrl-Enter)">&#128228;</button>
  @     </div>
  @   </div>
  @   <div id='chat-input-file-area'>
  @     <div class='file-selection-wrapper hidden'>





  @       <input type="file" name="file" id="chat-input-file">
  @     </div>
  @     <div id="chat-drop-details"></div>
  @   </div>
  @ </div>
  @ <div id='chat-user-list-wrapper' class='hidden'>
  @   <div class='legend'>
Changes to src/fossil.page.chat.js.
125
126
127
128
129
130
131

132
133
134
135
136
137
138
        pageTitle: E1('head title'),
        loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
        inputWrapper: E1("#chat-input-area"),
        inputLine: E1('#chat-input-line'),
        fileSelectWrapper: E1('#chat-input-file-area'),
        viewMessages: E1('#chat-messages-wrapper'),
        btnSubmit: E1('#chat-message-submit'),

        inputField: E1('#chat-input-field'),
        inputFile: E1('#chat-input-file'),
        contentDiv: E1('div.content'),
        viewConfig: E1('#chat-config'),
        viewPreview: E1('#chat-preview'),
        previewContent: E1('#chat-preview-content'),
        btnPreview: E1('#chat-preview-button'),







>







125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
        pageTitle: E1('head title'),
        loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
        inputWrapper: E1("#chat-input-area"),
        inputLine: E1('#chat-input-line'),
        fileSelectWrapper: E1('#chat-input-file-area'),
        viewMessages: E1('#chat-messages-wrapper'),
        btnSubmit: E1('#chat-message-submit'),
        btnAttach: E1('#chat-message-attach'),
        inputField: E1('#chat-input-field'),
        inputFile: E1('#chat-input-file'),
        contentDiv: E1('div.content'),
        viewConfig: E1('#chat-config'),
        viewPreview: E1('#chat-preview'),
        previewContent: E1('#chat-preview-content'),
        btnPreview: E1('#chat-preview-button'),
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
          "images-inline": !!F.config.chat.imagesInline,
          "edit-ctrl-send": false,
          "edit-compact-mode": true,
          "monospace-messages": true,
          "chat-only-mode": false,
          "audible-alert": true,
          "active-user-list": false,
          "active-user-list-timestamps": false,
          "hide-attachment-widget": false
        }
      },
      /** Plays a new-message notification sound IF the audible-alert
          setting is true, else this is a no-op. Returns this.
      */
      playNewMessageSound: function f(){
        if(f.uri){







|
<







380
381
382
383
384
385
386
387

388
389
390
391
392
393
394
          "images-inline": !!F.config.chat.imagesInline,
          "edit-ctrl-send": false,
          "edit-compact-mode": true,
          "monospace-messages": true,
          "chat-only-mode": false,
          "audible-alert": true,
          "active-user-list": false,
          "active-user-list-timestamps": false

        }
      },
      /** Plays a new-message notification sound IF the audible-alert
          setting is true, else this is a no-op. Returns this.
      */
      playNewMessageSound: function f(){
        if(f.uri){
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
        }
        if(theMsg) f.popup.show(theMsg);
      }/*_handleLegendClicked()*/
    };
    return cf;
  })()/*MessageWidget*/;

  const BlobXferState = (function(){/*drag/drop bits...*/
    /* State for paste and drag/drop */
    const bxs = {
      dropDetails: document.querySelector('#chat-drop-details'),
      blob: undefined,
      clear: function(){
        this.blob = undefined;
        D.clearElement(this.dropDetails);







|







1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
        }
        if(theMsg) f.popup.show(theMsg);
      }/*_handleLegendClicked()*/
    };
    return cf;
  })()/*MessageWidget*/;

  const BlobXferState = (function(){
    /* State for paste and drag/drop */
    const bxs = {
      dropDetails: document.querySelector('#chat-drop-details'),
      blob: undefined,
      clear: function(){
        this.blob = undefined;
        D.clearElement(this.dropDetails);
1057
1058
1059
1060
1061
1062
1063
1064
1065



1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
      const dd = bxs.dropDetails;
      bxs.blob = blob;
      D.clearElement(dd);
      if(!blob){
        Chat.e.inputFile.value = '';
        return;
      }
      D.append(dd, "Name: ", blob.name,
               D.br(), "Size: ",blob.size);



      if(blob.type && (blob.type.startsWith("image/") || blob.type==='BITMAP')){
        const img = D.img();
        D.append(dd, D.br(), img);
        const reader = new FileReader();
        reader.onload = (e)=>img.setAttribute('src', e.target.result);
        reader.readAsDataURL(blob);
      }
      const btn = D.button("Cancel");
      D.append(dd, D.br(), btn);
      btn.addEventListener('click', ()=>updateDropZoneContent(), false);
    };
    Chat.e.inputFile.addEventListener('change', function(ev){
      updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
    });
    /* Handle image paste from clipboard. TODO: figure out how we can
       paste non-image binary data as if it had been selected via the
       file selection element. */







|

>
>
>







<
<
<







1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075



1076
1077
1078
1079
1080
1081
1082
      const dd = bxs.dropDetails;
      bxs.blob = blob;
      D.clearElement(dd);
      if(!blob){
        Chat.e.inputFile.value = '';
        return;
      }
      D.append(dd, "Attached: ", blob.name,
               D.br(), "Size: ",blob.size);
      const btn = D.button("Cancel");
      D.append(dd, D.br(), btn);
      btn.addEventListener('click', ()=>updateDropZoneContent(), false);
      if(blob.type && (blob.type.startsWith("image/") || blob.type==='BITMAP')){
        const img = D.img();
        D.append(dd, D.br(), img);
        const reader = new FileReader();
        reader.onload = (e)=>img.setAttribute('src', e.target.result);
        reader.readAsDataURL(blob);
      }



    };
    Chat.e.inputFile.addEventListener('change', function(ev){
      updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
    });
    /* Handle image paste from clipboard. TODO: figure out how we can
       paste non-image binary data as if it had been selected via the
       file selection element. */
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147

1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
        }
        ev.target.textContent += pastedText;
        ev.preventDefault();
        return false;
      };
      Chat.e.inputField.addEventListener('paste', onPastePlainText, false);
    }
    /* Add help button for drag/drop/paste zone */
    Chat.e.inputFile.parentNode.insertBefore(
      F.helpButtonlets.create(
        Chat.e.fileSelectWrapper.querySelector('.help-buttonlet')
      ), Chat.e.inputFile
    );
    ////////////////////////////////////////////////////////////
    // File drag/drop visual notification.
    const dropHighlight = Chat.e.inputFile /* target zone */;
    const dropEvents = {
      drop: function(ev){
        D.removeClass(dropHighlight, 'dragover');
      },
      dragenter: function(ev){
        ev.preventDefault();
        ev.dataTransfer.dropEffect = "copy";
        D.addClass(dropHighlight, 'dragover');
        return false;
      },
      dragleave: function(ev){
        D.removeClass(dropHighlight, 'dragover');
      },
      dragend: function(ev){
        D.removeClass(dropHighlight, 'dragover');
      }
    };
    const noDragDropEvents = function(ev){
      /* contenteditable tries to do its own thing with dropped data,
         which is not compatible with how we use it, so... */
      ev.dataTransfer.effectAllowed = 'none';
      ev.dataTransfer.dropEffect = 'none';
      ev.preventDefault();
      ev.stopPropagation();
      return false;
    };
    Object.keys(dropEvents).forEach(

      (k)=>{
        Chat.e.inputFile.addEventListener(k, dropEvents[k], true);
        Chat.inputElement().addEventListener(k, noDragDropEvents, false);
      }
    );
    return bxs;
  })()/*drag/drop*/;

  const tzOffsetToString = function(off){
    const hours = Math.round(off/60), min = Math.round(off % 30);
    return ''+(hours + (min ? '.5' : ''));
  };
  const pad2 = (x)=>('0'+x).substr(-2);
  const localTime8601 = function(d){







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<









|
>

<




|







1105
1106
1107
1108
1109
1110
1111


























1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123

1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
        }
        ev.target.textContent += pastedText;
        ev.preventDefault();
        return false;
      };
      Chat.e.inputField.addEventListener('paste', onPastePlainText, false);
    }


























    const noDragDropEvents = function(ev){
      /* contenteditable tries to do its own thing with dropped data,
         which is not compatible with how we use it, so... */
      ev.dataTransfer.effectAllowed = 'none';
      ev.dataTransfer.dropEffect = 'none';
      ev.preventDefault();
      ev.stopPropagation();
      return false;
    };

    ['drop','dragenter','dragleave','dragend'].forEach(
      (k)=>{

        Chat.inputElement().addEventListener(k, noDragDropEvents, false);
      }
    );
    return bxs;
  })()/*drag/drop/paste*/;

  const tzOffsetToString = function(off){
    const hours = Math.round(off/60), min = Math.round(off % 30);
    return ''+(hours + (min ? '.5' : ''));
  };
  const pad2 = (x)=>('0'+x).substr(-2);
  const localTime8601 = function(d){
1284
1285
1286
1287
1288
1289
1290


1291
1292
1293
1294
1295
1296
1297
  };  
  Chat.e.inputField.addEventListener('keydown', inputWidgetKeydown, false);
  Chat.e.btnSubmit.addEventListener('click',(e)=>{
    e.preventDefault();
    Chat.submitMessage();
    return false;
  });



  (function(){/*Set up #chat-settings-button and related bits */
    if(window.innerWidth<window.innerHeight){
      // Must be set up before config view is...
      /* Alignment of 'my' messages: right alignment is conventional
         for mobile chat apps but can be difficult to read in wide
         windows (desktop/tablet landscape mode), so we default to a







>
>







1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
  };  
  Chat.e.inputField.addEventListener('keydown', inputWidgetKeydown, false);
  Chat.e.btnSubmit.addEventListener('click',(e)=>{
    e.preventDefault();
    Chat.submitMessage();
    return false;
  });
  Chat.e.btnAttach.addEventListener(
    'click', ()=>Chat.e.inputFile.click(), false);

  (function(){/*Set up #chat-settings-button and related bits */
    if(window.innerWidth<window.innerHeight){
      // Must be set up before config view is...
      /* Alignment of 'my' messages: right alignment is conventional
         for mobile chat apps but can be difficult to read in wide
         windows (desktop/tablet landscape mode), so we default to a
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
      hint: "Whether to show images inline or as a hyperlink.",
      boolValue: 'images-inline'
    },{
      label: "Timestamps in active users list",
      hint: "Whether to show last-message timestamps.",
      boolValue: 'active-user-list-timestamps'
    },
    namedOptions.activeUsers,{
      label: "Hide file attachment widget",
      boolValue: "hide-attachment-widget",
      hint: "Hides the file attachment widget, gaining more "+
        "screen space for messages."
    }];

    /** Set up selection list of notification sounds. */
    if(1){
      const selectSound = D.select();
      D.option(selectSound, "", "(no audio)");
      const firstSoundIndex = selectSound.options.length;
      F.config.chat.alerts.forEach((a)=>D.option(selectSound, a));







|
<
<
<
<
<







1373
1374
1375
1376
1377
1378
1379
1380





1381
1382
1383
1384
1385
1386
1387
      hint: "Whether to show images inline or as a hyperlink.",
      boolValue: 'images-inline'
    },{
      label: "Timestamps in active users list",
      hint: "Whether to show last-message timestamps.",
      boolValue: 'active-user-list-timestamps'
    },
    namedOptions.activeUsers];






    /** Set up selection list of notification sounds. */
    if(1){
      const selectSound = D.select();
      D.option(selectSound, "", "(no audio)");
      const firstSoundIndex = selectSound.options.length;
      F.config.chat.alerts.forEach((a)=>D.option(selectSound, a));
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
      Chat.chatOnlyMode(s.value);
    });
    Chat.settings.addListener('edit-compact-mode',function(s){
      Chat.e.inputLine.classList[
        s.value ? 'add' : 'remove'
      ]('compact');
    });
    Chat.settings.addListener('hide-attachment-widget',function(s){
      Chat.e.fileSelectWrapper.classList[
        s.value ? 'add' : 'remove'
      ]('hidden');
    });    
    Chat.settings.addListener('edit-ctrl-send',function(s){
      const label = (s.value ? "Ctrl-" : "")+"Enter submits messages.";
      const eInput = Chat.inputElement();
      eInput.dataset.placeholder = eInput.dataset.placeholder0 + " " +label;
      Chat.e.btnSubmit.title = label;
      F.toast.message(label);
    });







<
<
<
<
<







1499
1500
1501
1502
1503
1504
1505





1506
1507
1508
1509
1510
1511
1512
      Chat.chatOnlyMode(s.value);
    });
    Chat.settings.addListener('edit-compact-mode',function(s){
      Chat.e.inputLine.classList[
        s.value ? 'add' : 'remove'
      ]('compact');
    });





    Chat.settings.addListener('edit-ctrl-send',function(s){
      const label = (s.value ? "Ctrl-" : "")+"Enter submits messages.";
      const eInput = Chat.inputElement();
      eInput.dataset.placeholder = eInput.dataset.placeholder0 + " " +label;
      Chat.e.btnSubmit.title = label;
      F.toast.message(label);
    });
Changes to src/style.chat.css.
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
  padding: initial/*some skins mess this up for buttons*/;
  line-height: inherit/*undo skin funkiness*/;
  min-width: 4ex;
}
body.chat #chat-input-line:not(.compact) #chat-edit-buttons > * {
  max-width: 6ex;
  min-width: 6ex;
  min-height: 6ex;
  max-height: 6ex;
  margin: 0.125em;
}

body.chat #chat-input-line:not(.compact) #chat-input-field {
  /*border-left-style: double;
  border-left-width: 3px;







|







264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
  padding: initial/*some skins mess this up for buttons*/;
  line-height: inherit/*undo skin funkiness*/;
  min-width: 4ex;
}
body.chat #chat-input-line:not(.compact) #chat-edit-buttons > * {
  max-width: 6ex;
  min-width: 6ex;
  min-height: 5ex;
  max-height: 6ex;
  margin: 0.125em;
}

body.chat #chat-input-line:not(.compact) #chat-input-field {
  /*border-left-style: double;
  border-left-width: 3px;
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
355
356
357
358
359
360
361
362
363
364
365
366
body.chat #chat-input-line.compact > input[type=text] {
  margin: 0 0 0.25em 0/* gap for if/when buttons wrap*/;
}
/* Widget holding the file selection control and preview */
body.chat #chat-input-file-area  {
  display: flex;
  flex-direction: row;


  align-items: center;
  flex-wrap: wrap;
  margin: 0.25em 0 0 0 /* avoid nudging input area */;
}
body.chat #chat-input-file-area > .file-selection-wrapper {
  align-self: flex-start;
  margin-right: 0.5em;
  flex: 0 1 auto;
  padding: 0.25em 0.5em;
  white-space: nowrap;
}
body.chat #chat-input-file-area .file-selection-wrapper > * {
  vertical-align: middle;
  margin: 0;
}
body.chat #chat-input-file {
  border:1px solid rgba(0,0,0,0);/*avoid UI shift during drop-targeting*/
  border-radius: 0.25em;
  padding: 0.25em;
}
body.chat #chat-input-file > input {
  flex: 1 0 auto;
}
/* Indicator when a drag/drop is in progress */
body.chat #chat-input-file.dragover {
  border: 1px dashed green;
}
/* Widget holding the details of a selected/dropped file/image. */
body.chat #chat-drop-details {
  flex: 0 1 auto;
  padding: 0.5em 1em;
  margin-left: 0.5em;
  white-space: pre;
  font-family: monospace;
}

body.chat #chat-drop-details img {







>
>

<
<








<
<
<
<














<







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

355
356
357
358
359
360
361
body.chat #chat-input-line.compact > input[type=text] {
  margin: 0 0 0.25em 0/* gap for if/when buttons wrap*/;
}
/* Widget holding the file selection control and preview */
body.chat #chat-input-file-area  {
  display: flex;
  flex-direction: row;
  margin: 0;
  justify-content: flex-end;
  align-items: center;


}
body.chat #chat-input-file-area > .file-selection-wrapper {
  align-self: flex-start;
  margin-right: 0.5em;
  flex: 0 1 auto;
  padding: 0.25em 0.5em;
  white-space: nowrap;
}




body.chat #chat-input-file {
  border:1px solid rgba(0,0,0,0);/*avoid UI shift during drop-targeting*/
  border-radius: 0.25em;
  padding: 0.25em;
}
body.chat #chat-input-file > input {
  flex: 1 0 auto;
}
/* Indicator when a drag/drop is in progress */
body.chat #chat-input-file.dragover {
  border: 1px dashed green;
}
/* Widget holding the details of a selected/dropped file/image. */
body.chat #chat-drop-details {

  padding: 0.5em 1em;
  margin-left: 0.5em;
  white-space: pre;
  font-family: monospace;
}

body.chat #chat-drop-details img {