Fossil

Check-in [86d6be3fe2]
Login

Check-in [86d6be3fe2]

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

Overview
Comment:Forcibly disable drop support in the new editor widget, as the browser will otherwise allow the user to drop images to it, which is confusing and does not work with our ability to upload images. Found a way to implement placeholder text in the contenteditable field.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | chat-input-rework
Files: files | file ages | folders
SHA3-256: 86d6be3fe212ad50b0d51124712ac8a614cb8b849e2b5f7fcf32ee375118981f
User & Date: stephan 2021-09-30 19:32:25.977
Context
2021-09-30
19:56
Re-enabled ctrl-enter-sends when enter-sends mode is active (was disabled during testing). Update the tooltip on the send button to reflect the current send mode. ... (check-in: 7d6c07496e user: stephan tags: chat-input-rework)
19:32
Forcibly disable drop support in the new editor widget, as the browser will otherwise allow the user to drop images to it, which is confusing and does not work with our ability to upload images. Found a way to implement placeholder text in the contenteditable field. ... (check-in: 86d6be3fe2 user: stephan tags: chat-input-rework)
17:24
Minor restructuring and docs in the enter/ctrl-enter handling. Trying to get ctrl-enter to add newlines when in enter-sends mode, but it's not working for reasons beyond my ken. ... (check-in: ab9fef759e user: stephan tags: chat-input-rework)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/chat.c.
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164

165
166
167
168
169
170
171
172
173
**
** Other /chat-OP pages are used by XHR requests from this page to
** send new chat message, delete older messages, or poll for changes.
*/
void chat_webpage(void){
  char *zAlert;
  char *zProjectName;
  const char * zInputPlaceholder2 = /* Placeholder for textarea input*/
    "Ctrl-Enter sends and Shift-Enter previews.";
  char * zInputPlaceholder0;  /* Common text input placeholder value */
  login_check_credentials();
  if( !g.perm.Chat ){
    login_needed(g.anon.Chat);
    return;
  }
  zAlert = mprintf("%s/builtin/%s", g.zBaseURL,
                db_get("chat-alert-sound","alerts/plunk.wav"));
  zProjectName = db_get("project-name","Unnamed project");
  zInputPlaceholder0 =
    mprintf("Type markdown-formatted message for %h.", zProjectName);
  style_set_current_feature("chat");
  style_header("Chat");
  @ <div id='chat-input-area'>
  @   <div id='chat-input-line' class='single-line'>
  @     <div contenteditable id="chat-input-field" \

  @      placeholder="%h(zInputPlaceholder0) %h(zInputPlaceholder2)" \
  @      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>







<
<
















>
|
|







140
141
142
143
144
145
146


147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
**
** Other /chat-OP pages are used by XHR requests from this page to
** send new chat message, delete older messages, or poll for changes.
*/
void chat_webpage(void){
  char *zAlert;
  char *zProjectName;


  char * zInputPlaceholder0;  /* Common text input placeholder value */
  login_check_credentials();
  if( !g.perm.Chat ){
    login_needed(g.anon.Chat);
    return;
  }
  zAlert = mprintf("%s/builtin/%s", g.zBaseURL,
                db_get("chat-alert-sound","alerts/plunk.wav"));
  zProjectName = db_get("project-name","Unnamed project");
  zInputPlaceholder0 =
    mprintf("Type markdown-formatted message for %h.", zProjectName);
  style_set_current_feature("chat");
  style_header("Chat");
  @ <div id='chat-input-area'>
  @   <div id='chat-input-line' class='single-line'>
  @     <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>
Changes to src/fossil.page.chat.js.
1116
1117
1118
1119
1120
1121
1122

1123
1124
1125
1126
1127
1128
1129
1130








1131

1132


1133
1134
1135
1136
1137
1138
1139
      drop: function(ev){
        D.removeClass(dropHighlight, 'dragover');
      },
      dragenter: function(ev){
        ev.preventDefault();
        ev.dataTransfer.dropEffect = "copy";
        D.addClass(dropHighlight, 'dragover');

      },
      dragleave: function(ev){
        D.removeClass(dropHighlight, 'dragover');
      },
      dragend: function(ev){
        D.removeClass(dropHighlight, 'dragover');
      }
    };








    Object.keys(dropEvents).forEach(

      (k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)


    );
    return bxs;
  })()/*drag/drop*/;

  const tzOffsetToString = function(off){
    const hours = Math.round(off/60), min = Math.round(off % 30);
    return ''+(hours + (min ? '.5' : ''));







>








>
>
>
>
>
>
>
>

>
|
>
>







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
      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 cancelEvent = function(ev){
      /* contenteditable tries to do its own thing with dropped data,
         which is not compatible with how we use it, so... */
      //if(ev.dataTransfer) 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, cancelEvent, false);
      }
    );
    return bxs;
  })()/*drag/drop*/;

  const tzOffsetToString = function(off){
    const hours = Math.round(off/60), min = Math.round(off % 30);
    return ''+(hours + (min ? '.5' : ''));
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217

1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250















1251
1252
1253
1254
1255
1256
1257
1258
  };

  const inputWidgetKeydown = function f(ev){
    if(!f.$toggleCtrl){
      f.$toggleCtrl = function(currentMode){
        currentMode = !currentMode;
        Chat.settings.set('edit-ctrl-send', currentMode);
        F.toast.message((currentMode ? "Ctrl-" : "")
                        +"Enter submits messages.");
      };
      f.$toggleCompact = function(currentMode){
        currentMode = !currentMode;
        Chat.settings.set('edit-compact-mode', currentMode);
      };
    }
    //console.debug("Enter key event:", ev.keyCode, ev.ctrlKey, ev.shiftKey, ev);
    if(13 !== ev.keyCode) return;

    const ctrlMode = Chat.settings.getBool('edit-ctrl-send', false);
    //console.debug("Enter key event:", ctrlMode, ev.ctrlKey, ev.shiftKey, ev);
    const text = Chat.inputValue().trim();
    if(ev.shiftKey){
      const compactMode = Chat.settings.getBool('edit-compact-mode', false);
      ev.preventDefault();
      ev.stopPropagation();
      /* Shift-enter will run preview mode UNLESS preview mode is
         active AND the input field is empty, in which case it will
         switch back to message view. */
      if(Chat.e.currentView===Chat.e.viewPreview && !text){
        Chat.setCurrentView(Chat.e.viewMessages);
      }else if(!text){
        f.$toggleCompact(compactMode);
      }else{
        Chat.e.btnPreview.click();
      }
      return false;
    }
    if(0 && (!ctrlMode && ev.ctrlKey && text)){
      /* Ctrl-enter in Enter-sends mode SHOULD, with this logic add a
         newline, but that is not happening, for reasons i don't
         understand.  Forcibly appending a newline do the input area
         does not work, also for unknown reasons.
       */
      return;
    }
    if(ev.ctrlKey && !text){
      /* Ctrl-enter on an empty field toggles Enter/Ctrl-enter mode */
      ev.preventDefault();
      ev.stopPropagation();
      f.$toggleCtrl(ctrlMode);
      return false;















    }else if((!ctrlMode && !ev.ctrlKey) || (ev.ctrlKey && ctrlMode)){
      /* Ship it! */
      ev.preventDefault();
      ev.stopPropagation();
      Chat.submitMessage();
      return false;
    }
  };  







