Fossil SCM

Initial go at a preview mode for /chat. Still some UI/UX decisions to be made.

stephan 2021-09-21 11:20 chat-markdown
Commit f33eefe2bb8bf14e00a61b9c8add780ac2db308d4ea21acf277fdee536fab943
4 files changed +5 -3 +14 -5 +84 -33 +47 -13
+5 -3
--- src/ajax.c
+++ src/ajax.c
@@ -264,16 +264,18 @@
264264
}
265265
return rc;
266266
}
267267
268268
/*
269
-** AJAX route /ajax/preview-wiki
269
+** AJAX route /ajax/preview-text
270270
**
271271
** Required query parameters:
272272
**
273273
** filename=name of content, for use in determining the
274
-** mimetype/render mode. content=text
274
+** mimetype/render mode.
275
+**
276
+** content=text
275277
**
276278
** Optional query parameters:
277279
**
278280
** render_mode=integer (AJAX_RENDER_xxx) (default=AJAX_RENDER_GUESS)
279281
**
@@ -365,11 +367,11 @@
365367
const AjaxRoute * rB = (const AjaxRoute*)b;
366368
return fossil_strcmp(rA->zName, rB->zName);
367369
}
368370
369371
/*
370
-** WEBPAGE: ajax
372
+** WEBPAGE: ajax hidden
371373
**
372374
** The main dispatcher for shared ajax-served routes. Requires the
373375
** 'name' parameter be the main route's name (as defined in a list in
374376
** this function), noting that fossil automatically assigns all path
375377
** parts after "ajax" to "name", e.g. /ajax/foo/bar assigns
376378
--- src/ajax.c
+++ src/ajax.c
@@ -264,16 +264,18 @@
264 }
265 return rc;
266 }
267
268 /*
269 ** AJAX route /ajax/preview-wiki
270 **
271 ** Required query parameters:
272 **
273 ** filename=name of content, for use in determining the
274 ** mimetype/render mode. content=text
 
 
275 **
276 ** Optional query parameters:
277 **
278 ** render_mode=integer (AJAX_RENDER_xxx) (default=AJAX_RENDER_GUESS)
279 **
@@ -365,11 +367,11 @@
365 const AjaxRoute * rB = (const AjaxRoute*)b;
366 return fossil_strcmp(rA->zName, rB->zName);
367 }
368
369 /*
370 ** WEBPAGE: ajax
371 **
372 ** The main dispatcher for shared ajax-served routes. Requires the
373 ** 'name' parameter be the main route's name (as defined in a list in
374 ** this function), noting that fossil automatically assigns all path
375 ** parts after "ajax" to "name", e.g. /ajax/foo/bar assigns
376
--- src/ajax.c
+++ src/ajax.c
@@ -264,16 +264,18 @@
264 }
265 return rc;
266 }
267
268 /*
269 ** AJAX route /ajax/preview-text
270 **
271 ** Required query parameters:
272 **
273 ** filename=name of content, for use in determining the
274 ** mimetype/render mode.
275 **
276 ** content=text
277 **
278 ** Optional query parameters:
279 **
280 ** render_mode=integer (AJAX_RENDER_xxx) (default=AJAX_RENDER_GUESS)
281 **
@@ -365,11 +367,11 @@
367 const AjaxRoute * rB = (const AjaxRoute*)b;
368 return fossil_strcmp(rA->zName, rB->zName);
369 }
370
371 /*
372 ** WEBPAGE: ajax hidden
373 **
374 ** The main dispatcher for shared ajax-served routes. Requires the
375 ** 'name' parameter be the main route's name (as defined in a list in
376 ** this function), noting that fossil automatically assigns all path
377 ** parts after "ajax" to "name", e.g. /ajax/foo/bar assigns
378
+14 -5
--- src/chat.c
+++ src/chat.c
@@ -154,20 +154,23 @@
154154
zProjectName = db_get("project-name","Unnamed project");
155155
style_set_current_feature("chat");
156156
style_header("Chat");
157157
@ <form accept-encoding="utf-8" id="chat-form" autocomplete="off">
158158
@ <div id='chat-input-area'>
159
- @ <div id='chat-input-line'>
159
+ @ <div id='chat-input-line' class='single-line'>
160160
@ <input type="text" name="msg" id="chat-input-single" \
161161
@ placeholder="Type markdown-formatted message for %h(zProjectName)." \
162162
@ autocomplete="off">
163163
@ <textarea rows="8" id="chat-input-multi" \
164164
@ placeholder="Type markdown-formatted message for %h(zProjectName). Ctrl-Enter sends it." \
165165
@ class="hidden"></textarea>
166
- @ <input type="submit" value="Send" id="chat-message-submit">
167
- @ <span id="chat-settings-button" class="settings-icon" \
168
- @ aria-label="Settings..." aria-haspopup="true" ></span>
166
+ @ <div id='chat-edit-buttons'>
167
+ @ <button id="chat-preview-button">Preview</button>
168
+ @ <span id="chat-settings-button" class="settings-icon" \
169
+ @ aria-label="Settings..." aria-haspopup="true" ></span>
170
+ @ <button id="chat-message-submit">Send</button>
171
+ @ </div>
169172
@ </div>
170173
@ <div id='chat-input-file-area'>
171174
@ <div class='file-selection-wrapper'>
172175
@ <div class='help-buttonlet'>
173176
@ Select a file to upload, drag/drop a file into this spot,
@@ -178,14 +181,19 @@
178181
@ </div>
179182
@ <div id="chat-drop-details"></div>
180183
@ </div>
181184
@ </div>
182185
@ </form>
186
+ @ <div id='chat-preview' class='hidden'>
187
+ @ <header>Preview:</header>
188
+ @ <div id='chat-preview-content' class='message-widget-content'></div>
189
+ @ <div id='chat-preview-buttons'><button id='chat-preview-close'>Close Preview</button></div>
190
+ @ </div>
183191
@ <div id='chat-config' class='hidden'>
184192
@ <div id='chat-config-options'></div>
185193
/* ^^^populated client-side */
186
- @ <button>Close</button>
194
+ @ <button>Close Settings</button>
187195
@ </div>
188196
@ <div id='chat-messages-wrapper'>
189197
/* New chat messages get inserted immediately after this element */
190198
@ <span id='message-inject-point'></span>
191199
@ </div>
@@ -203,10 +211,11 @@
203211
@ fromcli: %h(PB("cli")?"true":"false"),
204212
@ alertSound: "%h(zAlert)",
205213
@ initSize: %d(db_get_int("chat-initial-history",50)),
206214
@ imagesInline: !!%d(db_get_boolean("chat-inline-images",1))
207215
@ };
216
+ ajax_emit_js_preview_modes(0);
208217
chat_emit_alert_list();
209218
cgi_append_content(builtin_text("chat.js"),-1);
210219
@ }, false);
211220
@ </script>
212221
213222
--- src/chat.c
+++ src/chat.c
@@ -154,20 +154,23 @@
154 zProjectName = db_get("project-name","Unnamed project");
155 style_set_current_feature("chat");
156 style_header("Chat");
157 @ <form accept-encoding="utf-8" id="chat-form" autocomplete="off">
158 @ <div id='chat-input-area'>
159 @ <div id='chat-input-line'>
160 @ <input type="text" name="msg" id="chat-input-single" \
161 @ placeholder="Type markdown-formatted message for %h(zProjectName)." \
162 @ autocomplete="off">
163 @ <textarea rows="8" id="chat-input-multi" \
164 @ placeholder="Type markdown-formatted message for %h(zProjectName). Ctrl-Enter sends it." \
165 @ class="hidden"></textarea>
166 @ <input type="submit" value="Send" id="chat-message-submit">
167 @ <span id="chat-settings-button" class="settings-icon" \
168 @ aria-label="Settings..." aria-haspopup="true" ></span>
 
 
 
