Check-in [4c34053c58]
Not logged in

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

Overview
Comment:Moved chat audio notification files to src/alerts, per chatroom discussion. Chat audio is now configurable using a selection of builtin WAV files and audio files stored in /uv/alert-sounds/*.XYZ (==ogg, wav, mp3). The addition of a selection list means that closing the chat settings popup now requires tapping either a popup entry or the settings button - tapping in the page body won't do it because that handling collides with the selection list event handling.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 4c34053c587cb375c0c74c8f34225b7fa64014b0fa67d72c8f3b2165e3ed3631
User & Date: stephan 2021-01-05 05:19:21.844
Context
2021-01-05
05:26
Changed a link from relative to absolute to resolve broken link report from /forumpost/f428a9a9ce. check-in: ca0eabfdae user: stephan tags: trunk
05:19
Moved chat audio notification files to src/alerts, per chatroom discussion. Chat audio is now configurable using a selection of builtin WAV files and audio files stored in /uv/alert-sounds/*.XYZ (==ogg, wav, mp3). The addition of a selection list means that closing the chat settings popup now requires tapping either a popup entry or the settings bu... check-in: 4c34053c58 user: stephan tags: trunk
05:11
Fixed /builtin to be able to deliver binary content. check-in: be93625468 user: stephan tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Name change from src/sounds/b-flat.wav to src/alerts/b-flat.wav.

cannot compute difference between binary files

Name change from src/sounds/g-minor-triad.wav to src/alerts/g-minor-triad.wav.

cannot compute difference between binary files

Name change from src/sounds/mkwav.c to src/alerts/mkwav.c.
Name change from src/sounds/plunk.wav to src/alerts/plunk.wav.

cannot compute difference between binary files

Changes to src/chat.c.
37
38
39
40
41
42
43






























44
45
46
47
48
49
50
** new content arrives.  Newer Web Sockets and Server Sent Event protocols are
** more elegant, but are not compatible with CGI, and would thus complicate
** configuration.  
*/
#include "config.h"
#include <assert.h>
#include "chat.h"































/* Settings that can be used to control chat */
/*
** SETTING: chat-initial-history    width=10 default=50
**
** If this setting has an integer value of N, then when /chat first
** starts up it initializes the screen with the N most recent chat







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







37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
** new content arrives.  Newer Web Sockets and Server Sent Event protocols are
** more elegant, but are not compatible with CGI, and would thus complicate
** configuration.  
*/
#include "config.h"
#include <assert.h>
#include "chat.h"

/*
** Outputs JS code to initialize a list of chat alert audio files for
** use by the chat front-end client. A handful of builtin files
** (from alerts/\*.wav) and all unversioned files matching
** alert-sounds/\*.{mp3,ogg,wav} are included.
*/
static void chat_emit_alert_list(void){
  Stmt q = empty_Stmt;
  unsigned int i;
  const char * azBuiltins[] = {
  "builtin/alerts/plunk.wav",
  "builtin/alerts/b-flat.wav",
  "builtin/alerts/g-minor-triad.wav"
  };
  CX("window.fossil.config.chat.alerts = [\n");
  for(i=0; i < sizeof(azBuiltins)/sizeof(azBuiltins[0]); ++i){
    CX("%s%!j", i ? ", " : "", azBuiltins[i]);
  }
  db_prepare(&q, "SELECT 'uv/'||name FROM unversioned "
             "WHERE content IS NOT NULL "
             "AND (name LIKE 'alert-sounds/%%.wav' "
             "OR name LIKE 'alert-sounds/%%.mp3' "
             "OR name LIKE 'alert-sounds/%%.ogg')");
  while(SQLITE_ROW==db_step(&q)){
    CX(", %!j", db_column_text(&q, 0));
  }
  db_finalize(&q);
  CX("\n].sort();\n");
}

