Fossil SCM

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.

stephan 2021-01-05 05:19 trunk
Commit 4c34053c587cb375c0c74c8f34225b7fa64014b0fa67d72c8f3b2165e3ed3631

Binary file

No diff available

Binary file

+31
--- src/chat.c
+++ src/chat.c
@@ -39,10 +39,40 @@
3939
** configuration.
4040
*/
4141
#include "config.h"
4242
#include <assert.h>
4343
#include "chat.h"
44
+
45
+/*
46
+** Outputs JS code to initialize a list of chat alert audio files for
47
+** use by the chat front-end client. A handful of builtin files
48
+** (from alerts/\*.wav) and all unversioned files matching
49
+** alert-sounds/\*.{mp3,ogg,wav} are included.
50
+*/
51
+static void chat_emit_alert_list(void){
52
+ Stmt q = empty_Stmt;
53
+ unsigned int i;
54
+ const char * azBuiltins[] = {
55
+ "builtin/alerts/plunk.wav",
56
+ "builtin/alerts/b-flat.wav",
57
+ "builtin/alerts/g-minor-triad.wav"
58
+ };
59
+ CX("window.fossil.config.chat.alerts = [\n");
60
+ for(i=0; i < sizeof(azBuiltins)/sizeof(azBuiltins[0]); ++i){
61
+ CX("%s%!j", i ? ", " : "", azBuiltins[i]);
62
+ }
63
+ db_prepare(&q, "SELECT 'uv/'||name FROM unversioned "
64
+ "WHERE content IS NOT NULL "
65
+ "AND (name LIKE 'alert-sounds/%%.wav' "
66
+ "OR name LIKE 'alert-sounds/%%.mp3' "
67
+ "OR name LIKE 'alert-sounds/%%.ogg')");
68
+ while(SQLITE_ROW==db_step(&q)){
69
+ CX(", %!j", db_column_text(&q, 0));
70
+ }
71
+ db_finalize(&q);
72
+ CX("\n].sort();\n");
73
+}
4474
4575
/* Settings that can be used to control chat */
4676
/*
4777
** SETTING: chat-initial-history width=10 default=50
4878
**
@@ -153,10 +183,11 @@
153183
@ window.fossil.config.chat = {
154184
@ fromcli: %h(PB("cli")?"true":"false"),
155185
@ initSize: %d(db_get_int("chat-initial-history",50)),
156186
@ imagesInline: !!%d(db_get_boolean("chat-inline-images",1))
157187
@ };
188
+ chat_emit_alert_list();
158189
cgi_append_content(builtin_text("chat.js"),-1);
159190
@ }, false);
160191
@ </script>
161192
162193
style_finish_page();
163194
--- src/chat.c
+++ src/chat.c
@@ -39,10 +39,40 @@
39 ** configuration.
40 */
41 #include "config.h"
42 #include <assert.h>
43 #include "chat.h"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
45 /* Settings that can be used to control chat */
46 /*
47 ** SETTING: chat-initial-history width=10 default=50
48 **
@@ -153,10 +183,11 @@
153 @ window.fossil.config.chat = {
154 @ fromcli: %h(PB("cli")?"true":"false"),
155 @ initSize: %d(db_get_int("chat-initial-history",50)),
156 @ imagesInline: !!%d(db_get_boolean("chat-inline-images",1))
157 @ };
 
158 cgi_append_content(builtin_text("chat.js"),-1);
159 @ }, false);
160 @ </script>
161
162 style_finish_page();
163
--- src/chat.c
+++ src/chat.c
@@ -39,10 +39,40 @@
39 ** configuration.
40 */
41 #include "config.h"
42 #include <assert.h>
43 #include "chat.h"
44
45 /*
46 ** Outputs JS code to initialize a list of chat alert audio files for
47 ** use by the chat front-end client. A handful of builtin files
48 ** (from alerts/\*.wav) and all unversioned files matching
49 ** alert-sounds/\*.{mp3,ogg,wav} are included.
50 */
51 static void chat_emit_alert_list(void){
52 Stmt q = empty_Stmt;
53 unsigned int i;
54 const char * azBuiltins[] = {
55 "builtin/alerts/plunk.wav",
56 "builtin/alerts/b-flat.wav",
57 "builtin/alerts/g-minor-triad.wav"
58 };
59 CX("window.fossil.config.chat.alerts = [\n");
60 for(i=0; i < sizeof(azBuiltins)/sizeof(azBuiltins[0]); ++i){
61 CX("%s%!j", i ? ", " : "", azBuiltins[i]);
62 }
63 db_prepare(&q, "SELECT 'uv/'||name FROM unversioned "
64 "WHERE content IS NOT NULL "
65 "AND (name LIKE 'alert-sounds/%%.wav' "
66 "OR name LIKE 'alert-sounds/%%.mp3' "
67 "OR name LIKE 'alert-sounds/%%.ogg')");
68 while(SQLITE_ROW==db_step(&q)){
69 CX(", %!j", db_column_text(&q, 0));
70 }
71 db_finalize(&q);
72 CX("\n].sort();\n");
73 }
74
75 /* Settings that can be used to control chat */
76 /*
77 ** SETTING: chat-initial-history width=10 default=50
78 **
@@ -153,10 +183,11 @@
183 @ window.fossil.config.chat = {
184 @ fromcli: %h(PB("cli")?"true":"false"),
185 @ initSize: %d(db_get_int("chat-initial-history",50)),
186 @ imagesInline: !!%d(db_get_boolean("chat-inline-images",1))
187 @ };
188 chat_emit_alert_list();
189 cgi_append_content(builtin_text("chat.js"),-1);
190 @ }, false);
191 @ </script>
192
193 style_finish_page();
194
+56 -17
--- src/chat.js
+++ src/chat.js
@@ -373,20 +373,34 @@
373373
},
374374
/** Plays a new-message notification sound IF the audible-alert
375375
setting is true, else this is a no-op. Returns this.
376376
*/
377377
playNewMessageSound: function f(){
378
- if(this.settings.getBool('audible-alert',false)){
378
+ if(f.uri){
379379
try{
380
- if(!f.audio) f.audio = new Audio(F.rootPath+"chat-alert");
381
- f.audio.currentTime = 0;
382
- f.audio.play();
380
+ if(!f.audio) f.audio = new Audio(F.rootPath+f.uri);
381
+ if(f.audio){
382
+ f.audio.currentTime = 0;
383
+ f.audio.play();
384
+ }
383385
}catch(e){
384386
console.error("Audio playblack failed.",e);
385387
}
386388
}
387389
return this;
390
+ },
391
+ /**
392
+ Sets the current new-message audio alert URI (must be a
393
+ repository-relative path which responds with an audio
394
+ file). Pass a falsy value to disable audio alerts. Returns
395
+ this. This setting is persistent.
396
+ */
397
+ setNewMessageSound: function f(uri){
398
+ delete this.playNewMessageSound.audio;
399
+ this.playNewMessageSound.uri = uri;
400
+ this.settings.set('audible-alert', uri || '');
401
+ return this;
388402
}
389403
};
390404
cs.e.inputCurrent = cs.e.inputSingle;
391405
/* Install default settings... */
392406
Object.keys(cs.settings.defaults).forEach(function(k){
@@ -933,30 +947,47 @@
933947
boolValue: ()=>!document.body.classList.contains('my-messages-right'),
934948
callback: function f(){
935949
document.body.classList.toggle('my-messages-right');
936950
}
937951
},{
938
- label: "Message home/end buttons",
939
- boolValue: ()=>!Chat.e.btnMsgHome.classList.contains('hidden'),
940
- callback: ()=>Chat.toggleNavButtons()
941
- },{
942952
label: "Images inline",
943953
boolValue: ()=>Chat.settings.getBool('images-inline'),
944954
callback: function(){
945955
const v = Chat.settings.toggle('images-inline');
946956
F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
947957
}
948958
},{
949
- label: "Audible alerts",
950
- boolValue: ()=>Chat.settings.getBool('audible-alert'),
951
- callback: function(){
952
- const v = Chat.settings.toggle('audible-alert');
953
- if(v) setTimeout(()=>Chat.playNewMessageSound(), 50);
954
- F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
955
- }
959
+ label: "Message home/end buttons",
960
+ boolValue: ()=>!Chat.e.btnMsgHome.classList.contains('hidden'),
961
+ callback: ()=>Chat.toggleNavButtons()
956962
}];
957963
964
+ /** Set up selection list of notification sounds. */
965
+ const selectSound = D.addClass(D.select(), 'menu-entry');
966
+ D.disable(D.option(selectSound, "0", "Audible alert..."));
967
+ D.option(selectSound, "", "(no audio)");
968
+ F.config.chat.alerts.forEach(function(a){
969
+ D.option(selectSound, a);
970
+ });
971
+ if(true===Chat.settings.getBool('audible-alert')){
972
+ selectSound.selectedIndex = 2/*first audio file in the list*/;
973
+ }else{
974
+ selectSound.value = Chat.settings.get('audible-alert','');
975
+ if(selectSound.selectedIndex<0){
976
+ /*Missing file - removed after this setting was applied. Fall back
977
+ to the first sound in the list. */
978
+ selectSound.selectedIndex = 2;
979
+ }
980
+ }
981
+ selectSound.addEventListener('change',function(){
982
+ const v = this.selectedIndex>1 ? this.value : '';
983
+ Chat.setNewMessageSound(v);
984
+ F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
985
+ if(v) setTimeout(()=>Chat.playNewMessageSound(), 50);
986
+ settingsPopup.hide();
987
+ }, false);
988
+ Chat.setNewMessageSound(selectSound.value);
958989
/**
959990
Rebuild the menu each time it's shown so that the toggles can
960991
show their current values.
961992
*/
962993
settingsPopup.options.refresh = function(){
@@ -978,19 +1009,27 @@
9781009
D.append(line, check);
9791010
}
9801011
D.append(settingsPopup.e, line);
9811012
line.addEventListener('click', callback);
9821013
});
1014
+ D.append(settingsPopup.e, selectSound);
9831015
};
984
- settingsPopup.installHideHandlers(false, true, true)
1016
+ settingsPopup.installHideHandlers(false, false, true)
9851017
/** Reminder: click-to-hide interferes with "?" embedded within
9861018
the popup, so cannot be used together with those. Enabling
9871019
this means, however, that tapping the menu button to toggle
9881020
the menu cannot work because tapping the menu button while the
9891021
menu is opened will, because of the click-to-hide handler,
9901022
hide the menu before the button gets an event saying to toggle
991
- it.*/;
1023
+ it.
1024
+
1025
+ Reminder: because we need a SELECT element for the audio file
1026
+ selection (since that list can be arbitrarily long), we have
1027
+ to disable tap-outside-the-popup-to-close-it via passing false
1028
+ as the 2nd argument to installHideHandlers(). If we don't,
1029
+ tapping on the select element is unreliable on desktop
1030
+ browsers and doesn't seem to work at all on mobile. */;
9921031
D.attr(settingsButton, 'role', 'button');
9931032
settingsButton.addEventListener('click',function(ev){
9941033
//ev.preventDefault();
9951034
if(settingsPopup.isShown()) settingsPopup.hide();
9961035
else settingsPopup.show(settingsButton);
9971036
--- src/chat.js
+++ src/chat.js
@@ -373,20 +373,34 @@
373 },
374 /** Plays a new-message notification sound IF the audible-alert
375 setting is true, else this is a no-op. Returns this.
376 */
377 playNewMessageSound: function f(){
378 if(this.settings.getBool('audible-alert',false)){
379 try{
380 if(!f.audio) f.audio = new Audio(F.rootPath+"chat-alert");
381 f.audio.currentTime = 0;
382 f.audio.play();
 
 
383 }catch(e){
384 console.error("Audio playblack failed.",e);
385 }
386 }
387 return this;
 
 
 
 
 
 
 
 
 
 
 
 
388 }
389 };
390 cs.e.inputCurrent = cs.e.inputSingle;
391 /* Install default settings... */
392 Object.keys(cs.settings.defaults).forEach(function(k){
@@ -933,30 +947,47 @@
933 boolValue: ()=>!document.body.classList.contains('my-messages-right'),
934 callback: function f(){
935 document.body.classList.toggle('my-messages-right');
936 }
937 },{
938 label: "Message home/end buttons",
939 boolValue: ()=>!Chat.e.btnMsgHome.classList.contains('hidden'),
940 callback: ()=>Chat.toggleNavButtons()
941 },{
942 label: "Images inline",
943 boolValue: ()=>Chat.settings.getBool('images-inline'),
944 callback: function(){
945 const v = Chat.settings.toggle('images-inline');
946 F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
947 }
948 },{
949 label: "Audible alerts",
950 boolValue: ()=>Chat.settings.getBool('audible-alert'),
951 callback: function(){
952 const v = Chat.settings.toggle('audible-alert');
953 if(v) setTimeout(()=>Chat.playNewMessageSound(), 50);
954 F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
955 }
956 }];
957
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
958 /**
959 Rebuild the menu each time it's shown so that the toggles can
960 show their current values.
961 */
962 settingsPopup.options.refresh = function(){
@@ -978,19 +1009,27 @@
978 D.append(line, check);
979 }
980 D.append(settingsPopup.e, line);
981 line.addEventListener('click', callback);
982 });
 
