Fossil

Check-in [99b23d0fa3]
Login

Check-in [99b23d0fa3]

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

Overview
Comment:Merged in trunk. /chat changed jump-to-message animation to fade out/in, per requests. Added HTML5 history to /chat clicks on #NNN message references but it's disabled because it's behaving unexpectedly.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | markdown-tagrefs
Files: files | file ages | folders
SHA3-256: 99b23d0fa357acce0f9445f6192284cd8008e8500e1dab0e432f869d55a80a1d
User & Date: stephan 2021-09-28 11:06:22.419
Context
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)
11:06
Merged in trunk. /chat changed jump-to-message animation to fade out/in, per requests. Added HTML5 history to /chat clicks on #NNN message references but it's disabled because it's behaving unexpectedly. ... (check-in: 99b23d0fa3 user: stephan tags: markdown-tagrefs)
09:45
Moved chat.js to fossil.page.chat.js, for consistency with the other single-page apps and to emphasize that it is not to be loaded on arbitrary pages. Changed chat's startup to wait until the page on-load event to avoid a related timing issue. ... (check-in: 3ec8c6c04d user: stephan tags: trunk)
2021-09-25
12:26
Added #NNN and #NNN.NNN references as a special case of hashtag, noting that it will currently match a prefix of #NNN.NNN.NNN. Taught /chat that clicking on such a reference should jump to the referenced message or toast the user that the message is not in the current history. ... (check-in: 4539bf8792 user: stephan tags: markdown-tagrefs)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/chat.c.
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
  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(){
  @ document.body.classList.add('chat')
  @ /*^^^for skins which add their own BODY tag */;
  @ window.fossil.config.chat = {
  @   fromcli: %h(PB("cli")?"true":"false"),
  @   alertSound: "%h(zAlert)",
  @   initSize: %d(db_get_int("chat-initial-history",50)),
  @   imagesInline: !!%d(db_get_boolean("chat-inline-images",1))
  @ };
  ajax_emit_js_preview_modes(0);
  chat_emit_alert_list();
  cgi_append_content(builtin_text("chat.js"),-1);
  @ }, false);
  @ </script>

  style_finish_page();
}

/* Definition of repository tables used by chat
*/
static const char zChatSchema1[] =
@ CREATE TABLE repository.chat(







|









<


|







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
  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(){
  @ document.body.classList.add('chat');
  @ /*^^^for skins which add their own BODY tag */;
  @ window.fossil.config.chat = {
  @   fromcli: %h(PB("cli")?"true":"false"),
  @   alertSound: "%h(zAlert)",
  @   initSize: %d(db_get_int("chat-initial-history",50)),
  @   imagesInline: !!%d(db_get_boolean("chat-inline-images",1))
  @ };
  ajax_emit_js_preview_modes(0);
  chat_emit_alert_list();

  @ }, false);
  @ </script>
  builtin_request_js("fossil.page.chat.js");
  style_finish_page();
}

/* Definition of repository tables used by chat
*/
static const char zChatSchema1[] =
@ CREATE TABLE repository.chat(
Changes to src/fossil.bootstrap.js.
278
279
280
281
282
283
284
285
286

287
288
289
290
291
292
293

294
        console.error(eventName,"event listener threw:",e);
      }
    }
    return this;
  };

  /**
     Sets the innerText of the page's TITLE tag to
     the given text and returns this object.

   */
  F.page.setPageTitle = function(title){
    const t = document.querySelector('title');
    if(t) t.innerText = title;
    return this;
  };


})(window);







|
|
>

|

|


|
>

278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
        console.error(eventName,"event listener threw:",e);
      }
    }
    return this;
  };

  /**
     Sets the innerText of the page's TITLE tag to the given text and
     returns this object. If passed a falsy value then the title is
     reverted to its page-load-time value.
   */
  F.page.setPageTitle = function f(title){
    const t = document.querySelector('title');
    if(t) t.innerText = title || f.$orig;
    return this;
  };
  F.onPageLoad(()=>F.page.setPageTitle.$orig
               = document.querySelector('title').innerText);
})(window);
Changes to src/fossil.diff.js.
115
116
117
118
119
120
121