/* Settings that can be used to control chat */
/*
** SETTING: chat-initial-history    width=10 default=50
**
** If this setting has an integer value of N, then when /chat first
** starts up it initializes the screen with the N most recent chat
151
152
153
154
155
156
157

158
159
160
161
162
163
164
  @ document.body.classList.add('chat')
  @ /*^^^for skins which add their own BODY tag */;
  @ window.fossil.config.chat = {
  @   fromcli: %h(PB("cli")?"true":"false"),
  @   initSize: %d(db_get_int("chat-initial-history",50)),
  @   imagesInline: !!%d(db_get_boolean("chat-inline-images",1))
  @ };

  cgi_append_content(builtin_text("chat.js"),-1);
  @ }, false);
  @ </script>

  style_finish_page();
}








>







181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
  @ document.body.classList.add('chat')
  @ /*^^^for skins which add their own BODY tag */;
  @ window.fossil.config.chat = {
  @   fromcli: %h(PB("cli")?"true":"false"),
  @   initSize: %d(db_get_int("chat-initial-history",50)),
  @   imagesInline: !!%d(db_get_boolean("chat-inline-images",1))
  @ };
  chat_emit_alert_list();
  cgi_append_content(builtin_text("chat.js"),-1);
  @ }, false);
  @ </script>

  style_finish_page();
}

Changes to src/chat.js.
371
372
373
374
375
376
377
378
379
380

381
382

383
384
385
386
387