<
<






<

>


<
















<
<
<
<
<
<
<
<






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







1213
1214
1215
1216
1217
1218
1219


1220
1221
1222
1223
1224
1225

1226
1227
1228
1229

1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245








1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
  };

  const inputWidgetKeydown = function f(ev){
    if(!f.$toggleCtrl){
      f.$toggleCtrl = function(currentMode){
        currentMode = !currentMode;
        Chat.settings.set('edit-ctrl-send', currentMode);


      };
      f.$toggleCompact = function(currentMode){
        currentMode = !currentMode;
        Chat.settings.set('edit-compact-mode', currentMode);
      };
    }

    if(13 !== ev.keyCode) return;
    const text = Chat.inputValue().trim();
    const ctrlMode = Chat.settings.getBool('edit-ctrl-send', false);
    //console.debug("Enter key event:", ctrlMode, ev.ctrlKey, ev.shiftKey, ev);

    if(ev.shiftKey){
      const compactMode = Chat.settings.getBool('edit-compact-mode', false);
      ev.preventDefault();
      ev.stopPropagation();
      /* Shift-enter will run preview mode UNLESS preview mode is
         active AND the input field is empty, in which case it will
         switch back to message view. */
      if(Chat.e.currentView===Chat.e.viewPreview && !text){
        Chat.setCurrentView(Chat.e.viewMessages);
      }else if(!text){
        f.$toggleCompact(compactMode);
      }else{
        Chat.e.btnPreview.click();
      }
      return false;
    }








    if(ev.ctrlKey && !text){
      /* Ctrl-enter on an empty field toggles Enter/Ctrl-enter mode */
      ev.preventDefault();
      ev.stopPropagation();
      f.$toggleCtrl(ctrlMode);
      return false;
    }
    if(!ctrlMode && ev.ctrlKey && text){
      //console.debug("!ctrlMode && ev.ctrlKey && text.");
      /* Ctrl-enter in Enter-sends mode SHOULD, with this logic add a
         newline, but that is not happening, for unknown reasons
         (possibly related to this element being a conteneditable DIV
         instead of a textarea). Forcibly appending a newline do the
         input area does not work, also for unknown reasons, and would
         only be suitable when we're at the end of the input.

         Strangely, this approach DOES work for shift-enter, but we
         need shift-enter as a hotkey for preview mode.
      */
      return;
    }
    if((!ctrlMode && !ev.ctrlKey) || (ev.ctrlKey && ctrlMode)){
      /* Ship it! */
      ev.preventDefault();
      ev.stopPropagation();
      Chat.submitMessage();
      return false;
    }
  };  
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
        "blank lines. "+
        "When off, both Enter and Ctrl-Enter send. "+
        "When the input field has focus, is empty, and preview "+
        "mode is NOT active then Ctrl-Enter toggles this setting.",
      boolValue: 'edit-ctrl-send'
    },{
      label: "Compact mode",
      hint: "Toggle between a space-saving and more spacious writing area. "+
        "When the input field has focus, is empty, and preview mode "+
        "is NOT active then Shift-Enter toggles this setting.",
      boolValue: 'edit-compact-mode'
    },{
      label: "Left-align my posts",
      hint: "Default alignment of your own messages is selected "
        +"based window width/height relationship.",







|







1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
        "blank lines. "+
        "When off, both Enter and Ctrl-Enter send. "+
        "When the input field has focus, is empty, and preview "+
        "mode is NOT active then Ctrl-Enter toggles this setting.",
      boolValue: 'edit-ctrl-send'
    },{
      label: "Compact mode",
      hint: "Toggle between a space-saving or more spacious writing area. "+
        "When the input field has focus, is empty, and preview mode "+
        "is NOT active then Shift-Enter toggles this setting.",
      boolValue: 'edit-compact-mode'
    },{
      label: "Left-align my posts",
      hint: "Default alignment of your own messages is selected "
        +"based window width/height relationship.",
1498
1499
1500
1501
1502
1503
1504






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






    });
    const valueKludges = {
      /* Convert certain string-format values to other types... */
      "false": false,
      "true": true
    };
    Object.keys(Chat.settings.defaults).forEach(function(k){







>
>
>
>
>
>







1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
    Chat.settings.addListener('chat-only-mode',function(s){
      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;
      F.toast.message(label);
    });
    const valueKludges = {
      /* Convert certain string-format values to other types... */
      "false": false,
      "true": true
    };
    Object.keys(Chat.settings.defaults).forEach(function(k){
Changes to src/style.chat.css.
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
  padding: 0.25em 0.5em;
  margin-top: 0;
  min-width: 9em /*avoid unsightly "underlap" with the neighboring
                   .message-widget-tab element*/;
  white-space: normal;
}
body.chat.monospace-messages .message-widget-content,
body.chat.monospace-messages textarea,
body.chat.monospace-messages input[type=text],
body.chat.monospace-messages #chat-input-field{
  font-family: monospace;  
}
body.chat .message-widget-content > * {
  margin: 0;
  padding: 0;
}







|
|







34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
  padding: 0.25em 0.5em;
  margin-top: 0;
  min-width: 9em /*avoid unsightly "underlap" with the neighboring
                   .message-widget-tab element*/;
  white-space: normal;
}
body.chat.monospace-messages .message-widget-content,
/*body.chat.monospace-messages textarea,*/
/*body.chat.monospace-messages input[type=text],*/
body.chat.monospace-messages #chat-input-field{
  font-family: monospace;  
}
body.chat .message-widget-content > * {
  margin: 0;
  padding: 0;
}
176
177
178
179
180
181
182