983 };
984 settingsPopup.installHideHandlers(false, true, true)
985 /** Reminder: click-to-hide interferes with "?" embedded within
986 the popup, so cannot be used together with those. Enabling
987 this means, however, that tapping the menu button to toggle
988 the menu cannot work because tapping the menu button while the
989 menu is opened will, because of the click-to-hide handler,
990 hide the menu before the button gets an event saying to toggle
991 it.*/;
 
 
 
 
 
 
 
992 D.attr(settingsButton, 'role', 'button');
993 settingsButton.addEventListener('click',function(ev){
994 //ev.preventDefault();
995 if(settingsPopup.isShown()) settingsPopup.hide();
996 else settingsPopup.show(settingsButton);
997
--- src/chat.js
+++ src/chat.js
@@ -373,20 +373,34 @@
373 },
374 /** Plays a new-message notification sound IF the audible-alert
375 setting is true, else this is a no-op. Returns this.
376 */
377 playNewMessageSound: function f(){
378 if(f.uri){
379 try{
380 if(!f.audio) f.audio = new Audio(F.rootPath+f.uri);
381 if(f.audio){
382 f.audio.currentTime = 0;
383 f.audio.play();
384 }
385 }catch(e){
386 console.error("Audio playblack failed.",e);
387 }
388 }
389 return this;
390 },
391 /**
392 Sets the current new-message audio alert URI (must be a
393 repository-relative path which responds with an audio
394 file). Pass a falsy value to disable audio alerts. Returns
395 this. This setting is persistent.
396 */
397 setNewMessageSound: function f(uri){
398 delete this.playNewMessageSound.audio;
399 this.playNewMessageSound.uri = uri;
400 this.settings.set('audible-alert', uri || '');
401 return this;
402 }
403 };
404 cs.e.inputCurrent = cs.e.inputSingle;
405 /* Install default settings... */
406 Object.keys(cs.settings.defaults).forEach(function(k){
@@ -933,30 +947,47 @@
947 boolValue: ()=>!document.body.classList.contains('my-messages-right'),
948 callback: function f(){
949 document.body.classList.toggle('my-messages-right');
950 }
951 },{
 
 
 
 
952 label: "Images inline",
953 boolValue: ()=>Chat.settings.getBool('images-inline'),
954 callback: function(){
955 const v = Chat.settings.toggle('images-inline');
956 F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
957 }
958 },{
959 label: "Message home/end buttons",
960 boolValue: ()=>!Chat.e.btnMsgHome.classList.contains('hidden'),
961 callback: ()=>Chat.toggleNavButtons()
 
 
 
 
962 }];
963
964 /** Set up selection list of notification sounds. */
965 const selectSound = D.addClass(D.select(), 'menu-entry');
966 D.disable(D.option(selectSound, "0", "Audible alert..."));
967 D.option(selectSound, "", "(no audio)");
968 F.config.chat.alerts.forEach(function(a){
969 D.option(selectSound, a);
970 });
971 if(true===Chat.settings.getBool('audible-alert')){
972 selectSound.selectedIndex = 2/*first audio file in the list*/;
973 }else{
974 selectSound.value = Chat.settings.get('audible-alert','');
975 if(selectSound.selectedIndex<0){
976 /*Missing file - removed after this setting was applied. Fall back
977 to the first sound in the list. */
978 selectSound.selectedIndex = 2;
979 }
980 }
981 selectSound.addEventListener('change',function(){
982 const v = this.selectedIndex>1 ? this.value : '';
983 Chat.setNewMessageSound(v);
984 F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
985 if(v) setTimeout(()=>Chat.playNewMessageSound(), 50);
986 settingsPopup.hide();
987 }, false);
988 Chat.setNewMessageSound(selectSound.value);
989 /**
990 Rebuild the menu each time it's shown so that the toggles can
991 show their current values.
992 */
993 settingsPopup.options.refresh = function(){
@@ -978,19 +1009,27 @@
1009 D.append(line, check);
1010 }
1011 D.append(settingsPopup.e, line);
1012 line.addEventListener('click', callback);
1013 });
1014 D.append(settingsPopup.e, selectSound);
1015 };
1016 settingsPopup.installHideHandlers(false, false, true)
1017 /** Reminder: click-to-hide interferes with "?" embedded within
1018 the popup, so cannot be used together with those. Enabling
1019 this means, however, that tapping the menu button to toggle
1020 the menu cannot work because tapping the menu button while the
1021 menu is opened will, because of the click-to-hide handler,
1022 hide the menu before the button gets an event saying to toggle
1023 it.
1024
1025 Reminder: because we need a SELECT element for the audio file
1026 selection (since that list can be arbitrarily long), we have
1027 to disable tap-outside-the-popup-to-close-it via passing false
1028 as the 2nd argument to installHideHandlers(). If we don't,
1029 tapping on the select element is unreliable on desktop
1030 browsers and doesn't seem to work at all on mobile. */;
1031 D.attr(settingsButton, 'role', 'button');
1032 settingsButton.addEventListener('click',function(ev){
1033 //ev.preventDefault();
1034 if(settingsPopup.isShown()) settingsPopup.hide();
1035 else settingsPopup.show(settingsButton);
1036
--- src/default.css
+++ src/default.css
@@ -1640,10 +1640,22 @@
16401640
flex: 1 1 auto/*eliminates dead no-click zones on the right*/;
16411641
}
16421642
body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
16431643
cursor: inherit;
16441644
}
1645
+body.chat .chat-settings-popup > select.menu-entry {
1646
+ flex: 1 1 auto;
1647
+ padding: 0;
1648
+ cursor: pointer;
1649
+ min-height: 2.5em;
1650
+ border-radius: 0.25em;
1651
+}
1652
+body.chat .chat-settings-popup > select.menu-entry > option {
1653
+ /*Recall that many browsers don't allow styling of OPTION
1654
+ elements, or allow only very limited styling.*/
1655
+}
1656
+
16451657
/** Container for the list of /chat messages. */
16461658
body.chat #chat-messages-wrapper {
16471659
overflow: auto;
16481660
/*max-height: 800em*//*will be re-calc'd in JS*/;
16491661
flex: 2 1 auto;
16501662
--- src/default.css
+++ src/default.css
@@ -1640,10 +1640,22 @@
1640 flex: 1 1 auto/*eliminates dead no-click zones on the right*/;
1641 }
1642 body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
1643 cursor: inherit;
1644 }
 
 
 
 
 
 
 
 
 
 
 
 
1645 /** Container for the list of /chat messages. */
1646 body.chat #chat-messages-wrapper {
1647 overflow: auto;
1648 /*max-height: 800em*//*will be re-calc'd in JS*/;
1649 flex: 2 1 auto;
1650
--- src/default.css
+++ src/default.css
@@ -1640,10 +1640,22 @@
1640 flex: 1 1 auto/*eliminates dead no-click zones on the right*/;
1641 }
1642 body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
1643 cursor: inherit;
1644 }
1645 body.chat .chat-settings-popup > select.menu-entry {
1646 flex: 1 1 auto;
1647 padding: 0;
1648 cursor: pointer;
1649 min-height: 2.5em;
1650 border-radius: 0.25em;
1651 }
1652 body.chat .chat-settings-popup > select.menu-entry > option {
1653 /*Recall that many browsers don't allow styling of OPTION
1654 elements, or allow only very limited styling.*/
1655 }
1656
1657 /** Container for the list of /chat messages. */
1658 body.chat #chat-messages-wrapper {
1659 overflow: auto;
1660 /*max-height: 800em*//*will be re-calc'd in JS*/;
1661 flex: 2 1 auto;
1662
+3 -1
--- src/main.mk
+++ src/main.mk
@@ -220,10 +220,13 @@
220220
$(SRCDIR)/../skins/xekri/css.txt \
221221
$(SRCDIR)/../skins/xekri/details.txt \
222222
$(SRCDIR)/../skins/xekri/footer.txt \
223223
$(SRCDIR)/../skins/xekri/header.txt \
224224
$(SRCDIR)/accordion.js \
225
+ $(SRCDIR)/alerts/b-flat.wav \
226
+ $(SRCDIR)/alerts/g-minor-triad.wav \
227
+ $(SRCDIR)/alerts/plunk.wav \
225228
$(SRCDIR)/chat.js \
226229
$(SRCDIR)/ci_edit.js \
227230
$(SRCDIR)/copybtn.js \
228231
$(SRCDIR)/default.css \
229232
$(SRCDIR)/diff.tcl \
@@ -266,11 +269,10 @@
266269
$(SRCDIR)/sounds/b.wav \
267270
$(SRCDIR)/sounds/c.wav \
268271
$(SRCDIR)/sounds/d.wav \
269272
$(SRCDIR)/sounds/e.wav \
270273
$(SRCDIR)/sounds/f.wav \
271
- $(SRCDIR)/sounds/plunk.wav \
272274
$(SRCDIR)/style.admin_log.css \
273275
$(SRCDIR)/style.fileedit.css \
274276
$(SRCDIR)/style.wikiedit.css \
275277
$(SRCDIR)/tree.js \
276278
$(SRCDIR)/useredit.js \
277279
--- src/main.mk
+++ src/main.mk
@@ -220,10 +220,13 @@
220 $(SRCDIR)/../skins/xekri/css.txt \
221 $(SRCDIR)/../skins/xekri/details.txt \
222 $(SRCDIR)/../skins/xekri/footer.txt \
223 $(SRCDIR)/../skins/xekri/header.txt \
224 $(SRCDIR)/accordion.js \
 
 
 
