Fossil SCM

Integrated client-local timestamp into chat so that participants can see the local time in their colleagues' time zones.

stephan 2020-12-26 20:24 trunk
Commit 3c5e2badc8254dc72c400f6e557a5667f61b173cb6abdaa5e6dd41957842e71c
2 files changed +21 -9 +25 -4
+21 -9
--- src/chat.c
+++ src/chat.c
@@ -167,10 +167,11 @@
167167
*/
168168
static const char zChatSchema1[] =
169169
@ CREATE TABLE repository.chat(
170170
@ msgid INTEGER PRIMARY KEY AUTOINCREMENT,
171171
@ mtime JULIANDAY, -- Time for this entry - Julianday Zulu
172
+@ lmtime TEXT, -- Localtime when message originall sent
172173
@ xfrom TEXT, -- Login of the sender
173174
@ xmsg TEXT, -- Raw, unformatted text of the message
174175
@ file BLOB, -- Text of the uploaded file, or NULL
175176
@ fname TEXT, -- Filename of the uploaded file, or NULL
176177
@ fmime TEXT, -- MIMEType of the upload file, or NULL
@@ -184,12 +185,15 @@
184185
** if they do not.
185186
*/
186187
static void chat_create_tables(void){
187188
if( !db_table_exists("repository","chat") ){
188189
db_multi_exec(zChatSchema1/*works-like:""*/);
189
- }else if( !db_table_has_column("repository","chat","mdel") ){
190
- db_multi_exec("ALTER TABLE chat ADD COLUMN mdel INT");
190
+ }else if( !db_table_has_column("repository","chat","lmtime") ){
191
+ if( !db_table_has_column("repository","chat","mdel") ){
192
+ db_multi_exec("ALTER TABLE chat ADD COLUMN mdel INT");
193
+ }
194
+ db_multi_exec("ALTER TABLE chat ADD COLUMN lmtime TEXT");
191195
}
192196
}
193197
194198
/*
195199
** Delete old content from the chat table.
@@ -233,22 +237,22 @@
233237
db_begin_write();
234238
chat_purge();
235239
if( nByte==0 ){
236240
if( zMsg[0] ){
237241
db_multi_exec(
238
- "INSERT INTO chat(mtime,xfrom,xmsg)"
239
- "VALUES(julianday('now'),%Q,%Q)",
240
- g.zLogin, zMsg
242
+ "INSERT INTO chat(mtime,lmtime,xfrom,xmsg)"
243
+ "VALUES(julianday('now'),%Q,%Q,%Q)",
244
+ P("lmtime"), g.zLogin, zMsg
241245
);
242246
}
243247
}else{
244248
Stmt q;
245249
Blob b;
246250
db_prepare(&q,
247
- "INSERT INTO chat(mtime, xfrom,xmsg,file,fname,fmime)"
248
- "VALUES(julianday('now'),%Q,%Q,:file,%Q,%Q)",
249
- g.zLogin, zMsg, PD("file:filename",""),
251
+ "INSERT INTO chat(mtime,lmtime,xfrom,xmsg,file,fname,fmime)"
252
+ "VALUES(julianday('now'),%Q,%Q,%Q,:file,%Q,%Q)",
253
+ P("lmtime"), g.zLogin, zMsg, PD("file:filename",""),
250254
PD("file:mimetype","application/octet-stream"));
251255
blob_init(&b, P("file"), nByte);
252256
db_bind_blob(&q, ":file", &b);
253257
db_step(&q);
254258
db_finalize(&q);
@@ -392,10 +396,11 @@
392396
** | {
393397
** | "msg":[
394398
** | {
395399
** | "msgid": integer // message id
396400
** | "mtime": text // When sent: YYYY-MM-DD HH:MM:SS UTC
401
+** | "lmtime: text // Localtime where the message was sent from
397402
** | "xfrom": text // Login name of sender
398403
** | "uclr": text // Color string associated with the user
399404
** | "xmsg": text // HTML text of the message
400405
** | "fsize": integer // file attachment size in bytes
401406
** | "fname": text // Name of file attachment
@@ -409,10 +414,12 @@
409414
** than zero. The "xmsg" field may be an empty string if "fsize" is zero.
410415
**
411416
** The "msgid" values will be in increasing order.
412417
**
413418
** The "mdel" will only exist if "xmsg" is an empty string and "fsize" is zero.
419
+**
420
+** The "lmtime" value might be known, in which case it is omitted.
414421
**
415422
** The messages are ordered oldest first unless "before" is provided, in which
416423
** case they are sorted newest first (to facilitate the client-side UI update).
417424
*/
418425
void chat_poll_webpage(void){
@@ -431,11 +438,11 @@
431438
chat_create_tables();
432439
cgi_set_content_type("text/json");
433440
dataVersion = db_int64(0, "PRAGMA data_version");
434441
blob_append_sql(&sql,
435442
"SELECT msgid, datetime(mtime), xfrom, xmsg, length(file),"
436
- " fname, fmime, %s"
443
+ " fname, fmime, %s, lmtime"
437444
" FROM chat ",
438445
msgBefore>0 ? "0 as mdel" : "mdel");
439446
if( msgid<=0 || msgBefore>0 ){
440447
db_begin_write();
441448
chat_purge();
@@ -475,16 +482,20 @@
475482
const char *zRawMsg = db_column_text(&q1, 3);
476483
int nByte = db_column_int(&q1, 4);
477484
const char *zFName = db_column_text(&q1, 5);
478485
const char *zFMime = db_column_text(&q1, 6);
479486
int iToDel = db_column_int(&q1, 7);
487
+ const char *zLMtime = db_column_text(&q1, 8);
480488
char *zMsg;
481489
if(cnt++){
482490
blob_append(&json, ",\n", 2);
483491
}
484492
blob_appendf(&json, "{\"msgid\":%d,", id);
485493
blob_appendf(&json, "\"mtime\":\"%.10sT%sZ\",", zDate, zDate+11);
494
+ if( zLMtime && zLMtime[0] ){
495
+ blob_appendf(&json, "\"lmtime\":%!j,", zLMtime);
496
+ }
486497
blob_appendf(&json, "\"xfrom\":%!j,", zFrom);
487498
blob_appendf(&json, "\"uclr\":%!j,", hash_color(zFrom));
488499
489500
zMsg = chat_format_to_html(zRawMsg ? zRawMsg : "");
490501
blob_appendf(&json, "\"xmsg\":%!j,", zMsg);
@@ -494,10 +505,11 @@
494505
blob_appendf(&json, "\"fsize\":0");
495506
}else{
496507
blob_appendf(&json, "\"fsize\":%d,\"fname\":%!j,\"fmime\":%!j",
497508
nByte, zFName, zFMime);
498509
}
510
+
499511
if( iToDel ){
500512
blob_appendf(&json, ",\"mdel\":%d}", iToDel);
501513
}else{
502514
blob_append(&json, "}", 1);
503515
}
504516
--- src/chat.c
+++ src/chat.c
@@ -167,10 +167,11 @@
167 */
168 static const char zChatSchema1[] =
169 @ CREATE TABLE repository.chat(
170 @ msgid INTEGER PRIMARY KEY AUTOINCREMENT,
171 @ mtime JULIANDAY, -- Time for this entry - Julianday Zulu
 
172 @ xfrom TEXT, -- Login of the sender
173 @ xmsg TEXT, -- Raw, unformatted text of the message
174 @ file BLOB, -- Text of the uploaded file, or NULL
175 @ fname TEXT, -- Filename of the uploaded file, or NULL
176 @ fmime TEXT, -- MIMEType of the upload file, or NULL
@@ -184,12 +185,15 @@
184 ** if they do not.
185 */
186 static void chat_create_tables(void){
187 if( !db_table_exists("repository","chat") ){
188 db_multi_exec(zChatSchema1/*works-like:""*/);
189 }else if( !db_table_has_column("repository","chat","mdel") ){
190 db_multi_exec("ALTER TABLE chat ADD COLUMN mdel INT");
 
 
 
191 }
192 }
193
194 /*
195 ** Delete old content from the chat table.
@@ -233,22 +237,22 @@
233 db_begin_write();
234 chat_purge();
235 if( nByte==0 ){
236 if( zMsg[0] ){
237 db_multi_exec(
238 "INSERT INTO chat(mtime,xfrom,xmsg)"
239 "VALUES(julianday('now'),%Q,%Q)",
240 g.zLogin, zMsg
241 );
242 }
243 }else{
244 Stmt q;
245 Blob b;
246 db_prepare(&q,
247 "INSERT INTO chat(mtime, xfrom,xmsg,file,fname,fmime)"
248 "VALUES(julianday('now'),%Q,%Q,:file,%Q,%Q)",
249 g.zLogin, zMsg, PD("file:filename",""),
250 PD("file:mimetype","application/octet-stream"));
251 blob_init(&b, P("file"), nByte);
252 db_bind_blob(&q, ":file", &b);
253 db_step(&q);
254 db_finalize(&q);
@@ -392,10 +396,11 @@
392 ** | {
393 ** | "msg":[
394 ** | {
395 ** | "msgid": integer // message id
396 ** | "mtime": text // When sent: YYYY-MM-DD HH:MM:SS UTC
 
397 ** | "xfrom": text // Login name of sender
398 ** | "uclr": text // Color string associated with the user
399 ** | "xmsg": text // HTML text of the message
400 ** | "fsize": integer // file attachment size in bytes
401 ** | "fname": text // Name of file attachment
@@ -409,10 +414,12 @@
409 ** than zero. The "xmsg" field may be an empty string if "fsize" is zero.
410 **
411 ** The "msgid" values will be in increasing order.
412 **
413 ** The "mdel" will only exist if "xmsg" is an empty string and "fsize" is zero.
 
 
414 **
415 ** The messages are ordered oldest first unless "before" is provided, in which
416 ** case they are sorted newest first (to facilitate the client-side UI update).
417 */
418 void chat_poll_webpage(void){
@@ -431,11 +438,11 @@
431 chat_create_tables();
432 cgi_set_content_type("text/json");
433 dataVersion = db_int64(0, "PRAGMA data_version");
434 blob_append_sql(&sql,
435 "SELECT msgid, datetime(mtime), xfrom, xmsg, length(file),"
436 " fname, fmime, %s"
437 " FROM chat ",
438 msgBefore>0 ? "0 as mdel" : "mdel");
439 if( msgid<=0 || msgBefore>0 ){
440 db_begin_write();
441 chat_purge();
@@ -475,16 +482,20 @@
475 const char *zRawMsg = db_column_text(&q1, 3);
476 int nByte = db_column_int(&q1, 4);
477 const char *zFName = db_column_text(&q1, 5);
478 const char *zFMime = db_column_text(&q1, 6);
479 int iToDel = db_column_int(&q1, 7);
 
480 char *zMsg;
481 if(cnt++){
482 blob_append(&json, ",\n", 2);
483 }
484 blob_appendf(&json, "{\"msgid\":%d,", id);
485 blob_appendf(&json, "\"mtime\":\"%.10sT%sZ\",", zDate, zDate+11);
 
 
 
486 blob_appendf(&json, "\"xfrom\":%!j,", zFrom);
487 blob_appendf(&json, "\"uclr\":%!j,", hash_color(zFrom));
488
489 zMsg = chat_format_to_html(zRawMsg ? zRawMsg : "");
490 blob_appendf(&json, "\"xmsg\":%!j,", zMsg);
@@ -494,10 +505,11 @@
494 blob_appendf(&json, "\"fsize\":0");
495 }else{
496 blob_appendf(&json, "\"fsize\":%d,\"fname\":%!j,\"fmime\":%!j",
497 nByte, zFName, zFMime);
498 }
 
499 if( iToDel ){
500 blob_appendf(&json, ",\"mdel\":%d}", iToDel);
501 }else{
502 blob_append(&json, "}", 1);
503 }
504
--- src/chat.c
+++ src/chat.c
@@ -167,10 +167,11 @@
167 */
168 static const char zChatSchema1[] =
169 @ CREATE TABLE repository.chat(
170 @ msgid INTEGER PRIMARY KEY AUTOINCREMENT,
171 @ mtime JULIANDAY, -- Time for this entry - Julianday Zulu
172 @ lmtime TEXT, -- Localtime when message originall sent
173 @ xfrom TEXT, -- Login of the sender
174 @ xmsg TEXT, -- Raw, unformatted text of the message
175 @ file BLOB, -- Text of the uploaded file, or NULL
176 @ fname TEXT, -- Filename of the uploaded file, or NULL
177 @ fmime TEXT, -- MIMEType of the upload file, or NULL
@@ -184,12 +185,15 @@
185 ** if they do not.
186 */
187 static void chat_create_tables(void){
188 if( !db_table_exists("repository","chat") ){
189 db_multi_exec(zChatSchema1/*works-like:""*/);
190 }else if( !db_table_has_column("repository","chat","lmtime") ){
191 if( !db_table_has_column("repository","chat","mdel") ){
192 db_multi_exec("ALTER TABLE chat ADD COLUMN mdel INT");
193 }
194 db_multi_exec("ALTER TABLE chat ADD COLUMN lmtime TEXT");
195 }
196 }
197
198 /*
199 ** Delete old content from the chat table.
@@ -233,22 +237,22 @@
237 db_begin_write();
238 chat_purge();
239 if( nByte==0 ){
240 if( zMsg[0] ){
241 db_multi_exec(
242 "INSERT INTO chat(mtime,lmtime,xfrom,xmsg)"
243 "VALUES(julianday('now'),%Q,%Q,%Q)",
244 P("lmtime"), g.zLogin, zMsg
245 );
246 }
247 }else{
248 Stmt q;
249 Blob b;
250 db_prepare(&q,
251 "INSERT INTO chat(mtime,lmtime,xfrom,xmsg,file,fname,fmime)"
252 "VALUES(julianday('now'),%Q,%Q,%Q,:file,%Q,%Q)",
253 P("lmtime"), g.zLogin, zMsg, PD("file:filename",""),
254 PD("file:mimetype","application/octet-stream"));
255 blob_init(&b, P("file"), nByte);
256 db_bind_blob(&q, ":file", &b);
257 db_step(&q);
258 db_finalize(&q);
@@ -392,10 +396,11 @@
396 ** | {
397 ** | "msg":[
398 ** | {
399 ** | "msgid": integer // message id
400 ** | "mtime": text // When sent: YYYY-MM-DD HH:MM:SS UTC
401 ** | "lmtime: text // Localtime where the message was sent from
402 ** | "xfrom": text // Login name of sender
403 ** | "uclr": text // Color string associated with the user
404 ** | "xmsg": text // HTML text of the message
405 ** | "fsize": integer // file attachment size in bytes
406 ** | "fname": text // Name of file attachment
@@ -409,10 +414,12 @@
414 ** than zero. The "xmsg" field may be an empty string if "fsize" is zero.
415 **
416 ** The "msgid" values will be in increasing order.
417 **
418 ** The "mdel" will only exist if "xmsg" is an empty string and "fsize" is zero.
419 **
420 ** The "lmtime" value might be known, in which case it is omitted.
421 **
422 ** The messages are ordered oldest first unless "before" is provided, in which
423 ** case they are sorted newest first (to facilitate the client-side UI update).
424 */
425 void chat_poll_webpage(void){
@@ -431,11 +438,11 @@
438 chat_create_tables();
439 cgi_set_content_type("text/json");
440 dataVersion = db_int64(0, "PRAGMA data_version");
441 blob_append_sql(&sql,
442 "SELECT msgid, datetime(mtime), xfrom, xmsg, length(file),"
443 " fname, fmime, %s, lmtime"
444 " FROM chat ",
445 msgBefore>0 ? "0 as mdel" : "mdel");
446 if( msgid<=0 || msgBefore>0 ){
447 db_begin_write();
448 chat_purge();
@@ -475,16 +482,20 @@
482 const char *zRawMsg = db_column_text(&q1, 3);
483 int nByte = db_column_int(&q1, 4);
484 const char *zFName = db_column_text(&q1, 5);
485 const char *zFMime = db_column_text(&q1, 6);
486 int iToDel = db_column_int(&q1, 7);
487 const char *zLMtime = db_column_text(&q1, 8);
488 char *zMsg;
489 if(cnt++){
490 blob_append(&json, ",\n", 2);
491 }
492 blob_appendf(&json, "{\"msgid\":%d,", id);
493 blob_appendf(&json, "\"mtime\":\"%.10sT%sZ\",", zDate, zDate+11);
494 if( zLMtime && zLMtime[0] ){
495 blob_appendf(&json, "\"lmtime\":%!j,", zLMtime);
496 }
497 blob_appendf(&json, "\"xfrom\":%!j,", zFrom);
498 blob_appendf(&json, "\"uclr\":%!j,", hash_color(zFrom));
499
500 zMsg = chat_format_to_html(zRawMsg ? zRawMsg : "");
501 blob_appendf(&json, "\"xmsg\":%!j,", zMsg);
@@ -494,10 +505,11 @@
505 blob_appendf(&json, "\"fsize\":0");
506 }else{
507 blob_appendf(&json, "\"fsize\":%d,\"fname\":%!j,\"fmime\":%!j",
508 nByte, zFName, zFMime);
509 }
510
511 if( iToDel ){
512 blob_appendf(&json, ",\"mdel\":%d}", iToDel);
513 }else{
514 blob_append(&json, "}", 1);
515 }
516
+25 -4
--- src/chat.js
+++ src/chat.js
@@ -352,10 +352,11 @@
352352
return this;
353353
},
354354
setMessage: function(m){
355355
const ds = this.e.body.dataset;
356356
ds.timestamp = m.mtime;
357
+ ds.lmtime = m.lmtime;
357358
ds.msgid = m.msgid;
358359
ds.xfrom = m.xfrom;
359360
if(m.xfrom === Chat.me){
360361
D.addClass(this.e.body, 'mine');
361362
}
@@ -492,21 +493,34 @@
492493
(k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
493494
);
494495
return bxs;
495496
})()/*drag/drop*/;
496497
498
+ const tzOffsetToString = function(off){
499
+ const hours = Math.round(off/60), min = Math.round(off % 30);
500
+ return ''+(hours + (min ? '.5' : ''));
501
+ };
502
+ const pad2 = (x)=>('0'+x).substr(-2);
503
+ const localTime8601 = function(d){
504
+ return [
505
+ d.getYear()+1900, '-', pad2(d.getMonth()+1), '-', pad2(d.getDate()+1),
506
+ 'T', pad2(d.getHours()),':', pad2(d.getMinutes()),':',pad2(d.getSeconds()),
507
+ 'Z', tzOffsetToString(d.getTimezoneOffset())
508
+ ].join('');
509
+ };
510
+
497511
Chat.submitMessage = function(){
498512
const fd = new FormData(this.e.inputForm)
499513
/* ^^^^ we don't really want/need the FORM element, but when
500514
FormData() is default-constructed here then the server
501515
segfaults, and i have no clue why! */;
502516
const msg = this.inputValue();
503517
if(msg) fd.set('msg',msg);
504518
const file = BlobXferState.blob || this.e.inputFile.files[0];
505519
if(file) fd.set("file", file);
506
- fd.set("lmtime", new Date().toISOString());
507520
if( msg || file ){
521
+ fd.set("lmtime", localTime8601(new Date()));
508522
fetch("chat-send",{
509523
method: 'POST',
510524
body: fd
511525
});
512526
}
@@ -552,11 +566,11 @@
552566
].join('');
553567
};
554568
/* Returns an almost-ISO8601 form of Date object d. */
555569
const iso8601ish = function(d){
556570
return d.toISOString()
557
- .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' GMT');
571
+ .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' Zulu');
558572
};
559573
/* Event handler for clicking .message-user elements to show their
560574
timestamps. */
561575
const handleLegendClicked = function f(ev){
562576
if(!f.popup){
@@ -568,16 +582,23 @@
568582
if(!eMsg) return;
569583
D.clearElement(this.e);
570584
const d = new Date(eMsg.dataset.timestamp);
571585
if(d.getMinutes().toString()!=="NaN"){
572586
// Date works, render informative timestamps
587
+ const xfrom = eMsg.dataset.xfrom;
573588
D.append(this.e,
574
- D.append(D.span(), localTimeString(d)," client-local"),
589
+ D.append(D.span(), localTimeString(d)," ",Chat.me," time"),
575590
D.append(D.span(), iso8601ish(d)));
591
+ if(xfrom!==Chat.me){
592
+ D.append(this.e,
593
+ D.append(D.span(), localTime8601(
594
+ new Date(eMsg.dataset.lmtime)
595
+ )," ",xfrom," time"));
596
+ }
576597
}else{
577598
// Date doesn't work, so dumb it down...
578
- D.append(this.e, D.append(D.span(), eMsg.dataset.timestamp," GMT"));
599
+ D.append(this.e, D.append(D.span(), eMsg.dataset.timestamp," Zulu"));
579600
}
580601
const toolbar = D.addClass(D.div(), 'toolbar');
581602
D.append(this.e, toolbar);
582603
const btnDeleteLocal = D.button("Delete locally");
583604
D.append(toolbar, btnDeleteLocal);
584605
--- src/chat.js
+++ src/chat.js
@@ -352,10 +352,11 @@
352 return this;
353 },
354 setMessage: function(m){
355 const ds = this.e.body.dataset;
356 ds.timestamp = m.mtime;
 
357 ds.msgid = m.msgid;
358 ds.xfrom = m.xfrom;
359 if(m.xfrom === Chat.me){
360 D.addClass(this.e.body, 'mine');
361 }
@@ -492,21 +493,34 @@
492 (k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
493 );
494 return bxs;
495 })()/*drag/drop*/;
496
 
 
 
 
 
 
 
 
 
 
 
 
 
497 Chat.submitMessage = function(){
498 const fd = new FormData(this.e.inputForm)
499 /* ^^^^ we don't really want/need the FORM element, but when
500 FormData() is default-constructed here then the server
501 segfaults, and i have no clue why! */;
502 const msg = this.inputValue();
503 if(msg) fd.set('msg',msg);
504 const file = BlobXferState.blob || this.e.inputFile.files[0];
505 if(file) fd.set("file", file);
506 fd.set("lmtime", new Date().toISOString());
507 if( msg || file ){
 
508 fetch("chat-send",{
509 method: 'POST',
510 body: fd
511 });
512 }
@@ -552,11 +566,11 @@
552 ].join('');
553 };
554 /* Returns an almost-ISO8601 form of Date object d. */
555 const iso8601ish = function(d){
556 return d.toISOString()
557 .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' GMT');
558 };
559 /* Event handler for clicking .message-user elements to show their
560 timestamps. */
561 const handleLegendClicked = function f(ev){
562 if(!f.popup){
@@ -568,16 +582,23 @@
568 if(!eMsg) return;
569 D.clearElement(this.e);
570 const d = new Date(eMsg.dataset.timestamp);
571 if(d.getMinutes().toString()!=="NaN"){
572 // Date works, render informative timestamps
 
573 D.append(this.e,
574 D.append(D.span(), localTimeString(d)," client-local"),
575 D.append(D.span(), iso8601ish(d)));
 
 
 
 
 
 
576 }else{
577 // Date doesn't work, so dumb it down...
578 D.append(this.e, D.append(D.span(), eMsg.dataset.timestamp," GMT"));
579 }
580 const toolbar = D.addClass(D.div(), 'toolbar');
581 D.append(this.e, toolbar);
582 const btnDeleteLocal = D.button("Delete locally");
583 D.append(toolbar, btnDeleteLocal);
584
--- src/chat.js
+++ src/chat.js
@@ -352,10 +352,11 @@
352 return this;
353 },
354 setMessage: function(m){
355 const ds = this.e.body.dataset;
356 ds.timestamp = m.mtime;
357 ds.lmtime = m.lmtime;
358 ds.msgid = m.msgid;
359 ds.xfrom = m.xfrom;
360 if(m.xfrom === Chat.me){
361 D.addClass(this.e.body, 'mine');
362 }
@@ -492,21 +493,34 @@
493 (k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
494 );
495 return bxs;
496 })()/*drag/drop*/;
497
498 const tzOffsetToString = function(off){
499 const hours = Math.round(off/60), min = Math.round(off % 30);
500 return ''+(hours + (min ? '.5' : ''));
501 };
502 const pad2 = (x)=>('0'+x).substr(-2);
503 const localTime8601 = function(d){
504 return [
505 d.getYear()+1900, '-', pad2(d.getMonth()+1), '-', pad2(d.getDate()+1),
506 'T', pad2(d.getHours()),':', pad2(d.getMinutes()),':',pad2(d.getSeconds()),
507 'Z', tzOffsetToString(d.getTimezoneOffset())
508 ].join('');
509 };
510
511 Chat.submitMessage = function(){
512 const fd = new FormData(this.e.inputForm)
513 /* ^^^^ we don't really want/need the FORM element, but when
514 FormData() is default-constructed here then the server
515 segfaults, and i have no clue why! */;
516 const msg = this.inputValue();
517 if(msg) fd.set('msg',msg);
518 const file = BlobXferState.blob || this.e.inputFile.files[0];
519 if(file) fd.set("file", file);
 
520 if( msg || file ){
521 fd.set("lmtime", localTime8601(new Date()));
522 fetch("chat-send",{
523 method: 'POST',
524 body: fd
525 });
526 }
@@ -552,11 +566,11 @@
566 ].join('');
567 };
568 /* Returns an almost-ISO8601 form of Date object d. */
569 const iso8601ish = function(d){
570 return d.toISOString()
571 .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' Zulu');
572 };
573 /* Event handler for clicking .message-user elements to show their
574 timestamps. */
575 const handleLegendClicked = function f(ev){
576 if(!f.popup){
@@ -568,16 +582,23 @@
582 if(!eMsg) return;
583 D.clearElement(this.e);
584 const d = new Date(eMsg.dataset.timestamp);
585 if(d.getMinutes().toString()!=="NaN"){
586 // Date works, render informative timestamps
587 const xfrom = eMsg.dataset.xfrom;
588 D.append(this.e,
589 D.append(D.span(), localTimeString(d)," ",Chat.me," time"),
590 D.append(D.span(), iso8601ish(d)));
591 if(xfrom!==Chat.me){
592 D.append(this.e,
593 D.append(D.span(), localTime8601(
594 new Date(eMsg.dataset.lmtime)
595 )," ",xfrom," time"));
596 }
597 }else{
598 // Date doesn't work, so dumb it down...
599 D.append(this.e, D.append(D.span(), eMsg.dataset.timestamp," Zulu"));
600 }
601 const toolbar = D.addClass(D.div(), 'toolbar');
602 D.append(this.e, toolbar);
603 const btnDeleteLocal = D.button("Delete locally");
604 D.append(toolbar, btnDeleteLocal);
605

Keyboard Shortcuts

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