Fossil

Check-in [09f86815c6]
Login

Check-in [09f86815c6]

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

Overview
Comment:Merged in trunk for latest changes.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | markdown-tagrefs
Files: files | file ages | folders
SHA3-256: 09f86815c6ca0f27903440aba7052bf54ca5ff950a9794b6d31264353db7b401
User & Date: stephan 2021-09-29 16:50:40.151
Context
2021-10-04
19:48
Merged in trunk for latest (and conflicting) /chat changes. ... (check-in: 7cae4c0981 user: stephan tags: markdown-tagrefs)
2021-09-29
16:50
Merged in trunk for latest changes. ... (check-in: 09f86815c6 user: stephan tags: markdown-tagrefs)
16:45
Consolidated /wikiedit, /pikchrshow, /fileedit, and /chat to use shift-enter to run preview mode. The former 3 previously used ctrl-enter but it was poorly documented and probably not widely used like ctrl-enter is in chat (to send a message). ... (check-in: 13fabf3f4d user: stephan tags: trunk)
2021-09-28
11:22
/chat: experimental HTML5 history support for using the back button to return to a message from which a #nnn message ID was clicked. ... (check-in: 9df3fc6b0f user: stephan tags: markdown-tagrefs)
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
174
175
176
177
178
**
** 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;





  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");


  style_set_current_feature("chat");
  style_header("Chat");
  @ <div id='chat-input-area'>
  @   <div id='chat-input-line' class='single-line'>
  @     <input type="text" name="msg" id="chat-input-single" \
  @      placeholder="Type markdown-formatted message for %h(zProjectName)." \
  @      autocomplete="off">
  @     <textarea rows="8" id="chat-input-multi" \
  @      placeholder="Type markdown-formatted message for %h(zProjectName). Ctrl-Enter sends it." \
  @      class="hidden"></textarea>
  @     <div id='chat-edit-buttons'>
  @       <button id="chat-preview-button" \
  @         title="Preview message">&#128065;</button>
  @       <button id="chat-settings-button" \
  @         title="Configure chat">&#9881;</button>
  @       <button id="chat-message-submit" \
  @         title="Send message">&#128228;</button>
  @     </div>
  @   </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







>
>
>
>
>








>
>





|


|



|



|







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
174
175
176
177
178
179
180
181
182
183
184
185
**
** 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 * zInputPlaceholder1 = /* Placeholder for 1-line input */
    "Enter sends and Shift-Enter previews.";
  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'>
  @     <input type="text" name="msg" id="chat-input-single" \
  @      placeholder="%h(zInputPlaceholder0) %h(zInputPlaceholder1)" \
  @      autocomplete="off">
  @     <textarea rows="8" id="chat-input-multi" \
  @      placeholder="%h(zInputPlaceholder0) %h(zInputPlaceholder2)" \
  @      class="hidden"></textarea>
  @     <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'>
  @     <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
208
209
210
211
212
213
214

