Fossil SCM

/chat now uses markdown, instead of its minimal custom markup. Chat messages are rendered at send-time, not save-time, so this retroactively affects all messages.

stephan 2021-09-19 04:11 trunk
Commit 52d40548ed2a1176d4bc2fef710f11ae6c5c74ea0d985e13bc405f47bcaca6ad
2 files changed +17 -70 +11 -1
+17 -70
--- src/chat.c
+++ src/chat.c
@@ -125,11 +125,11 @@
125125
*/
126126
/*
127127
** SETTING: chat-alert-sound width=10
128128
**
129129
** This is the name of the builtin sound file to use for the alert tone.
130
-** The value must be the name of one of a builtin WAV file.
130
+** The value must be the name of a builtin WAV file.
131131
*/
132132
/*
133133
** WEBPAGE: chat
134134
**
135135
** Start up a browser-based chat session.
@@ -156,13 +156,14 @@
156156
style_header("Chat");
157157
@ <form accept-encoding="utf-8" id="chat-form" autocomplete="off">
158158
@ <div id='chat-input-area'>
159159
@ <div id='chat-input-line'>
160160
@ <input type="text" name="msg" id="chat-input-single" \
161
- @ placeholder="Type message for %h(zProjectName)." autocomplete="off">
161
+ @ placeholder="Type markdown-formatted message for %h(zProjectName)." \
162
+ @ autocomplete="off">
162163
@ <textarea rows="8" id="chat-input-multi" \
163
- @ placeholder="Type message for %h(zProjectName). Ctrl-Enter sends it." \
164
+ @ placeholder="Type markdown-formatted message for %h(zProjectName). Ctrl-Enter sends it." \
164165
@ class="hidden"></textarea>
165166
@ <input type="submit" value="Send" id="chat-message-submit">
166167
@ <span id="chat-settings-button" class="settings-icon" \
167168
@ aria-label="Settings..." aria-haspopup="true" ></span>
168169
@ </div>
@@ -294,11 +295,11 @@
294295
}
295296
fossil_free(zTime);
296297
}
297298
298299
/*
299
-** WEBPAGE: chat-send
300
+** WEBPAGE: chat-send hidden
300301
**
301302
** This page receives (via XHR) a new chat-message and/or a new file
302303
** to be entered into the chat history.
303304
**
304305
** On success it responds with an empty response: the new message
@@ -347,79 +348,25 @@
347348
db_commit_transaction();
348349
}
349350
350351
/*
351352
** This routine receives raw (user-entered) message text and transforms
352
-** it into HTML that is safe to insert using innerHTML.
353
-**
354
-** * HTML in the original text is escaped.
355
-**
356
-** * Hyperlinks are identified and tagged. Hyperlinks are:
357
-**
358
-** - Undelimited text of the form https:... or http:...
359
-** - Any text enclosed within [...]
353
+** it into HTML that is safe to insert using innerHTML. As of 2021-09-19,
354
+** it does so by using markdown_to_html() to convert markdown-formatted
355
+** zMsg to HTML.
360356
**
361357
** Space to hold the returned string is obtained from fossil_malloc()
362358
** and must be freed by the caller.
363359
*/
364360
static char *chat_format_to_html(const char *zMsg){
365
- char *zSafe = mprintf("%h", zMsg);
366
- int i, j, k;
367361
Blob out;
368
- char zClose[20];
369
- blob_init(&out, 0, 0);
370
- for(i=j=0; zSafe[i]; i++){
371
- if( zSafe[i]=='[' ){
372
- for(k=i+1; zSafe[k] && zSafe[k]!=']'; k++){}
373
- if( zSafe[k]==']' ){
374
- zSafe[k] = 0;
375
- if( j<i ){
376
- blob_append(&out, zSafe + j, i-j);
377
- j = i;
378
- }
379
- blob_append_char(&out, '[');
380
- wiki_resolve_hyperlink(&out,
381
- WIKI_NOBADLINKS|WIKI_TARGET_BLANK|WIKI_NOBRACKET,
382
- zSafe+i+1, zClose, sizeof(zClose), zSafe, 0);
383
- zSafe[k] = ']';
384
- j++;
385
- blob_append(&out, zSafe + j, k - j);
386
- blob_append(&out, zClose, -1);
387
- blob_append_char(&out, ']');
388
- i = k;
389
- j = k+1;
390
- continue;
391
- }
392
- }else if( zSafe[i]=='h'
393
- && (strncmp(zSafe+i,"http:",5)==0
394
- || strncmp(zSafe+i,"https:",6)==0) ){
395
- for(k=i+1; zSafe[k] && !fossil_isspace(zSafe[k]); k++){}
396
- if( k>i+7 ){
397
- char c = zSafe[k];
398
- if( !fossil_isalnum(zSafe[k-1]) && zSafe[k-1]!='/' ){
399
- k--;
400
- c = zSafe[k];
401
- }
402
- if( j<i ){
403
- blob_append(&out, zSafe + j, i-j);
404
- j = i;
405
- }
406
- zSafe[k] = 0;
407
- wiki_resolve_hyperlink(&out, WIKI_NOBADLINKS|WIKI_TARGET_BLANK,
408
- zSafe+i, zClose, sizeof(zClose), zSafe, 0);
409
- zSafe[k] = c;
410
- blob_append(&out, zSafe + j, k - j);
411
- blob_append(&out, zClose, -1);
412
- i = j = k;
413
- continue;
414
- }
415
- }
416
- }
417
- if( j<i ){
418
- blob_append(&out, zSafe+j, j-i);
419
- }
420
- fossil_free(zSafe);
362
+ blob_init(&out, "", 0);
363
+ if(*zMsg){
364
+ Blob bIn;
365
+ blob_init(&bIn, zMsg, (int)strlen(zMsg));
366
+ markdown_to_html(&bIn, NULL, &out);
367
+ }
421368
return blob_str(&out);
422369
}
423370
424371
/*
425372
** COMMAND: test-chat-formatter
@@ -442,11 +389,11 @@
442389
fossil_free(zOut);
443390
}
444391
}
445392
446393
/*
447
-** WEBPAGE: chat-poll
394
+** WEBPAGE: chat-poll hidden
448395
**
449396
** The chat page generated by /chat using an XHR to this page to
450397
** request new chat content. A typical invocation is:
451398
**
452399
** /chat-poll/N
@@ -651,11 +598,11 @@
651598
cgi_set_content(&json);
652599
return;
653600
}
654601
655602
/*
656
-** WEBPAGE: chat-download
603
+** WEBPAGE: chat-download hidden
657604
**
658605
** Download the CHAT.FILE attachment associated with a single chat
659606
** entry. The "name" query parameter begins with an integer that
660607
** identifies the particular chat message. The integer may be followed
661608
** by a / and a filename, which will indicate to the browser to use
@@ -684,11 +631,11 @@
684631
cgi_set_content(&r);
685632
}
686633
687634
688635
/*
689
-** WEBPAGE: chat-delete
636
+** WEBPAGE: chat-delete hidden
690637
**
691638
** Delete the chat entry identified by the name query parameter.
692639
** Invoking fetch("chat-delete/"+msgid) from javascript in the client
693640
** will delete a chat entry from the CHAT table.
694641
**
695642
--- src/chat.c
+++ src/chat.c
@@ -125,11 +125,11 @@
125 */
126 /*
127 ** SETTING: chat-alert-sound width=10
128 **
129 ** This is the name of the builtin sound file to use for the alert tone.
130 ** The value must be the name of one of a builtin WAV file.
131 */
132 /*
133 ** WEBPAGE: chat
134 **
135 ** Start up a browser-based chat session.
@@ -156,13 +156,14 @@
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 message for %h(zProjectName)." autocomplete="off">
 
162 @ <textarea rows="8" id="chat-input-multi" \
163 @ placeholder="Type message for %h(zProjectName). Ctrl-Enter sends it." \
164 @ class="hidden"></textarea>
165 @ <input type="submit" value="Send" id="chat-message-submit">
166 @ <span id="chat-settings-button" class="settings-icon" \
167 @ aria-label="Settings..." aria-haspopup="true" ></span>
168 @ </div>
@@ -294,11 +295,11 @@
294 }
295 fossil_free(zTime);
296 }
297
298 /*
299 ** WEBPAGE: chat-send
300 **
301 ** This page receives (via XHR) a new chat-message and/or a new file
302 ** to be entered into the chat history.
303 **
304 ** On success it responds with an empty response: the new message
@@ -347,79 +348,25 @@
347 db_commit_transaction();
348 }
349
350 /*
351 ** This routine receives raw (user-entered) message text and transforms
352 ** it into HTML that is safe to insert using innerHTML.
353 **
354 ** * HTML in the original text is escaped.
355 **
356 ** * Hyperlinks are identified and tagged. Hyperlinks are:
357 **
358 ** - Undelimited text of the form https:... or http:...
359 ** - Any text enclosed within [...]
360 **
361 ** Space to hold the returned string is obtained from fossil_malloc()
362 ** and must be freed by the caller.
363 */
364 static char *chat_format_to_html(const char *zMsg){
365 char *zSafe = mprintf("%h", zMsg);
366 int i, j, k;
367 Blob out;
368 char zClose[20];
369 blob_init(&out, 0, 0);
370 for(i=j=0; zSafe[i]; i++){
371 if( zSafe[i]=='[' ){
372 for(k=i+1; zSafe[k] && zSafe[k]!=']'; k++){}
373 if( zSafe[k]==']' ){
374 zSafe[k] = 0;
375 if( j<i ){
376 blob_append(&out, zSafe + j, i-j);
377 j = i;
378 }
379 blob_append_char(&out, '[');
380 wiki_resolve_hyperlink(&out,
381 WIKI_NOBADLINKS|WIKI_TARGET_BLANK|WIKI_NOBRACKET,
382 zSafe+i+1, zClose, sizeof(zClose), zSafe, 0);
383 zSafe[k] = ']';
384 j++;
385 blob_append(&out, zSafe + j, k - j);
386 blob_append(&out, zClose, -1);
387 blob_append_char(&out, ']');
388 i = k;
389 j = k+1;
390 continue;
391 }
392 }else if( zSafe[i]=='h'
393 && (strncmp(zSafe+i,"http:",5)==0
394 || strncmp(zSafe+i,"https:",6)==0) ){
395 for(k=i+1; zSafe[k] && !fossil_isspace(zSafe[k]); k++){}
396 if( k>i+7 ){
397 char c = zSafe[k];
398 if( !fossil_isalnum(zSafe[k-1]) && zSafe[k-1]!='/' ){
399 k--;
400 c = zSafe[k];
401 }
402 if( j<i ){
403 blob_append(&out, zSafe + j, i-j);
404 j = i;
405 }
406 zSafe[k] = 0;
407 wiki_resolve_hyperlink(&out, WIKI_NOBADLINKS|WIKI_TARGET_BLANK,
408 zSafe+i, zClose, sizeof(zClose), zSafe, 0);
409 zSafe[k] = c;
410 blob_append(&out, zSafe + j, k - j);
411 blob_append(&out, zClose, -1);
412 i = j = k;
413 continue;
414 }
415 }
416 }
417 if( j<i ){
418 blob_append(&out, zSafe+j, j-i);
419 }
420 fossil_free(zSafe);
421 return blob_str(&out);
422 }
423
424 /*
425 ** COMMAND: test-chat-formatter
@@ -442,11 +389,11 @@
442 fossil_free(zOut);
443 }
444 }
445
446 /*
447 ** WEBPAGE: chat-poll
448 **
449 ** The chat page generated by /chat using an XHR to this page to
450 ** request new chat content. A typical invocation is:
451 **
452 ** /chat-poll/N
@@ -651,11 +598,11 @@
651 cgi_set_content(&json);
652 return;
653 }
654
655 /*
656 ** WEBPAGE: chat-download
657 **
658 ** Download the CHAT.FILE attachment associated with a single chat
659 ** entry. The "name" query parameter begins with an integer that
660 ** identifies the particular chat message. The integer may be followed
661 ** by a / and a filename, which will indicate to the browser to use
@@ -684,11 +631,11 @@
684 cgi_set_content(&r);
685 }
686
687
688 /*
689 ** WEBPAGE: chat-delete
690 **
691 ** Delete the chat entry identified by the name query parameter.
692 ** Invoking fetch("chat-delete/"+msgid) from javascript in the client
693 ** will delete a chat entry from the CHAT table.
694 **
695
--- src/chat.c
+++ src/chat.c
@@ -125,11 +125,11 @@
125 */
126 /*
127 ** SETTING: chat-alert-sound width=10
128 **
129 ** This is the name of the builtin sound file to use for the alert tone.
130 ** The value must be the name of a builtin WAV file.
131 */
132 /*
133 ** WEBPAGE: chat
134 **
135 ** Start up a browser-based chat session.
@@ -156,13 +156,14 @@
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>
@@ -294,11 +295,11 @@
295 }
296 fossil_free(zTime);
297 }
298
299 /*
300 ** WEBPAGE: chat-send hidden
301 **
302 ** This page receives (via XHR) a new chat-message and/or a new file
303 ** to be entered into the chat history.
304 **
305 ** On success it responds with an empty response: the new message
@@ -347,79 +348,25 @@
348 db_commit_transaction();
349 }
350
351 /*
352 ** This routine receives raw (user-entered) message text and transforms
353 ** it into HTML that is safe to insert using innerHTML. As of 2021-09-19,
354 ** it does so by using markdown_to_html() to convert markdown-formatted
355 ** zMsg to HTML.
 
 
 
 
 
356 **
357 ** Space to hold the returned string is obtained from fossil_malloc()
358 ** and must be freed by the caller.
359 */
360 static char *chat_format_to_html(const char *zMsg){
 
 
361 Blob out;
362 blob_init(&out, "", 0);
363 if(*zMsg){
364 Blob bIn;
365 blob_init(&bIn, zMsg, (int)strlen(zMsg));
366 markdown_to_html(&bIn, NULL, &out);
367 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368 return blob_str(&out);
369 }
370
371 /*
372 ** COMMAND: test-chat-formatter
@@ -442,11 +389,11 @@
389 fossil_free(zOut);
390 }
391 }
392
393 /*
394 ** WEBPAGE: chat-poll hidden
395 **
396 ** The chat page generated by /chat using an XHR to this page to
397 ** request new chat content. A typical invocation is:
398 **
399 ** /chat-poll/N
@@ -651,11 +598,11 @@
598 cgi_set_content(&json);
599 return;
600 }
601
602 /*
603 ** WEBPAGE: chat-download hidden
604 **
605 ** Download the CHAT.FILE attachment associated with a single chat
606 ** entry. The "name" query parameter begins with an integer that
607 ** identifies the particular chat message. The integer may be followed
608 ** by a / and a filename, which will indicate to the browser to use
@@ -684,11 +631,11 @@
631 cgi_set_content(&r);
632 }
633
634
635 /*
636 ** WEBPAGE: chat-delete hidden
637 **
638 ** Delete the chat entry identified by the name query parameter.
639 ** Invoking fetch("chat-delete/"+msgid) from javascript in the client
640 ** will delete a chat entry from the CHAT table.
641 **
642
+11 -1
--- src/default.css
+++ src/default.css
@@ -1629,16 +1629,26 @@
16291629
box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);
16301630
padding: 0.25em 0.5em;
16311631
margin-top: 0;
16321632
min-width: 9em /*avoid unsightly "underlap" with the neighboring
16331633
.message-widget-tab element*/;
1634
- white-space: pre-wrap/*needed for multi-line edits*/;
1634
+ white-space: normal;
16351635
}
16361636
body.chat.monospace-messages .message-widget-content,
16371637
body.chat.monospace-messages textarea,
16381638
body.chat.monospace-messages input[type=text]{
16391639
font-family: monospace;
1640
+}
1641
+body.chat .message-widget-content > .markdown {
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 {
1649
+ margin-bottom: 0;
16401650
}
16411651
/* User name and timestamp (a LEGEND-like element) */
16421652
body.chat .message-widget .message-widget-tab {
16431653
border-radius: 0.25em 0.25em 0 0;
16441654
margin: 0 0.25em 0em 0.15em;
16451655
--- src/default.css
+++ src/default.css
@@ -1629,16 +1629,26 @@
1629 box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);
1630 padding: 0.25em 0.5em;
1631 margin-top: 0;
1632 min-width: 9em /*avoid unsightly "underlap" with the neighboring
1633 .message-widget-tab element*/;
1634 white-space: pre-wrap/*needed for multi-line edits*/;
1635 }
1636 body.chat.monospace-messages .message-widget-content,
1637 body.chat.monospace-messages textarea,
1638 body.chat.monospace-messages input[type=text]{
1639 font-family: monospace;
 
 
 
 
 
 
 
 
 
 
1640 }
1641 /* User name and timestamp (a LEGEND-like element) */
1642 body.chat .message-widget .message-widget-tab {
1643 border-radius: 0.25em 0.25em 0 0;
1644 margin: 0 0.25em 0em 0.15em;
1645
--- src/default.css
+++ src/default.css
@@ -1629,16 +1629,26 @@
1629 box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);
1630 padding: 0.25em 0.5em;
1631 margin-top: 0;
1632 min-width: 9em /*avoid unsightly "underlap" with the neighboring
1633 .message-widget-tab element*/;
1634 white-space: normal;
1635 }
1636 body.chat.monospace-messages .message-widget-content,
1637 body.chat.monospace-messages textarea,
1638 body.chat.monospace-messages input[type=text]{
1639 font-family: monospace;
1640 }
1641 body.chat .message-widget-content > .markdown {
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 {
1649 margin-bottom: 0;
1650 }
1651 /* User name and timestamp (a LEGEND-like element) */
1652 body.chat .message-widget .message-widget-tab {
1653 border-radius: 0.25em 0.25em 0 0;
1654 margin: 0 0.25em 0em 0.15em;
1655

Keyboard Shortcuts

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