Fossil SCM

Reimplemented /chat settings selection to be more usable, device-portable, and extensible. Re-enabled client-side selection of repo-specific chat nofication sounds.

stephan 2021-09-18 03:40 trunk merge
Commit 9c777150ed38697145ef4b6aa232e930bb4478d5364ab26515948fff278ee0ac
+20 -19
--- src/chat.c
+++ src/chat.c
@@ -47,37 +47,33 @@
4747
** use by the chat front-end client. A handful of builtin files
4848
** (from alerts/\*.wav) and all unversioned files matching
4949
** alert-sounds/\*.{mp3,ogg,wav} are included.
5050
*/
5151
static void chat_emit_alert_list(void){
52
- /*Stmt q = empty_Stmt;*/
5352
unsigned int i;
5453
const char * azBuiltins[] = {
5554
"builtin/alerts/plunk.wav",
56
- "builtin/alerts/b-flat.wav"
55
+ "builtin/alerts/bflat2.wav",
56
+ "builtin/alerts/bflat3.wav",
57
+ "builtin/alerts/bloop.wav"
5758
};
5859
CX("window.fossil.config.chat.alerts = [\n");
5960
for(i=0; i < sizeof(azBuiltins)/sizeof(azBuiltins[0]); ++i){
6061
CX("%s%!j", i ? ", " : "", azBuiltins[i]);
6162
}
62
-#if 0
63
- /*
64
- ** 2021-01-05 temporarily disabled until we decide whether we're
65
- ** going to keep configurable audio files or not. If we do, this
66
- ** code needs to check whether the [unversioned] table exists before
67
- ** querying it.
68
- */
69
- db_prepare(&q, "SELECT 'uv/'||name FROM unversioned "
70
- "WHERE content IS NOT NULL "
71
- "AND (name LIKE 'alert-sounds/%%.wav' "
72
- "OR name LIKE 'alert-sounds/%%.mp3' "
73
- "OR name LIKE 'alert-sounds/%%.ogg')");
74
- while(SQLITE_ROW==db_step(&q)){
75
- CX(", %!j", db_column_text(&q, 0));
76
- }
77
- db_finalize(&q);
78
-#endif
63
+ if( db_table_exists("repository","unversioned") ){
64
+ Stmt q = empty_Stmt;
65
+ db_prepare(&q, "SELECT 'uv/'||name FROM unversioned "
66
+ "WHERE content IS NOT NULL "
67
+ "AND (name LIKE 'alert-sounds/%%.wav' "
68
+ "OR name LIKE 'alert-sounds/%%.mp3' "
69
+ "OR name LIKE 'alert-sounds/%%.ogg')");
70
+ while(SQLITE_ROW==db_step(&q)){
71
+ CX(", %!j", db_column_text(&q, 0));
72
+ }
73
+ db_finalize(&q);
74
+ }
7975
CX("\n];\n");
8076
}
8177
8278
/* Settings that can be used to control chat */
8379
/*
@@ -181,10 +177,15 @@
181177
@ </div>
182178
@ <div id="chat-drop-details"></div>
183179
@ </div>
184180
@ </div>
185181
@ </form>
182
+ @ <div id='chat-config' class='hidden'>
183
+ @ <div id='chat-config-options'></div>
184
+ /* ^^^populated client-side */
185
+ @ <button>Close</button>
186
+ @ </div>
186187
@ <div id='chat-messages-wrapper'>
187188
/* New chat messages get inserted immediately after this element */
188189
@ <span id='message-inject-point'></span>
189190
@ </div>
190191
fossil_free(zProjectName);
191192
--- src/chat.c
+++ src/chat.c
@@ -47,37 +47,33 @@
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 };
58 CX("window.fossil.config.chat.alerts = [\n");
59 for(i=0; i < sizeof(azBuiltins)/sizeof(azBuiltins[0]); ++i){
60 CX("%s%!j", i ? ", " : "", azBuiltins[i]);
61 }
62 #if 0
63 /*
64 ** 2021-01-05 temporarily disabled until we decide whether we're
65 ** going to keep configurable audio files or not. If we do, this
66 ** code needs to check whether the [unversioned] table exists before
67 ** querying it.
68 */
69 db_prepare(&q, "SELECT 'uv/'||name FROM unversioned "
70 "WHERE content IS NOT NULL "
71 "AND (name LIKE 'alert-sounds/%%.wav' "
72 "OR name LIKE 'alert-sounds/%%.mp3' "
73 "OR name LIKE 'alert-sounds/%%.ogg')");
74 while(SQLITE_ROW==db_step(&q)){
75 CX(", %!j", db_column_text(&q, 0));
76 }
77 db_finalize(&q);
78 #endif
79 CX("\n];\n");
80 }
81
82 /* Settings that can be used to control chat */
83 /*
@@ -181,10 +177,15 @@
181 @ </div>
182 @ <div id="chat-drop-details"></div>
183 @ </div>
184 @ </div>
185 @ </form>
 
 
 
 
 
186 @ <div id='chat-messages-wrapper'>
187 /* New chat messages get inserted immediately after this element */
188 @ <span id='message-inject-point'></span>
189 @ </div>
190 fossil_free(zProjectName);
191
--- src/chat.c
+++ src/chat.c
@@ -47,37 +47,33 @@
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 unsigned int i;
53 const char * azBuiltins[] = {
54 "builtin/alerts/plunk.wav",
55 "builtin/alerts/bflat2.wav",
56 "builtin/alerts/bflat3.wav",
57 "builtin/alerts/bloop.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 if( db_table_exists("repository","unversioned") ){
64 Stmt q = empty_Stmt;
65 db_prepare(&q, "SELECT 'uv/'||name FROM unversioned "
66 "WHERE content IS NOT NULL "
67 "AND (name LIKE 'alert-sounds/%%.wav' "
68 "OR name LIKE 'alert-sounds/%%.mp3' "
69 "OR name LIKE 'alert-sounds/%%.ogg')");
70 while(SQLITE_ROW==db_step(&q)){
71 CX(", %!j", db_column_text(&q, 0));
72 }
73 db_finalize(&q);
74 }
 
 
 
 
 