215
216
217
218
219
220
221
  @ <button>Close Settings</button>
  @ </div>
  @ <div id='chat-messages-wrapper' class='chat-view'>
  /* New chat messages get inserted immediately after this element */
  @ <span id='message-inject-point'></span>
  @ </div>
  fossil_free(zProjectName);

  builtin_fossil_js_bundle_or("popupwidget", "storage", "fetch",
                              "pikchr", "confirmer", 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(){







>







215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
  @ <button>Close Settings</button>
  @ </div>
  @ <div id='chat-messages-wrapper' class='chat-view'>
  /* New chat messages get inserted immediately after this element */
  @ <span id='message-inject-point'></span>
  @ </div>
  fossil_free(zProjectName);
  fossil_free(zInputPlaceholder0);
  builtin_fossil_js_bundle_or("popupwidget", "storage", "fetch",
                              "pikchr", "confirmer", 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(){
Changes to src/default.css.
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
  vertical-align: top;
  padding: 0;
  overflow: hidden /*work around inner PRE slight overflow/overlap*/;
}
table.diff pre {
  margin: 0 0 0 0;
  padding: 0 0.5em;
  line-height: 1.225/*for mobile: forum post e6f4ee7de98b55c0*/;
  text-size-adjust: none
  /* ^^^ attempt to keep mobile from inflating some text */;
}
table.diff pre > ins,
table.diff pre > del {
  /* Fill platform-dependent color gaps caused by
     inflated line-height */;







|







551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
  vertical-align: top;
  padding: 0;
  overflow: hidden /*work around inner PRE slight overflow/overlap*/;
}
table.diff pre {
  margin: 0 0 0 0;
  padding: 0 0.5em;
  line-height: 1.275/*for mobile: forum post e6f4ee7de98b55c0*/;
  text-size-adjust: none
  /* ^^^ attempt to keep mobile from inflating some text */;
}
table.diff pre > ins,
table.diff pre > del {
  /* Fill platform-dependent color gaps caused by
     inflated line-height */;
Changes to src/fossil.diff.js.
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
        this.pos.next.startRhs -= lines.length;
        this.pos.next.startLhs -= lines.length;
        const selector = '.difflnr > pre';
        td = tr.querySelector(selector);
        const lineNoTxt = lineno.join('\n')+'\n';
        lineno.length = 0;
        td.innerHTML = lineNoTxt + td.innerHTML;
        if(this.pos.endLhs<=1
           || lines.length < (urlParam.to - urlParam.from)){
          /* No more data. */
          this.destroy();
        }else{
          this.maybeReplaceButtons();
          this.updatePosDebug();
        }







|







446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
        this.pos.next.startRhs -= lines.length;
        this.pos.next.startLhs -= lines.length;
        const selector = '.difflnr > pre';
        td = tr.querySelector(selector);
        const lineNoTxt = lineno.join('\n')+'\n';
        lineno.length = 0;
        td.innerHTML = lineNoTxt + td.innerHTML;
        if(this.pos.endLhs<1
           || lines.length < (urlParam.to - urlParam.from)){
          /* No more data. */
          this.destroy();
        }else{
          this.maybeReplaceButtons();
          this.updatePosDebug();
        }
502
503
504
505
506
507
508

509
510
511
512
513
514
515
516
517





518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535



536
537
538
539
540
541
542
543
       This is an async operation. While it is in transit, any calls
       to this function will have no effect except (possibly) to emit
       a warning. Returns this object.
    */
    fetchChunk: function(fetchType){
      if( !this.$fetchQueue ) return this;  // HACKHACK: are we destroyed?
      if( fetchType==this.FetchType.ProcessQueue ){

        if( this.$fetchQueue.length==0 ) return this;
        //console.log('fetchChunk: processing queue ...');
      }
      else{
        this.$fetchQueue.push(fetchType);
        if( this.$fetchQueue.length!=1 ) return this;
        //console.log('fetchChunk: processing user input ...');
      }
      fetchType = this.$fetchQueue[0];





      /* Forewarning, this is a bit confusing: when fetching the
         previous lines, we're doing so on behalf of the *next* diff
         chunk (this.pos.next), and vice versa. */
      if(fetchType===this.FetchType.NextUp && !this.pos.next
        || fetchType===this.FetchType.PrevDown && !this.pos.prev){
        console.error("Attempt to fetch diff lines but don't have any.");
        return this;
      }
      this.msg(false,"Fetching diff chunk...");
      const self = this;
      const fOpt = {
        urlParams:{
          name: this.fileHash, from: 0, to: 0
        },
        aftersend: ()=>this.msg(false),
        onload: function(list){
          self.injectResponse(fetchType,up,list);
          if( !self.$fetchQueue || self.$fetchQueue.length==0 ) return;



          self.$fetchQueue.shift();
          setTimeout(self.fetchChunk.bind(self,self.FetchType.ProcessQueue));
        }
      };
      const up = fOpt.urlParams;
      if(fetchType===this.FetchType.FillGap){
        /* Easiest case: filling a whole gap. */
        up.from = this.pos.startLhs;







>









>
>
>
>
>


















>
>
>
|







502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
       This is an async operation. While it is in transit, any calls
       to this function will have no effect except (possibly) to emit
       a warning. Returns this object.
    */
    fetchChunk: function(fetchType){
      if( !this.$fetchQueue ) return this;  // HACKHACK: are we destroyed?
      if( fetchType==this.FetchType.ProcessQueue ){
        this.$fetchQueue.shift();
        if( this.$fetchQueue.length==0 ) return this;
        //console.log('fetchChunk: processing queue ...');
      }
      else{
        this.$fetchQueue.push(fetchType);
        if( this.$fetchQueue.length!=1 ) return this;
        //console.log('fetchChunk: processing user input ...');
      }
      fetchType = this.$fetchQueue[0];
      if( fetchType==this.FetchType.ProcessQueue ){
        /* Unexpected! Clear queue so recovery (manual restart) is possible. */
        this.$fetchQueue.length = 0;
        return this;
      }
      /* Forewarning, this is a bit confusing: when fetching the
         previous lines, we're doing so on behalf of the *next* diff
         chunk (this.pos.next), and vice versa. */
      if(fetchType===this.FetchType.NextUp && !this.pos.next
        || fetchType===this.FetchType.PrevDown && !this.pos.prev){
        console.error("Attempt to fetch diff lines but don't have any.");
        return this;
      }
      this.msg(false,"Fetching diff chunk...");
      const self = this;
      const fOpt = {
        urlParams:{
          name: this.fileHash, from: 0, to: 0
        },
        aftersend: ()=>this.msg(false),
        onload: function(list){
          self.injectResponse(fetchType,up,list);
          if( !self.$fetchQueue || self.$fetchQueue.length==0 ) return;
          /* Keep queue length > 0, or clicks stalled during (unusually lengthy)
             injectResponse() may sneak in as soon as setTimeout() allows, find
             an empty queue, and therefore start over with queue processing. */
          self.$fetchQueue[0] = self.FetchType.ProcessQueue;
          setTimeout(self.fetchChunk.bind(self,self.FetchType.ProcessQueue));
        }
      };
      const up = fOpt.urlParams;
      if(fetchType===this.FetchType.FillGap){
        /* Easiest case: filling a whole gap. */
        up.from = this.pos.startLhs;
Changes to src/fossil.page.chat.js.
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
              mh1 = m.clientHeight;
        D.addClass(old, 'hidden');
        D.removeClass(this.e.inputCurrent, 'hidden');
        const mh2 = m.clientHeight;
        m.scrollTo(0, sTop + (mh1-mh2));
        this.e.inputCurrent.value = old.value;
        old.value = '';
        this.animate(this.e.inputCurrent, "anim-flip-v");
        return this;
      },
      /**
         If passed true or no arguments, switches to multi-line mode
         if currently in single-line mode. If passed false, switches
         to single-line mode if currently in multi-line mode. Returns
         this.







<







216
217
218
219
220
221
222

223
224
225
226
227
228
229
              mh1 = m.clientHeight;
        D.addClass(old, 'hidden');
        D.removeClass(this.e.inputCurrent, 'hidden');
        const mh2 = m.clientHeight;
        m.scrollTo(0, sTop + (mh1-mh2));
        this.e.inputCurrent.value = old.value;
        old.value = '';

        return this;
      },
      /**
         If passed true or no arguments, switches to multi-line mode
         if currently in single-line mode. If passed false, switches
         to single-line mode if currently in multi-line mode. Returns
         this.
1373
1374
1375
1376
1377
1378
1379
1380
1381

1382
1383
1384
1385
1386
1387
1388
1389


1390
1391
1392
1393
1394



1395


1396
1397
1398
1399
1400
1401
1402
        }
      }
    });
    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;
  });

  (function(){/*Set up #chat-settings-button */







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







1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385


1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
        }
      }
    });
    BlobXferState.clear();
    Chat.inputValue("").inputFocus();
  };

  const inputWidgetKeydown = function(ev){
    if(13 === ev.keyCode){
      if(ev.shiftKey){
        ev.preventDefault();
        ev.stopPropagation();
        Chat.e.btnPreview.click();
        return false;


      }else if((Chat.e.inputSingle===ev.target)
               || (ev.ctrlKey && Chat.e.inputMulti===ev.target)){
        /* ^^^ note that it is intended that both ctrl-enter and enter
           work for single-line input mode. */
        ev.preventDefault();
        ev.stopPropagation();
        Chat.submitMessage();
        return false;
      }
    }
  };  
  Chat.e.inputSingle
    .addEventListener('keydown', inputWidgetKeydown, false);
  Chat.e.inputMulti
    .addEventListener('keydown', inputWidgetKeydown, false);
  Chat.e.btnSubmit.addEventListener('click',(e)=>{
    e.preventDefault();
    Chat.submitMessage();
    return false;
  });

  (function(){/*Set up #chat-settings-button */
Changes to src/fossil.page.fileedit.js.
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
        }
      }
    );
    ////////////////////////////////////////////////////////////
    // Trigger preview on Ctrl-Enter. This only works on the built-in
    // editor widget, not a client-provided one.
    P.e.taEditor.addEventListener('keydown',function(ev){
      if(ev.ctrlKey && 13 === ev.keyCode){
        ev.preventDefault();
        ev.stopPropagation();
        P.e.taEditor.blur(/*force change event, if needed*/);
        P.tabs.switchToTab(P.e.tabs.preview);
        if(!P.e.cbAutoPreview.checked){/* If NOT in auto-preview mode, trigger an update. */
          P.preview();
        }
        return false;
      }
    }, false);
    // If we're in the preview tab, have ctrl-enter switch back to the editor.
    document.body.addEventListener('keydown',function(ev){
      if(ev.ctrlKey && 13 === ev.keyCode){
        if(currentTab === P.e.tabs.preview){
          ev.preventDefault();
          ev.stopPropagation();
          P.tabs.switchToTab(P.e.tabs.content);
          P.e.taEditor.focus(/*doesn't work for client-supplied editor widget!
                              And it's slow as molasses for long docs, as focus()
                              forces a document reflow.*/);







|












|







722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
        }
      }
    );
    ////////////////////////////////////////////////////////////
    // Trigger preview on Ctrl-Enter. This only works on the built-in
    // editor widget, not a client-provided one.
    P.e.taEditor.addEventListener('keydown',function(ev){
      if(ev.shiftKey && 13 === ev.keyCode){
        ev.preventDefault();
        ev.stopPropagation();
        P.e.taEditor.blur(/*force change event, if needed*/);
        P.tabs.switchToTab(P.e.tabs.preview);
        if(!P.e.cbAutoPreview.checked){/* If NOT in auto-preview mode, trigger an update. */
          P.preview();
        }
        return false;
      }
    }, false);
    // If we're in the preview tab, have ctrl-enter switch back to the editor.
    document.body.addEventListener('keydown',function(ev){
      if(ev.shiftKey && 13 === ev.keyCode){
        if(currentTab === P.e.tabs.preview){
          ev.preventDefault();
          ev.stopPropagation();
          P.tabs.switchToTab(P.e.tabs.content);
          P.e.taEditor.focus(/*doesn't work for client-supplied editor widget!
                              And it's slow as molasses for long docs, as focus()
                              forces a document reflow.*/);
Changes to src/fossil.page.pikchrshow.js.
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
              P.e.previewModeToggle,
              '\u00a0',
              P.e.previewCopyButton,
              P.e.previewModeLabel,
              P.e.markupAlignWrapper );

    ////////////////////////////////////////////////////////////
    // Trigger preview on Ctrl-Enter.
    P.e.taContent.addEventListener('keydown',function(ev){
      if(ev.ctrlKey && 13 === ev.keyCode){
        ev.preventDefault();
        ev.stopPropagation();
        P.preview();
        return false;
      }
    }, false);








|

|







96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
              P.e.previewModeToggle,
              '\u00a0',
              P.e.previewCopyButton,
              P.e.previewModeLabel,
              P.e.markupAlignWrapper );

    ////////////////////////////////////////////////////////////
    // Trigger preview on Shift-Enter.
    P.e.taContent.addEventListener('keydown',function(ev){
      if(ev.shiftKey && 13 === ev.keyCode){
        ev.preventDefault();
        ev.stopPropagation();
        P.preview();
        return false;
      }
    }, false);

388
389
390
391
392
393
394

395
396
397
398
399
400
401
          this.response.raw;
        console.error("svg parsed HTML nodes:",childs);
      }
      D.append(D.clearElement(preTgt), this.e.taPreviewText);
      break;
    }
    this.e.previewModeLabel.innerText = label;

  };

  /**
     Fetches the preview from the server and updates the preview to
     the rendered SVG content or error report.
  */
  P.preview = function fp(){







>







388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
          this.response.raw;
        console.error("svg parsed HTML nodes:",childs);
      }
      D.append(D.clearElement(preTgt), this.e.taPreviewText);
      break;
    }
    this.e.previewModeLabel.innerText = label;
    this.e.taContent.focus(/*not sure why this gets lost on preview!*/);
  };

  /**
     Fetches the preview from the server and updates the preview to
     the rendered SVG content or error report.
  */
  P.preview = function fp(){
Changes to src/fossil.page.wikiedit.js.
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
        }
      }
    );
    ////////////////////////////////////////////////////////////
    // Trigger preview on Ctrl-Enter. This only works on the built-in
    // editor widget, not a client-provided one.
    P.e.taEditor.addEventListener('keydown',function(ev){
      if(ev.ctrlKey && 13 === ev.keyCode){
        ev.preventDefault();
        ev.stopPropagation();
        P.e.taEditor.blur(/*force change event, if needed*/);
        P.tabs.switchToTab(P.e.tabs.preview);
        if(!P.e.cbAutoPreview.checked){/* If NOT in auto-preview mode, trigger an update. */
          P.preview();
        }
      }
    }, false);
    // If we're in the preview tab, have ctrl-enter switch back to the editor.
    document.body.addEventListener('keydown',function(ev){
      if(ev.ctrlKey && 13 === ev.keyCode){
        if(currentTab === P.e.tabs.preview){
          ev.preventDefault();
          ev.stopPropagation();
          P.tabs.switchToTab(P.e.tabs.content);
          P.e.taEditor.focus(/*doesn't work for client-supplied editor widget!
                              And it's slow as molasses for long docs, as focus()
                              forces a document reflow. */);







|











|







912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
        }
      }
    );
    ////////////////////////////////////////////////////////////
    // Trigger preview on Ctrl-Enter. This only works on the built-in
    // editor widget, not a client-provided one.
    P.e.taEditor.addEventListener('keydown',function(ev){
      if(ev.shiftKey && 13 === ev.keyCode){
        ev.preventDefault();
        ev.stopPropagation();
        P.e.taEditor.blur(/*force change event, if needed*/);
        P.tabs.switchToTab(P.e.tabs.preview);
        if(!P.e.cbAutoPreview.checked){/* If NOT in auto-preview mode, trigger an update. */
          P.preview();
        }
      }
    }, false);
    // If we're in the preview tab, have ctrl-enter switch back to the editor.
    document.body.addEventListener('keydown',function(ev){
      if(ev.shiftKey && 13 === ev.keyCode){
        if(currentTab === P.e.tabs.preview){
          ev.preventDefault();
          ev.stopPropagation();
          P.tabs.switchToTab(P.e.tabs.content);
          P.e.taEditor.focus(/*doesn't work for client-supplied editor widget!
                              And it's slow as molasses for long docs, as focus()
                              forces a document reflow. */);
Changes to src/pikchrshow.c.
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
       "margin-right: 0.5em; vertical-align: middle;"
       "}\n");
    CX("body.pikchrshow .v-align-middle{"
       "vertical-align: middle"
       "}\n");
    CX(".dragover {border: 3px dotted rgba(0,255,0,0.6)}\n");
  } CX("</style>");
  CX("<div>Input pikchr code and tap Preview (or Ctrl-Enter) to render "
     "it:</div>");
  CX("<div id='sbs-wrapper'>"); {
    CX("<div id='pikchrshow-form'>"); {
      CX("<textarea id='content' name='content' rows='15'>"
         "%s</textarea>",zContent/*safe-for-%s*/);
      CX("<div id='pikchrshow-controls'>"); {
        CX("<button id='pikchr-submit-preview'>Preview</button>");







|







333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
       "margin-right: 0.5em; vertical-align: middle;"
       "}\n");
    CX("body.pikchrshow .v-align-middle{"
       "vertical-align: middle"
       "}\n");
    CX(".dragover {border: 3px dotted rgba(0,255,0,0.6)}\n");
  } CX("</style>");
  CX("<div>Input pikchr code and tap Preview (or Shift-Enter) to render "
     "it:</div>");
  CX("<div id='sbs-wrapper'>"); {
    CX("<div id='pikchrshow-form'>"); {
      CX("<textarea id='content' name='content' rows='15'>"
         "%s</textarea>",zContent/*safe-for-%s*/);
      CX("<div id='pikchrshow-controls'>"); {
        CX("<button id='pikchr-submit-preview'>Preview</button>");
Changes to src/style.fileedit.css.
68
69
70
71
72
73
74



75
76
77
78
79
80
81
  padding: 0;
}
body.fileedit #fileedit-tabs {
  margin: 0.5em 0 0 0;
}
body.fileedit #fileedit-tab-preview-wrapper {
  overflow: auto;



}
body.fileedit #fileedit-tab-fileselect > h1 {
  margin: 0;
}
body.fileedit .fileedit-options.commit-message > div {
  display: flex;
  flex-direction: column;







>
>
>







68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
  padding: 0;
}
body.fileedit #fileedit-tabs {
  margin: 0.5em 0 0 0;
}
body.fileedit #fileedit-tab-preview-wrapper {
  overflow: auto;
}
body.fileedit #fileedit-tab-preview-wrapper > pre {
  margin: 0;
}
body.fileedit #fileedit-tab-fileselect > h1 {
  margin: 0;
}
body.fileedit .fileedit-options.commit-message > div {
  display: flex;
  flex-direction: column;
Changes to src/url.c.
194
195
196
197
198
199
200

201
202
203
204
205
206
207
    if( c==':' ){
      pUrlData->port = 0;
      i++;
      while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){
        pUrlData->port = pUrlData->port*10 + c - '0';
        i++;
      }

      pUrlData->hostname = mprintf("%s:%d", pUrlData->name, pUrlData->port);
    }else{
      pUrlData->port = pUrlData->dfltPort;
      pUrlData->hostname = pUrlData->name;
    }
    dehttpize(pUrlData->name);
    pUrlData->path = mprintf("%s", &zUrl[i]);







