Diff
Not logged in

Differences From Artifact [27c9b830b4]:

To Artifact [34b589cee1]:


906
907
908
909
910
911
912
913

914
915
916
917
918
919
920
906
907
908
909
910
911
912

913
914
915
916
917
918
919
920







-
+







     deficiencies in Safari.
  */
  Chat.MessageWidget = (function(){
    /**
       Constructor. If passed an argument, it is passed to
       this.setMessage() after initialization.
    */
    const cf = function(){
    const ctor = function(){
      this.e = {
        body: D.addClass(D.div(), 'message-widget'),
        tab: D.addClass(D.div(), 'message-widget-tab'),
        content: D.addClass(D.div(), 'message-widget-content')
      };
      D.append(this.e.body, this.e.tab, this.e.content);
      this.e.tab.setAttribute('role', 'button');
929
930
931
932
933
934
935
936

937
938
939
940
941
942

943
944
945
946
947
948
949
929
930
931
932
933
934
935

936
937
938

939
940

941
942
943
944
945
946
947
948







-
+


-


-
+







      3: "Wednesday", 4: "Thursday", 5: "Friday",
      6: "Saturday"
    };
    /* Given a Date, returns the timestamp string used in the "tab"
       part of message widgets. If longFmt is true then a verbose
       format is used, else a brief format is used. The returned string
       is in client-local time. */
    const theTime = function(d, longFmt=true){
    const theTime = function(d, longFmt=false){
      const li = [];
      if( longFmt ){
        //li.push( d.toISOString() );
        li.push(
          d.getFullYear(),
          '-', pad2(d.getMonth()+1/*sigh*/),
          '-', pad2(d.getMonth()+1),
          '-', pad2(d.getDate()),
          ' ',
          d.getHours(), ":",
          (d.getMinutes()+100).toString().slice(1,3)
        );
      }else{
        li.push(
959
960
961
962
963
964
965

966
967
968
969
970
971
972
973
974


975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992

993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972


973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991

992
993
994
995
996
997
998
999


1000
1001
1002
1003
1004
1005
1006







+







-
-
+
+

















-
+







-
-







       Returns true if this page believes it can embed a view of the
       file wrapped by the given message object, else returns false.
    */
    const canEmbedFile = function f(msg){
      if(!f.$rx){
        f.$rx = /\.((html?)|(txt)|(md)|(wiki)|(pikchr))$/i;
        f.$specificTypes = [
          /* Mime types we know we can embed, sans image/... */
          'text/plain',
          'text/html',
          'text/x-markdown',
          /* Firefox sends text/markdown when uploading .md files */
          'text/markdown',
          'text/x-pikchr',
          'text/x-fossil-wiki'
          // add more as we discover which ones Firefox won't
          // force the user to try to download.
          /* Add more as we discover which ones Firefox won't
             force the user to try to download. */
        ];
      }
      if(msg.fmime){
        if(msg.fmime.startsWith("image/")
           || f.$specificTypes.indexOf(msg.fmime)>=0){
          return true;
        }
      }
      return (msg.fname && f.$rx.test(msg.fname));
    };

    /**
      Returns true if the given message object "should"
      be embedded in fossil-rendered form instead of
      raw content form. This is only intended to be passed
      message objects for which canEmbedFile() returns true.
    */
    const shouldWikiRenderEmbed = function f(msg){
    const shouldFossilRenderEmbed = function f(msg){
      if(!f.$rx){
        f.$rx = /\.((md)|(wiki)|(pikchr))$/i;
        f.$specificTypes = [
          'text/x-markdown',
          'text/markdown' /* Firefox-uploaded md files */,
          'text/x-pikchr',
          'text/x-fossil-wiki'
          // add more as we discover which ones Firefox won't
          // force the user to try to download.
        ];
      }
      if(msg.fmime){
        if(f.$specificTypes.indexOf(msg.fmime)>=0) return true;
      }
      return msg.fname && f.$rx.test(msg.fname);
    };
1025
1026
1027
1028
1029
1030
1031
1032

1033
1034
1035
1036
1037
1038
1039
1023
1024
1025
1026
1027
1028
1029

1030
1031
1032
1033
1034
1035
1036
1037







-
+







        if(isHidden) D.removeClass(iframe, 'hidden');
        iframe.style.maxHeight = iframe.style.height
          = iframe.contentWindow.document.documentElement.scrollHeight + 'px';
        if(isHidden) D.addClass(iframe, 'hidden');
      }
    };

    cf.prototype = {
    ctor.prototype = {
      scrollIntoView: function(){
        this.e.content.scrollIntoView();
      },
      setMessage: function(m){
        const ds = this.e.body.dataset;
        ds.timestamp = m.mtime;
        ds.lmtime = m.lmtime;
1050
1051
1052
1053
1054
1055
1056


1057





1058
1059
1060
1061
1062
1063
1064
1065
1066

1067
1068
1069
1070
1071
1072
1073
1048
1049
1050
1051
1052
1053
1054
1055
1056

1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069

1070
1071
1072
1073
1074
1075
1076
1077







+
+
-
+
+
+
+
+








-
+







        D.clearElement(this.e.tab);
        var contentTarget = this.e.content;
        var eXFrom /* element holding xfrom name */;
        if(m.xfrom){
          eXFrom = D.append(D.addClass(D.span(), 'xfrom'), m.xfrom);
          const wrapper = D.append(
            D.span(), eXFrom,
            ' ',
            D.append(D.addClass(D.span(), 'msgid'),
            D.text(" #",(m.msgid||'???'),' ',theTime(d)))
                     '#' + (m.msgid||'???')),
            (m.isSearchResult ? ' ' : ' @ '),
            D.append(D.addClass(D.span(), 'timestamp'),
                     theTime(d,!!m.isSearchResult))
          );
          D.append(this.e.tab, wrapper);
        }else{/*notification*/
          D.addClass(this.e.body, 'notification');
          if(m.isError){
            D.addClass([contentTarget, this.e.tab], 'error');
          }
          D.append(
            this.e.tab,
            D.append(D.code(), 'notification @ ',theTime(d))
            D.append(D.code(), 'notification @ ',theTime(d,false))
          );
        }
        if( m.xfrom && m.fsize>0 ){
          if( m.fmime
              && m.fmime.startsWith("image/")
              && Chat.settings.getBool('images-inline',true)
            ){
1086
1087
1088
1089
1090
1091
1092
1093
1094


1095
1096
1097
1098
1099
1100

1101
1102
1103
1104
1105
1106
1107
1090
1091
1092
1093
1094
1095
1096


1097
1098
1099
1100
1101
1102
1103

1104
1105
1106
1107
1108
1109
1110
1111







-
-
+
+





-
+







              "(" + m.fname + " " + m.fsize + " bytes)"
            )
            D.attr(a,'target','_blank');
            D.append(w, a);
            if(canEmbedFile(m)){
              /* Add an option to embed HTML attachments in an iframe. The primary
                 use case is attached diffs. */
              const shouldWikiRender = shouldWikiRenderEmbed(m);
              const downloadArgs = shouldWikiRender ? '?render' : '';
              const shouldFossilRender = shouldFossilRenderEmbed(m);
              const downloadArgs = shouldFossilRender ? '?render' : '';
              D.addClass(contentTarget, 'wide');
              const embedTarget = this.e.content;
              const self = this;
              const btnEmbed = D.attr(D.checkbox("1", false), 'id',
                                      'embed-'+ds.msgid);
              const btnLabel = D.label(btnEmbed, shouldWikiRender
              const btnLabel = D.label(btnEmbed, shouldFossilRender
                                       ? "Embed (fossil-rendered)" : "Embed");
              /* Maintenance reminder: do not disable the toggle
                 button while the content is loading because that will
                 cause it to get stuck in disabled mode if the browser
                 decides that loading the content should prompt the
                 user to download it, rather than embed it in the
                 iframe. */
1302
1303
1304
1305
1306
1307
1308
1309

1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320

1321
1322
1323
1324
1325
1326
1327
1306
1307
1308
1309
1310
1311
1312

1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323

1324
1325
1326
1327
1328
1329
1330
1331







-
+










-
+







            }
          }/*f.popup*/;
        }/*end static init*/
        const theMsg = findMessageWidgetParent(ev.target);
        if(theMsg) f.popup.show(theMsg);
      }/*_handleLegendClicked()*/
    };
    return cf;
    return ctor;
  })()/*MessageWidget*/;

  /**
     A widget for loading more messages (context) around a /chat-query
     result message.
  */
  Chat.SearchCtxLoader = (function(){
    const nMsgContext = 5;
    const zUpArrow = '\u25B2';
    const zDownArrow = '\u25BC';
    const cf = function(o){
    const ctor = function(o){

      /* iFirstInTable:
      **   msgid of first row in chatfts table.
      **
      ** iLastInTable:
      **   msgid of last row in chatfts table.
      **
1362
1363
1364
1365
1366
1367
1368
1369

1370
1371
1372
1373
1374
1375
1376
1366
1367
1368
1369
1370
1371
1372

1373
1374
1375
1376
1377
1378
1379
1380







-
+







      const ms = this;
      this.e.up.addEventListener('click', ()=>ms.load_messages(false));
      this.e.down.addEventListener('click', ()=>ms.load_messages(true));
      this.e.all.addEventListener('click', ()=>ms.load_messages( (ms.o.iPrevId==0) ));
      this.set_button_visibility();
    };

    cf.prototype = {
    ctor.prototype = {
      set_button_visibility: function() {
        if( !this.e ) return;
        const o = this.o;

        const iPrevId = (o.iPrevId!=0) ? o.iPrevId : o.iFirstInTable-1;
        const iNextId = (o.iNextId!=0) ? o.iNextId : o.iLastInTable+1;
        let nDiff = (iNextId - iPrevId) - 1;
1471
1472
1473
1474
1475
1476
1477
1478

1479
1480
1481
1482
1483
1484
1485
1475
1476
1477
1478
1479
1480
1481

1482
1483
1484
1485
1486
1487
1488
1489







-
+







            ms.set_button_visibility();
            ms.bIgnoreClick = false;
          }
        });
      }
    };

    return cf;
    return ctor;
  })() /*SearchCtxLoader*/;

  const BlobXferState = (function(){
    /* State for paste and drag/drop */
    const bxs = {
      dropDetails: document.querySelector('#chat-drop-details'),
      blob: undefined,
2394
2395
2396
2397
2398
2399
2400

2401
2402
2403
2404
2405
2406
2407
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412







+







      "chat-query", {
        urlParams: {q: term},
        responseType: 'json',
        onload:function(jx){
          let previd = 0;
          D.clearElement(eMsgTgt);
          jx.msgs.forEach((m)=>{
            m.isSearchResult = true;
            const mw = new Chat.MessageWidget(m);
            const spacer = new Chat.SearchCtxLoader({
              first: jx.first,
              last: jx.last,
              previd: previd,
              nextid: m.msgid
            });