75 CX("\n];\n");
76 }
77
78 /* Settings that can be used to control chat */
79 /*
@@ -181,10 +177,15 @@
177 @ </div>
178 @ <div id="chat-drop-details"></div>
179 @ </div>
180 @ </div>
181 @ </form>
182 @ <div id='chat-config' class='hidden'>
183 @ <div id='chat-config-options'></div>
184 /* ^^^populated client-side */
185 @ <button>Close</button>
186 @ </div>
187 @ <div id='chat-messages-wrapper'>
188 /* New chat messages get inserted immediately after this element */
189 @ <span id='message-inject-point'></span>
190 @ </div>
191 fossil_free(zProjectName);
192
+54 -81
--- src/chat.js
+++ src/chat.js
@@ -109,11 +109,12 @@
109109
btnSubmit: E1('#chat-message-submit'),
110110
inputSingle: E1('#chat-input-single'),
111111
inputMulti: E1('#chat-input-multi'),
112112
inputCurrent: undefined/*one of inputSingle or inputMulti*/,
113113
inputFile: E1('#chat-input-file'),
114
- contentDiv: E1('div.content')
114
+ contentDiv: E1('div.content'),
115
+ configArea: E1('#chat-config')
115116
},
116117
me: F.user.name,
117118
mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
118119
mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
119120
pageIsActive: 'visible'===document.visibilityState,
@@ -385,11 +386,11 @@
385386
this.
386387
*/
387388
setNewMessageSound: function f(uri){
388389
delete this.playNewMessageSound.audio;
389390
this.playNewMessageSound.uri = uri;
390
- this.settings.set('audible-alert', !!uri);
391
+ this.settings.set('audible-alert', uri);
391392
return this;
392393
}
393394
};
394395
F.fetch.beforesend = ()=>cs.ajaxStart();
395396
F.fetch.aftersend = ()=>cs.ajaxEnd();
@@ -950,14 +951,16 @@
950951
.replace('T',' ').replace(/\.\d+/,'').replace('Z', ' zulu');
951952
};
952953
953954
(function(){/*Set up #chat-settings-button */
954955
const settingsButton = document.querySelector('#chat-settings-button');
955
- var popupSize = undefined/*placement workaround*/;
956
- const settingsPopup = new F.PopupWidget({
957
- cssClass: ['fossil-tooltip', 'chat-settings-popup']
958
- });
956
+ const optionsMenu = E1('#chat-config-options');
957
+ const cbToggle = function(){
958
+ D.toggleClass([Chat.e.messagesWrapper, Chat.e.configArea], 'hidden');
959
+ };
960
+ D.attr(settingsButton, 'role', 'button').addEventListener('click', cbToggle, false);
961
+ Chat.e.configArea.querySelector('button').addEventListener('click', cbToggle, false);
959962
/* Settings menu entries... */
960963
const settingsOps = [{
961964
label: "Multi-line input",
962965
boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
963966
persistentSetting: 'edit-multiline',
@@ -992,11 +995,11 @@
992995
F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
993996
}
994997
}];
995998
996999
/** Set up selection list of notification sounds. */
997
- if(true/*flip this to false to enable selection of audio files*/){
1000
+ if(false/*flip this to false to enable selection of audio files*/){
9981001
settingsOps.push({
9991002
label: "Audible alerts",
10001003
boolValue: ()=>Chat.settings.getBool('audible-alert'),
10011004
callback: function(){
10021005
const v = Chat.settings.toggle('audible-alert');
@@ -1009,103 +1012,73 @@
10091012
Chat.settings.getBool('audible-alert') ? F.config.chat.alertSound : false
10101013
);
10111014
}else{
10121015
/* Disabled per chatroom discussion: selection list of audio files for
10131016
chat notification. */
1014
- const selectSound = settingsOps.selectSound = D.addClass(D.select(), 'menu-entry');
1015
- D.disable(D.option(selectSound, "0", "Audible alert..."));
1017
+ settingsOps.selectSound = D.addClass(D.div(), 'menu-entry');
1018
+ const selectSound = D.select();
1019
+ D.append(settingsOps.selectSound,
1020
+ D.append(D.span(),"Audio alert"),
1021
+ selectSound);
10161022
D.option(selectSound, "", "(no audio)");
1023
+ const firstSoundIndex = selectSound.options.length;
10171024
F.config.chat.alerts.forEach(function(a){
10181025
D.option(selectSound, a);
10191026
});
10201027
if(true===Chat.settings.getBool('audible-alert')){
1021
- selectSound.selectedIndex = 2/*first audio file in the list*/;
1028
+ selectSound.selectedIndex = firstSoundIndex;
10221029
}else{
10231030
selectSound.value = Chat.settings.get('audible-alert','');
10241031
if(selectSound.selectedIndex<0){
10251032
/*Missing file - removed after this setting was applied. Fall back
10261033
to the first sound in the list. */
1027
- selectSound.selectedIndex = 2;
1034
+ selectSound.selectedIndex = firstSoundIndex;
10281035
}
10291036
}
10301037
selectSound.addEventListener('change',function(){
1031
- const v = this.selectedIndex>1 ? this.value : '';
1038
+ const v = this.value;
10321039
Chat.setNewMessageSound(v);
10331040
F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
1034
- if(v) setTimeout(()=>Chat.playNewMessageSound(), 50);
1035
- settingsPopup.hide();
1041
+ if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
10361042
}, false);
10371043
Chat.setNewMessageSound(selectSound.value);
10381044
}/*audio notification config*/
10391045
/**
1040
- Rebuild the menu each time it's shown so that the toggles can
1041
- show their current values.
1042
- */
1043
- settingsPopup.options.refresh = function(){
1044
- D.clearElement(this.e);
1045
- settingsOps.forEach(function(op){
1046
- const line = D.addClass(D.span(), 'menu-entry');
1047
- const btn = D.append(D.addClass(D.span(), 'button'), op.label);
1048
- const callback = function(ev){
1049
- settingsPopup.hide();
1050
- op.callback(ev);
1051
- if(op.persistentSetting){
1052
- Chat.settings.set(op.persistentSetting, op.boolValue());
1053
- }
1054
- };
1055
- D.append(line, btn);
1056
- if(op.hasOwnProperty('boolValue')){
1057
- const check = D.attr(D.checkbox(1, op.boolValue()),
1058
- 'aria-label', op.label);
1059
- D.append(line, check);
1060
- }
1061
- D.append(settingsPopup.e, line);
1062
- line.addEventListener('click', callback);
1063
- });
1064
- if(settingsOps.selectSound){
1065
- D.append(settingsPopup.e, settingsOps.selectSound);
1066
- }
1067
- };
1068
- settingsPopup.installHideHandlers(
1069
- false, settingsOps.selectSound ? false : true,
1070
- true)
1071
- /** Reminder: click-to-hide interferes with "?" embedded within
1072
- the popup, so cannot be used together with those. Enabling
1073
- this means, however, that tapping the menu button to toggle
1074
- the menu cannot work because tapping the menu button while the
1075
- menu is opened will, because of the click-to-hide handler,
1076
- hide the menu before the button gets an event saying to toggle
1077
- it.
1078
-
1079
- Reminder: because we need a SELECT element for the audio file
1080
- selection (since that list can be arbitrarily long), we have
1081
- to disable tap-outside-the-popup-to-close-it via passing false
1082
- as the 2nd argument to installHideHandlers(). If we don't,
1083
- tapping on the select element is unreliable on desktop
1084
- browsers and doesn't seem to work at all on mobile. */;
1085
- D.attr(settingsButton, 'role', 'button');
1086
- settingsButton.addEventListener('click',function(ev){
1087
- //ev.preventDefault();
1088
- if(settingsPopup.isShown()) settingsPopup.hide();
1089
- else settingsPopup.show(settingsButton);
1090
- /* Reminder: we cannot toggle the visibility from her
1091
- */
1092
- }, false);
1093
-
1094
- /* Find an ideal X/Y position for the popup, directly above the settings
1095
- button, based on the size of the popup... */
1096
- settingsPopup.show(document.body);
1097
- popupSize = settingsPopup.e.getBoundingClientRect();
1098
- settingsPopup.hide();
1099
- settingsPopup.options.adjustX = function(x){
1100
- const rect = settingsButton.getBoundingClientRect();
1101
- return rect.right - popupSize.width;
1102
- };
1103
- settingsPopup.options.adjustY = function(y){
1104
- const rect = settingsButton.getBoundingClientRect();
1105
- return rect.top - popupSize.height -2;
1106
- };
1046
+ Build list of options...
1047
+ */
1048
+ settingsOps.forEach(function f(op){
1049
+ const line = D.addClass(D.div(), 'menu-entry');
1050
+ const btn = D.append(
1051
+ D.addClass(D.label(), 'cbutton'/*bootstrap skin hijacks 'button'*/),
1052
+ op.label);
1053
+ const callback = function(ev){
1054
+ op.callback(ev);
1055
+ if(op.persistentSetting){
1056
+ Chat.settings.set(op.persistentSetting, op.boolValue());
1057
+ }
1058
+ };
1059
+ if(op.hasOwnProperty('boolValue')){
1060
+ if(undefined === f.$id) f.$id = 0;
1061
+ ++f.$id;
1062
+ const check = D.attr(D.checkbox(1, op.boolValue()),
1063
+ 'aria-label', op.label);
1064
+ const id = 'cfgopt'+f.$id;
1065
+ if(op.boolValue()) check.checked = true;
1066
+ D.attr(check, 'id', id);
1067
+ D.attr(btn, 'for', id);
1068
+ D.append(line, check);
1069
+ check.addEventListener('change', callback);
1070
+ }else{
1071
+ line.addEventListener('click', callback);
1072
+ }
1073
+ D.append(line, btn);
1074
+ D.append(optionsMenu, line);
1075
+ });
1076
+ if(settingsOps.selectSound){
1077
+ D.append(optionsMenu, settingsOps.selectSound);
1078
+ }
1079
+ //settingsButton.click()/*for for development*/;
11071080
})()/*#chat-settings-button setup*/;
11081081
11091082
/** Callback for poll() to inject new content into the page. jx ==
11101083
the response from /chat-poll. If atEnd is true, the message is
11111084
appended to the end of the chat list (for loading older
11121085
--- src/chat.js
+++ src/chat.js
@@ -109,11 +109,12 @@
109 btnSubmit: E1('#chat-message-submit'),
110 inputSingle: E1('#chat-input-single'),
111 inputMulti: E1('#chat-input-multi'),
112 inputCurrent: undefined/*one of inputSingle or inputMulti*/,
113 inputFile: E1('#chat-input-file'),
114 contentDiv: E1('div.content')
 
115 },
116 me: F.user.name,
117 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
118 mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
119 pageIsActive: 'visible'===document.visibilityState,
@@ -385,11 +386,11 @@
385 this.
386 */
387 setNewMessageSound: function f(uri){
388 delete this.playNewMessageSound.audio;
389 this.playNewMessageSound.uri = uri;
390 this.settings.set('audible-alert', !!uri);
391 return this;
392 }
393 };
394 F.fetch.beforesend = ()=>cs.ajaxStart();
395 F.fetch.aftersend = ()=>cs.ajaxEnd();
@@ -950,14 +951,16 @@
950 .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' zulu');
951 };
952
953 (function(){/*Set up #chat-settings-button */
954 const settingsButton = document.querySelector('#chat-settings-button');
955 var popupSize = undefined/*placement workaround*/;
956 const settingsPopup = new F.PopupWidget({
957 cssClass: ['fossil-tooltip', 'chat-settings-popup']
958 });
 
 
959 /* Settings menu entries... */
960 const settingsOps = [{
961 label: "Multi-line input",
962 boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
963 persistentSetting: 'edit-multiline',
@@ -992,11 +995,11 @@
992 F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
993 }
994 }];
995
996 /** Set up selection list of notification sounds. */
997 if(true/*flip this to false to enable selection of audio files*/){
998 settingsOps.push({
999 label: "Audible alerts",
1000 boolValue: ()=>Chat.settings.getBool('audible-alert'),
1001 callback: function(){
1002 const v = Chat.settings.toggle('audible-alert');
@@ -1009,103 +1012,73 @@
1009 Chat.settings.getBool('audible-alert') ? F.config.chat.alertSound : false
1010 );
1011 }else{
1012 /* Disabled per chatroom discussion: selection list of audio files for
1013 chat notification. */
1014 const selectSound = settingsOps.selectSound = D.addClass(D.select(), 'menu-entry');
1015 D.disable(D.option(selectSound, "0", "Audible alert..."));
 
 
 
1016 D.option(selectSound, "", "(no audio)");
 
1017 F.config.chat.alerts.forEach(function(a){
1018 D.option(selectSound, a);
1019 });
1020 if(true===Chat.settings.getBool('audible-alert')){
1021 selectSound.selectedIndex = 2/*first audio file in the list*/;
1022 }else{
1023 selectSound.value = Chat.settings.get('audible-alert','');
1024 if(selectSound.selectedIndex<0){
1025 /*Missing file - removed after this setting was applied. Fall back
1026 to the first sound in the list. */
1027 selectSound.selectedIndex = 2;
1028 }
1029 }
1030 selectSound.addEventListener('change',function(){
1031 const v = this.selectedIndex>1 ? this.value : '';
1032 Chat.setNewMessageSound(v);
1033 F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
1034 if(v) setTimeout(()=>Chat.playNewMessageSound(), 50);
1035 settingsPopup.hide();
1036 }, false);
1037 Chat.setNewMessageSound(selectSound.value);
1038 }/*audio notification config*/
1039 /**
1040 Rebuild the menu each time it's shown so that the toggles can
1041 show their current values.
1042 */
1043 settingsPopup.options.refresh = function(){
1044 D.clearElement(this.e);
1045 settingsOps.forEach(function(op){
1046 const line = D.addClass(D.span(), 'menu-entry');
1047 const btn = D.append(D.addClass(D.span(), 'button'), op.label);
1048 const callback = function(ev){
1049 settingsPopup.hide();
1050 op.callback(ev);
1051 if(op.persistentSetting){
1052 Chat.settings.set(op.persistentSetting, op.boolValue());
1053 }
1054 };
1055 D.append(line, btn);
1056 if(op.hasOwnProperty('boolValue')){
1057 const check = D.attr(D.checkbox(1, op.boolValue()),
1058 'aria-label', op.label);
1059 D.append(line, check);
1060 }
1061 D.append(settingsPopup.e, line);
1062 line.addEventListener('click', callback);
1063 });
1064 if(settingsOps.selectSound){
1065 D.append(settingsPopup.e, settingsOps.selectSound);
1066 }
1067 };
1068 settingsPopup.installHideHandlers(
1069 false, settingsOps.selectSound ? false : true,
1070 true)
1071 /** Reminder: click-to-hide interferes with "?" embedded within
1072 the popup, so cannot be used together with those. Enabling
1073 this means, however, that tapping the menu button to toggle
1074 the menu cannot work because tapping the menu button while the
1075 menu is opened will, because of the click-to-hide handler,
1076 hide the menu before the button gets an event saying to toggle
1077 it.
1078
1079 Reminder: because we need a SELECT element for the audio file
1080 selection (since that list can be arbitrarily long), we have
1081 to disable tap-outside-the-popup-to-close-it via passing false
1082 as the 2nd argument to installHideHandlers(). If we don't,
1083 tapping on the select element is unreliable on desktop
1084 browsers and doesn't seem to work at all on mobile. */;
1085 D.attr(settingsButton, 'role', 'button');
1086 settingsButton.addEventListener('click',function(ev){
1087 //ev.preventDefault();
1088 if(settingsPopup.isShown()) settingsPopup.hide();
1089 else settingsPopup.show(settingsButton);
1090 /* Reminder: we cannot toggle the visibility from her
1091 */
1092 }, false);
1093
1094 /* Find an ideal X/Y position for the popup, directly above the settings
1095 button, based on the size of the popup... */
1096 settingsPopup.show(document.body);
1097 popupSize = settingsPopup.e.getBoundingClientRect();
1098 settingsPopup.hide();
1099 settingsPopup.options.adjustX = function(x){
1100 const rect = settingsButton.getBoundingClientRect();
1101 return rect.right - popupSize.width;
1102 };
1103 settingsPopup.options.adjustY = function(y){
1104 const rect = settingsButton.getBoundingClientRect();
1105 return rect.top - popupSize.height -2;
1106 };
1107 })()/*#chat-settings-button setup*/;
1108
1109 /** Callback for poll() to inject new content into the page. jx ==
1110 the response from /chat-poll. If atEnd is true, the message is
1111 appended to the end of the chat list (for loading older
1112
--- src/chat.js
+++ src/chat.js
@@ -109,11 +109,12 @@
109 btnSubmit: E1('#chat-message-submit'),
110 inputSingle: E1('#chat-input-single'),
111 inputMulti: E1('#chat-input-multi'),
112 inputCurrent: undefined/*one of inputSingle or inputMulti*/,
113 inputFile: E1('#chat-input-file'),
114 contentDiv: E1('div.content'),
115 configArea: E1('#chat-config')
116 },
117 me: F.user.name,
118 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
119 mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
120 pageIsActive: 'visible'===document.visibilityState,
@@ -385,11 +386,11 @@
386 this.
387 */
388 setNewMessageSound: function f(uri){
389 delete this.playNewMessageSound.audio;
390 this.playNewMessageSound.uri = uri;
391 this.settings.set('audible-alert', uri);
392 return this;
393 }
394 };
395 F.fetch.beforesend = ()=>cs.ajaxStart();
396 F.fetch.aftersend = ()=>cs.ajaxEnd();
@@ -950,14 +951,16 @@
951 .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' zulu');
952 };
953
954 (function(){/*Set up #chat-settings-button */
955 const settingsButton = document.querySelector('#chat-settings-button');
956 const optionsMenu = E1('#chat-config-options');
957 const cbToggle = function(){
958 D.toggleClass([Chat.e.messagesWrapper, Chat.e.configArea], 'hidden');
959 };
960 D.attr(settingsButton, 'role', 'button').addEventListener('click', cbToggle, false);
961 Chat.e.configArea.querySelector('button').addEventListener('click', cbToggle, false);
962 /* Settings menu entries... */
963 const settingsOps = [{
964 label: "Multi-line input",
965 boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
966 persistentSetting: 'edit-multiline',
@@ -992,11 +995,11 @@
995 F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
996 }
997 }];
998
999 /** Set up selection list of notification sounds. */
1000 if(false/*flip this to false to enable selection of audio files*/){
1001 settingsOps.push({
1002 label: "Audible alerts",
1003 boolValue: ()=>Chat.settings.getBool('audible-alert'),
1004 callback: function(){
1005 const v = Chat.settings.toggle('audible-alert');
@@ -1009,103 +1012,73 @@
1012 Chat.settings.getBool('audible-alert') ? F.config.chat.alertSound : false
1013 );
1014 }else{
1015 /* Disabled per chatroom discussion: selection list of audio files for
1016 chat notification. */
1017 settingsOps.selectSound = D.addClass(D.div(), 'menu-entry');
1018 const selectSound = D.select();
1019 D.append(settingsOps.selectSound,
1020 D.append(D.span(),"Audio alert"),
1021 selectSound);
1022 D.option(selectSound, "", "(no audio)");
1023 const firstSoundIndex = selectSound.options.length;
1024 F.config.chat.alerts.forEach(function(a){
1025 D.option(selectSound, a);
1026 });
1027 if(true===Chat.settings.getBool('audible-alert')){
1028 selectSound.selectedIndex = firstSoundIndex;
1029 }else{
1030 selectSound.value = Chat.settings.get('audible-alert','');
1031 if(selectSound.selectedIndex<0){
1032 /*Missing file - removed after this setting was applied. Fall back
1033 to the first sound in the list. */
1034 selectSound.selectedIndex = firstSoundIndex;
1035 }
1036 }
1037 selectSound.addEventListener('change',function(){
1038 const v = this.value;
1039 Chat.setNewMessageSound(v);
1040 F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
1041 if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
 
1042 }, false);
1043 Chat.setNewMessageSound(selectSound.value);
1044 }/*audio notification config*/
1045 /**
1046 Build list of options...
1047 */
1048 settingsOps.forEach(function f(op){
1049 const line = D.addClass(D.div(), 'menu-entry');
1050 const btn = D.append(
1051 D.addClass(D.label(), 'cbutton'/*bootstrap skin hijacks 'button'*/),
1052 op.label);
1053 const callback = function(ev){
1054 op.callback(ev);
1055 if(op.persistentSetting){
1056 Chat.settings.set(op.persistentSetting, op.boolValue());
1057 }
1058 };
1059 if(op.hasOwnProperty('boolValue')){
1060 if(undefined === f.$id) f.$id = 0;
1061 ++f.$id;
1062 const check = D.attr(D.checkbox(1, op.boolValue()),
1063 'aria-label', op.label);
1064 const id = 'cfgopt'+f.$id;
1065 if(op.boolValue()) check.checked = true;
1066 D.attr(check, 'id', id);
1067 D.attr(btn, 'for', id);
1068 D.append(line, check);
1069 check.addEventListener('change', callback);
1070 }else{
1071 line.addEventListener('click', callback);
1072 }
1073 D.append(line, btn);
1074 D.append(optionsMenu, line);
1075 });
1076 if(settingsOps.selectSound){
1077 D.append(optionsMenu, settingsOps.selectSound);
1078 }
1079 //settingsButton.click()/*for for development*/;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1080 })()/*#chat-settings-button setup*/;
1081
1082 /** Callback for poll() to inject new content into the page. jx ==
1083 the response from /chat-poll. If atEnd is true, the message is
1084 appended to the end of the chat list (for loading older
1085
+31 -1
--- src/default.css
+++ src/default.css
@@ -1897,11 +1897,41 @@
18971897
18981898
body.chat #chat-drop-details img {
18991899
max-width: 45%;
19001900
max-height: 45%;
19011901
}
1902
-
1902
+body.chat #chat-config {
1903
+ /* /chat configuration widget */
1904
+ display: flex;
1905
+ flex-direction: column;
1906
+ flex: 1 0 auto;
1907
+ overflow: auto;
1908
+ flex: 2 1 auto;
1909
+ padding: 0 0.25em;
1910
+}
1911
+body.chat #chat-config > button {
1912
+ padding: 0.5em;
1913
+ flex: 0 1 auto;
1914
+ margin: 0.25em 0;
1915
+}
1916
+body.chat #chat-config #chat-config-options {
1917
+ /* /chat config options go here */
1918
+ flex: 1 1 auto;
1919
+ display: flex;
1920
+ flex-direction: column;
1921
+ overflow: auto;
1922
+}
1923
+body.chat #chat-config #chat-config-options .menu-entry {
1924
+ display: flex;
1925
+ align-items: center;
1926
+ flex-direction: row;
1927
+ flex-wrap: wrap;
1928
+ padding: 1em;
1929
+}
1930
+body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
1931
+ margin-right: 1em;
1932
+}
19031933
input[type="checkbox"].diff-toggle {
19041934
float: right;
19051935
}
19061936
19071937
body.branch .brlist > table > tbody > tr:hover:not(.selected),
19081938
--- src/default.css
+++ src/default.css
@@ -1897,11 +1897,41 @@
1897
1898 body.chat #chat-drop-details img {
1899 max-width: 45%;
1900 max-height: 45%;
1901 }
1902
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1903 input[type="checkbox"].diff-toggle {
1904 float: right;
1905 }
1906
1907 body.branch .brlist > table > tbody > tr:hover:not(.selected),
1908
--- src/default.css
+++ src/default.css
@@ -1897,11 +1897,41 @@
1897
1898 body.chat #chat-drop-details img {
1899 max-width: 45%;
1900 max-height: 45%;
1901 }
1902 body.chat #chat-config {
1903 /* /chat configuration widget */
1904 display: flex;
1905 flex-direction: column;
1906 flex: 1 0 auto;
1907 overflow: auto;
1908 flex: 2 1 auto;
1909 padding: 0 0.25em;
1910 }
1911 body.chat #chat-config > button {
1912 padding: 0.5em;
1913 flex: 0 1 auto;
1914 margin: 0.25em 0;
1915 }
1916 body.chat #chat-config #chat-config-options {
1917 /* /chat config options go here */
1918 flex: 1 1 auto;
1919 display: flex;
1920 flex-direction: column;
1921 overflow: auto;
1922 }
1923 body.chat #chat-config #chat-config-options .menu-entry {
1924 display: flex;
1925 align-items: center;
1926 flex-direction: row;
1927 flex-wrap: wrap;
1928 padding: 1em;
1929 }
1930 body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
1931 margin-right: 1em;
1932 }
1933 input[type="checkbox"].diff-toggle {
1934 float: right;
1935 }
1936
1937 body.branch .brlist > table > tbody > tr:hover:not(.selected),
1938
--- www/changes.wiki
+++ www/changes.wiki
@@ -36,10 +36,13 @@
3636
* The [/help?cmd=wiki|wiki list command] no longer lists "deleted"
3737
pages by default. Use the new <tt>--all</tt> option to include deleted
3838
pages in the output.
3939
* The [/help?cmd=all|fossil all git status] command only shows reports for
4040
the subset of repositories that have a configured Git export.
41
+ * Enhanced the [/help?cmd=/chat|/chat page] configuration and added the ability
42
+ for a repository administrator to [./chat.md#notifications|extend the
43
+ selection of notification sounds via unversioned files].
4144
4245
<h2 id='v2_16'>Changes for Version 2.16 (2021-07-02)</h2>
4346
* <b>Security:</b> Fix the client-side TLS so that it verifies that the
4447
server hostname matches its certificate.
4548
* The default "ssh" command on Windows is changed to "ssh" instead of the
4649
--- www/changes.wiki
+++ www/changes.wiki
@@ -36,10 +36,13 @@
36 * The [/help?cmd=wiki|wiki list command] no longer lists "deleted"
37 pages by default. Use the new <tt>--all</tt> option to include deleted
38 pages in the output.
39 * The [/help?cmd=all|fossil all git status] command only shows reports for
40 the subset of repositories that have a configured Git export.
 
 
 