225 $(SRCDIR)/chat.js \
226 $(SRCDIR)/ci_edit.js \
227 $(SRCDIR)/copybtn.js \
228 $(SRCDIR)/default.css \
229 $(SRCDIR)/diff.tcl \
@@ -266,11 +269,10 @@
266 $(SRCDIR)/sounds/b.wav \
267 $(SRCDIR)/sounds/c.wav \
268 $(SRCDIR)/sounds/d.wav \
269 $(SRCDIR)/sounds/e.wav \
270 $(SRCDIR)/sounds/f.wav \
271 $(SRCDIR)/sounds/plunk.wav \
272 $(SRCDIR)/style.admin_log.css \
273 $(SRCDIR)/style.fileedit.css \
274 $(SRCDIR)/style.wikiedit.css \
275 $(SRCDIR)/tree.js \
276 $(SRCDIR)/useredit.js \
277
--- src/main.mk
+++ src/main.mk
@@ -220,10 +220,13 @@
220 $(SRCDIR)/../skins/xekri/css.txt \
221 $(SRCDIR)/../skins/xekri/details.txt \
222 $(SRCDIR)/../skins/xekri/footer.txt \
223 $(SRCDIR)/../skins/xekri/header.txt \
224 $(SRCDIR)/accordion.js \
225 $(SRCDIR)/alerts/b-flat.wav \
226 $(SRCDIR)/alerts/g-minor-triad.wav \
227 $(SRCDIR)/alerts/plunk.wav \
228 $(SRCDIR)/chat.js \
229 $(SRCDIR)/ci_edit.js \
230 $(SRCDIR)/copybtn.js \
231 $(SRCDIR)/default.css \
232 $(SRCDIR)/diff.tcl \
@@ -266,11 +269,10 @@
269 $(SRCDIR)/sounds/b.wav \
270 $(SRCDIR)/sounds/c.wav \
271 $(SRCDIR)/sounds/d.wav \
272 $(SRCDIR)/sounds/e.wav \
273 $(SRCDIR)/sounds/f.wav \
 