>







194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
    if( c==':' ){
      pUrlData->port = 0;
      i++;
      while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){
        pUrlData->port = pUrlData->port*10 + c - '0';
        i++;
      }
      if( c!=0 && c!='/' ) fossil_fatal("url missing '/' after port number");
      pUrlData->hostname = mprintf("%s:%d", pUrlData->name, pUrlData->port);
    }else{
      pUrlData->port = pUrlData->dfltPort;
      pUrlData->hostname = pUrlData->name;
    }
    dehttpize(pUrlData->name);
    pUrlData->path = mprintf("%s", &zUrl[i]);
674
675
676
677
678
679
680
681

682
683
684
685
686
687
688
  if( sqlite3_strnicmp(zTail, "www.", 4)==0 && strchr(zTail+4,'.')!=0 ){
    /* Remove the "www." prefix if there are more "." characters later.
    ** But don't remove the "www." prefix if what follows is the suffix.
    ** forum:/forumpost/74e111a2ee */
    zTail += 4;
  }
  if( zTail[0]==0 ) return 0;
  for(i=0; zTail[i] && zTail[i]!='.' && zTail[i]!='?'; i++){}

  if( i==0 ) return 0;
  return mprintf("%.*s", i, zTail);
}

/*
** COMMAND: test-url-basename
** Usage: %fossil test-url-basenames URL ...







|
>







675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
  if( sqlite3_strnicmp(zTail, "www.", 4)==0 && strchr(zTail+4,'.')!=0 ){
    /* Remove the "www." prefix if there are more "." characters later.
    ** But don't remove the "www." prefix if what follows is the suffix.
    ** forum:/forumpost/74e111a2ee */
    zTail += 4;
  }
  if( zTail[0]==0 ) return 0;
  for(i=0; zTail[i] && zTail[i]!='.' && zTail[i]!='?' &&
           zTail[i]!=':' && zTail[i]!='/'; i++){}
  if( i==0 ) return 0;
  return mprintf("%.*s", i, zTail);
}