169 @ </div>
170 @ <div id='chat-input-file-area'>
171 @ <div class='file-selection-wrapper'>
172 @ <div class='help-buttonlet'>
173 @ Select a file to upload, drag/drop a file into this spot,
@@ -178,14 +181,19 @@
178 @ </div>
179 @ <div id="chat-drop-details"></div>
180 @ </div>
181 @ </div>
182 @ </form>
 
 
 
 
 
183 @ <div id='chat-config' class='hidden'>
184 @ <div id='chat-config-options'></div>
185 /* ^^^populated client-side */
186 @ <button>Close</button>
187 @ </div>
188 @ <div id='chat-messages-wrapper'>
189 /* New chat messages get inserted immediately after this element */
190 @ <span id='message-inject-point'></span>
191 @ </div>
@@ -203,10 +211,11 @@
203 @ fromcli: %h(PB("cli")?"true":"false"),
204 @ alertSound: "%h(zAlert)",
205 @ initSize: %d(db_get_int("chat-initial-history",50)),
206 @ imagesInline: !!%d(db_get_boolean("chat-inline-images",1))
207 @ };
 
208 chat_emit_alert_list();
209 cgi_append_content(builtin_text("chat.js"),-1);
210 @ }, false);
211 @ </script>
212
213
--- src/chat.c
+++ src/chat.c
@@ -154,20 +154,23 @@
154 zProjectName = db_get("project-name","Unnamed project");
155 style_set_current_feature("chat");
156 style_header("Chat");
157 @ <form accept-encoding="utf-8" id="chat-form" autocomplete="off">
158 @ <div id='chat-input-area'>
159 @ <div id='chat-input-line' class='single-line'>
160 @ <input type="text" name="msg" id="chat-input-single" \
161 @ placeholder="Type markdown-formatted message for %h(zProjectName)." \
162 @ autocomplete="off">
163 @ <textarea rows="8" id="chat-input-multi" \
164 @ placeholder="Type markdown-formatted message for %h(zProjectName). Ctrl-Enter sends it." \
165 @ class="hidden"></textarea>
166 @ <div id='chat-edit-buttons'>
167 @ <button id="chat-preview-button">Preview</button>
168 @ <span id="chat-settings-button" class="settings-icon" \
169 @ aria-label="Settings..." aria-haspopup="true" ></span>
170 @ <button id="chat-message-submit">Send</button>
171 @ </div>
172 @ </div>
173 @ <div id='chat-input-file-area'>
174 @ <div class='file-selection-wrapper'>
175 @ <div class='help-buttonlet'>
176 @ Select a file to upload, drag/drop a file into this spot,
@@ -178,14 +181,19 @@
181 @ </div>
182 @ <div id="chat-drop-details"></div>
183 @ </div>
184 @ </div>
185 @ </form>
186 @ <div id='chat-preview' class='hidden'>
187 @ <header>Preview:</header>
188 @ <div id='chat-preview-content' class='message-widget-content'></div>
189 @ <div id='chat-preview-buttons'><button id='chat-preview-close'>Close Preview</button></div>
190 @ </div>
191 @ <div id='chat-config' class='hidden'>
192 @ <div id='chat-config-options'></div>
193 /* ^^^populated client-side */
194 @ <button>Close Settings</button>
195 @ </div>
196 @ <div id='chat-messages-wrapper'>
197 /* New chat messages get inserted immediately after this element */
198 @ <span id='message-inject-point'></span>
199 @ </div>
@@ -203,10 +211,11 @@
211 @ fromcli: %h(PB("cli")?"true":"false"),
212 @ alertSound: "%h(zAlert)",
213 @ initSize: %d(db_get_int("chat-initial-history",50)),
214 @ imagesInline: !!%d(db_get_boolean("chat-inline-images",1))
215 @ };
216 ajax_emit_js_preview_modes(0);
217 chat_emit_alert_list();
218 cgi_append_content(builtin_text("chat.js"),-1);
219 @ }, false);
220 @ </script>
221
222
+84 -33
--- src/chat.js
+++ src/chat.js
@@ -101,20 +101,24 @@
101101
e:{/*map of certain DOM elements.*/
102102
messageInjectPoint: E1('#message-inject-point'),
103103
pageTitle: E1('head title'),
104104
loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
105105
inputWrapper: E1("#chat-input-area"),
106
+ inputLine: E1('#chat-input-line'),
106107
fileSelectWrapper: E1('#chat-input-file-area'),
107108
messagesWrapper: E1('#chat-messages-wrapper'),
108109
inputForm: E1('#chat-form'),
109110
btnSubmit: E1('#chat-message-submit'),
110111
inputSingle: E1('#chat-input-single'),
111112
inputMulti: E1('#chat-input-multi'),
112113
inputCurrent: undefined/*one of inputSingle or inputMulti*/,
113114
inputFile: E1('#chat-input-file'),
114115
contentDiv: E1('div.content'),
115
- configArea: E1('#chat-config')
116
+ configArea: E1('#chat-config'),
117
+ previewArea: E1('#chat-preview'),
118
+ previewContent: E1('#chat-preview-content'),
119
+ btnPreview: E1('#chat-preview-button')
116120
},
117121
me: F.user.name,
118122
mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
119123
mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
120124
pageIsActive: 'visible'===document.visibilityState,
@@ -147,12 +151,14 @@
147151
/** Toggles between single- and multi-line edit modes. Returns this. */
148152
inputToggleSingleMulti: function(){
149153
const old = this.e.inputCurrent;
150154
if(this.e.inputCurrent === this.e.inputSingle){
151155
this.e.inputCurrent = this.e.inputMulti;
156
+ this.e.inputLine.classList.remove('single-line');
152157
}else{
153158
this.e.inputCurrent = this.e.inputSingle;
159
+ this.e.inputLine.classList.add('single-line');
154160
}
155161
const m = this.e.messagesWrapper,
156162
sTop = m.scrollTop,
157163
mh1 = m.clientHeight;
158164
D.addClass(old, 'hidden');
@@ -947,10 +953,11 @@
947953
*/
948954
Chat.submitMessage = function f(){
949955
if(!f.spaces){
950956
f.spaces = /\s+$/;
951957
}
958
+ this.revealPreview(false);
952959
const fd = new FormData(this.e.inputForm)
953960
/* ^^^^ we don't really want/need the FORM element, but when
954961
FormData() is default-constructed here then the server
955962
segfaults, and i have no clue why! */;
956963
var msg = this.inputValue().trim();
@@ -1019,11 +1026,17 @@
10191026
10201027
(function(){/*Set up #chat-settings-button */
10211028
const settingsButton = document.querySelector('#chat-settings-button');
10221029
const optionsMenu = E1('#chat-config-options');
10231030
const cbToggle = function(){
1024
- D.toggleClass([Chat.e.messagesWrapper, Chat.e.configArea], 'hidden');
1031
+ if(Chat.e.configArea.classList.contains('hidden')){
1032
+ D.removeClass(Chat.e.configArea, 'hidden');
1033
+ D.addClass([Chat.e.messagesWrapper, Chat.e.previewArea], 'hidden');
1034
+ }else{
1035
+ D.addClass(Chat.e.configArea, 'hidden');
1036
+ D.removeClass(Chat.e.messagesWrapper, 'hidden');
1037
+ }
10251038
};
10261039
D.attr(settingsButton, 'role', 'button').addEventListener('click', cbToggle, false);
10271040
Chat.e.configArea.querySelector('button').addEventListener('click', cbToggle, false);
10281041
/* Settings menu entries... */
10291042
const settingsOps = [{
@@ -1061,11 +1074,11 @@
10611074
F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
10621075
}
10631076
}];
10641077
10651078
/** Set up selection list of notification sounds. */
1066
- if(false/*flip this to false to enable selection of audio files*/){
1079
+ if(1){
10671080
settingsOps.push({
10681081
label: "Audible alerts",
10691082
boolValue: ()=>Chat.settings.getBool('audible-alert'),
10701083
callback: function(){
10711084
const v = Chat.settings.toggle('audible-alert');
@@ -1075,40 +1088,10 @@
10751088
}
10761089
});
10771090
Chat.setNewMessageSound(
10781091
Chat.settings.getBool('audible-alert') ? F.config.chat.alertSound : false
10791092
);
1080
- }else{
1081
- /* Disabled per chatroom discussion: selection list of audio files for
1082
- chat notification. */
1083
- settingsOps.selectSound = D.addClass(D.div(), 'menu-entry');
1084
- const selectSound = D.select();
1085
- D.append(settingsOps.selectSound,
1086
- D.append(D.span(),"Audio alert"),
1087
- selectSound);
1088
- D.option(selectSound, "", "(no audio)");
1089
- const firstSoundIndex = selectSound.options.length;
1090
- F.config.chat.alerts.forEach(function(a){
1091
- D.option(selectSound, a);
1092
- });
1093
- if(true===Chat.settings.getBool('audible-alert')){
1094
- selectSound.selectedIndex = firstSoundIndex;
1095
- }else{
1096
- selectSound.value = Chat.settings.get('audible-alert','');
1097
- if(selectSound.selectedIndex<0){
1098
- /*Missing file - removed after this setting was applied. Fall back
1099
- to the first sound in the list. */
1100
- selectSound.selectedIndex = firstSoundIndex;
1101
- }
1102
- }
1103
- selectSound.addEventListener('change',function(){
1104
- const v = this.value;
1105
- Chat.setNewMessageSound(v);
1106
- F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
1107
- if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
1108
- }, false);
1109
- Chat.setNewMessageSound(selectSound.value);
11101093
}/*audio notification config*/
11111094
/**
11121095
Build list of options...
11131096
*/
11141097
settingsOps.forEach(function f(op){
@@ -1142,10 +1125,78 @@
11421125
if(settingsOps.selectSound){
11431126
D.append(optionsMenu, settingsOps.selectSound);
11441127
}
11451128
//settingsButton.click()/*for for development*/;
11461129
})()/*#chat-settings-button setup*/;
1130
+
1131
+ (function(){/*set up message preview*/
1132
+ const btnPreview = Chat.e.btnPreview;
1133
+ Chat.setPreviewText = function(t){
1134
+ this.revealPreview(true).e.previewContent.innerHTML = t;
1135
+ };
1136
+ /**
1137
+ Reveals preview area if showIt is true, else hides it.
1138
+ This also shows/hides other elements, "as appropriate."
1139
+ */
1140
+ Chat.revealPreview = function(showIt){
1141
+ if(showIt){
1142
+ D.removeClass(Chat.e.previewArea, 'hidden');
1143
+ D.addClass([Chat.e.messagesWrapper, Chat.e.configArea],
1144
+ 'hidden');
1145
+ }else{
1146
+ D.addClass(Chat.e.previewArea, 'hidden');
1147
+ D.removeClass(Chat.e.messagesWrapper, 'hidden');
1148
+ }
1149
+ return this;
1150
+ };
1151
+ Chat.e.previewArea.querySelector('#chat-preview-close').
1152
+ addEventListener('click', ()=>Chat.revealPreview(false), false);
1153
+ let previewPending = false;
1154
+ const elemsToEnable = [
1155
+ btnPreview, Chat.e.btnSubmit,
1156
+ Chat.e.inputSingle, Chat.e.inputMulti];
1157
+ Chat.disableDuringAjax.push(btnPreview);
1158
+ const submit = function(ev){
1159
+ ev.preventDefault();
1160
+ ev.stopPropagation();
1161
+ if(previewPending) return false;
1162
+ const txt = Chat.e.inputCurrent.value;
1163
+ if(!txt){
1164
+ Chat.setPreviewText('');
1165
+ previewPending = false;
1166
+ return false;
1167
+ }
1168
+ const fd = new FormData();
1169
+ fd.append('content', txt);
1170
+ fd.append('filename','chat.md'
1171
+ /*filename needed for mimetype determination*/);
1172
+ fd.append('render_mode',F.page.previewModes.wiki);
1173
+ F.fetch('ajax/preview-text',{
1174
+ payload: fd,
1175
+ onload: (html)=>Chat.setPreviewText(html),
1176
+ onerror: function(e){
1177
+ F.fetch.onerror(e);
1178
+ Chat.setPreviewText("ERROR: "+(
1179
+ e.message || 'Unknown error fetching preview!'
1180
+ ));
1181
+ },
1182
+ beforesend: function(){
1183
+ D.disable(elemsToEnable);
1184
+ Chat.ajaxStart();
1185
+ previewPending = true;
1186
+ Chat.setPreviewText("Loading preview...");
1187
+ },
1188
+ aftersend:function(){
1189
+ previewPending = false;
1190
+ D.enable(elemsToEnable);
1191
+ Chat.ajaxEnd();
1192
+ }
1193
+ });
1194
+ return false;
1195
+ };
1196
+ btnPreview.addEventListener('click', submit, false);
1197
+ })()/*message preview setup*/;
11471198
11481199
/** Callback for poll() to inject new content into the page. jx ==
11491200
the response from /chat-poll. If atEnd is true, the message is
11501201
appended to the end of the chat list (for loading older
11511202
messages), else the beginning (the default). */
11521203
--- src/chat.js
+++ src/chat.js
@@ -101,20 +101,24 @@
101 e:{/*map of certain DOM elements.*/
102 messageInjectPoint: E1('#message-inject-point'),
103 pageTitle: E1('head title'),
104 loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
105 inputWrapper: E1("#chat-input-area"),
 
106 fileSelectWrapper: E1('#chat-input-file-area'),
107 messagesWrapper: E1('#chat-messages-wrapper'),
108 inputForm: E1('#chat-form'),
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,
@@ -147,12 +151,14 @@
147 /** Toggles between single- and multi-line edit modes. Returns this. */
148 inputToggleSingleMulti: function(){
149 const old = this.e.inputCurrent;
150 if(this.e.inputCurrent === this.e.inputSingle){
151 this.e.inputCurrent = this.e.inputMulti;
 
152 }else{
153 this.e.inputCurrent = this.e.inputSingle;
 
154 }
155 const m = this.e.messagesWrapper,
156 sTop = m.scrollTop,
157 mh1 = m.clientHeight;
158 D.addClass(old, 'hidden');
@@ -947,10 +953,11 @@
947 */
948 Chat.submitMessage = function f(){
949 if(!f.spaces){
950 f.spaces = /\s+$/;
951 }
 
952 const fd = new FormData(this.e.inputForm)
953 /* ^^^^ we don't really want/need the FORM element, but when
954 FormData() is default-constructed here then the server
955 segfaults, and i have no clue why! */;
956 var msg = this.inputValue().trim();
@@ -1019,11 +1026,17 @@
1019
1020 (function(){/*Set up #chat-settings-button */
1021 const settingsButton = document.querySelector('#chat-settings-button');
1022 const optionsMenu = E1('#chat-config-options');
1023 const cbToggle = function(){
1024 D.toggleClass([Chat.e.messagesWrapper, Chat.e.configArea], 'hidden');
 
 
 
 
 
 
1025 };
1026 D.attr(settingsButton, 'role', 'button').addEventListener('click', cbToggle, false);
1027 Chat.e.configArea.querySelector('button').addEventListener('click', cbToggle, false);
1028 /* Settings menu entries... */
1029 const settingsOps = [{
@@ -1061,11 +1074,11 @@
1061 F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
1062 }
1063 }];
1064
1065 /** Set up selection list of notification sounds. */
1066 if(false/*flip this to false to enable selection of audio files*/){
1067 settingsOps.push({
1068 label: "Audible alerts",
1069 boolValue: ()=>Chat.settings.getBool('audible-alert'),
1070 callback: function(){
1071 const v = Chat.settings.toggle('audible-alert');
@@ -1075,40 +1088,10 @@
1075 }
1076 });
1077 Chat.setNewMessageSound(
1078 Chat.settings.getBool('audible-alert') ? F.config.chat.alertSound : false
1079 );
1080 }else{
1081 /* Disabled per chatroom discussion: selection list of audio files for
1082 chat notification. */
1083 settingsOps.selectSound = D.addClass(D.div(), 'menu-entry');
1084 const selectSound = D.select();
1085 D.append(settingsOps.selectSound,
1086 D.append(D.span(),"Audio alert"),
1087 selectSound);
1088 D.option(selectSound, "", "(no audio)");
1089 const firstSoundIndex = selectSound.options.length;
1090 F.config.chat.alerts.forEach(function(a){
1091 D.option(selectSound, a);
1092 });
1093 if(true===Chat.settings.getBool('audible-alert')){
1094 selectSound.selectedIndex = firstSoundIndex;
1095 }else{
1096 selectSound.value = Chat.settings.get('audible-alert','');
1097 if(selectSound.selectedIndex<0){
1098 /*Missing file - removed after this setting was applied. Fall back
1099 to the first sound in the list. */
1100 selectSound.selectedIndex = firstSoundIndex;
1101 }
1102 }
1103 selectSound.addEventListener('change',function(){
1104 const v = this.value;
1105 Chat.setNewMessageSound(v);
1106 F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
1107 if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
1108 }, false);
1109 Chat.setNewMessageSound(selectSound.value);
1110 }/*audio notification config*/
1111 /**
1112 Build list of options...
1113 */
1114 settingsOps.forEach(function f(op){
@@ -1142,10 +1125,78 @@
1142 if(settingsOps.selectSound){
1143 D.append(optionsMenu, settingsOps.selectSound);
1144 }
1145 //settingsButton.click()/*for for development*/;
1146 })()/*#chat-settings-button setup*/;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1147
1148 /** Callback for poll() to inject new content into the page. jx ==
1149 the response from /chat-poll. If atEnd is true, the message is
1150 appended to the end of the chat list (for loading older
1151 messages), else the beginning (the default). */
1152
--- src/chat.js
+++ src/chat.js
@@ -101,20 +101,24 @@
101 e:{/*map of certain DOM elements.*/
102 messageInjectPoint: E1('#message-inject-point'),
103 pageTitle: E1('head title'),
104 loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
105 inputWrapper: E1("#chat-input-area"),
106 inputLine: E1('#chat-input-line'),
107 fileSelectWrapper: E1('#chat-input-file-area'),
108 messagesWrapper: E1('#chat-messages-wrapper'),
109 inputForm: E1('#chat-form'),
110 btnSubmit: E1('#chat-message-submit'),
111 inputSingle: E1('#chat-input-single'),
112 inputMulti: E1('#chat-input-multi'),
113 inputCurrent: undefined/*one of inputSingle or inputMulti*/,
114 inputFile: E1('#chat-input-file'),
115 contentDiv: E1('div.content'),
116 configArea: E1('#chat-config'),
117 previewArea: E1('#chat-preview'),
118 previewContent: E1('#chat-preview-content'),
119 btnPreview: E1('#chat-preview-button')
120 },
121 me: F.user.name,
122 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
123 mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
124 pageIsActive: 'visible'===document.visibilityState,
@@ -147,12 +151,14 @@
151 /** Toggles between single- and multi-line edit modes. Returns this. */
152 inputToggleSingleMulti: function(){
153 const old = this.e.inputCurrent;
154 if(this.e.inputCurrent === this.e.inputSingle){
155 this.e.inputCurrent = this.e.inputMulti;
156 this.e.inputLine.classList.remove('single-line');
157 }else{
158 this.e.inputCurrent = this.e.inputSingle;
159 this.e.inputLine.classList.add('single-line');
160 }
161 const m = this.e.messagesWrapper,
162 sTop = m.scrollTop,
163 mh1 = m.clientHeight;
164 D.addClass(old, 'hidden');
@@ -947,10 +953,11 @@
953 */
954 Chat.submitMessage = function f(){
955 if(!f.spaces){
956 f.spaces = /\s+$/;
957 }
958 this.revealPreview(false);
959 const fd = new FormData(this.e.inputForm)
960 /* ^^^^ we don't really want/need the FORM element, but when
961 FormData() is default-constructed here then the server
962 segfaults, and i have no clue why! */;
963 var msg = this.inputValue().trim();
@@ -1019,11 +1026,17 @@
1026
1027 (function(){/*Set up #chat-settings-button */
1028 const settingsButton = document.querySelector('#chat-settings-button');
1029 const optionsMenu = E1('#chat-config-options');
1030 const cbToggle = function(){
1031 if(Chat.e.configArea.classList.contains('hidden')){
1032 D.removeClass(Chat.e.configArea, 'hidden');
1033 D.addClass([Chat.e.messagesWrapper, Chat.e.previewArea], 'hidden');
1034 }else{
1035 D.addClass(Chat.e.configArea, 'hidden');
1036 D.removeClass(Chat.e.messagesWrapper, 'hidden');
1037 }
1038 };
1039 D.attr(settingsButton, 'role', 'button').addEventListener('click', cbToggle, false);
1040 Chat.e.configArea.querySelector('button').addEventListener('click', cbToggle, false);
1041 /* Settings menu entries... */
1042 const settingsOps = [{
@@ -1061,11 +1074,11 @@
1074 F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
1075 }
1076 }];
1077
1078 /** Set up selection list of notification sounds. */
1079 if(1){
1080 settingsOps.push({
1081 label: "Audible alerts",
1082 boolValue: ()=>Chat.settings.getBool('audible-alert'),
1083 callback: function(){
1084 const v = Chat.settings.toggle('audible-alert');
@@ -1075,40 +1088,10 @@
1088 }
1089 });
1090 Chat.setNewMessageSound(
1091 Chat.settings.getBool('audible-alert') ? F.config.chat.alertSound : false
1092 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1093 }/*audio notification config*/
1094 /**
1095 Build list of options...
1096 */
1097 settingsOps.forEach(function f(op){
@@ -1142,10 +1125,78 @@
1125 if(settingsOps.selectSound){
1126 D.append(optionsMenu, settingsOps.selectSound);
1127 }
1128 //settingsButton.click()/*for for development*/;
1129 })()/*#chat-settings-button setup*/;
1130
1131 (function(){/*set up message preview*/
1132 const btnPreview = Chat.e.btnPreview;
1133 Chat.setPreviewText = function(t){
1134 this.revealPreview(true).e.previewContent.innerHTML = t;
1135 };
1136 /**
1137 Reveals preview area if showIt is true, else hides it.
1138 This also shows/hides other elements, "as appropriate."
1139 */
1140 Chat.revealPreview = function(showIt){
1141 if(showIt){
1142 D.removeClass(Chat.e.previewArea, 'hidden');
1143 D.addClass([Chat.e.messagesWrapper, Chat.e.configArea],
1144 'hidden');
1145 }else{
1146 D.addClass(Chat.e.previewArea, 'hidden');
1147 D.removeClass(Chat.e.messagesWrapper, 'hidden');
1148 }
1149 return this;
1150 };
1151 Chat.e.previewArea.querySelector('#chat-preview-close').
1152 addEventListener('click', ()=>Chat.revealPreview(false), false);
1153 let previewPending = false;
1154 const elemsToEnable = [
1155 btnPreview, Chat.e.btnSubmit,
1156 Chat.e.inputSingle, Chat.e.inputMulti];
1157 Chat.disableDuringAjax.push(btnPreview);
1158 const submit = function(ev){
1159 ev.preventDefault();
1160 ev.stopPropagation();
1161 if(previewPending) return false;
1162 const txt = Chat.e.inputCurrent.value;
1163 if(!txt){
1164 Chat.setPreviewText('');
1165 previewPending = false;
1166 return false;
1167 }
1168 const fd = new FormData();
1169 fd.append('content', txt);
1170 fd.append('filename','chat.md'
1171 /*filename needed for mimetype determination*/);
1172 fd.append('render_mode',F.page.previewModes.wiki);
1173 F.fetch('ajax/preview-text',{
1174 payload: fd,
1175 onload: (html)=>Chat.setPreviewText(html),
1176 onerror: function(e){
1177 F.fetch.onerror(e);
1178 Chat.setPreviewText("ERROR: "+(
1179 e.message || 'Unknown error fetching preview!'
1180 ));
1181 },
1182 beforesend: function(){
1183 D.disable(elemsToEnable);
1184 Chat.ajaxStart();
1185 previewPending = true;
1186 Chat.setPreviewText("Loading preview...");
1187 },
1188 aftersend:function(){
1189 previewPending = false;
1190 D.enable(elemsToEnable);
1191 Chat.ajaxEnd();
1192 }
1193 });
1194 return false;
1195 };
1196 btnPreview.addEventListener('click', submit, false);
1197 })()/*message preview setup*/;
1198
1199 /** Callback for poll() to inject new content into the page. jx ==
1200 the response from /chat-poll. If atEnd is true, the message is
1201 appended to the end of the chat list (for loading older
1202 messages), else the beginning (the default). */
1203
+47 -13
--- src/default.css
+++ src/default.css
@@ -1639,10 +1639,13 @@
16391639
font-family: monospace;
16401640
}
16411641
body.chat .message-widget-content > * {
16421642
margin: 0;
16431643
padding: 0;
1644
+}
1645
+body.chat .message-widget-content > pre {
1646
+ white-space: pre-wrap;
16441647
}
16451648
body.chat .message-widget-content > .markdown > *:first-child {
16461649
margin-top: 0;
16471650
}
16481651
body.chat .message-widget-content > .markdown > *:last-child {
@@ -1849,16 +1852,25 @@
18491852
settings button. */
18501853
body.chat #chat-input-line {
18511854
display: flex;
18521855
flex-direction: row;
18531856
margin-bottom: 0.25em;
1854
- align-items: self-start;
1857
+ align-items: stretch;
1858
+}
1859
+body.chat #chat-edit-buttons {
1860
+ display: flex;
1861
+ flex-direction: column;
1862
+ justify-content: space-between;
1863
+}
1864
+body.chat #chat-input-line.single-line {
1865
+ flex-direction: column;
1866
+}
1867
+body.chat #chat-input-line.single-line #chat-edit-buttons {
1868
+ flex-direction: row;
18551869
}
1856
-body.chat #chat-input-line > input[type=submit],
1857
-body.chat #chat-input-line > #chat-settings-button,
1858
-body.chat #chat-input-line > button {
1859
- flex: 1 5 auto;
1870
+body.chat #chat-edit-buttons > * {
1871
+ /*flex: 1 5 auto;*/
18601872
max-width: 6em;
18611873
margin: 0 0.25em;
18621874
}
18631875
body.chat #chat-input-line > button {
18641876
max-width: 4em;
@@ -1868,10 +1880,11 @@
18681880
max-width: 2em;
18691881
}
18701882
body.chat #chat-input-line > input[type=text],
18711883
body.chat #chat-input-line > textarea {
18721884
flex: 5 1 auto;
1885
+ max-width: revert;
18731886
}
18741887
/* Widget holding the file selection control and preview */
18751888
body.chat #chat-input-file-area {
18761889
display: flex;
18771890
flex-direction: row;
@@ -1911,23 +1924,22 @@
19111924
19121925
body.chat #chat-drop-details img {
19131926
max-width: 45%;
19141927
max-height: 45%;
19151928
}
1916
-body.chat #chat-config {
1929
+
1930
+body.chat #chat-config,
1931
+body.chat #chat-preview {
19171932
/* /chat configuration widget */
19181933
display: flex;
19191934
flex-direction: column;
1920
- flex: 1 0 auto;
19211935
overflow: auto;
19221936
flex: 2 1 auto;
1923
- padding: 0 0.25em;
1924
-}
1925
-body.chat #chat-config > button {
1926
- padding: 0.5em;
1927
- flex: 0 1 auto;
1928
- margin: 0.25em 0;
1937
+ padding: 0;
1938
+ margin: 0;
1939
+ align-items: stretch;
1940
+ min-height: 6em;
19291941
}
19301942
body.chat #chat-config #chat-config-options {
19311943
/* /chat config options go here */
19321944
flex: 1 1 auto;
19331945
display: flex;
@@ -1942,10 +1954,32 @@
19421954
padding: 1em;
19431955
}
19441956
body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
19451957
margin-right: 1em;
19461958
}
1959
+body.chat #chat-preview #chat-preview-content {
1960
+ overflow: auto;
1961
+ flex: 1 1 auto;
1962
+ padding: 0.5em;
1963
+ border: 1px dotted;
1964
+}
1965
+body.chat #chat-preview #chat-preview-content > * {
1966
+ margin: 0;
1967
+ padding: 0;
1968
+}
1969
+body.chat #chat-preview #chat-preview-buttons {
1970
+ flex: 0 1 auto;
1971
+ display: flex;
1972
+ flex-direction: column;
1973
+}
1974
+body.chat #chat-config > button,
1975
+body.chat #chat-preview #chat-preview-buttons > button {
1976
+ padding: 0.5em;
1977
+ flex: 0 1 auto;
1978
+ margin: 0.25em 0;
1979
+}
1980
+
19471981
input[type="checkbox"].diff-toggle {
19481982
float: right;
19491983
}
19501984
19511985
body.branch .brlist > table > tbody > tr:hover:not(.selected),
19521986
--- src/default.css
+++ src/default.css
@@ -1639,10 +1639,13 @@
1639 font-family: monospace;
1640 }
1641 body.chat .message-widget-content > * {
1642 margin: 0;
1643 padding: 0;
 
 
 
1644 }
1645 body.chat .message-widget-content > .markdown > *:first-child {
1646 margin-top: 0;
1647 }
1648 body.chat .message-widget-content > .markdown > *:last-child {
@@ -1849,16 +1852,25 @@
1849 settings button. */
1850 body.chat #chat-input-line {
1851 display: flex;
1852 flex-direction: row;
1853 margin-bottom: 0.25em;
1854 align-items: self-start;
 
 
 
 
 
 
 
 
 
 
 
1855 }
1856 body.chat #chat-input-line > input[type=submit],
1857 body.chat #chat-input-line > #chat-settings-button,
1858 body.chat #chat-input-line > button {
1859 flex: 1 5 auto;
1860 max-width: 6em;
1861 margin: 0 0.25em;
1862 }
1863 body.chat #chat-input-line > button {
1864 max-width: 4em;
@@ -1868,10 +1880,11 @@
1868 max-width: 2em;
1869 }
1870 body.chat #chat-input-line > input[type=text],
1871 body.chat #chat-input-line > textarea {
1872 flex: 5 1 auto;
 