274 $(SRCDIR)/style.admin_log.css \
275 $(SRCDIR)/style.fileedit.css \
276 $(SRCDIR)/style.wikiedit.css \
277 $(SRCDIR)/tree.js \
278 $(SRCDIR)/useredit.js \
279
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -185,10 +185,11 @@
185185
*.js
186186
default.css
187187
style.*.css
188188
../skins/*/*.txt
189189
sounds/*.wav
190
+ alerts/*.wav
190191
}
191192
192193
# Options used to compile the included SQLite library.
193194
#
194195
set SQLITE_OPTIONS {
195196
196197
DELETED src/sounds/b-flat.wav
197198
DELETED src/sounds/g-minor-triad.wav
198199
DELETED src/sounds/mkwav.c
199200
DELETED src/sounds/plunk.wav
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -185,10 +185,11 @@
185 *.js
186 default.css
187 style.*.css
188 ../skins/*/*.txt
189 sounds/*.wav
 
190 }
191
192 # Options used to compile the included SQLite library.
193 #
194 set SQLITE_OPTIONS {
195
196 ELETED src/sounds/b-flat.wav
197 ELETED src/sounds/g-minor-triad.wav
198 ELETED src/sounds/mkwav.c
199 ELETED src/sounds/plunk.wav
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -185,10 +185,11 @@
185 *.js
186 default.css
187 style.*.css
188 ../skins/*/*.txt
189 sounds/*.wav
190 alerts/*.wav
191 }
192
193 # Options used to compile the included SQLite library.
194 #
195 set SQLITE_OPTIONS {
196
197 ELETED src/sounds/b-flat.wav
198 ELETED src/sounds/g-minor-triad.wav
199 ELETED src/sounds/mkwav.c
200 ELETED src/sounds/plunk.wav
D src/sounds/b-flat.wav

Binary file

D src/sounds/g-minor-triad.wav

Binary file

D src/sounds/mkwav.c
-66
--- a/src/sounds/mkwav.c
+++ b/src/sounds/mkwav.c
@@ -1,66 +0,0 @@
1
-/*
2
-** This C program was used to generate the "g-minor-triad.wav" file.
3
-** A small modification generated the "b-flat.wav" file.
4
-**
5
-** This code is saved as an historical reference. It is not part
6
-** of Fossil.
7
-*/
8
-#include <stdio.h>
9
-#include <math.h>
10
-#include <stdlib.h>
11
-
12
-/*
13
-** Write a four-byte little-endian integer value to out.
14
-*/
15
-void write_int4(FILE *out, unsigned int i){
16
- unsigned char z[4];
17
- z[0] = i&0xff;
18
- z[1] = (i>>8)&0xff;
19
- z[2] = (i>>16)&0xff;
20
- z[3] = (i>>24)&0xff;
21
- fwrite(z, 4, 1, out);
22
-}
23
-
24
-/*
25
-** Write out the WAV file
26
-*/
27
-void write_wave(
28
- const char *zFilename, /* The file to write */
29
- unsigned int nData, /* Bytes of data */
30
- unsigned char *aData /* 8000 samples/sec, 8 bit samples */
31
-){
32
- const unsigned char aWavFmt[] = {
33
- 0x57, 0x41, 0x56, 0x45, /* "WAVE" */
34
- 0x66, 0x6d, 0x74, 0x20, /* "fmt " */
35
- 0x10, 0x00, 0x00, 0x00, /* 16 bytes in the "fmt " section */
36
- 0x01, 0x00, /* FormatTag: WAVE_FORMAT_PCM */
37
- 0x01, 0x00, /* 1 channel */
38
- 0x40, 0x1f, 0x00, 0x00, /* 8000 samples/second */
39
- 0x40, 0x1f, 0x00, 0x00, /* 8000 bytes/second */
40
- 0x01, 0x00, /* Block alignment */
41
- 0x08, 0x00, /* bits/sample */
42
- 0x64, 0x61, 0x74, 0x61, /* "data" */
43
- };
44
- FILE *out = fopen(zFilename,"wb");
45
- if( out==0 ){
46
- fprintf(stderr, "cannot open \"%s\" for writing\n", zFilename);
47
- exit(1);
48
- }
49
- fwrite("RIFF", 4, 1, out);
50
- write_int4(out, nData+4+20+8);
51
- fwrite(aWavFmt, sizeof(aWavFmt), 1, out);
52
- write_int4(out, nData);
53
- fwrite(aData, nData, 1, out);
54
- fclose(out);
55
-}
56
-
57
-int main(int argc, char **argv){
58
- int i = 0;
59
- unsigned char aBuf[800];
60
-# define N sizeof(aBuf)
61
-# define pitch1 195.9977*2 /* G */
62
-# define pitch2 233.0819*2 /* B-flat */
63
-# define pitch3 293.6648*2 /* D */
64
- while( i<N ){
65
- double v;
66
- v = 33v += 33.0*sin((2*M_PI*p
--- a/src/sounds/mkwav.c
+++ b/src/sounds/mkwav.c
@@ -1,66 +0,0 @@
1 /*
2 ** This C program was used to generate the "g-minor-triad.wav" file.
3 ** A small modification generated the "b-flat.wav" file.
4 **
5 ** This code is saved as an historical reference. It is not part
6 ** of Fossil.
7 */
8 #include <stdio.h>
9 #include <math.h>
10 #include <stdlib.h>
11
12 /*
13 ** Write a four-byte little-endian integer value to out.
14 */
15 void write_int4(FILE *out, unsigned int i){
16 unsigned char z[4];
17 z[0] = i&0xff;
18 z[1] = (i>>8)&0xff;
19 z[2] = (i>>16)&0xff;
20 z[3] = (i>>24)&0xff;
21 fwrite(z, 4, 1, out);
22 }
23
24 /*
25 ** Write out the WAV file
26 */
27 void write_wave(
28 const char *zFilename, /* The file to write */
29 unsigned int nData, /* Bytes of data */
30 unsigned char *aData /* 8000 samples/sec, 8 bit samples */
31 ){
32 const unsigned char aWavFmt[] = {
33 0x57, 0x41, 0x56, 0x45, /* "WAVE" */
34 0x66, 0x6d, 0x74, 0x20, /* "fmt " */
35 0x10, 0x00, 0x00, 0x00, /* 16 bytes in the "fmt " section */
36 0x01, 0x00, /* FormatTag: WAVE_FORMAT_PCM */
37 0x01, 0x00, /* 1 channel */
38 0x40, 0x1f, 0x00, 0x00, /* 8000 samples/second */
39 0x40, 0x1f, 0x00, 0x00, /* 8000 bytes/second */
40 0x01, 0x00, /* Block alignment */
41 0x08, 0x00, /* bits/sample */
42 0x64, 0x61, 0x74, 0x61, /* "data" */
43 };
44 FILE *out = fopen(zFilename,"wb");
45 if( out==0 ){
46 fprintf(stderr, "cannot open \"%s\" for writing\n", zFilename);
47 exit(1);
48 }
49 fwrite("RIFF", 4, 1, out);
50 write_int4(out, nData+4+20+8);
51 fwrite(aWavFmt, sizeof(aWavFmt), 1, out);
52 write_int4(out, nData);
53 fwrite(aData, nData, 1, out);
54 fclose(out);
55 }
56
57 int main(int argc, char **argv){
58 int i = 0;
59 unsigned char aBuf[800];
60 # define N sizeof(aBuf)
61 # define pitch1 195.9977*2 /* G */
62 # define pitch2 233.0819*2 /* B-flat */
63 # define pitch3 293.6648*2 /* D */
64 while( i<N ){
65 double v;
66 v = 33v += 33.0*sin((2*M_PI*p
--- a/src/sounds/mkwav.c
+++ b/src/sounds/mkwav.c
@@ -1,66 +0,0 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
D src/sounds/plunk.wav

Binary file

--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -632,10 +632,13 @@
632632
$(SRCDIR)/../skins/xekri/css.txt \
633633
$(SRCDIR)/../skins/xekri/details.txt \
634634
$(SRCDIR)/../skins/xekri/footer.txt \
635635
$(SRCDIR)/../skins/xekri/header.txt \
636636
$(SRCDIR)/accordion.js \
637
+ $(SRCDIR)/alerts/b-flat.wav \
638
+ $(SRCDIR)/alerts/g-minor-triad.wav \
639
+ $(SRCDIR)/alerts/plunk.wav \
637640
$(SRCDIR)/chat.js \
638641
$(SRCDIR)/ci_edit.js \
639642
$(SRCDIR)/copybtn.js \
640643
$(SRCDIR)/default.css \
641644
$(SRCDIR)/diff.tcl \
@@ -678,11 +681,10 @@
678681
$(SRCDIR)/sounds/b.wav \
679682
$(SRCDIR)/sounds/c.wav \
680683
$(SRCDIR)/sounds/d.wav \
681684
$(SRCDIR)/sounds/e.wav \
682685
$(SRCDIR)/sounds/f.wav \
683
- $(SRCDIR)/sounds/plunk.wav \
684686
$(SRCDIR)/style.admin_log.css \
685687
$(SRCDIR)/style.fileedit.css \
686688
$(SRCDIR)/style.wikiedit.css \
687689
$(SRCDIR)/tree.js \
688690
$(SRCDIR)/useredit.js \
689691
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -632,10 +632,13 @@
632 $(SRCDIR)/../skins/xekri/css.txt \
633 $(SRCDIR)/../skins/xekri/details.txt \
634 $(SRCDIR)/../skins/xekri/footer.txt \
635 $(SRCDIR)/../skins/xekri/header.txt \
636 $(SRCDIR)/accordion.js \
 
 
 
637 $(SRCDIR)/chat.js \
638 $(SRCDIR)/ci_edit.js \
639 $(SRCDIR)/copybtn.js \
640 $(SRCDIR)/default.css \
641 $(SRCDIR)/diff.tcl \
@@ -678,11 +681,10 @@
678 $(SRCDIR)/sounds/b.wav \
679 $(SRCDIR)/sounds/c.wav \
680 $(SRCDIR)/sounds/d.wav \
681 $(SRCDIR)/sounds/e.wav \
682 $(SRCDIR)/sounds/f.wav \
683 $(SRCDIR)/sounds/plunk.wav \
684 $(SRCDIR)/style.admin_log.css \
685 $(SRCDIR)/style.fileedit.css \
686 $(SRCDIR)/style.wikiedit.css \
687 $(SRCDIR)/tree.js \
688 $(SRCDIR)/useredit.js \
689
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -632,10 +632,13 @@
632 $(SRCDIR)/../skins/xekri/css.txt \
633 $(SRCDIR)/../skins/xekri/details.txt \
634 $(SRCDIR)/../skins/xekri/footer.txt \
635 $(SRCDIR)/../skins/xekri/header.txt \
636 $(SRCDIR)/accordion.js \
637 $(SRCDIR)/alerts/b-flat.wav \
638 $(SRCDIR)/alerts/g-minor-triad.wav \
639 $(SRCDIR)/alerts/plunk.wav \
640 $(SRCDIR)/chat.js \
641 $(SRCDIR)/ci_edit.js \
642 $(SRCDIR)/copybtn.js \
643 $(SRCDIR)/default.css \
644 $(SRCDIR)/diff.tcl \
@@ -678,11 +681,10 @@
681 $(SRCDIR)/sounds/b.wav \
682 $(SRCDIR)/sounds/c.wav \
683 $(SRCDIR)/sounds/d.wav \
684 $(SRCDIR)/sounds/e.wav \
685 $(SRCDIR)/sounds/f.wav \
 
686 $(SRCDIR)/style.admin_log.css \
687 $(SRCDIR)/style.fileedit.css \
688 $(SRCDIR)/style.wikiedit.css \
689 $(SRCDIR)/tree.js \
690 $(SRCDIR)/useredit.js \
691
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -553,10 +553,13 @@
553553
"$(SRCDIR)\..\skins\xekri\css.txt" \
554554
"$(SRCDIR)\..\skins\xekri\details.txt" \
555555
"$(SRCDIR)\..\skins\xekri\footer.txt" \
556556
"$(SRCDIR)\..\skins\xekri\header.txt" \
557557
"$(SRCDIR)\accordion.js" \
558
+ "$(SRCDIR)\alerts\b-flat.wav" \
559
+ "$(SRCDIR)\alerts\g-minor-triad.wav" \
560
+ "$(SRCDIR)\alerts\plunk.wav" \
558561
"$(SRCDIR)\chat.js" \
559562
"$(SRCDIR)\ci_edit.js" \
560563
"$(SRCDIR)\copybtn.js" \
561564
"$(SRCDIR)\default.css" \
562565
"$(SRCDIR)\diff.tcl" \
@@ -599,11 +602,10 @@
599602
"$(SRCDIR)\sounds\b.wav" \
600603
"$(SRCDIR)\sounds\c.wav" \
601604
"$(SRCDIR)\sounds\d.wav" \
602605
"$(SRCDIR)\sounds\e.wav" \
603606
"$(SRCDIR)\sounds\f.wav" \
604
- "$(SRCDIR)\sounds\plunk.wav" \
605607
"$(SRCDIR)\style.admin_log.css" \
606608
"$(SRCDIR)\style.fileedit.css" \
607609
"$(SRCDIR)\style.wikiedit.css" \
608610
"$(SRCDIR)\tree.js" \
609611
"$(SRCDIR)\useredit.js" \
@@ -1163,10 +1165,13 @@
11631165
echo "$(SRCDIR)\../skins/xekri/css.txt" >> $@
11641166
echo "$(SRCDIR)\../skins/xekri/details.txt" >> $@
11651167
echo "$(SRCDIR)\../skins/xekri/footer.txt" >> $@
11661168
echo "$(SRCDIR)\../skins/xekri/header.txt" >> $@
11671169
echo "$(SRCDIR)\accordion.js" >> $@
1170
+ echo "$(SRCDIR)\alerts/b-flat.wav" >> $@
1171
+ echo "$(SRCDIR)\alerts/g-minor-triad.wav" >> $@
1172
+ echo "$(SRCDIR)\alerts/plunk.wav" >> $@
11681173
echo "$(SRCDIR)\chat.js" >> $@
11691174
echo "$(SRCDIR)\ci_edit.js" >> $@
11701175
echo "$(SRCDIR)\copybtn.js" >> $@
11711176
echo "$(SRCDIR)\default.css" >> $@
11721177
echo "$(SRCDIR)\diff.tcl" >> $@
@@ -1209,11 +1214,10 @@
12091214
echo "$(SRCDIR)\sounds/b.wav" >> $@
12101215
echo "$(SRCDIR)\sounds/c.wav" >> $@
12111216
echo "$(SRCDIR)\sounds/d.wav" >> $@
12121217
echo "$(SRCDIR)\sounds/e.wav" >> $@
12131218
echo "$(SRCDIR)\sounds/f.wav" >> $@
1214
- echo "$(SRCDIR)\sounds/plunk.wav" >> $@
12151219
echo "$(SRCDIR)\style.admin_log.css" >> $@
12161220
echo "$(SRCDIR)\style.fileedit.css" >> $@
12171221
echo "$(SRCDIR)\style.wikiedit.css" >> $@
12181222
echo "$(SRCDIR)\tree.js" >> $@
12191223
echo "$(SRCDIR)\useredit.js" >> $@
12201224
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -553,10 +553,13 @@
553 "$(SRCDIR)\..\skins\xekri\css.txt" \
554 "$(SRCDIR)\..\skins\xekri\details.txt" \
555 "$(SRCDIR)\..\skins\xekri\footer.txt" \
556 "$(SRCDIR)\..\skins\xekri\header.txt" \
557 "$(SRCDIR)\accordion.js" \
 
 
 
558 "$(SRCDIR)\chat.js" \
559 "$(SRCDIR)\ci_edit.js" \
560 "$(SRCDIR)\copybtn.js" \
561 "$(SRCDIR)\default.css" \
562 "$(SRCDIR)\diff.tcl" \
@@ -599,11 +602,10 @@
599 "$(SRCDIR)\sounds\b.wav" \
600 "$(SRCDIR)\sounds\c.wav" \
601 "$(SRCDIR)\sounds\d.wav" \
602 "$(SRCDIR)\sounds\e.wav" \
603 "$(SRCDIR)\sounds\f.wav" \
604 "$(SRCDIR)\sounds\plunk.wav" \
605 "$(SRCDIR)\style.admin_log.css" \
606 "$(SRCDIR)\style.fileedit.css" \
607 "$(SRCDIR)\style.wikiedit.css" \
608 "$(SRCDIR)\tree.js" \
609 "$(SRCDIR)\useredit.js" \
@@ -1163,10 +1165,13 @@
1163 echo "$(SRCDIR)\../skins/xekri/css.txt" >> $@
1164 echo "$(SRCDIR)\../skins/xekri/details.txt" >> $@
1165 echo "$(SRCDIR)\../skins/xekri/footer.txt" >> $@
1166 echo "$(SRCDIR)\../skins/xekri/header.txt" >> $@
1167 echo "$(SRCDIR)\accordion.js" >> $@
 
 
 
1168 echo "$(SRCDIR)\chat.js" >> $@
1169 echo "$(SRCDIR)\ci_edit.js" >> $@
1170 echo "$(SRCDIR)\copybtn.js" >> $@
1171 echo "$(SRCDIR)\default.css" >> $@
1172 echo "$(SRCDIR)\diff.tcl" >> $@
@@ -1209,11 +1214,10 @@
1209 echo "$(SRCDIR)\sounds/b.wav" >> $@
1210 echo "$(SRCDIR)\sounds/c.wav" >> $@
1211 echo "$(SRCDIR)\sounds/d.wav" >> $@
1212 echo "$(SRCDIR)\sounds/e.wav" >> $@
1213 echo "$(SRCDIR)\sounds/f.wav" >> $@
1214 echo "$(SRCDIR)\sounds/plunk.wav" >> $@
1215 echo "$(SRCDIR)\style.admin_log.css" >> $@
1216 echo "$(SRCDIR)\style.fileedit.css" >> $@
1217 echo "$(SRCDIR)\style.wikiedit.css" >> $@
1218 echo "$(SRCDIR)\tree.js" >> $@
1219 echo "$(SRCDIR)\useredit.js" >> $@
1220
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -553,10 +553,13 @@
553 "$(SRCDIR)\..\skins\xekri\css.txt" \
554 "$(SRCDIR)\..\skins\xekri\details.txt" \
555 "$(SRCDIR)\..\skins\xekri\footer.txt" \
556 "$(SRCDIR)\..\skins\xekri\header.txt" \
557 "$(SRCDIR)\accordion.js" \
558 "$(SRCDIR)\alerts\b-flat.wav" \
559 "$(SRCDIR)\alerts\g-minor-triad.wav" \
560 "$(SRCDIR)\alerts\plunk.wav" \
561 "$(SRCDIR)\chat.js" \
562 "$(SRCDIR)\ci_edit.js" \
563 "$(SRCDIR)\copybtn.js" \
564 "$(SRCDIR)\default.css" \
565 "$(SRCDIR)\diff.tcl" \
@@ -599,11 +602,10 @@
602 "$(SRCDIR)\sounds\b.wav" \
603 "$(SRCDIR)\sounds\c.wav" \
604 "$(SRCDIR)\sounds\d.wav" \
605 "$(SRCDIR)\sounds\e.wav" \
606 "$(SRCDIR)\sounds\f.wav" \
 
607 "$(SRCDIR)\style.admin_log.css" \
608 "$(SRCDIR)\style.fileedit.css" \
609 "$(SRCDIR)\style.wikiedit.css" \
610 "$(SRCDIR)\tree.js" \
611 "$(SRCDIR)\useredit.js" \
@@ -1163,10 +1165,13 @@
1165 echo "$(SRCDIR)\../skins/xekri/css.txt" >> $@
1166 echo "$(SRCDIR)\../skins/xekri/details.txt" >> $@
1167 echo "$(SRCDIR)\../skins/xekri/footer.txt" >> $@
1168 echo "$(SRCDIR)\../skins/xekri/header.txt" >> $@
1169 echo "$(SRCDIR)\accordion.js" >> $@
1170 echo "$(SRCDIR)\alerts/b-flat.wav" >> $@
1171 echo "$(SRCDIR)\alerts/g-minor-triad.wav" >> $@
1172 echo "$(SRCDIR)\alerts/plunk.wav" >> $@
1173 echo "$(SRCDIR)\chat.js" >> $@
1174 echo "$(SRCDIR)\ci_edit.js" >> $@
1175 echo "$(SRCDIR)\copybtn.js" >> $@
1176 echo "$(SRCDIR)\default.css" >> $@
1177 echo "$(SRCDIR)\diff.tcl" >> $@
@@ -1209,11 +1214,10 @@
1214 echo "$(SRCDIR)\sounds/b.wav" >> $@
1215 echo "$(SRCDIR)\sounds/c.wav" >> $@
1216 echo "$(SRCDIR)\sounds/d.wav" >> $@
1217 echo "$(SRCDIR)\sounds/e.wav" >> $@
1218 echo "$(SRCDIR)\sounds/f.wav" >> $@
 
1219 echo "$(SRCDIR)\style.admin_log.css" >> $@
1220 echo "$(SRCDIR)\style.fileedit.css" >> $@
1221 echo "$(SRCDIR)\style.wikiedit.css" >> $@
1222 echo "$(SRCDIR)\tree.js" >> $@
1223 echo "$(SRCDIR)\useredit.js" >> $@
1224
+24
--- www/chat.md
+++ www/chat.md
@@ -99,10 +99,34 @@
9999
page is reloaded. Admin users may additionally choose to globally
100100
delete a message from the chat record, which deletes it not only from
101101
their own browser but also propagates the removal to all connected
102102
clients the next time they poll for new messages.
103103
104
+### Audible Notifications
105
+
106
+On platforms which support it, chat can optionally play an audio file
107
+when a new message arrives from any user other than oneself. The sound
108
+can be selected or disabled via the settings menu. The list of sounds
109
+includes a small selection of sounds built in to the fossil binary and
110
+new sound files may be added to a repository as unversioned content:
111
+when the chat page is loaded, it includes a list of all unversioned
112
+files named `alert-sounds/*.XYZ`, where `XYZ` is one of (`wav`, `mp3`,
113
+`ogg`) case-insensitive.
114
+
115
+For example, a Unix-style shell command like the following would
116
+install all WAV files in a local directory to the list:
117
+
118
+```
119
+for i in *.wav; do fossil uv add "$i" --as "alert-sounds/$i"; done
120
+```
121
+
122
+The list of sound files is sorted client-side for display in the
123
+selection list. The user's selection of audio file is persistent in
124
+a given browser, but if the file is subsequently removed from the
125
+server then the next time the chat page is reloaded the sound will
126
+fall back to the first one in the selection list.
127
+
104128
## Implementation Details
105129
106130
*You do not need to understand how Fossil chat works in order to use it.
107131
But many developers prefer to know how their tools work.
108132
This section is provided for the benefit of those curious developers.*
109133
--- www/chat.md
+++ www/chat.md
@@ -99,10 +99,34 @@
99 page is reloaded. Admin users may additionally choose to globally
100 delete a message from the chat record, which deletes it not only from
101 their own browser but also propagates the removal to all connected
102 clients the next time they poll for new messages.
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104 ## Implementation Details
105
106 *You do not need to understand how Fossil chat works in order to use it.
107 But many developers prefer to know how their tools work.
108 This section is provided for the benefit of those curious developers.*
109
--- www/chat.md
+++ www/chat.md
@@ -99,10 +99,34 @@
99 page is reloaded. Admin users may additionally choose to globally
100 delete a message from the chat record, which deletes it not only from
101 their own browser but also propagates the removal to all connected
102 clients the next time they poll for new messages.
103
104 ### Audible Notifications
105
106 On platforms which support it, chat can optionally play an audio file
107 when a new message arrives from any user other than oneself. The sound
108 can be selected or disabled via the settings menu. The list of sounds
109 includes a small selection of sounds built in to the fossil binary and
110 new sound files may be added to a repository as unversioned content:
111 when the chat page is loaded, it includes a list of all unversioned
112 files named `alert-sounds/*.XYZ`, where `XYZ` is one of (`wav`, `mp3`,
113 `ogg`) case-insensitive.
114
115 For example, a Unix-style shell command like the following would
116 install all WAV files in a local directory to the list:
117
118 ```
119 for i in *.wav; do fossil uv add "$i" --as "alert-sounds/$i"; done
120 ```
121
122 The list of sound files is sorted client-side for display in the
123 selection list. The user's selection of audio file is persistent in
124 a given browser, but if the file is subsequently removed from the
125 server then the next time the chat page is reloaded the sound will
126 fall back to the first one in the selection list.
127
128 ## Implementation Details
129
130 *You do not need to understand how Fossil chat works in order to use it.
131 But many developers prefer to know how their tools work.
132 This section is provided for the benefit of those curious developers.*
133

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button