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.
Commit
52d40548ed2a1176d4bc2fef710f11ae6c5c74ea0d985e13bc405f47bcaca6ad
Parent
9d4a13276110757…
2 files changed
+17
-70
+11
-1
+17
-70
| --- src/chat.c | ||
| +++ src/chat.c | ||
| @@ -125,11 +125,11 @@ | ||
| 125 | 125 | */ |
| 126 | 126 | /* |
| 127 | 127 | ** SETTING: chat-alert-sound width=10 |
| 128 | 128 | ** |
| 129 | 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. | |
| 130 | +** The value must be the name of a builtin WAV file. | |
| 131 | 131 | */ |
| 132 | 132 | /* |
| 133 | 133 | ** WEBPAGE: chat |
| 134 | 134 | ** |
| 135 | 135 | ** Start up a browser-based chat session. |
| @@ -156,13 +156,14 @@ | ||
| 156 | 156 | style_header("Chat"); |
| 157 | 157 | @ <form accept-encoding="utf-8" id="chat-form" autocomplete="off"> |
| 158 | 158 | @ <div id='chat-input-area'> |
| 159 | 159 | @ <div id='chat-input-line'> |
| 160 | 160 | @ <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"> | |
| 162 | 163 | @ <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." \ | |
| 164 | 165 | @ class="hidden"></textarea> |
| 165 | 166 | @ <input type="submit" value="Send" id="chat-message-submit"> |
| 166 | 167 | @ <span id="chat-settings-button" class="settings-icon" \ |
| 167 | 168 | @ aria-label="Settings..." aria-haspopup="true" ></span> |
| 168 | 169 | @ </div> |
| @@ -294,11 +295,11 @@ | ||
| 294 | 295 | } |
| 295 | 296 | fossil_free(zTime); |
| 296 | 297 | } |
| 297 | 298 | |
| 298 | 299 | /* |
| 299 | -** WEBPAGE: chat-send | |
| 300 | +** WEBPAGE: chat-send hidden | |
| 300 | 301 | ** |
| 301 | 302 | ** This page receives (via XHR) a new chat-message and/or a new file |
| 302 | 303 | ** to be entered into the chat history. |
| 303 | 304 | ** |
| 304 | 305 | ** On success it responds with an empty response: the new message |
| @@ -347,79 +348,25 @@ | ||
| 347 | 348 | db_commit_transaction(); |
| 348 | 349 | } |
| 349 | 350 | |
| 350 | 351 | /* |
| 351 | 352 | ** 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. | |
| 360 | 356 | ** |
| 361 | 357 | ** Space to hold the returned string is obtained from fossil_malloc() |
| 362 | 358 | ** and must be freed by the caller. |
| 363 | 359 | */ |
| 364 | 360 | static char *chat_format_to_html(const char *zMsg){ |
| 365 | - char *zSafe = mprintf("%h", zMsg); | |
| 366 | - int i, j, k; | |
| 367 | 361 | 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 | + } | |
| 421 | 368 | return blob_str(&out); |
| 422 | 369 | } |
| 423 | 370 | |
| 424 | 371 | /* |
| 425 | 372 | ** COMMAND: test-chat-formatter |
| @@ -442,11 +389,11 @@ | ||
| 442 | 389 | fossil_free(zOut); |
| 443 | 390 | } |
| 444 | 391 | } |
| 445 | 392 | |
| 446 | 393 | /* |
| 447 | -** WEBPAGE: chat-poll | |
| 394 | +** WEBPAGE: chat-poll hidden | |
| 448 | 395 | ** |
| 449 | 396 | ** The chat page generated by /chat using an XHR to this page to |
| 450 | 397 | ** request new chat content. A typical invocation is: |
| 451 | 398 | ** |
| 452 | 399 | ** /chat-poll/N |
| @@ -651,11 +598,11 @@ | ||
| 651 | 598 | cgi_set_content(&json); |
| 652 | 599 | return; |
| 653 | 600 | } |
| 654 | 601 | |
| 655 | 602 | /* |
| 656 | -** WEBPAGE: chat-download | |
| 603 | +** WEBPAGE: chat-download hidden | |
| 657 | 604 | ** |
| 658 | 605 | ** Download the CHAT.FILE attachment associated with a single chat |
| 659 | 606 | ** entry. The "name" query parameter begins with an integer that |
| 660 | 607 | ** identifies the particular chat message. The integer may be followed |
| 661 | 608 | ** by a / and a filename, which will indicate to the browser to use |
| @@ -684,11 +631,11 @@ | ||
| 684 | 631 | cgi_set_content(&r); |
| 685 | 632 | } |
| 686 | 633 | |
| 687 | 634 | |
| 688 | 635 | /* |
| 689 | -** WEBPAGE: chat-delete | |
| 636 | +** WEBPAGE: chat-delete hidden | |
| 690 | 637 | ** |
| 691 | 638 | ** Delete the chat entry identified by the name query parameter. |
| 692 | 639 | ** Invoking fetch("chat-delete/"+msgid) from javascript in the client |
| 693 | 640 | ** will delete a chat entry from the CHAT table. |
| 694 | 641 | ** |
| 695 | 642 |
| --- 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 @@ | ||
| 1629 | 1629 | box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29); |
| 1630 | 1630 | padding: 0.25em 0.5em; |
| 1631 | 1631 | margin-top: 0; |
| 1632 | 1632 | min-width: 9em /*avoid unsightly "underlap" with the neighboring |
| 1633 | 1633 | .message-widget-tab element*/; |
| 1634 | - white-space: pre-wrap/*needed for multi-line edits*/; | |
| 1634 | + white-space: normal; | |
| 1635 | 1635 | } |
| 1636 | 1636 | body.chat.monospace-messages .message-widget-content, |
| 1637 | 1637 | body.chat.monospace-messages textarea, |
| 1638 | 1638 | body.chat.monospace-messages input[type=text]{ |
| 1639 | 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; | |
| 1640 | 1650 | } |
| 1641 | 1651 | /* User name and timestamp (a LEGEND-like element) */ |
| 1642 | 1652 | body.chat .message-widget .message-widget-tab { |
| 1643 | 1653 | border-radius: 0.25em 0.25em 0 0; |
| 1644 | 1654 | margin: 0 0.25em 0em 0.15em; |
| 1645 | 1655 |
| --- 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 |