183
184
185
186
187





188
189
190
191
192
193
194
body.chat:not(.chat-only-mode) #chat-input-area{
  /* Safari user reports that 2em is necessary to keep the file selection
     widget from overlapping the page footer, whereas a margin of 0 is fine
     for FF/Chrome (and 2em is a *huge* waste of space for those). */
  margin-bottom: 0;
}
#chat-input-field {

  padding: 0.2em;
  flex: 50 1 85%;
  background-color: rgba(156,156,156,0.3);
  overflow: auto;
  max-height: 8rem /*arbitrary!*/;





}
#chat-input-field:not(:focus){
  border-width: 1px;
  border-style: solid;
  border-radius: 0.25em;
}
#chat-input-field:focus{







>





>
>
>
>
>







176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
body.chat:not(.chat-only-mode) #chat-input-area{
  /* Safari user reports that 2em is necessary to keep the file selection
     widget from overlapping the page footer, whereas a margin of 0 is fine
     for FF/Chrome (and 2em is a *huge* waste of space for those). */
  margin-bottom: 0;
}
#chat-input-field {
  display: inline-block/*supposed workaround for Chrome weirdness*/;
  padding: 0.2em;
  flex: 50 1 85%;
  background-color: rgba(156,156,156,0.3);
  overflow: auto;
  max-height: 8rem /*arbitrary!*/;
  /*white-space: pre-wrap;*/
}
#chat-input-field:empty::before {
  content: attr(data-placeholder);
  opacity: 0.6;
}
#chat-input-field:not(:focus){
  border-width: 1px;
  border-style: solid;
  border-radius: 0.25em;
}
#chat-input-field:focus{