Fossil

Check-in [9a17e76ebf]
Login

Check-in [9a17e76ebf]

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: 9a17e76ebf6eb6095f5665ee63bd27dffe1dbf6631b844b864bf798049e72de0
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
Unified Diff Ignore Whitespace Patch
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
747
748
749
750








751
752
753
754
755
756
757
                const btnDeleteGlobal = D.button("Delete globally");
                D.append(toolbar, btnDeleteGlobal);
                btnDeleteGlobal.addEventListener('click', function(){
                  self.hide();
                  Chat.deleteMessage(eMsg);
                });
              }
              if(eMsg.dataset.xfrom){
                /* Add a link to the /timeline filtered on this user. */
                const toolbar2 = D.addClass(D.div(), 'toolbar');
                D.append(this.e, toolbar2);








                const timelineLink = D.attr(
                  D.a(F.repoUrl('timeline',{
                    u: eMsg.dataset.xfrom,
                    y: 'a'
                  }), "User's Timeline"),
                  'target', '_blank'
                );







<
<
|
|
>
>
>
>
>
>
>
>







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
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 > .markdown {
  margin: 0;
  padding: 0;
}
body.chat .message-widget-content > .markdown > *:first-child {
  margin-top: 0;
}
body.chat .message-widget-content > .markdown > *:last-child {







|







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
1684
1685
1686
1687
1688
1689
1690
1691




1692
1693
1694
1695
1696
1697
1698
  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.2em;
  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;




}
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;







|







>
>
>
>







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;