Fossil SCM

chat: added preliminary audio notification support (may require toggling that capability on for a given server in the browser). Center-aligned chat error messages and removed the 'fossil' user name from them, for compatibility with upcoming timeline-style notifications. Added wav files to the binary-glob versioned setting.

stephan 2021-01-03 11:08 trunk
Commit 99caeec643bd1fc87da245180b1bb9b81fcabe500230d40a2a359b8dde0c8c48
--- .fossil-settings/binary-glob
+++ .fossil-settings/binary-glob
@@ -3,9 +3,10 @@
33
*.jpg
44
*.odp
55
*.dia
66
*.pdf
77
*.png
8
+*.wav
89
compat/zlib/contrib/blast/test.pk
910
compat/zlib/contrib/dotzlib/DotZLib.chm
1011
compat/zlib/contrib/puff/zeros.raw
1112
compat/zlib/zlib.3.pdf
1213
--- .fossil-settings/binary-glob
+++ .fossil-settings/binary-glob
@@ -3,9 +3,10 @@
3 *.jpg
4 *.odp
5 *.dia
6 *.pdf
7 *.png
 
8 compat/zlib/contrib/blast/test.pk
9 compat/zlib/contrib/dotzlib/DotZLib.chm
10 compat/zlib/contrib/puff/zeros.raw
11 compat/zlib/zlib.3.pdf
12
--- .fossil-settings/binary-glob
+++ .fossil-settings/binary-glob
@@ -3,9 +3,10 @@
3 *.jpg
4 *.odp
5 *.dia
6 *.pdf
7 *.png
8 *.wav
9 compat/zlib/contrib/blast/test.pk
10 compat/zlib/contrib/dotzlib/DotZLib.chm
11 compat/zlib/contrib/puff/zeros.raw
12 compat/zlib/zlib.3.pdf
13
+18 -2
--- src/chat.c
+++ src/chat.c
@@ -235,11 +235,11 @@
235235
if(fAsMessageList){
236236
CX("{\"msgs\":[{");
237237
}else{
238238
CX("{");
239239
}
240
- CX("\"isError\": true, \"xfrom\": \"fossil\",");
240
+ CX("\"isError\": true, \"xfrom\": null,");
241241
CX("\"mtime\": %!j, \"lmtime\": %!j,", zTime, zTime);
242242
CX("\"xmsg\": \"Missing permissions or not logged in. "
243243
"Try <a href='%R/login?g=%R/chat'>logging in</a>.\"");
244244
if(fAsMessageList){
245245
CX("}]}");
@@ -466,11 +466,11 @@
466466
**
467467
** | {
468468
** | "msgs":[
469469
** | {
470470
** | "isError": true,
471
-** | "xfrom": "fossil",
471
+** | "xfrom": null,
472472
** | "xmsg": "error details"
473473
** | "mtime": as above,
474474
** | "ltime": same as mtime
475475
** | }
476476
** | ]
@@ -676,10 +676,26 @@
676676
if( cgi_is_loopback(zIpAddr) ){
677677
cgi_append_header("Access-Control-Allow-Origin: *\r\n");
678678
fputc(7, stderr);
679679
}
680680
}
681
+
682
+/*
683
+** WEBPAGE: chat-audio-received
684
+**
685
+** Responds with an audio stream suitable for use as a /chat
686
+** new-message-arrived notification.
687
+*/
688
+void chat_audio_alert(void){
689
+ Blob audio = empty_blob;
690
+ int n = 0;
691
+ const char * zAudio =
692
+ (const char *)builtin_file("sounds/chat-received.wav", &n);
693
+ blob_init(&audio, zAudio, n);
694
+ cgi_set_content_type("audio/wav");
695
+ cgi_set_content(&audio);
696
+}
681697
682698
/*
683699
** COMMAND: chat
684700
**
685701
** Usage: %fossil chat ?URL?
686702
--- src/chat.c
+++ src/chat.c
@@ -235,11 +235,11 @@
235 if(fAsMessageList){
236 CX("{\"msgs\":[{");
237 }else{
238 CX("{");
239 }
240 CX("\"isError\": true, \"xfrom\": \"fossil\",");
241 CX("\"mtime\": %!j, \"lmtime\": %!j,", zTime, zTime);
242 CX("\"xmsg\": \"Missing permissions or not logged in. "
243 "Try <a href='%R/login?g=%R/chat'>logging in</a>.\"");
244 if(fAsMessageList){
245 CX("}]}");
@@ -466,11 +466,11 @@
466 **
467 ** | {
468 ** | "msgs":[
469 ** | {
470 ** | "isError": true,
471 ** | "xfrom": "fossil",
472 ** | "xmsg": "error details"
473 ** | "mtime": as above,
474 ** | "ltime": same as mtime
475 ** | }
476 ** | ]
@@ -676,10 +676,26 @@
676 if( cgi_is_loopback(zIpAddr) ){
677 cgi_append_header("Access-Control-Allow-Origin: *\r\n");
678 fputc(7, stderr);
679 }
680 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
681
682 /*
683 ** COMMAND: chat
684 **
685 ** Usage: %fossil chat ?URL?
686
--- src/chat.c
+++ src/chat.c
@@ -235,11 +235,11 @@
235 if(fAsMessageList){
236 CX("{\"msgs\":[{");
237 }else{
238 CX("{");
239 }
240 CX("\"isError\": true, \"xfrom\": null,");
241 CX("\"mtime\": %!j, \"lmtime\": %!j,", zTime, zTime);
242 CX("\"xmsg\": \"Missing permissions or not logged in. "
243 "Try <a href='%R/login?g=%R/chat'>logging in</a>.\"");
244 if(fAsMessageList){
245 CX("}]}");
@@ -466,11 +466,11 @@
466 **
467 ** | {
468 ** | "msgs":[
469 ** | {
470 ** | "isError": true,
471 ** | "xfrom": null,
472 ** | "xmsg": "error details"
473 ** | "mtime": as above,
474 ** | "ltime": same as mtime
475 ** | }
476 ** | ]
@@ -676,10 +676,26 @@
676 if( cgi_is_loopback(zIpAddr) ){
677 cgi_append_header("Access-Control-Allow-Origin: *\r\n");
678 fputc(7, stderr);
679 }
680 }
681
682 /*
683 ** WEBPAGE: chat-audio-received
684 **
685 ** Responds with an audio stream suitable for use as a /chat
686 ** new-message-arrived notification.
687 */
688 void chat_audio_alert(void){
689 Blob audio = empty_blob;
690 int n = 0;
691 const char * zAudio =
692 (const char *)builtin_file("sounds/chat-received.wav", &n);
693 blob_init(&audio, zAudio, n);
694 cgi_set_content_type("audio/wav");
695 cgi_set_content(&audio);
696 }
697
698 /*
699 ** COMMAND: chat
700 **
701 ** Usage: %fossil chat ?URL?
702
+66 -21
--- src/chat.js
+++ src/chat.js
@@ -354,12 +354,28 @@
354354
set: (k,v)=>F.storage.set(k,v),
355355
defaults:{
356356
"images-inline": !!F.config.chat.imagesInline,
357357
"edit-multiline": false,
358358
"monospace-messages": false,
359
- "chat-only-mode": false
359
+ "chat-only-mode": false,
360
+ "audio-notification": true,
361
+ }
362
+ },
363
+ /** Plays a new-message notification sound IF the audio-notification
364
+ setting is true, else this is a no-op. Returns this.
365
+ */
366
+ playNewMessageSound: function f(){
367
+ if(this.settings.getBool('audio-notification',false)){
368
+ try{
369
+ if(!f.audio) f.audio = new Audio(F.rootPath+"chat-audio-received");
370
+ f.audio.currentTime = 0;
371
+ f.audio.play();
372
+ }catch(e){
373
+ console.error("Audio playblack failed.",e);
374
+ }
360375
}
376
+ return this;
361377
}
362378
};
363379
cs.e.inputCurrent = cs.e.inputSingle;
364380
/* Install default settings... */
365381
Object.keys(cs.settings.defaults).forEach(function(k){
@@ -405,17 +421,16 @@
405421
const args = argsToArray(arguments);
406422
console.error("chat error:",args);
407423
const d = new Date().toISOString(),
408424
msg = {
409425
isError: true,
410
- xfrom: "chat.js",
426
+ xfrom: null,
411427
msgid: -1,
412428
mtime: d,
413429
lmtime: d,
414430
xmsg: args
415
- }, mw = new this.MessageWidget();
416
- mw.setMessage(msg);
431
+ }, mw = new this.MessageWidget(msg);
417432
this.injectMessageElem(mw.e.body);
418433
mw.scrollIntoView();
419434
};
420435
421436
cs.getMessageElemById = function(id){
@@ -528,18 +543,30 @@
528543
features of the FIELDSET/LEGEND combination, e.g. inability to
529544
align legends via CSS in Firefox and clicking-related
530545
deficiencies in Safari.
531546
*/
532547
Chat.MessageWidget = (function(){
548
+ /**
549
+ Constructor. If passed an argument, it is passed to
550
+ this.setMessage() after initialization.
551
+ */
533552
const cf = function(){
534553
this.e = {
535554
body: D.addClass(D.div(), 'message-widget'),
536555
tab: D.addClass(D.span(), 'message-widget-tab'),
537556
content: D.addClass(D.div(), 'message-widget-content')
538557
};
539558
D.append(this.e.body, this.e.tab, this.e.content);
540559
this.e.tab.setAttribute('role', 'button');
560
+ if(arguments.length){
561
+ this.setMessage(arguments[0]);
562
+ }
563
+ };
564
+ const theTime = function(d){
565
+ return [d.getHours(),":",
566
+ (d.getMinutes()+100).toString().slice(1,3)
567
+ ].join('');
541568
};
542569
cf.prototype = {
543570
setLabel: function(label){
544571
return this;
545572
},
@@ -549,28 +576,37 @@
549576
setMessage: function(m){
550577
const ds = this.e.body.dataset;
551578
ds.timestamp = m.mtime;
552579
ds.lmtime = m.lmtime;
553580
ds.msgid = m.msgid;
554
- ds.xfrom = m.xfrom;
581
+ ds.xfrom = m.xfrom || '';
555582
if(m.xfrom === Chat.me){
556583
D.addClass(this.e.body, 'mine');
557584
}
558
- this.e.content.style.backgroundColor = m.uclr;
559
- this.e.tab.style.backgroundColor = m.uclr;
585
+ if(m.uclr){
586
+ this.e.content.style.backgroundColor = m.uclr;
587
+ this.e.tab.style.backgroundColor = m.uclr;
588
+ }
560589
const d = new Date(m.mtime);
561
- D.append(
562
- D.clearElement(this.e.tab),
563
- D.text(
564
- m.xfrom," #",(m.msgid||'???'),' @ ',d.getHours(),":",
565
- (d.getMinutes()+100).toString().slice(1,3)
566
- )
567
- );
590
+ D.clearElement(this.e.tab);
568591
var contentTarget = this.e.content;
569
- if(m.isError){
570
- D.addClass([contentTarget, this.e.tab], 'error');
571
- }else if( m.fsize>0 ){
592
+ if(m.xfrom){
593
+ D.append(
594
+ this.e.tab,
595
+ D.text(m.xfrom," #",(m.msgid||'???'),' @ ',theTime(d))
596
+ );
597
+ }else{/*notification*/
598
+ D.addClass(this.e.body, 'notification');
599
+ if(m.isError){
600
+ D.addClass([contentTarget, this.e.tab], 'error');
601
+ }
602
+ D.append(
603
+ this.e.tab,
604
+ D.text('notification @ ',theTime(d))
605
+ );
606
+ }
607
+ if( m.xfrom && m.fsize>0 ){
572608
if( m.fmime
573609
&& m.fmime.startsWith("image/")
574610
&& Chat.settings.getBool('images-inline',true)
575611
){
576612
contentTarget.appendChild(D.img("chat-download/" + m.msgid));
@@ -624,11 +660,11 @@
624660
if(!eMsg) return;
625661
D.clearElement(this.e);
626662
const d = new Date(eMsg.dataset.timestamp);
627663
if(d.getMinutes().toString()!=="NaN"){
628664
// Date works, render informative timestamps
629
- const xfrom = eMsg.dataset.xfrom;
665
+ const xfrom = eMsg.dataset.xfrom || 'server';
630666
D.append(this.e,
631667
D.append(D.span(), localTimeString(d)," ",Chat.me," time"),
632668
D.append(D.span(), iso8601ish(d)));
633669
if(eMsg.dataset.lmtime && xfrom!==Chat.me){
634670
D.append(this.e,
@@ -892,16 +928,23 @@
892928
boolValue: ()=>!Chat.e.btnMsgHome.classList.contains('hidden'),
893929
callback: ()=>Chat.toggleNavButtons()
894930
},{
895931
label: "Images inline",
896932
boolValue: ()=>Chat.settings.getBool('images-inline'),
897
- persistentSetting: 'images-inline',
898933
callback: function(){
899934
const v = Chat.settings.getBool('images-inline',true);
900935
Chat.settings.set('images-inline', !v);
901936
F.toast.message("Image mode set to "+(v ? "hyperlink" : "inline")+".");
902937
}
938
+ },{
939
+ label: "Audio notifications",
940
+ boolValue: ()=>Chat.settings.getBool('audio-notification'),
941
+ callback: function(){
942
+ const v = Chat.settings.getBool('audio-notification');
943
+ Chat.settings.set('audio-notification', !v);
944
+ F.toast.message("Audio notifications "+(v ? "disabled" : "enabled")+".");
945
+ }
903946
}];
904947
905948
/**
906949
Rebuild the menu each time it's shown so that the toggles can
907950
show their current values.
@@ -989,12 +1032,14 @@
9891032
if( m.mdel ){
9901033
/* A record deletion notice. */
9911034
Chat.deleteMessageElem(m.mdel);
9921035
return;
9931036
}
994
- const row = new Chat.MessageWidget()
995
- row.setMessage(m);
1037
+ if(!Chat._isBatchLoading && Chat.me!==m.xfrom && Chat.playNewMessageSound){
1038
+ Chat.playNewMessageSound();
1039
+ }
1040
+ const row = new Chat.MessageWidget(m);
9961041
Chat.injectMessageElem(row.e.body,atEnd);
9971042
if(m.isError){
9981043
Chat._gotServerError = m;
9991044
}
10001045
}/*processPost()*/;
10011046
--- src/chat.js
+++ src/chat.js
@@ -354,12 +354,28 @@
354 set: (k,v)=>F.storage.set(k,v),
355 defaults:{
356 "images-inline": !!F.config.chat.imagesInline,
357 "edit-multiline": false,
358 "monospace-messages": false,
359 "chat-only-mode": false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360 }
 
361 }
362 };
363 cs.e.inputCurrent = cs.e.inputSingle;
364 /* Install default settings... */
365 Object.keys(cs.settings.defaults).forEach(function(k){
@@ -405,17 +421,16 @@
405 const args = argsToArray(arguments);
406 console.error("chat error:",args);
407 const d = new Date().toISOString(),
408 msg = {
409 isError: true,
410 xfrom: "chat.js",
411 msgid: -1,
412 mtime: d,
413 lmtime: d,
414 xmsg: args
415 }, mw = new this.MessageWidget();
416 mw.setMessage(msg);
417 this.injectMessageElem(mw.e.body);
418 mw.scrollIntoView();
419 };
420
421 cs.getMessageElemById = function(id){
@@ -528,18 +543,30 @@
528 features of the FIELDSET/LEGEND combination, e.g. inability to
529 align legends via CSS in Firefox and clicking-related
530 deficiencies in Safari.
531 */
532 Chat.MessageWidget = (function(){
 
 
 
 
533 const cf = function(){
534 this.e = {
535 body: D.addClass(D.div(), 'message-widget'),
536 tab: D.addClass(D.span(), 'message-widget-tab'),
537 content: D.addClass(D.div(), 'message-widget-content')
538 };
539 D.append(this.e.body, this.e.tab, this.e.content);
540 this.e.tab.setAttribute('role', 'button');
 
 
 
 
 
 
 
 
541 };
542 cf.prototype = {
543 setLabel: function(label){
544 return this;
545 },
@@ -549,28 +576,37 @@
549 setMessage: function(m){
550 const ds = this.e.body.dataset;
551 ds.timestamp = m.mtime;
552 ds.lmtime = m.lmtime;
553 ds.msgid = m.msgid;
554 ds.xfrom = m.xfrom;
555 if(m.xfrom === Chat.me){
556 D.addClass(this.e.body, 'mine');
557 }
558 this.e.content.style.backgroundColor = m.uclr;
559 this.e.tab.style.backgroundColor = m.uclr;
 
 
560 const d = new Date(m.mtime);
561 D.append(
562 D.clearElement(this.e.tab),
563 D.text(
564 m.xfrom," #",(m.msgid||'???'),' @ ',d.getHours(),":",
565 (d.getMinutes()+100).toString().slice(1,3)
566 )
567 );
568 var contentTarget = this.e.content;
569 if(m.isError){
570 D.addClass([contentTarget, this.e.tab], 'error');
571 }else if( m.fsize>0 ){
 
 
 
 
 
 
 
 
 
 
 
 
 
572 if( m.fmime
573 && m.fmime.startsWith("image/")
574 && Chat.settings.getBool('images-inline',true)
575 ){
576 contentTarget.appendChild(D.img("chat-download/" + m.msgid));
@@ -624,11 +660,11 @@
624 if(!eMsg) return;
625 D.clearElement(this.e);
626 const d = new Date(eMsg.dataset.timestamp);
627 if(d.getMinutes().toString()!=="NaN"){
628 // Date works, render informative timestamps
629 const xfrom = eMsg.dataset.xfrom;
630 D.append(this.e,
631 D.append(D.span(), localTimeString(d)," ",Chat.me," time"),
632 D.append(D.span(), iso8601ish(d)));
633 if(eMsg.dataset.lmtime && xfrom!==Chat.me){
634 D.append(this.e,
@@ -892,16 +928,23 @@
892 boolValue: ()=>!Chat.e.btnMsgHome.classList.contains('hidden'),
893 callback: ()=>Chat.toggleNavButtons()
894 },{
895 label: "Images inline",
896 boolValue: ()=>Chat.settings.getBool('images-inline'),
897 persistentSetting: 'images-inline',
898 callback: function(){
899 const v = Chat.settings.getBool('images-inline',true);
900 Chat.settings.set('images-inline', !v);
901 F.toast.message("Image mode set to "+(v ? "hyperlink" : "inline")+".");
902 }
 
 
 
 
 
 
 
 
903 }];
904
905 /**
906 Rebuild the menu each time it's shown so that the toggles can
907 show their current values.
@@ -989,12 +1032,14 @@
989 if( m.mdel ){
990 /* A record deletion notice. */
991 Chat.deleteMessageElem(m.mdel);
992 return;
993 }
994 const row = new Chat.MessageWidget()
995 row.setMessage(m);
 
 
996 Chat.injectMessageElem(row.e.body,atEnd);
997 if(m.isError){
998 Chat._gotServerError = m;
999 }
1000 }/*processPost()*/;
1001
--- src/chat.js
+++ src/chat.js
@@ -354,12 +354,28 @@
354 set: (k,v)=>F.storage.set(k,v),
355 defaults:{
356 "images-inline": !!F.config.chat.imagesInline,
357 "edit-multiline": false,
358 "monospace-messages": false,
359 "chat-only-mode": false,
360 "audio-notification": true,
361 }
362 },
363 /** Plays a new-message notification sound IF the audio-notification
364 setting is true, else this is a no-op. Returns this.
365 */
366 playNewMessageSound: function f(){
367 if(this.settings.getBool('audio-notification',false)){
368 try{
369 if(!f.audio) f.audio = new Audio(F.rootPath+"chat-audio-received");
370 f.audio.currentTime = 0;
371 f.audio.play();
372 }catch(e){
373 console.error("Audio playblack failed.",e);
374 }
375 }
376 return this;
377 }
378 };
379 cs.e.inputCurrent = cs.e.inputSingle;
380 /* Install default settings... */
381 Object.keys(cs.settings.defaults).forEach(function(k){
@@ -405,17 +421,16 @@
421 const args = argsToArray(arguments);
422 console.error("chat error:",args);
423 const d = new Date().toISOString(),
424 msg = {
425 isError: true,
426 xfrom: null,
427 msgid: -1,
428 mtime: d,
429 lmtime: d,
430 xmsg: args
431 }, mw = new this.MessageWidget(msg);
 
432 this.injectMessageElem(mw.e.body);
433 mw.scrollIntoView();
434 };
435
436 cs.getMessageElemById = function(id){
@@ -528,18 +543,30 @@
543 features of the FIELDSET/LEGEND combination, e.g. inability to
544 align legends via CSS in Firefox and clicking-related
545 deficiencies in Safari.
546 */
547 Chat.MessageWidget = (function(){
548 /**
549 Constructor. If passed an argument, it is passed to
550 this.setMessage() after initialization.
551 */
552 const cf = function(){
553 this.e = {
554 body: D.addClass(D.div(), 'message-widget'),
555 tab: D.addClass(D.span(), 'message-widget-tab'),
556 content: D.addClass(D.div(), 'message-widget-content')
557 };
558 D.append(this.e.body, this.e.tab, this.e.content);
559 this.e.tab.setAttribute('role', 'button');
560 if(arguments.length){
561 this.setMessage(arguments[0]);
562 }
563 };
564 const theTime = function(d){
565 return [d.getHours(),":",
566 (d.getMinutes()+100).toString().slice(1,3)
567 ].join('');
568 };
569 cf.prototype = {
570 setLabel: function(label){
571 return this;
572 },
@@ -549,28 +576,37 @@
576 setMessage: function(m){
577 const ds = this.e.body.dataset;
578 ds.timestamp = m.mtime;
579 ds.lmtime = m.lmtime;
580 ds.msgid = m.msgid;
581 ds.xfrom = m.xfrom || '';
582 if(m.xfrom === Chat.me){
583 D.addClass(this.e.body, 'mine');
584 }
585 if(m.uclr){
586 this.e.content.style.backgroundColor = m.uclr;
587 this.e.tab.style.backgroundColor = m.uclr;
588 }
589 const d = new Date(m.mtime);
590 D.clearElement(this.e.tab);
 
 
 
 
 
 
591 var contentTarget = this.e.content;
592 if(m.xfrom){
593 D.append(
594 this.e.tab,
595 D.text(m.xfrom," #",(m.msgid||'???'),' @ ',theTime(d))
596 );
597 }else{/*notification*/
598 D.addClass(this.e.body, 'notification');
599 if(m.isError){
600 D.addClass([contentTarget, this.e.tab], 'error');
601 }
602 D.append(
603 this.e.tab,
604 D.text('notification @ ',theTime(d))
605 );
606 }
607 if( m.xfrom && m.fsize>0 ){
608 if( m.fmime
609 && m.fmime.startsWith("image/")
610 && Chat.settings.getBool('images-inline',true)
611 ){
612 contentTarget.appendChild(D.img("chat-download/" + m.msgid));
@@ -624,11 +660,11 @@
660 if(!eMsg) return;
661 D.clearElement(this.e);
662 const d = new Date(eMsg.dataset.timestamp);
663 if(d.getMinutes().toString()!=="NaN"){
664 // Date works, render informative timestamps
665 const xfrom = eMsg.dataset.xfrom || 'server';
666 D.append(this.e,
667 D.append(D.span(), localTimeString(d)," ",Chat.me," time"),
668 D.append(D.span(), iso8601ish(d)));
669 if(eMsg.dataset.lmtime && xfrom!==Chat.me){
670 D.append(this.e,
@@ -892,16 +928,23 @@
928 boolValue: ()=>!Chat.e.btnMsgHome.classList.contains('hidden'),
929 callback: ()=>Chat.toggleNavButtons()
930 },{
931 label: "Images inline",
932 boolValue: ()=>Chat.settings.getBool('images-inline'),
 
933 callback: function(){
934 const v = Chat.settings.getBool('images-inline',true);
935 Chat.settings.set('images-inline', !v);
936 F.toast.message("Image mode set to "+(v ? "hyperlink" : "inline")+".");
937 }
938 },{
939 label: "Audio notifications",
940 boolValue: ()=>Chat.settings.getBool('audio-notification'),
941 callback: function(){
942 const v = Chat.settings.getBool('audio-notification');
943 Chat.settings.set('audio-notification', !v);
944 F.toast.message("Audio notifications "+(v ? "disabled" : "enabled")+".");
945 }
946 }];
947
948 /**
949 Rebuild the menu each time it's shown so that the toggles can
950 show their current values.
@@ -989,12 +1032,14 @@
1032 if( m.mdel ){
1033 /* A record deletion notice. */
1034 Chat.deleteMessageElem(m.mdel);
1035 return;
1036 }
1037 if(!Chat._isBatchLoading && Chat.me!==m.xfrom && Chat.playNewMessageSound){
1038 Chat.playNewMessageSound();
1039 }
1040 const row = new Chat.MessageWidget(m);
1041 Chat.injectMessageElem(row.e.body,atEnd);
1042 if(m.isError){
1043 Chat._gotServerError = m;
1044 }
1045 }/*processPost()*/;
1046
--- src/default.css
+++ src/default.css
@@ -1485,10 +1485,14 @@
14851485
}
14861486
body.chat.my-messages-right .message-widget.mine {
14871487
/* Right-aligns a user's own chat messages, similar to how
14881488
most mobile messaging apps do it. */
14891489
align-items: flex-end;
1490
+}
1491
+body.chat.my-messages-right .message-widget.notification {
1492
+ /* Center-aligns a system-level notification message. */
1493
+ align-items: center;
14901494
}
14911495
/* The content area of a message. */
14921496
body.chat .message-widget-content {
14931497
display: inline-block;
14941498
border-radius: 0.25em;
14951499
--- src/default.css
+++ src/default.css
@@ -1485,10 +1485,14 @@
1485 }
1486 body.chat.my-messages-right .message-widget.mine {
1487 /* Right-aligns a user's own chat messages, similar to how
1488 most mobile messaging apps do it. */
1489 align-items: flex-end;
 
 
 
 
1490 }
1491 /* The content area of a message. */
1492 body.chat .message-widget-content {
1493 display: inline-block;
1494 border-radius: 0.25em;
1495
--- src/default.css
+++ src/default.css
@@ -1485,10 +1485,14 @@
1485 }
1486 body.chat.my-messages-right .message-widget.mine {
1487 /* Right-aligns a user's own chat messages, similar to how
1488 most mobile messaging apps do it. */
1489 align-items: flex-end;
1490 }
1491 body.chat.my-messages-right .message-widget.notification {
1492 /* Center-aligns a system-level notification message. */
1493 align-items: center;
1494 }
1495 /* The content area of a message. */
1496 body.chat .message-widget-content {
1497 display: inline-block;
1498 border-radius: 0.25em;
1499
--- src/main.mk
+++ src/main.mk
@@ -263,10 +263,11 @@
263263
$(SRCDIR)/sounds/8.wav \
264264
$(SRCDIR)/sounds/9.wav \
265265
$(SRCDIR)/sounds/a.wav \
266266
$(SRCDIR)/sounds/b.wav \
267267
$(SRCDIR)/sounds/c.wav \
268
+ $(SRCDIR)/sounds/chat-received.wav \
268269
$(SRCDIR)/sounds/d.wav \
269270
$(SRCDIR)/sounds/e.wav \
270271
$(SRCDIR)/sounds/f.wav \
271272
$(SRCDIR)/style.admin_log.css \
272273
$(SRCDIR)/style.fileedit.css \
273274
--- src/main.mk
+++ src/main.mk
@@ -263,10 +263,11 @@
263 $(SRCDIR)/sounds/8.wav \
264 $(SRCDIR)/sounds/9.wav \
265 $(SRCDIR)/sounds/a.wav \
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)/style.admin_log.css \
272 $(SRCDIR)/style.fileedit.css \
273
--- src/main.mk
+++ src/main.mk
@@ -263,10 +263,11 @@
263 $(SRCDIR)/sounds/8.wav \
264 $(SRCDIR)/sounds/9.wav \
265 $(SRCDIR)/sounds/a.wav \
266 $(SRCDIR)/sounds/b.wav \
267 $(SRCDIR)/sounds/c.wav \
268 $(SRCDIR)/sounds/chat-received.wav \
269 $(SRCDIR)/sounds/d.wav \
270 $(SRCDIR)/sounds/e.wav \
271 $(SRCDIR)/sounds/f.wav \
272 $(SRCDIR)/style.admin_log.css \
273 $(SRCDIR)/style.fileedit.css \
274
--- src/sounds/README.md
+++ src/sounds/README.md
@@ -1,13 +1,28 @@
1
-The *.wav files in this directory contain a human voice speaking each
2
-of the 16 hexadecimal digits. If a captcha string consists of just
3
-hexadecimal digits (as is the case for captchas generated by the
4
-[../captcha.c module](../captcha.c)) then these WAV files can be
5
-concatenated together to generate an audio reading of the captcha, which
6
-enables visually impaired users to complete the captcha.
1
+The `[0-9a-f].wav` files in this directory contain a human voice
2
+speaking each of the 16 hexadecimal digits. If a captcha string
3
+consists of just hexadecimal digits (as is the case for captchas
4
+generated by the [../captcha.c module](../captcha.c)) then these WAV
5
+files can be concatenated together to generate an audio reading of the
6
+captcha, which enables visually impaired users to complete the
7
+captcha.
78
89
Each of the WAV files uses 8000 samples per second, 8 bits per sample
910
and are 6000 samples in length.
1011
1112
The recordings are made by Philip Bennefall and are of his own voice.
1213
Mr. Bennefall is himself blind and uses this system implemented with these
1314
recordings to complete captchas for Fossil.
15
+
16
+========================================================================
17
+
18
+`chat-received.wav` is a Creative Commons Attribution-licensed audio
19
+file obtained from:
20
+
21
+ https://notificationsounds.com/notification-sounds/time-is-now-585
22
+
23
+License:
24
+
25
+ https://creativecommons.org/licenses/by/4.0/legalcode
26
+
27
+This copy has been truncated, down-sampled, and converted to a
28
+different format than the original.
1429
1530
ADDED src/sounds/chat-received.wav
--- src/sounds/README.md
+++ src/sounds/README.md
@@ -1,13 +1,28 @@
1 The *.wav files in this directory contain a human voice speaking each
2 of the 16 hexadecimal digits. If a captcha string consists of just
3 hexadecimal digits (as is the case for captchas generated by the
4 [../captcha.c module](../captcha.c)) then these WAV files can be
5 concatenated together to generate an audio reading of the captcha, which
6 enables visually impaired users to complete the captcha.
 
7
8 Each of the WAV files uses 8000 samples per second, 8 bits per sample
9 and are 6000 samples in length.
10
11 The recordings are made by Philip Bennefall and are of his own voice.
12 Mr. Bennefall is himself blind and uses this system implemented with these
13 recordings to complete captchas for Fossil.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
15 DDED src/sounds/chat-received.wav
--- src/sounds/README.md
+++ src/sounds/README.md
@@ -1,13 +1,28 @@
1 The `[0-9a-f].wav` files in this directory contain a human voice
2 speaking each of the 16 hexadecimal digits. If a captcha string
3 consists of just hexadecimal digits (as is the case for captchas
4 generated by the [../captcha.c module](../captcha.c)) then these WAV
5 files can be concatenated together to generate an audio reading of the
6 captcha, which enables visually impaired users to complete the
7 captcha.
8
9 Each of the WAV files uses 8000 samples per second, 8 bits per sample
10 and are 6000 samples in length.
11
12 The recordings are made by Philip Bennefall and are of his own voice.
13 Mr. Bennefall is himself blind and uses this system implemented with these
14 recordings to complete captchas for Fossil.
15
16 ========================================================================
17
18 `chat-received.wav` is a Creative Commons Attribution-licensed audio
19 file obtained from:
20
21 https://notificationsounds.com/notification-sounds/time-is-now-585
22
23 License:
24
25 https://creativecommons.org/licenses/by/4.0/legalcode
26
27 This copy has been truncated, down-sampled, and converted to a
28 different format than the original.
29
30 DDED src/sounds/chat-received.wav
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -675,10 +675,11 @@
675675
$(SRCDIR)/sounds/8.wav \
676676
$(SRCDIR)/sounds/9.wav \
677677
$(SRCDIR)/sounds/a.wav \
678678
$(SRCDIR)/sounds/b.wav \
679679
$(SRCDIR)/sounds/c.wav \
680
+ $(SRCDIR)/sounds/chat-received.wav \
680681
$(SRCDIR)/sounds/d.wav \
681682
$(SRCDIR)/sounds/e.wav \
682683
$(SRCDIR)/sounds/f.wav \
683684
$(SRCDIR)/style.admin_log.css \
684685
$(SRCDIR)/style.fileedit.css \
685686
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -675,10 +675,11 @@
675 $(SRCDIR)/sounds/8.wav \
676 $(SRCDIR)/sounds/9.wav \
677 $(SRCDIR)/sounds/a.wav \
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)/style.admin_log.css \
684 $(SRCDIR)/style.fileedit.css \
685
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -675,10 +675,11 @@
675 $(SRCDIR)/sounds/8.wav \
676 $(SRCDIR)/sounds/9.wav \
677 $(SRCDIR)/sounds/a.wav \
678 $(SRCDIR)/sounds/b.wav \
679 $(SRCDIR)/sounds/c.wav \
680 $(SRCDIR)/sounds/chat-received.wav \
681 $(SRCDIR)/sounds/d.wav \
682 $(SRCDIR)/sounds/e.wav \
683 $(SRCDIR)/sounds/f.wav \
684 $(SRCDIR)/style.admin_log.css \
685 $(SRCDIR)/style.fileedit.css \
686
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -596,10 +596,11 @@
596596
"$(SRCDIR)\sounds\8.wav" \
597597
"$(SRCDIR)\sounds\9.wav" \
598598
"$(SRCDIR)\sounds\a.wav" \
599599
"$(SRCDIR)\sounds\b.wav" \
600600
"$(SRCDIR)\sounds\c.wav" \
601
+ "$(SRCDIR)\sounds\chat-received.wav" \
601602
"$(SRCDIR)\sounds\d.wav" \
602603
"$(SRCDIR)\sounds\e.wav" \
603604
"$(SRCDIR)\sounds\f.wav" \
604605
"$(SRCDIR)\style.admin_log.css" \
605606
"$(SRCDIR)\style.fileedit.css" \
@@ -1205,10 +1206,11 @@
12051206
echo "$(SRCDIR)\sounds/8.wav" >> $@
12061207
echo "$(SRCDIR)\sounds/9.wav" >> $@
12071208
echo "$(SRCDIR)\sounds/a.wav" >> $@
12081209
echo "$(SRCDIR)\sounds/b.wav" >> $@
12091210
echo "$(SRCDIR)\sounds/c.wav" >> $@
1211
+ echo "$(SRCDIR)\sounds/chat-received.wav" >> $@
12101212
echo "$(SRCDIR)\sounds/d.wav" >> $@
12111213
echo "$(SRCDIR)\sounds/e.wav" >> $@
12121214
echo "$(SRCDIR)\sounds/f.wav" >> $@
12131215
echo "$(SRCDIR)\style.admin_log.css" >> $@
12141216
echo "$(SRCDIR)\style.fileedit.css" >> $@
12151217
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -596,10 +596,11 @@
596 "$(SRCDIR)\sounds\8.wav" \
597 "$(SRCDIR)\sounds\9.wav" \
598 "$(SRCDIR)\sounds\a.wav" \
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)\style.admin_log.css" \
605 "$(SRCDIR)\style.fileedit.css" \
@@ -1205,10 +1206,11 @@
1205 echo "$(SRCDIR)\sounds/8.wav" >> $@
1206 echo "$(SRCDIR)\sounds/9.wav" >> $@
1207 echo "$(SRCDIR)\sounds/a.wav" >> $@
1208 echo "$(SRCDIR)\sounds/b.wav" >> $@
1209 echo "$(SRCDIR)\sounds/c.wav" >> $@
 
1210 echo "$(SRCDIR)\sounds/d.wav" >> $@
1211 echo "$(SRCDIR)\sounds/e.wav" >> $@
1212 echo "$(SRCDIR)\sounds/f.wav" >> $@
1213 echo "$(SRCDIR)\style.admin_log.css" >> $@
1214 echo "$(SRCDIR)\style.fileedit.css" >> $@
1215
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -596,10 +596,11 @@
596 "$(SRCDIR)\sounds\8.wav" \
597 "$(SRCDIR)\sounds\9.wav" \
598 "$(SRCDIR)\sounds\a.wav" \
599 "$(SRCDIR)\sounds\b.wav" \
600 "$(SRCDIR)\sounds\c.wav" \
601 "$(SRCDIR)\sounds\chat-received.wav" \
602 "$(SRCDIR)\sounds\d.wav" \
603 "$(SRCDIR)\sounds\e.wav" \
604 "$(SRCDIR)\sounds\f.wav" \
605 "$(SRCDIR)\style.admin_log.css" \
606 "$(SRCDIR)\style.fileedit.css" \
@@ -1205,10 +1206,11 @@
1206 echo "$(SRCDIR)\sounds/8.wav" >> $@
1207 echo "$(SRCDIR)\sounds/9.wav" >> $@
1208 echo "$(SRCDIR)\sounds/a.wav" >> $@
1209 echo "$(SRCDIR)\sounds/b.wav" >> $@
1210 echo "$(SRCDIR)\sounds/c.wav" >> $@
1211 echo "$(SRCDIR)\sounds/chat-received.wav" >> $@
1212 echo "$(SRCDIR)\sounds/d.wav" >> $@
1213 echo "$(SRCDIR)\sounds/e.wav" >> $@
1214 echo "$(SRCDIR)\sounds/f.wav" >> $@
1215 echo "$(SRCDIR)\style.admin_log.css" >> $@
1216 echo "$(SRCDIR)\style.fileedit.css" >> $@
1217

Keyboard Shortcuts

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