41
42 <h2 id='v2_16'>Changes for Version 2.16 (2021-07-02)</h2>
43 * <b>Security:</b> Fix the client-side TLS so that it verifies that the
44 server hostname matches its certificate.
45 * The default "ssh" command on Windows is changed to "ssh" instead of the
46
--- www/changes.wiki
+++ www/changes.wiki
@@ -36,10 +36,13 @@
36 * The [/help?cmd=wiki|wiki list command] no longer lists "deleted"
37 pages by default. Use the new <tt>--all</tt> option to include deleted
38 pages in the output.
39 * The [/help?cmd=all|fossil all git status] command only shows reports for
40 the subset of repositories that have a configured Git export.
41 * Enhanced the [/help?cmd=/chat|/chat page] configuration and added the ability
42 for a repository administrator to [./chat.md#notifications|extend the
43 selection of notification sounds via unversioned files].
44
45 <h2 id='v2_16'>Changes for Version 2.16 (2021-07-02)</h2>
46 * <b>Security:</b> Fix the client-side TLS so that it verifies that the
47 server hostname matches its certificate.
48 * The default "ssh" command on Windows is changed to "ssh" instead of the
49
--- www/chat.md
+++ www/chat.md
@@ -99,10 +99,18 @@
9999
is reloaded. The user who posted a given message, or any Admin users,
100100
may additionally choose to globally delete a message from the chat
101101
record, which deletes it not only from their own browser but also
102102
propagates the removal to all connected clients the next time they
103103
poll for new messages.
104
+
105
+### <a id='notifications'></a>Customizing New-message Notification Sounds
106
+
107
+By default, the list of new-message notification sounds is limited to
108
+a few built in to the fossil binary. In addition, any
109
+[unversioned files](./unvers.wiki) named `alert-sounds/*.{mp3,wav,ogg}`
110
+will be included in that list. To switch sounds, tap the "settings"
111
+button.
104112
105113
## Implementation Details
106114
107115
*You do not need to understand how Fossil chat works in order to use it.
108116
But many developers prefer to know how their tools work.
109117
--- www/chat.md
+++ www/chat.md
@@ -99,10 +99,18 @@
99 is reloaded. The user who posted a given message, or any Admin users,
100 may additionally choose to globally delete a message from the chat
101 record, which deletes it not only from their own browser but also
102 propagates the removal to all connected clients the next time they
103 poll for new messages.
 
 
 
 
 
 
 
 
104
105 ## Implementation Details
106
107 *You do not need to understand how Fossil chat works in order to use it.
108 But many developers prefer to know how their tools work.
109
--- www/chat.md
+++ www/chat.md
@@ -99,10 +99,18 @@
99 is reloaded. The user who posted a given message, or any Admin users,
100 may additionally choose to globally delete a message from the chat
101 record, which deletes it not only from their own browser but also
102 propagates the removal to all connected clients the next time they
103 poll for new messages.
104
105 ### <a id='notifications'></a>Customizing New-message Notification Sounds
106
107 By default, the list of new-message notification sounds is limited to
108 a few built in to the fossil binary. In addition, any
109 [unversioned files](./unvers.wiki) named `alert-sounds/*.{mp3,wav,ogg}`
110 will be included in that list. To switch sounds, tap the "settings"
111 button.
112
113 ## Implementation Details
114
115 *You do not need to understand how Fossil chat works in order to use it.
116 But many developers prefer to know how their tools work.
117

Keyboard Shortcuts

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