122
123
124
125
126
127
128

     The goal is to base these controls roughly on github's, a good
     example of which, for use as a model, is:

     https://github.com/msteveb/autosetup/commit/235925e914a52a542
  */
  const ChunkLoadControls = function(tr){

    this.e = {/*DOM elements*/
      tr: tr,
      table: tr.parentElement/*TBODY*/.parentElement
    };
    this.isSplit = this.e.table.classList.contains('splitdiff')/*else udiff*/;
    this.fileHash = this.e.table.dataset.lefthash;
    tr.$chunker = this /* keep GC from reaping this */;







>







115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

     The goal is to base these controls roughly on github's, a good
     example of which, for use as a model, is:

     https://github.com/msteveb/autosetup/commit/235925e914a52a542
  */
  const ChunkLoadControls = function(tr){
    this.$fetchQueue = [];
    this.e = {/*DOM elements*/
      tr: tr,
      table: tr.parentElement/*TBODY*/.parentElement
    };
    this.isSplit = this.e.table.classList.contains('splitdiff')/*else udiff*/;
    this.fileHash = this.e.table.dataset.lefthash;
    tr.$chunker = this /* keep GC from reaping this */;
207
208
209
210
211
212
213
214
215

216
217
218
219
220
221
222
223
224
225
226
227
      /** Append context to the bottom of the previous diff chunk. */
      PrevDown: 1,
      /** Fill a complete gap between the previous/next diff chunks
          or at the start of the next chunk or end of the previous
          chunks. */
      FillGap: 0,
      /** Prepend context to the start of the next diff chunk. */
      NextUp: -1
    },

    config: {
      /*
      glyphUp: '⇡', //'&#uarr;',
      glyphDown: '⇣' //'&#darr;'
      */
    },

    /**
       Creates and returns a button element for fetching a chunk in
       the given fetchType (as documented for fetchChunk()).
    */
    createButton: function(fetchType){







|
<
>
|
<
<
<
<







208
209
210
211
212
213
214
215

216
217




218
219
220
221
222
223
224
      /** Append context to the bottom of the previous diff chunk. */
      PrevDown: 1,
      /** Fill a complete gap between the previous/next diff chunks
          or at the start of the next chunk or end of the previous
          chunks. */
      FillGap: 0,
      /** Prepend context to the start of the next diff chunk. */
      NextUp: -1,

      /** Process the next queued action. */
      ProcessQueue: 0x7fffffff




    },

    /**
       Creates and returns a button element for fetching a chunk in
       the given fetchType (as documented for fetchChunk()).
    */
    createButton: function(fetchType){
260
261
262
263
264
265
266

267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286



287
288
289
290
291
292
293
      }
      return this;
    },

    /* Attempt to clean up resources and remove some circular references to
       that GC can do the right thing. */
    destroy: function(){

      D.remove(this.e.tr);
      delete this.e.tr.$chunker;
      delete this.e.tr;
      delete this.e;
      delete this.pos;
    },

    /**
       If the gap between this.pos.endLhs/startLhs is less than or equal to
       Diff.config.chunkLoadLines then this function replaces any up/down buttons
       with a gap-filler button, else it's a no-op. Returns this object.

       As a special case, do not apply this at the start or bottom
       of the diff, only between two diff chunks.
    */
    maybeReplaceButtons: function(){
      if(this.pos.next && this.pos.prev
         && (this.pos.endLhs - this.pos.startLhs <= Diff.config.chunkLoadLines)){
        D.clearElement(this.e.btnWrapper);
        D.append(this.e.btnWrapper, this.createButton(this.FetchType.FillGap));



      }
      return this;
    },

    /**
       Callack for /jchunk responses.
    */







>




















>
>
>







257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
      }
      return this;
    },

    /* Attempt to clean up resources and remove some circular references to
       that GC can do the right thing. */
    destroy: function(){
      delete this.$fetchQueue;
      D.remove(this.e.tr);
      delete this.e.tr.$chunker;
      delete this.e.tr;
      delete this.e;
      delete this.pos;
    },

    /**
       If the gap between this.pos.endLhs/startLhs is less than or equal to
       Diff.config.chunkLoadLines then this function replaces any up/down buttons
       with a gap-filler button, else it's a no-op. Returns this object.

       As a special case, do not apply this at the start or bottom
       of the diff, only between two diff chunks.
    */
    maybeReplaceButtons: function(){
      if(this.pos.next && this.pos.prev
         && (this.pos.endLhs - this.pos.startLhs <= Diff.config.chunkLoadLines)){
        D.clearElement(this.e.btnWrapper);
        D.append(this.e.btnWrapper, this.createButton(this.FetchType.FillGap));
        if( this.$fetchQueue && this.$fetchQueue.length>0 ){
          this.$fetchQueue = [this.FetchType.FillGap];
        }
      }
      return this;
    },

    /**
       Callack for /jchunk responses.
    */