388
389
390
391
392
393
394
          "audible-alert": true
        }
      },
      /** 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(this.settings.getBool('audible-alert',false)){
          try{
            if(!f.audio) f.audio = new Audio(F.rootPath+"chat-alert");

            f.audio.currentTime = 0;
            f.audio.play();

          }catch(e){
            console.error("Audio playblack failed.",e);
          }
        }
        return this;












      }
    };
    cs.e.inputCurrent = cs.e.inputSingle;
    /* Install default settings... */
    Object.keys(cs.settings.defaults).forEach(function(k){
      const v = cs.settings.get(k,cs);
      if(cs===v) cs.settings.set(k,cs.settings.defaults[k]);







|

|
>
|
|
>





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







371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
          "audible-alert": true
        }
      },
      /** 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){
          try{
            if(!f.audio) f.audio = new Audio(F.rootPath+f.uri);
            if(f.audio){
              f.audio.currentTime = 0;
              f.audio.play();
            }
          }catch(e){
            console.error("Audio playblack failed.",e);
          }
        }
        return this;
      },
      /**
         Sets the current new-message audio alert URI (must be a
         repository-relative path which responds with an audio
         file). Pass a falsy value to disable audio alerts. Returns
         this. This setting is persistent.
      */
      setNewMessageSound: function f(uri){
        delete this.playNewMessageSound.audio;
        this.playNewMessageSound.uri = uri;
        this.settings.set('audible-alert', uri || '');
        return this;
      }
    };
    cs.e.inputCurrent = cs.e.inputSingle;
    /* Install default settings... */
    Object.keys(cs.settings.defaults).forEach(function(k){
      const v = cs.settings.get(k,cs);
      if(cs===v) cs.settings.set(k,cs.settings.defaults[k]);
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948







949




950
951


952
953
954



955
956
957








958
959
960
961
962
963
964
    },{
      label: "Left-align my posts",
      boolValue: ()=>!document.body.classList.contains('my-messages-right'),
      callback: function f(){
        document.body.classList.toggle('my-messages-right');
      }
    },{
      label: "Message home/end buttons",
      boolValue: ()=>!Chat.e.btnMsgHome.classList.contains('hidden'),
      callback: ()=>Chat.toggleNavButtons()
    },{
      label: "Images inline",
      boolValue: ()=>Chat.settings.getBool('images-inline'),
      callback: function(){
        const v = Chat.settings.toggle('images-inline');
        F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
      }
    },{







      label: "Audible alerts",




      boolValue: ()=>Chat.settings.getBool('audible-alert'),
      callback: function(){


        const v = Chat.settings.toggle('audible-alert');
        if(v) setTimeout(()=>Chat.playNewMessageSound(), 50);
        F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");



      }
    }];









    /**
       Rebuild the menu each time it's shown so that the toggles can
       show their current values.
    */
    settingsPopup.options.refresh = function(){
      D.clearElement(this.e);
      settingsOps.forEach(function(op){







<
<
<
<







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

<
|
>
>
>
>
>
>
>
>







945
946
947
948
949
950
951




952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971

972
973
974
975

976
977
978
979

980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
    },{
      label: "Left-align my posts",
      boolValue: ()=>!document.body.classList.contains('my-messages-right'),
      callback: function f(){
        document.body.classList.toggle('my-messages-right');
      }
    },{




      label: "Images inline",
      boolValue: ()=>Chat.settings.getBool('images-inline'),
      callback: function(){
        const v = Chat.settings.toggle('images-inline');
        F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
      }
    },{
      label: "Message home/end buttons",
      boolValue: ()=>!Chat.e.btnMsgHome.classList.contains('hidden'),
      callback: ()=>Chat.toggleNavButtons()
    }];

    /** Set up selection list of notification sounds. */
    const selectSound = D.addClass(D.select(), 'menu-entry');
    D.disable(D.option(selectSound, "0", "Audible alert..."));
    D.option(selectSound, "", "(no audio)");
    F.config.chat.alerts.forEach(function(a){
      D.option(selectSound, a);
    });
    if(true===Chat.settings.getBool('audible-alert')){

      selectSound.selectedIndex = 2/*first audio file in the list*/;
    }else{
      selectSound.value = Chat.settings.get('audible-alert','');
      if(selectSound.selectedIndex<0){

        /*Missing file - removed after this setting was applied. Fall back
          to the first sound in the list. */
        selectSound.selectedIndex = 2;
      }

    }
    selectSound.addEventListener('change',function(){
      const v = this.selectedIndex>1 ? this.value : '';
      Chat.setNewMessageSound(v);
      F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
      if(v) setTimeout(()=>Chat.playNewMessageSound(), 50);
      settingsPopup.hide();
    }, false);
    Chat.setNewMessageSound(selectSound.value);
    /**
       Rebuild the menu each time it's shown so that the toggles can
       show their current values.
    */
    settingsPopup.options.refresh = function(){
      D.clearElement(this.e);
      settingsOps.forEach(function(op){
976
977
978
979
980
981
982

983
984
985
986
987
988
989
990
991







992
993
994
995
996
997
998
          const check = D.attr(D.checkbox(1, op.boolValue()),
                                          'aria-label', op.label);
          D.append(line, check);
        }
        D.append(settingsPopup.e, line);
        line.addEventListener('click', callback);
      });

    };
    settingsPopup.installHideHandlers(false, true, true)
    /** Reminder: click-to-hide interferes with "?" embedded within
        the popup, so cannot be used together with those. Enabling
        this means, however, that tapping the menu button to toggle
        the menu cannot work because tapping the menu button while the
        menu is opened will, because of the click-to-hide handler,
        hide the menu before the button gets an event saying to toggle
        it.*/;







    D.attr(settingsButton, 'role', 'button');
    settingsButton.addEventListener('click',function(ev){
      //ev.preventDefault();
      if(settingsPopup.isShown()) settingsPopup.hide();
      else settingsPopup.show(settingsButton);
      /* Reminder: we cannot toggle the visibility from her
       */







>

|






|
>
>
>
>
>
>
>







1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
          const check = D.attr(D.checkbox(1, op.boolValue()),
                                          'aria-label', op.label);
          D.append(line, check);
        }
        D.append(settingsPopup.e, line);
        line.addEventListener('click', callback);
      });
      D.append(settingsPopup.e, selectSound);
    };
    settingsPopup.installHideHandlers(false, false, true)
    /** Reminder: click-to-hide interferes with "?" embedded within
        the popup, so cannot be used together with those. Enabling
        this means, however, that tapping the menu button to toggle
        the menu cannot work because tapping the menu button while the
        menu is opened will, because of the click-to-hide handler,
        hide the menu before the button gets an event saying to toggle
        it.

        Reminder: because we need a SELECT element for the audio file
        selection (since that list can be arbitrarily long), we have
        to disable tap-outside-the-popup-to-close-it via passing false
        as the 2nd argument to installHideHandlers(). If we don't,
        tapping on the select element is unreliable on desktop
        browsers and doesn't seem to work at all on mobile. */;
    D.attr(settingsButton, 'role', 'button');
    settingsButton.addEventListener('click',function(ev){
      //ev.preventDefault();
      if(settingsPopup.isShown()) settingsPopup.hide();
      else settingsPopup.show(settingsButton);
      /* Reminder: we cannot toggle the visibility from her
       */
Changes to src/default.css.
1638
1639
1640
1641
1642
1643
1644












1645
1646
1647
1648
1649
1650
1651
  margin: 0.25em 0.2em;
  padding: 0.25em;
  flex: 1 1 auto/*eliminates dead no-click zones on the right*/;
}
body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
  cursor: inherit;
}