/*
** COMMAND: test-url-basename
** Usage: %fossil test-url-basenames URL ...
Changes to src/wiki.c.
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
    const int fTechnote = find_option("technote","t",0)!=0;
    const int showIds = find_option("show-technote-ids","s",0)!=0;
    verify_all_options();
    if (fTechnote==0){
      db_prepare(&q, listAllWikiPages/*works-like:""*/);
    }else{
      db_prepare(&q,
        "SELECT datetime(e.mtime), substr(t.tagname,7)"
         " FROM event e, tag t"
        " WHERE e.type='e'"
          " AND e.tagid IS NOT NULL"
          " AND t.tagid=e.tagid"
        " ORDER BY e.mtime DESC /*sort*/"
      );
    }







|







2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
    const int fTechnote = find_option("technote","t",0)!=0;
    const int showIds = find_option("show-technote-ids","s",0)!=0;
    verify_all_options();
    if (fTechnote==0){
      db_prepare(&q, listAllWikiPages/*works-like:""*/);
    }else{
      db_prepare(&q,
        "SELECT datetime(e.mtime), substr(t.tagname,7), e.objid"
         " FROM event e, tag t"
        " WHERE e.type='e'"
          " AND e.tagid IS NOT NULL"
          " AND t.tagid=e.tagid"
        " ORDER BY e.mtime DESC /*sort*/"
      );
    }