1873 }
1874 /* Widget holding the file selection control and preview */
1875 body.chat #chat-input-file-area {
1876 display: flex;
1877 flex-direction: row;
@@ -1911,23 +1924,22 @@
1911
1912 body.chat #chat-drop-details img {
1913 max-width: 45%;
1914 max-height: 45%;
1915 }
1916 body.chat #chat-config {
 
 
1917 /* /chat configuration widget */
1918 display: flex;
1919 flex-direction: column;
1920 flex: 1 0 auto;
1921 overflow: auto;
1922 flex: 2 1 auto;
1923 padding: 0 0.25em;
1924 }
1925 body.chat #chat-config > button {
1926 padding: 0.5em;
1927 flex: 0 1 auto;
1928 margin: 0.25em 0;
1929 }
1930 body.chat #chat-config #chat-config-options {
1931 /* /chat config options go here */
1932 flex: 1 1 auto;
1933 display: flex;
@@ -1942,10 +1954,32 @@
1942 padding: 1em;
1943 }
1944 body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
1945 margin-right: 1em;
1946 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1947 input[type="checkbox"].diff-toggle {
1948 float: right;
1949 }
1950
1951 body.branch .brlist > table > tbody > tr:hover:not(.selected),
1952
--- src/default.css
+++ src/default.css
@@ -1639,10 +1639,13 @@
1639 font-family: monospace;
1640 }
1641 body.chat .message-widget-content > * {
1642 margin: 0;
1643 padding: 0;
1644 }
1645 body.chat .message-widget-content > pre {
1646 white-space: pre-wrap;
1647 }
1648 body.chat .message-widget-content > .markdown > *:first-child {
1649 margin-top: 0;
1650 }
1651 body.chat .message-widget-content > .markdown > *:last-child {
@@ -1849,16 +1852,25 @@
1852 settings button. */
1853 body.chat #chat-input-line {
1854 display: flex;
1855 flex-direction: row;
1856 margin-bottom: 0.25em;
1857 align-items: stretch;
1858 }
1859 body.chat #chat-edit-buttons {
1860 display: flex;
1861 flex-direction: column;
1862 justify-content: space-between;
1863 }
1864 body.chat #chat-input-line.single-line {
1865 flex-direction: column;
1866 }
1867 body.chat #chat-input-line.single-line #chat-edit-buttons {
1868 flex-direction: row;
1869 }
1870 body.chat #chat-edit-buttons > * {
1871 /*flex: 1 5 auto;*/
 
 
1872 max-width: 6em;
1873 margin: 0 0.25em;
1874 }
1875 body.chat #chat-input-line > button {
1876 max-width: 4em;
@@ -1868,10 +1880,11 @@
1880 max-width: 2em;
1881 }
1882 body.chat #chat-input-line > input[type=text],
1883 body.chat #chat-input-line > textarea {
1884 flex: 5 1 auto;
1885 max-width: revert;
1886 }
1887 /* Widget holding the file selection control and preview */
1888 body.chat #chat-input-file-area {
1889 display: flex;
1890 flex-direction: row;
@@ -1911,23 +1924,22 @@
1924
1925 body.chat #chat-drop-details img {
1926 max-width: 45%;
1927 max-height: 45%;
1928 }
1929
1930 body.chat #chat-config,
1931 body.chat #chat-preview {
1932 /* /chat configuration widget */
1933 display: flex;
1934 flex-direction: column;
 
