Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | /chat: add a button to the user/timestamp popup to toggle between parsed and plain-text rendering for that message, per /chat request. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | chat-markdown |
| Files: | files | file ages | folders |
| SHA3-256: |
9a17e76ebf6eb6095f5665ee63bd27df |
| User & Date: | stephan 2021-09-21 04:09:44.052 |
Context
|
2021-09-21
| ||
| 10:08 | Merge fixes from trunk ... (check-in: 3f2365fe26 user: drh tags: chat-markdown) | |
| 04:09 | /chat: add a button to the user/timestamp popup to toggle between parsed and plain-text rendering for that message, per /chat request. ... (check-in: 9a17e76ebf user: stephan tags: chat-markdown) | |
|
2021-09-20
| ||
| 12:14 | Merge trunk changes into the chat-markdown branch. ... (check-in: 807b8d9bf4 user: drh tags: chat-markdown) | |
Changes
Changes to src/chat.c.
| ︙ | ︙ | |||
595 596 597 598 599 600 601 602 603 604 605 606 607 608 |
}
} /* Exit by "break" */
db_finalize(&q1);
blob_append(&json, "\n]}", 3);
cgi_set_content(&json);
return;
}
/*
** WEBPAGE: chat-download hidden
**
** Download the CHAT.FILE attachment associated with a single chat
** entry. The "name" query parameter begins with an integer that
** identifies the particular chat message. The integer may be followed
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 |
}
} /* Exit by "break" */
db_finalize(&q1);
blob_append(&json, "\n]}", 3);
cgi_set_content(&json);
return;
}
/*
** WEBPAGE: chat-fetch-one hidden
**
** /chat-fetch-one/N
**
** Fetches a single message with the given ID, if available.
**
** Options:
**
** raw = the xmsg field will be returned unparsed.
**
** Response is either a single object in the format returned by
** /chat-poll (without the wrapper array) or a JSON-format error
** response, as documented for ajax_route_error().
*/
void chat_fetch_one(void){
Blob json = empty_blob; /* The json to be constructed and returned */
const int fRaw = PD("raw",0)!=0;
const int msgid = atoi(PD("name","0"));
Stmt q;
login_check_credentials();
if( !g.perm.Chat ) {
chat_emit_permissions_error(0);
return;
}
chat_create_tables();
cgi_set_content_type("application/json");
db_prepare(&q,
"SELECT datetime(mtime), xfrom, xmsg, length(file),"
" fname, fmime, lmtime"
" FROM chat WHERE msgid=%d AND mdel IS NULL",
msgid);
if(SQLITE_ROW==db_step(&q)){
const char *zDate = db_column_text(&q, 0);
const char *zFrom = db_column_text(&q, 1);
const char *zRawMsg = db_column_text(&q, 2);
const int nByte = db_column_int(&q, 3);
const char *zFName = db_column_text(&q, 4);
const char *zFMime = db_column_text(&q, 5);
const char *zLMtime = db_column_text(&q, 7);
blob_appendf(&json,"{\"msgid\": %d,", msgid);
blob_appendf(&json, "\"mtime\":\"%.10sT%sZ\",", zDate, zDate+11);
if( zLMtime && zLMtime[0] ){
blob_appendf(&json, "\"lmtime\":%!j,", zLMtime);
}
blob_append(&json, "\"xfrom\":", -1);
if(zFrom){
blob_appendf(&json, "%!j,", zFrom);
}else{
/* see https://fossil-scm.org/forum/forumpost/e0be0eeb4c */
blob_appendf(&json, "null,");
}
blob_appendf(&json, "\"uclr\":%!j,",
user_color(zFrom ? zFrom : "nobody"));
blob_append(&json,"\"xmsg\":", 7);
if(fRaw){
blob_appendf(&json, "%!j,", zRawMsg);
}else{
char * zMsg = chat_format_to_html(zRawMsg ? zRawMsg : "");
blob_appendf(&json, "%!j,", zMsg);
fossil_free(zMsg);
}
if( nByte==0 ){
blob_appendf(&json, "\"fsize\":0");
}else{
blob_appendf(&json, "\"fsize\":%d,\"fname\":%!j,\"fmime\":%!j",
nByte, zFName, zFMime);
}
blob_append(&json,"}",1);
cgi_set_content(&json);
}else{
ajax_route_error(404,"Chat message #%d not found.", msgid);
}
db_finalize(&q);
}
/*
** WEBPAGE: chat-download hidden
**
** Download the CHAT.FILE attachment associated with a single chat
** entry. The "name" query parameter begins with an integer that
** identifies the particular chat message. The integer may be followed
|
| ︙ | ︙ |
Changes to src/chat.js.
| ︙ | ︙ | |||
483 484 485 486 487 488 489 490 491 492 493 494 495 496 |
this.fetchLastMessageElem();
}
F.toast.message("Deleted message "+id+".");
}
return !!e;
};
/** Given a .message-row element, this function returns whethe the
current user may, at least hypothetically, delete the message
globally. A user may always delete a local copy of a
post. The server may trump this, e.g. if the login has been
cancelled after this page was loaded.
*/
cs.userMayDelete = function(eMsg){
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 |
this.fetchLastMessageElem();
}
F.toast.message("Deleted message "+id+".");
}
return !!e;
};
/**
Toggles the given message between its parsed and plain-text
representations. It requires a server round-trip to collect the
plain-text form but caches it for subsequent toggles.
Expects the ID of a currently-loaded message or a
message-widget DOM elment from which it can extract an id.
This is an aync operation the first time it's passed a given
message and synchronous on subsequent calls for that
message. It is a no-op if id does not resolve to a loaded
message.
*/
cs.toggleTextMode = function(id){
var e;
if(id instanceof HTMLElement){
e = id;
id = e.dataset.msgid;
}else{
e = this.getMessageElemById(id);
}
if(!e || !id) return false;
else if(e.$isToggling) return;
e.$isToggling = true;
const content = e.querySelector('.message-widget-content');
if(!content.$elems){
content.$elems = [
content.firstElementChild, // parsed elem
undefined // plaintext elem
];
}else if(content.$elems[1]){
// We have both content types. Simply toggle them.
const child = (
content.firstElementChild===content.$elems[0]
? content.$elems[1]
: content.$elems[0]
);
delete e.$isToggling;
D.append(D.clearElement(content), child);
return;
}
// We need to fetch the plain-text version...
const self = this;
F.fetch('chat-fetch-one',{
urlParams:{ name: id, raw: true},
responseType: 'json',
onload: function(msg){
content.$elems[1] = D.append(D.pre(),msg.xmsg);
self.toggleTextMode(e);
},
aftersend:function(){
delete e.$isToggling;
Chat.ajaxEnd();
}
});
return true;
};
/** Given a .message-row element, this function returns whethe the
current user may, at least hypothetically, delete the message
globally. A user may always delete a local copy of a
post. The server may trump this, e.g. if the login has been
cancelled after this page was loaded.
*/
cs.userMayDelete = function(eMsg){
|
| ︙ | ︙ | |||
740 741 742 743 744 745 746 |
const btnDeleteGlobal = D.button("Delete globally");
D.append(toolbar, btnDeleteGlobal);
btnDeleteGlobal.addEventListener('click', function(){
self.hide();
Chat.deleteMessage(eMsg);
});
}
| < < | | > > > > > > > > | 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 |
const btnDeleteGlobal = D.button("Delete globally");
D.append(toolbar, btnDeleteGlobal);
btnDeleteGlobal.addEventListener('click', function(){
self.hide();
Chat.deleteMessage(eMsg);
});
}
const toolbar2 = D.addClass(D.div(), 'toolbar');
D.append(this.e, toolbar2);
const btnToggleText = D.button("Toggle text mode");
btnToggleText.addEventListener('click', function(){
self.hide();
Chat.toggleTextMode(eMsg);
});
D.append(toolbar2, btnToggleText);
if(eMsg.dataset.xfrom){
/* Add a link to the /timeline filtered on this user. */
const timelineLink = D.attr(
D.a(F.repoUrl('timeline',{
u: eMsg.dataset.xfrom,
y: 'a'
}), "User's Timeline"),
'target', '_blank'
);
|
| ︙ | ︙ |
Changes to src/default.css.
| ︙ | ︙ | |||
1634 1635 1636 1637 1638 1639 1640 |
white-space: normal;
}
body.chat.monospace-messages .message-widget-content,
body.chat.monospace-messages textarea,
body.chat.monospace-messages input[type=text]{
font-family: monospace;
}
| | | 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 |
white-space: normal;
}
body.chat.monospace-messages .message-widget-content,
body.chat.monospace-messages textarea,
body.chat.monospace-messages input[type=text]{
font-family: monospace;
}
body.chat .message-widget-content > * {
margin: 0;
padding: 0;
}
body.chat .message-widget-content > .markdown > *:first-child {
margin-top: 0;
}
body.chat .message-widget-content > .markdown > *:last-child {
|
| ︙ | ︙ | |||
1677 1678 1679 1680 1681 1682 1683 |
padding: 0.25em;
z-index: 200;
}
/* Full message timestamps. */
body.chat .chat-message-popup > span { white-space: nowrap; }
/* Container for the message deletion buttons. */
body.chat .chat-message-popup > .toolbar {
| | > > > > | 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 |
padding: 0.25em;
z-index: 200;
}
/* Full message timestamps. */
body.chat .chat-message-popup > span { white-space: nowrap; }
/* Container for the message deletion buttons. */
body.chat .chat-message-popup > .toolbar {
padding: 0;
margin: 0;
border: 2px inset rgba(0,0,0,0.3);
border-radius: 0.25em;
display: flex;
flex-direction: row;
justify-content: stretch;
flex-wrap: wrap;
align-items: center;
}
body.chat .chat-message-popup > .toolbar > * {
margin: 0.35em;
}
body.chat .chat-message-popup > .toolbar > button {
flex: 1 1 auto;
}
/* The widget for loading more/older chat messages. */
body.chat #load-msg-toolbar {
border-radius: 0.25em;
|
| ︙ | ︙ |