499
500
501
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
       fetchTypes NextUp and PrevDown.

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











      /* 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(this.$isFetching){
        return this.msg(true,"Cannot load chunk while a load is pending.");
      }
      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 fOpt = {
        urlParams:{
          name: this.fileHash, from: 0, to: 0
        },
        aftersend: ()=>delete this.$isFetching,

        onload: (list)=>this.injectResponse(fetchType,up,list)




      };
      const up = fOpt.urlParams;
      if(fetchType===this.FetchType.FillGap){
        /* Easiest case: filling a whole gap. */
        up.from = this.pos.startLhs;
        up.to = this.pos.endLhs;
      }else if(this.FetchType.PrevDown===fetchType){







>
>
>
>
>
>
>
>
>
>
>



<
<
<






>




|
>
|
>
>
>
>







500
501
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
       fetchTypes NextUp and PrevDown.

       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;
        up.to = this.pos.endLhs;
      }else if(this.FetchType.PrevDown===fetchType){
549
550
551
552
553
554
555
556
557

558


559
560
561
562
563
564
565
        up.to = this.pos.next.startLhs - 1;
        up.from = Math.max(1, up.to - Diff.config.chunkLoadLines + 1);
        if( this.pos.prev && this.pos.prev.endLhs >= up.from ){
          up.from = this.pos.prev.endLhs + 1;
          fetchType = this.FetchType.FillGap;
        }
      }
      this.$isFetching = true;
      //console.debug("fetchChunk(",fetchType,")",up);

      fOpt.onerror = (err)=>this.msg(true,err.message);


      Diff.fetchArtifactChunk(fOpt);
      return this;
    }
  };

  /**
     Adds context-loading buttons to one or more tables. The argument







<

>
|
>
>







564
565
566
567
568
569
570

571
572
573
574
575
576
577
578
579
580
581
582
        up.to = this.pos.next.startLhs - 1;
        up.from = Math.max(1, up.to - Diff.config.chunkLoadLines + 1);
        if( this.pos.prev && this.pos.prev.endLhs >= up.from ){
          up.from = this.pos.prev.endLhs + 1;
          fetchType = this.FetchType.FillGap;
        }
      }

      //console.debug("fetchChunk(",fetchType,")",up);
      fOpt.onerror = function(err){
        self.msg(true,err.message);
        self.$fetchQueue = [];
      };
      Diff.fetchArtifactChunk(fOpt);
      return this;
    }
  };

  /**
     Adds context-loading buttons to one or more tables. The argument
Name change from src/chat.js to src/fossil.page.chat.js.
1
2
3
4
5
6
7
8
9
10
11
12
/**
   This file contains the client-side implementation of fossil's /chat
   application. 
*/
(function(){
  const F = window.fossil, D = F.dom;
  const E1 = function(selector){
    const e = document.querySelector(selector);
    if(!e) throw new Error("missing required DOM element: "+selector);
    return e;
  };
  /**




|







1
2
3
4
5
6
7
8
9
10
11
12
/**
   This file contains the client-side implementation of fossil's /chat
   application. 
*/
window.fossil.onPageLoad(function(){
  const F = window.fossil, D = F.dom;
  const E1 = function(selector){
    const e = document.querySelector(selector);
    if(!e) throw new Error("missing required DOM element: "+selector);
    return e;
  };
  /**
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
        document.querySelector('body > div.header'),
        document.querySelector('body > div.mainmenu'),
        document.querySelector('body > #hbdrop'),
        document.querySelector('body > div.footer')
      ];
      f.contentArea = E1('div.content');
    }
    const bcl = document.body.classList;
    const resized = function(){
      const wh = window.innerHeight,
            com = bcl.contains('chat-only-mode');
      var ht;
      var extra = 0;
      if(com){
        ht = wh;
      }else{
        f.elemsToCount.forEach((e)=>e ? extra += D.effectiveHeight(e) : false);
        ht = wh - extra;







<


|







77
78
79
80
81
82
83

84
85
86
87
88
89
90
91
92
93
        document.querySelector('body > div.header'),
        document.querySelector('body > div.mainmenu'),
        document.querySelector('body > #hbdrop'),
        document.querySelector('body > div.footer')
      ];
      f.contentArea = E1('div.content');
    }

    const resized = function(){
      const wh = window.innerHeight,
            com = document.body.classList.contains('chat-only-mode');
      var ht;
      var extra = 0;
      if(com){
        ht = wh;
      }else{
        f.elemsToCount.forEach((e)=>e ? extra += D.effectiveHeight(e) : false);
        ht = wh - extra;
887
888
889
890
891
892
893



























894
895
896
897
898
899
900
    cs.e.btnClearFilter.addEventListener('click',function(){
      D.addClass(this,'hidden');
      cs.clearFilters();
    }, false);
    return cs;
  })()/*Chat initialization*/;




























  /** To be passed each MessageWidget's top-level DOM element
      after initial processing of the message, to set up
      hashtag references. */
  const setupHashtags = function f(elem){
    if(!f.$click){
      f.$click = function(ev){
        /* Click handler for hashtags */







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







886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
    cs.e.btnClearFilter.addEventListener('click',function(){
      D.addClass(this,'hidden');
      cs.clearFilters();
    }, false);
    return cs;
  })()/*Chat initialization*/;

  /**
     An experiment in history navigation: when a message numref is
     clicked, we push the origin message onto the history and
     set up the back button to return to that message.
  */
  if(0) window.onpopstate = function(event){
    console.debug("onpopstate event",event.state, event);
    if(event.state && event.state.msgId){
      const e = Chat.setCurrentView(Chat.e.viewMessages).
            querySelector('.message-widget[data-msgid="'+event.state.msgId+'"]');
      console.debug("Popping history back to",event.state, e);
      if(e){
        Chat.MessageWidget.scrollToMessageElem(e);
        //F.page.setPageTitle("Fossil Chat #"+event.state.msgId);
        return;
      }
    }
    Chat.scrollMessagesTo(1);
  };

  const findParentWithClass = function(e, className){
    while(e && !e.classList.contains(className)){
      e = e.parentNode;
    }
    return e;
  };
  
  /** To be passed each MessageWidget's top-level DOM element
      after initial processing of the message, to set up
      hashtag references. */
  const setupHashtags = function f(elem){
    if(!f.$click){
      f.$click = function(ev){
        /* Click handler for hashtags */
911
912
913
914
915
916
917








918
919
920
921
922
923
924
        const tag = ev.target.dataset.numtag;
        if(tag){
          const e = Chat.e.viewMessages.querySelector(
            '.message-widget[data-msgid="'+tag+'"]'
          );
          if(e){
            Chat.MessageWidget.scrollToMessageElem(e);








          }else{
            F.toast.warning("Message #"+tag+" not found in loaded messages.");
          }
        }
      };
    }
    elem.querySelectorAll('[data-hashtag]').forEach(function(e){







>
>
>
>
>
>
>
>







937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
        const tag = ev.target.dataset.numtag;
        if(tag){
          const e = Chat.e.viewMessages.querySelector(
            '.message-widget[data-msgid="'+tag+'"]'
          );
          if(e){
            Chat.MessageWidget.scrollToMessageElem(e);
            //Set up window.history() state...
            const p = 1 ? false : findParentWithClass(ev.target, 'message-widget');
            if(p){
              const state = {msgId: p.dataset.msgid};
              console.debug("Pushing history for msgid", state);
              const rc = window.history.pushState(state, "?");
              console.debug("History length =",window.history.length, rc);
            }
          }else{
            F.toast.warning("Message #"+tag+" not found in loaded messages.");
          }
        }
      };
    }
    elem.querySelectorAll('[data-hashtag]').forEach(function(e){
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
    /** Assumes that e is a MessageWidget element, ensures that
        Chat.e.viewMessages is visible, scrolls the message,
        and animates it a bit to make it more visible. */
    cf.scrollToMessageElem = function(e){
      if(e.firstElementChild){
        Chat.setCurrentView(Chat.e.viewMessages);
        e.scrollIntoView(false);
        Chat.animate(e.firstElementChild, 'anim-flip-h');
      }
    };
    return cf;
  })()/*MessageWidget*/;

  const BlobXferState = (function(){/*drag/drop bits...*/
    /* State for paste and drag/drop */







|







1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
    /** Assumes that e is a MessageWidget element, ensures that
        Chat.e.viewMessages is visible, scrolls the message,
        and animates it a bit to make it more visible. */
    cf.scrollToMessageElem = function(e){
      if(e.firstElementChild){
        Chat.setCurrentView(Chat.e.viewMessages);
        e.scrollIntoView(false);
        Chat.animate(e, 'anim-fade-out-in');
      }
    };
    return cf;
  })()/*MessageWidget*/;

  const BlobXferState = (function(){/*drag/drop bits...*/
    /* State for paste and drag/drop */
1788
1789
1790
1791
1792
1793
1794
1795
    document.querySelectorAll('#chat-edit-buttons button').forEach(function(e){
      e.addEventListener('click',flip, false);
    });
  }
  setTimeout( ()=>Chat.inputFocus(), 0 );
  Chat.animate.$disabled = false;
  F.page.chat = Chat/* enables testing the APIs via the dev tools */;
})();







|
1822
1823
1824
1825
1826
1827
1828
1829
    document.querySelectorAll('#chat-edit-buttons button').forEach(function(e){
      e.addEventListener('click',flip, false);
    });
  }
  setTimeout( ()=>Chat.inputFocus(), 0 );
  Chat.animate.$disabled = false;
  F.page.chat = Chat/* enables testing the APIs via the dev tools */;
});
Changes to src/main.mk.
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
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/accordion.js \
  $(SRCDIR)/alerts/bflat2.wav \
  $(SRCDIR)/alerts/bflat3.wav \
  $(SRCDIR)/alerts/bloop.wav \
  $(SRCDIR)/alerts/plunk.wav \
  $(SRCDIR)/chat.js \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/copybtn.js \
  $(SRCDIR)/default.css \
  $(SRCDIR)/diff.js \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/fossil.bootstrap.js \
  $(SRCDIR)/fossil.confirmer.js \
  $(SRCDIR)/fossil.copybutton.js \
  $(SRCDIR)/fossil.diff.js \
  $(SRCDIR)/fossil.dom.js \
  $(SRCDIR)/fossil.fetch.js \
  $(SRCDIR)/fossil.numbered-lines.js \
  $(SRCDIR)/fossil.page.brlist.js \

  $(SRCDIR)/fossil.page.fileedit.js \
  $(SRCDIR)/fossil.page.forumpost.js \
  $(SRCDIR)/fossil.page.pikchrshow.js \
  $(SRCDIR)/fossil.page.whistory.js \
  $(SRCDIR)/fossil.page.wikiedit.js \
  $(SRCDIR)/fossil.pikchr.js \
  $(SRCDIR)/fossil.popupwidget.js \







<














>







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
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/accordion.js \
  $(SRCDIR)/alerts/bflat2.wav \
  $(SRCDIR)/alerts/bflat3.wav \
  $(SRCDIR)/alerts/bloop.wav \
  $(SRCDIR)/alerts/plunk.wav \

  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/copybtn.js \
  $(SRCDIR)/default.css \
  $(SRCDIR)/diff.js \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/fossil.bootstrap.js \
  $(SRCDIR)/fossil.confirmer.js \
  $(SRCDIR)/fossil.copybutton.js \
  $(SRCDIR)/fossil.diff.js \
  $(SRCDIR)/fossil.dom.js \
  $(SRCDIR)/fossil.fetch.js \
  $(SRCDIR)/fossil.numbered-lines.js \
  $(SRCDIR)/fossil.page.brlist.js \
  $(SRCDIR)/fossil.page.chat.js \
  $(SRCDIR)/fossil.page.fileedit.js \
  $(SRCDIR)/fossil.page.forumpost.js \
  $(SRCDIR)/fossil.page.pikchrshow.js \
  $(SRCDIR)/fossil.page.whistory.js \
  $(SRCDIR)/fossil.page.wikiedit.js \
  $(SRCDIR)/fossil.pikchr.js \
  $(SRCDIR)/fossil.popupwidget.js \
Changes to src/style.chat.css.
442
443
444
445
446
447
448








body.chat .anim-fade-out-fast {
  animation: fade-out 300ms linear;
}
@keyframes fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}















>
>
>
>
>
>
>
>
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
body.chat .anim-fade-out-fast {
  animation: fade-out 300ms linear;
}
@keyframes fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

body.chat .anim-fade-out-in {
  animation: fade-out-in 1000ms linear;
}
@keyframes fade-out-in {
  0%,100% { opacity: 0 }
  50% { opacity: 1 }
}
Changes to test/tester.tcl.
303
304
305
306
307
308
309

310
311
312
313
314
315
316
      default-perms \
      diff-binary \
      diff-command \
      dont-push \
      dotfiles \
      editor \
      email-admin \

      email-self \
      email-send-command \
      email-send-db \
      email-send-dir \
      email-send-method \
      email-send-relayhost \
      email-subname \







>







303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
      default-perms \
      diff-binary \
      diff-command \
      dont-push \
      dotfiles \
      editor \
      email-admin \
      email-renew-interval \
      email-self \
      email-send-command \
      email-send-db \
      email-send-dir \
      email-send-method \
      email-send-relayhost \
      email-subname \
329
330
331
332
333
334
335

336
337
338
339
340
341
342
      ignore-glob \
      keep-glob \
      localauth \
      lock-timeout \
      main-branch \
      mainmenu \
      manifest \

      max-loadavg \
      max-upload \
      mimetypes \
      mtime-changes \
      pgp-command \
      preferred-diff-type \
      proxy \







>







330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
      ignore-glob \
      keep-glob \
      localauth \
      lock-timeout \
      main-branch \
      mainmenu \
      manifest \
      max-cache-entry \
      max-loadavg \
      max-upload \
      mimetypes \
      mtime-changes \
      pgp-command \
      preferred-diff-type \
      proxy \
Changes to win/Makefile.mingw.
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642

643
644
645
646
647
648
649
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/accordion.js \
  $(SRCDIR)/alerts/bflat2.wav \
  $(SRCDIR)/alerts/bflat3.wav \
  $(SRCDIR)/alerts/bloop.wav \
  $(SRCDIR)/alerts/plunk.wav \
  $(SRCDIR)/chat.js \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/copybtn.js \
  $(SRCDIR)/default.css \
  $(SRCDIR)/diff.js \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/fossil.bootstrap.js \
  $(SRCDIR)/fossil.confirmer.js \
  $(SRCDIR)/fossil.copybutton.js \
  $(SRCDIR)/fossil.diff.js \
  $(SRCDIR)/fossil.dom.js \
  $(SRCDIR)/fossil.fetch.js \
  $(SRCDIR)/fossil.numbered-lines.js \
  $(SRCDIR)/fossil.page.brlist.js \

  $(SRCDIR)/fossil.page.fileedit.js \
  $(SRCDIR)/fossil.page.forumpost.js \
  $(SRCDIR)/fossil.page.pikchrshow.js \
  $(SRCDIR)/fossil.page.whistory.js \
  $(SRCDIR)/fossil.page.wikiedit.js \
  $(SRCDIR)/fossil.pikchr.js \
  $(SRCDIR)/fossil.popupwidget.js \







<














>







621
622
623
624
625
626
627

628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/accordion.js \
  $(SRCDIR)/alerts/bflat2.wav \
  $(SRCDIR)/alerts/bflat3.wav \
  $(SRCDIR)/alerts/bloop.wav \
  $(SRCDIR)/alerts/plunk.wav \

  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/copybtn.js \
  $(SRCDIR)/default.css \
  $(SRCDIR)/diff.js \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/fossil.bootstrap.js \
  $(SRCDIR)/fossil.confirmer.js \
  $(SRCDIR)/fossil.copybutton.js \
  $(SRCDIR)/fossil.diff.js \
  $(SRCDIR)/fossil.dom.js \
  $(SRCDIR)/fossil.fetch.js \
  $(SRCDIR)/fossil.numbered-lines.js \
  $(SRCDIR)/fossil.page.brlist.js \
  $(SRCDIR)/fossil.page.chat.js \
  $(SRCDIR)/fossil.page.fileedit.js \
  $(SRCDIR)/fossil.page.forumpost.js \
  $(SRCDIR)/fossil.page.pikchrshow.js \
  $(SRCDIR)/fossil.page.whistory.js \
  $(SRCDIR)/fossil.page.wikiedit.js \
  $(SRCDIR)/fossil.pikchr.js \
  $(SRCDIR)/fossil.popupwidget.js \
Changes to win/Makefile.msc.
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584

585
586
587
588
589
590
591
        "$(SRCDIR)\..\skins\xekri\footer.txt" \
        "$(SRCDIR)\..\skins\xekri\header.txt" \
        "$(SRCDIR)\accordion.js" \
        "$(SRCDIR)\alerts\bflat2.wav" \
        "$(SRCDIR)\alerts\bflat3.wav" \
        "$(SRCDIR)\alerts\bloop.wav" \
        "$(SRCDIR)\alerts\plunk.wav" \
        "$(SRCDIR)\chat.js" \
        "$(SRCDIR)\ci_edit.js" \
        "$(SRCDIR)\copybtn.js" \
        "$(SRCDIR)\default.css" \
        "$(SRCDIR)\diff.js" \
        "$(SRCDIR)\diff.tcl" \
        "$(SRCDIR)\forum.js" \
        "$(SRCDIR)\fossil.bootstrap.js" \
        "$(SRCDIR)\fossil.confirmer.js" \
        "$(SRCDIR)\fossil.copybutton.js" \
        "$(SRCDIR)\fossil.diff.js" \
        "$(SRCDIR)\fossil.dom.js" \
        "$(SRCDIR)\fossil.fetch.js" \
        "$(SRCDIR)\fossil.numbered-lines.js" \
        "$(SRCDIR)\fossil.page.brlist.js" \

        "$(SRCDIR)\fossil.page.fileedit.js" \
        "$(SRCDIR)\fossil.page.forumpost.js" \
        "$(SRCDIR)\fossil.page.pikchrshow.js" \
        "$(SRCDIR)\fossil.page.whistory.js" \
        "$(SRCDIR)\fossil.page.wikiedit.js" \
        "$(SRCDIR)\fossil.pikchr.js" \
        "$(SRCDIR)\fossil.popupwidget.js" \







<














>







563
564
565
566
567
568
569

570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
        "$(SRCDIR)\..\skins\xekri\footer.txt" \
        "$(SRCDIR)\..\skins\xekri\header.txt" \
        "$(SRCDIR)\accordion.js" \
        "$(SRCDIR)\alerts\bflat2.wav" \
        "$(SRCDIR)\alerts\bflat3.wav" \
        "$(SRCDIR)\alerts\bloop.wav" \
        "$(SRCDIR)\alerts\plunk.wav" \

        "$(SRCDIR)\ci_edit.js" \
        "$(SRCDIR)\copybtn.js" \
        "$(SRCDIR)\default.css" \
        "$(SRCDIR)\diff.js" \
        "$(SRCDIR)\diff.tcl" \
        "$(SRCDIR)\forum.js" \
        "$(SRCDIR)\fossil.bootstrap.js" \
        "$(SRCDIR)\fossil.confirmer.js" \
        "$(SRCDIR)\fossil.copybutton.js" \
        "$(SRCDIR)\fossil.diff.js" \
        "$(SRCDIR)\fossil.dom.js" \
        "$(SRCDIR)\fossil.fetch.js" \
        "$(SRCDIR)\fossil.numbered-lines.js" \
        "$(SRCDIR)\fossil.page.brlist.js" \
        "$(SRCDIR)\fossil.page.chat.js" \
        "$(SRCDIR)\fossil.page.fileedit.js" \
        "$(SRCDIR)\fossil.page.forumpost.js" \
        "$(SRCDIR)\fossil.page.pikchrshow.js" \
        "$(SRCDIR)\fossil.page.whistory.js" \
        "$(SRCDIR)\fossil.page.wikiedit.js" \
        "$(SRCDIR)\fossil.pikchr.js" \
        "$(SRCDIR)\fossil.popupwidget.js" \
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193

1194
1195
1196
1197
1198
1199
1200
	echo "$(SRCDIR)\../skins/xekri/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/header.txt" >> $@
	echo "$(SRCDIR)\accordion.js" >> $@
	echo "$(SRCDIR)\alerts/bflat2.wav" >> $@
	echo "$(SRCDIR)\alerts/bflat3.wav" >> $@
	echo "$(SRCDIR)\alerts/bloop.wav" >> $@
	echo "$(SRCDIR)\alerts/plunk.wav" >> $@
	echo "$(SRCDIR)\chat.js" >> $@
	echo "$(SRCDIR)\ci_edit.js" >> $@
	echo "$(SRCDIR)\copybtn.js" >> $@
	echo "$(SRCDIR)\default.css" >> $@
	echo "$(SRCDIR)\diff.js" >> $@
	echo "$(SRCDIR)\diff.tcl" >> $@
	echo "$(SRCDIR)\forum.js" >> $@
	echo "$(SRCDIR)\fossil.bootstrap.js" >> $@
	echo "$(SRCDIR)\fossil.confirmer.js" >> $@
	echo "$(SRCDIR)\fossil.copybutton.js" >> $@
	echo "$(SRCDIR)\fossil.diff.js" >> $@
	echo "$(SRCDIR)\fossil.dom.js" >> $@
	echo "$(SRCDIR)\fossil.fetch.js" >> $@
	echo "$(SRCDIR)\fossil.numbered-lines.js" >> $@
	echo "$(SRCDIR)\fossil.page.brlist.js" >> $@

	echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@
	echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@
	echo "$(SRCDIR)\fossil.page.pikchrshow.js" >> $@
	echo "$(SRCDIR)\fossil.page.whistory.js" >> $@
	echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@
	echo "$(SRCDIR)\fossil.pikchr.js" >> $@
	echo "$(SRCDIR)\fossil.popupwidget.js" >> $@







<














>







1172
1173
1174
1175
1176
1177
1178

1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
	echo "$(SRCDIR)\../skins/xekri/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/header.txt" >> $@
	echo "$(SRCDIR)\accordion.js" >> $@
	echo "$(SRCDIR)\alerts/bflat2.wav" >> $@
	echo "$(SRCDIR)\alerts/bflat3.wav" >> $@
	echo "$(SRCDIR)\alerts/bloop.wav" >> $@
	echo "$(SRCDIR)\alerts/plunk.wav" >> $@

	echo "$(SRCDIR)\ci_edit.js" >> $@
	echo "$(SRCDIR)\copybtn.js" >> $@
	echo "$(SRCDIR)\default.css" >> $@
	echo "$(SRCDIR)\diff.js" >> $@
	echo "$(SRCDIR)\diff.tcl" >> $@
	echo "$(SRCDIR)\forum.js" >> $@
	echo "$(SRCDIR)\fossil.bootstrap.js" >> $@
	echo "$(SRCDIR)\fossil.confirmer.js" >> $@
	echo "$(SRCDIR)\fossil.copybutton.js" >> $@
	echo "$(SRCDIR)\fossil.diff.js" >> $@
	echo "$(SRCDIR)\fossil.dom.js" >> $@
	echo "$(SRCDIR)\fossil.fetch.js" >> $@
	echo "$(SRCDIR)\fossil.numbered-lines.js" >> $@
	echo "$(SRCDIR)\fossil.page.brlist.js" >> $@
	echo "$(SRCDIR)\fossil.page.chat.js" >> $@
	echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@
	echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@
	echo "$(SRCDIR)\fossil.page.pikchrshow.js" >> $@
	echo "$(SRCDIR)\fossil.page.whistory.js" >> $@
	echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@
	echo "$(SRCDIR)\fossil.pikchr.js" >> $@
	echo "$(SRCDIR)\fossil.popupwidget.js" >> $@
Changes to www/changes.wiki.
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
     and similar.
  *  Print total payload bytes on a [/help?cmd=sync|fossil sync] when using
     the --verbose option.
  *  Add the <tt>close</tt>, <tt>reopen</tt>, <tt>hide</tt>, and
     </tt>unhide</tt> subcommands to [/help?cmd=branch|the branch command].
  *  The "-p" option to [/help?cmd=branch|fossil branch list] shows only
     private branches.
  *  The [/mdrules|Markdown formatter] now interprets the content of
     block HTML markup (such as &lt;table&gt;) in most cases.  Only content
     of &lt;pre&gt; and &lt;script&gt; is passed through verbatim.
  *  The [/help?cmd=wiki|wiki list command] no longer lists "deleted"
     pages by default. Use the new <tt>--all</tt> option to include deleted
     pages in the output.
  *  The [/help?cmd=all|fossil all git status] command only shows reports for
     the subset of repositories that have a configured Git export.







|







26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
     and similar.
  *  Print total payload bytes on a [/help?cmd=sync|fossil sync] when using
     the --verbose option.
  *  Add the <tt>close</tt>, <tt>reopen</tt>, <tt>hide</tt>, and
     </tt>unhide</tt> subcommands to [/help?cmd=branch|the branch command].
  *  The "-p" option to [/help?cmd=branch|fossil branch list] shows only
     private branches.
  *  The [/md_rules|Markdown formatter] now interprets the content of
     block HTML markup (such as &lt;table&gt;) in most cases.  Only content
     of &lt;pre&gt; and &lt;script&gt; is passed through verbatim.
  *  The [/help?cmd=wiki|wiki list command] no longer lists "deleted"
     pages by default. Use the new <tt>--all</tt> option to include deleted
     pages in the output.
  *  The [/help?cmd=all|fossil all git status] command only shows reports for
     the subset of repositories that have a configured Git export.