1935 overflow: auto;
1936 flex: 2 1 auto;
1937 padding: 0;
1938 margin: 0;
1939 align-items: stretch;
1940 min-height: 6em;
 
 
1941 }
1942 body.chat #chat-config #chat-config-options {
1943 /* /chat config options go here */
1944 flex: 1 1 auto;
1945 display: flex;
@@ -1942,10 +1954,32 @@
1954 padding: 1em;
1955 }
1956 body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
1957 margin-right: 1em;
1958 }
1959 body.chat #chat-preview #chat-preview-content {
1960 overflow: auto;
1961 flex: 1 1 auto;
1962 padding: 0.5em;
1963 border: 1px dotted;
1964 }
1965 body.chat #chat-preview #chat-preview-content > * {
1966 margin: 0;
1967 padding: 0;
1968 }
1969 body.chat #chat-preview #chat-preview-buttons {
1970 flex: 0 1 auto;
1971 display: flex;
1972 flex-direction: column;
1973 }
1974 body.chat #chat-config > button,
1975 body.chat #chat-preview #chat-preview-buttons > button {
1976 padding: 0.5em;
1977 flex: 0 1 auto;
1978 margin: 0.25em 0;
1979 }
1980
1981 input[type="checkbox"].diff-toggle {
1982 float: right;
1983 }
1984
1985 body.branch .brlist > table > tbody > tr:hover:not(.selected),
1986

Keyboard Shortcuts

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