/** Container for the list of /chat messages. */
body.chat #chat-messages-wrapper {
  overflow: auto;
  /*max-height: 800em*//*will be re-calc'd in JS*/;
  flex: 2 1 auto;
}
body.chat #chat-messages-wrapper.loading > * {







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







1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
  margin: 0.25em 0.2em;
  padding: 0.25em;
  flex: 1 1 auto/*eliminates dead no-click zones on the right*/;
}
body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
  cursor: inherit;
}
body.chat .chat-settings-popup > select.menu-entry {
  flex: 1 1 auto;
  padding: 0;
  cursor: pointer;
  min-height: 2.5em;
  border-radius: 0.25em;
}
body.chat .chat-settings-popup > select.menu-entry > option {
  /*Recall that many browsers don't allow styling of OPTION
    elements, or allow only very limited styling.*/
}

/** Container for the list of /chat messages. */
body.chat #chat-messages-wrapper {
  overflow: auto;
  /*max-height: 800em*//*will be re-calc'd in JS*/;
  flex: 2 1 auto;
}
body.chat #chat-messages-wrapper.loading > * {
Changes to src/main.mk.
218
219
220
221
222
223
224



225
226
227
228
229
230
231
  $(SRCDIR)/../skins/rounded1/footer.txt \
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/accordion.js \



  $(SRCDIR)/chat.js \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/copybtn.js \
  $(SRCDIR)/default.css \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/fossil.bootstrap.js \







>
>
>







218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
  $(SRCDIR)/../skins/rounded1/footer.txt \
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/accordion.js \
  $(SRCDIR)/alerts/b-flat.wav \
  $(SRCDIR)/alerts/g-minor-triad.wav \
  $(SRCDIR)/alerts/plunk.wav \
  $(SRCDIR)/chat.js \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/copybtn.js \
  $(SRCDIR)/default.css \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/fossil.bootstrap.js \
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
  $(SRCDIR)/sounds/9.wav \
  $(SRCDIR)/sounds/a.wav \
  $(SRCDIR)/sounds/b.wav \
  $(SRCDIR)/sounds/c.wav \
  $(SRCDIR)/sounds/d.wav \
  $(SRCDIR)/sounds/e.wav \
  $(SRCDIR)/sounds/f.wav \
  $(SRCDIR)/sounds/plunk.wav \
  $(SRCDIR)/style.admin_log.css \
  $(SRCDIR)/style.fileedit.css \
  $(SRCDIR)/style.wikiedit.css \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki








<







267
268
269
270
271
272
273

274
275
276
277
278
279
280
  $(SRCDIR)/sounds/9.wav \
  $(SRCDIR)/sounds/a.wav \
  $(SRCDIR)/sounds/b.wav \
  $(SRCDIR)/sounds/c.wav \
  $(SRCDIR)/sounds/d.wav \
  $(SRCDIR)/sounds/e.wav \
  $(SRCDIR)/sounds/f.wav \

  $(SRCDIR)/style.admin_log.css \
  $(SRCDIR)/style.fileedit.css \
  $(SRCDIR)/style.wikiedit.css \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