Changes to www/changes.wiki.
43
44
45
46
47
48
49





50
51
52
53
54
55
56
     selection of notification sounds via unversioned files].
  *  The [/help?cmd=/chat|/chat] messages now use fossil's full complement of
     markdown features, instead of the prior small subset of markup it
     previously supported. This retroactively applies to all chat messages,
     as they are markdown-processed when they are sent instead of when they
     are saved. Added a preview mode so messages can be previewed before
     being sent. See [./chat.md#usage|the chat docs] for more details.






<h2 id='v2_16'>Changes for Version 2.16 (2021-07-02)</h2>
  *  <b>Security:</b> Fix the client-side TLS so that it verifies that the
     server hostname matches its certificate.
  *  The default "ssh" command on Windows is changed to "ssh" instead of the
     legacy "plink", as ssh is now generally available on Windows systems.
     Installations that still need to use the legacy "plink" can make that







>
>
>
>
>







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
     selection of notification sounds via unversioned files].
  *  The [/help?cmd=/chat|/chat] messages now use fossil's full complement of
     markdown features, instead of the prior small subset of markup it
     previously supported. This retroactively applies to all chat messages,
     as they are markdown-processed when they are sent instead of when they
     are saved. Added a preview mode so messages can be previewed before
     being sent. See [./chat.md#usage|the chat docs] for more details.
  *  The hotkey to activate preview mode in [/help?cmd=/wikiedit|/wikiedit],
     [/help?cmd=/fileedit|/fileedit], and [/help?cmd=/pikchrshow|/pikchrshow]
     was changed from ctrl-enter to shift-enter in order to align with
     [/help?cmd=/chat|/chat]'s new preview feature and related future
     changes.

<h2 id='v2_16'>Changes for Version 2.16 (2021-07-02)</h2>
  *  <b>Security:</b> Fix the client-side TLS so that it verifies that the
     server hostname matches its certificate.
  *  The default "ssh" command on Windows is changed to "ssh" instead of the
     legacy "plink", as ssh is now generally available on Windows systems.
     Installations that still need to use the legacy "plink" can make that