Changes to src/makemake.tcl.
183
184
185
186
187
188
189

190
191
192
193
194
195
196
  markdown.md
  wiki.wiki
  *.js
  default.css
  style.*.css
  ../skins/*/*.txt
  sounds/*.wav

}

# Options used to compile the included SQLite library.
#
set SQLITE_OPTIONS {
  -DNDEBUG=1
  -DSQLITE_DQS=0







>







183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
  markdown.md
  wiki.wiki
  *.js
  default.css
  style.*.css
  ../skins/*/*.txt
  sounds/*.wav
  alerts/*.wav
}

# Options used to compile the included SQLite library.
#
set SQLITE_OPTIONS {
  -DNDEBUG=1
  -DSQLITE_DQS=0
Changes to win/Makefile.mingw.
630
631
632
633
634
635
636



637
638
639
640
641
642
643
  $(SRCDIR)/../skins/rounded1/footer.txt \
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/accordion.js \



  $(SRCDIR)/chat.js \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/copybtn.js \
  $(SRCDIR)/default.css \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/fossil.bootstrap.js \







>
>
>







630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
  $(SRCDIR)/../skins/rounded1/footer.txt \
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/accordion.js \
  $(SRCDIR)/alerts/b-flat.wav \
  $(SRCDIR)/alerts/g-minor-triad.wav \
  $(SRCDIR)/alerts/plunk.wav \
  $(SRCDIR)/chat.js \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/copybtn.js \
  $(SRCDIR)/default.css \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/fossil.bootstrap.js \
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
  $(SRCDIR)/sounds/9.wav \
  $(SRCDIR)/sounds/a.wav \
  $(SRCDIR)/sounds/b.wav \
  $(SRCDIR)/sounds/c.wav \
  $(SRCDIR)/sounds/d.wav \
  $(SRCDIR)/sounds/e.wav \
  $(SRCDIR)/sounds/f.wav \
  $(SRCDIR)/sounds/plunk.wav \
  $(SRCDIR)/style.admin_log.css \
  $(SRCDIR)/style.fileedit.css \
  $(SRCDIR)/style.wikiedit.css \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki








<







679
680
681
682
683
684
685

686
687
688
689
690
691
692
  $(SRCDIR)/sounds/9.wav \
  $(SRCDIR)/sounds/a.wav \
  $(SRCDIR)/sounds/b.wav \
  $(SRCDIR)/sounds/c.wav \
  $(SRCDIR)/sounds/d.wav \
  $(SRCDIR)/sounds/e.wav \
  $(SRCDIR)/sounds/f.wav \

  $(SRCDIR)/style.admin_log.css \
  $(SRCDIR)/style.fileedit.css \
  $(SRCDIR)/style.wikiedit.css \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

Changes to win/Makefile.msc.
551
552
553
554
555
556
557



558
559
560
561
562
563
564
        "$(SRCDIR)\..\skins\rounded1\footer.txt" \
        "$(SRCDIR)\..\skins\rounded1\header.txt" \
        "$(SRCDIR)\..\skins\xekri\css.txt" \
        "$(SRCDIR)\..\skins\xekri\details.txt" \
        "$(SRCDIR)\..\skins\xekri\footer.txt" \
        "$(SRCDIR)\..\skins\xekri\header.txt" \
        "$(SRCDIR)\accordion.js" \



        "$(SRCDIR)\chat.js" \
        "$(SRCDIR)\ci_edit.js" \
        "$(SRCDIR)\copybtn.js" \
        "$(SRCDIR)\default.css" \
        "$(SRCDIR)\diff.tcl" \
        "$(SRCDIR)\forum.js" \
        "$(SRCDIR)\fossil.bootstrap.js" \







>
>
>







551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
        "$(SRCDIR)\..\skins\rounded1\footer.txt" \
        "$(SRCDIR)\..\skins\rounded1\header.txt" \
        "$(SRCDIR)\..\skins\xekri\css.txt" \
        "$(SRCDIR)\..\skins\xekri\details.txt" \
        "$(SRCDIR)\..\skins\xekri\footer.txt" \
        "$(SRCDIR)\..\skins\xekri\header.txt" \
        "$(SRCDIR)\accordion.js" \
        "$(SRCDIR)\alerts\b-flat.wav" \
        "$(SRCDIR)\alerts\g-minor-triad.wav" \
        "$(SRCDIR)\alerts\plunk.wav" \
        "$(SRCDIR)\chat.js" \
        "$(SRCDIR)\ci_edit.js" \
        "$(SRCDIR)\copybtn.js" \
        "$(SRCDIR)\default.css" \
        "$(SRCDIR)\diff.tcl" \
        "$(SRCDIR)\forum.js" \
        "$(SRCDIR)\fossil.bootstrap.js" \
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
        "$(SRCDIR)\sounds\9.wav" \
        "$(SRCDIR)\sounds\a.wav" \
        "$(SRCDIR)\sounds\b.wav" \
        "$(SRCDIR)\sounds\c.wav" \
        "$(SRCDIR)\sounds\d.wav" \
        "$(SRCDIR)\sounds\e.wav" \
        "$(SRCDIR)\sounds\f.wav" \
        "$(SRCDIR)\sounds\plunk.wav" \
        "$(SRCDIR)\style.admin_log.css" \
        "$(SRCDIR)\style.fileedit.css" \
        "$(SRCDIR)\style.wikiedit.css" \
        "$(SRCDIR)\tree.js" \
        "$(SRCDIR)\useredit.js" \
        "$(SRCDIR)\wiki.wiki"








<







600
601
602
603
604
605
606

607
608
609
610
611
612
613
        "$(SRCDIR)\sounds\9.wav" \
        "$(SRCDIR)\sounds\a.wav" \
        "$(SRCDIR)\sounds\b.wav" \
        "$(SRCDIR)\sounds\c.wav" \
        "$(SRCDIR)\sounds\d.wav" \
        "$(SRCDIR)\sounds\e.wav" \
        "$(SRCDIR)\sounds\f.wav" \

        "$(SRCDIR)\style.admin_log.css" \
        "$(SRCDIR)\style.fileedit.css" \
        "$(SRCDIR)\style.wikiedit.css" \
        "$(SRCDIR)\tree.js" \
        "$(SRCDIR)\useredit.js" \
        "$(SRCDIR)\wiki.wiki"

1161
1162
1163
1164
1165
1166
1167



1168
1169
1170
1171
1172
1173
1174
	echo "$(SRCDIR)\../skins/rounded1/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/rounded1/header.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/css.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/details.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/header.txt" >> $@
	echo "$(SRCDIR)\accordion.js" >> $@



	echo "$(SRCDIR)\chat.js" >> $@
	echo "$(SRCDIR)\ci_edit.js" >> $@
	echo "$(SRCDIR)\copybtn.js" >> $@
	echo "$(SRCDIR)\default.css" >> $@
	echo "$(SRCDIR)\diff.tcl" >> $@
	echo "$(SRCDIR)\forum.js" >> $@
	echo "$(SRCDIR)\fossil.bootstrap.js" >> $@







>
>
>







1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
	echo "$(SRCDIR)\../skins/rounded1/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/rounded1/header.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/css.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/details.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/header.txt" >> $@
	echo "$(SRCDIR)\accordion.js" >> $@
	echo "$(SRCDIR)\alerts/b-flat.wav" >> $@
	echo "$(SRCDIR)\alerts/g-minor-triad.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.tcl" >> $@
	echo "$(SRCDIR)\forum.js" >> $@
	echo "$(SRCDIR)\fossil.bootstrap.js" >> $@
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
	echo "$(SRCDIR)\sounds/9.wav" >> $@
	echo "$(SRCDIR)\sounds/a.wav" >> $@
	echo "$(SRCDIR)\sounds/b.wav" >> $@
	echo "$(SRCDIR)\sounds/c.wav" >> $@
	echo "$(SRCDIR)\sounds/d.wav" >> $@
	echo "$(SRCDIR)\sounds/e.wav" >> $@
	echo "$(SRCDIR)\sounds/f.wav" >> $@
	echo "$(SRCDIR)\sounds/plunk.wav" >> $@
	echo "$(SRCDIR)\style.admin_log.css" >> $@
	echo "$(SRCDIR)\style.fileedit.css" >> $@
	echo "$(SRCDIR)\style.wikiedit.css" >> $@
	echo "$(SRCDIR)\tree.js" >> $@
	echo "$(SRCDIR)\useredit.js" >> $@
	echo "$(SRCDIR)\wiki.wiki" >> $@








<







1212
1213
1214
1215
1216
1217
1218

1219
1220
1221
1222
1223
1224
1225
	echo "$(SRCDIR)\sounds/9.wav" >> $@
	echo "$(SRCDIR)\sounds/a.wav" >> $@
	echo "$(SRCDIR)\sounds/b.wav" >> $@
	echo "$(SRCDIR)\sounds/c.wav" >> $@
	echo "$(SRCDIR)\sounds/d.wav" >> $@
	echo "$(SRCDIR)\sounds/e.wav" >> $@
	echo "$(SRCDIR)\sounds/f.wav" >> $@

	echo "$(SRCDIR)\style.admin_log.css" >> $@
	echo "$(SRCDIR)\style.fileedit.css" >> $@
	echo "$(SRCDIR)\style.wikiedit.css" >> $@
	echo "$(SRCDIR)\tree.js" >> $@
	echo "$(SRCDIR)\useredit.js" >> $@
	echo "$(SRCDIR)\wiki.wiki" >> $@

Changes to www/chat.md.
97
98
99
100
101
102
103
























104
105
106
107
108
109
110
at the top of the message and clicking the button which appears. Such
deletions are local-only and the messages will reappear if the page
page is reloaded. Admin users may additionally choose to globally
delete a message from the chat record, which deletes it not only from
their own browser but also propagates the removal to all connected
clients the next time they poll for new messages.

























## Implementation Details

*You do not need to understand how Fossil chat works in order to use it.
But many developers prefer to know how their tools work.
This section is provided for the benefit of those curious developers.*

The [/chat](/help?cmd=/chat) webpage downloads a small amount of HTML







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







97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
at the top of the message and clicking the button which appears. Such
deletions are local-only and the messages will reappear if the page
page is reloaded. Admin users may additionally choose to globally
delete a message from the chat record, which deletes it not only from
their own browser but also propagates the removal to all connected
clients the next time they poll for new messages.

### Audible Notifications

On platforms which support it, chat can optionally play an audio file
when a new message arrives from any user other than oneself. The sound
can be selected or disabled via the settings menu. The list of sounds
includes a small selection of sounds built in to the fossil binary and
new sound files may be added to a repository as unversioned content:
when the chat page is loaded, it includes a list of all unversioned
files named `alert-sounds/*.XYZ`, where `XYZ` is one of (`wav`, `mp3`,
`ogg`) case-insensitive.

For example, a Unix-style shell command like the following would
install all WAV files in a local directory to the list:

```
for i in *.wav; do fossil uv add "$i" --as "alert-sounds/$i"; done
```

The list of sound files is sorted client-side for display in the
selection list. The user's selection of audio file is persistent in
a given browser, but if the file is subsequently removed from the
server then the next time the chat page is reloaded the sound will
fall back to the first one in the selection list.

## Implementation Details

*You do not need to understand how Fossil chat works in order to use it.
But many developers prefer to know how their tools work.
This section is provided for the benefit of those curious developers.*

The [/chat](/help?cmd=/chat) webpage downloads a small amount of HTML