Fossil

Check-in [c7ee6f4ef1]
Login

Check-in [c7ee6f4ef1]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:/chat: experimentally render a list of users ordered by most recent activity. Until/unless we can find a useful function for the list, though, it's really just a somewhat pretty screen space hog.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | chat-user-last-seen
Files: files | file ages | folders
SHA3-256: c7ee6f4ef19a3b1ef94ded459b98bd94c91bf249f0d3b5d1bfd71f808ad588e4
User & Date: stephan 2021-06-14 13:48:08.910
Context
2021-06-15
03:00
Merged in trunk for SSL improvements and deployment to test server. ... (check-in: 422323618e user: stephan tags: chat-user-last-seen)
2021-06-14
13:48
/chat: experimentally render a list of users ordered by most recent activity. Until/unless we can find a useful function for the list, though, it's really just a somewhat pretty screen space hog. ... (check-in: c7ee6f4ef1 user: stephan tags: chat-user-last-seen)
10:52
/chat now experimentally keeps track of the timestamp of the most recent message received from each user so that we can eventually integrate that information into the UI to provide a list of currently-active users (noting that we have no way of tracking the existence of lurkers). ... (check-in: be07b8d137 user: stephan tags: chat-user-last-seen)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/chat.js.
107
108
109
110
111
112
113
114

115
116
117
118
119
120
121
        messagesWrapper: E1('#chat-messages-wrapper'),
        inputForm: E1('#chat-form'),
        btnSubmit: E1('#chat-message-submit'),
        inputSingle: E1('#chat-input-single'),
        inputMulti: E1('#chat-input-multi'),
        inputCurrent: undefined/*one of inputSingle or inputMulti*/,
        inputFile: E1('#chat-input-file'),
        contentDiv: E1('div.content')

      },
      me: F.user.name,
      mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
      mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
      pageIsActive: 'visible'===document.visibilityState,
      changesSincePageHidden: 0,
      notificationBubbleColor: 'white',







|
>







107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
        messagesWrapper: E1('#chat-messages-wrapper'),
        inputForm: E1('#chat-form'),
        btnSubmit: E1('#chat-message-submit'),
        inputSingle: E1('#chat-input-single'),
        inputMulti: E1('#chat-input-multi'),
        inputCurrent: undefined/*one of inputSingle or inputMulti*/,
        inputFile: E1('#chat-input-file'),
        contentDiv: E1('div.content'),
        activeUserList: undefined/*active user list (dynamically created later on)*/
      },
      me: F.user.name,
      mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
      mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
      pageIsActive: 'visible'===document.visibilityState,
      changesSincePageHidden: 0,
      notificationBubbleColor: 'white',
392
393
394
395
396
397
398







































399
400
401
402
403
404
405
         this.
      */
      setNewMessageSound: function f(uri){
        delete this.playNewMessageSound.audio;
        this.playNewMessageSound.uri = uri;
        this.settings.set('audible-alert', !!uri);
        return this;







































      }
    };
    F.fetch.beforesend = ()=>cs.ajaxStart();
    F.fetch.aftersend = ()=>cs.ajaxEnd();
    cs.e.inputCurrent = cs.e.inputSingle;
    /* Install default settings... */
    Object.keys(cs.settings.defaults).forEach(function(k){







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
         this.
      */
      setNewMessageSound: function f(uri){
        delete this.playNewMessageSound.audio;
        this.playNewMessageSound.uri = uri;
        this.settings.set('audible-alert', !!uri);
        return this;
      },

      /**
         Updates the "active user list" view.
      */
      updateActiveUserList: function callee(){
        if(!this.e.activeUserList){
          /** Array.sort() callback. Expects an array of user names and
              sorts them in last-received message order (newest first). */
          const usersLastSeen = this.usersLastSeen;
          callee.sortUsersSeen = function(l,r){
            l = usersLastSeen[l];
            r = usersLastSeen[r];
            if(l && r) return r - l;
            else if(l) return -1;
            else if(r) return 1;
            else return 0;
          };
          const content = document.querySelector('body > div.content');
          const ael = this.e.activeUserList =
                D.attr(D.div(),'id','active-user-list');
          D.append(ael, "user list placeholder");
          content.insertBefore(ael, content.firstElementChild)
          /*remember: layout is reversed!*/;
        }
        const self = this,
              users = Object.keys(this.usersLastSeen).sort(callee.sortUsersSeen);
        if(!users.length) return this;
        const ael = this.e.activeUserList;
        D.clearElement(ael);
        users.forEach(function(u){
          const uSpan = D.addClass(D.span(), 'chat-user');
          const uDate = self.usersLastSeen[u];
          D.append(uSpan, u);
          if(uDate.$uColor){
            uSpan.style.backgroundColor = uDate.$uColor;
          }
          D.append(ael, uSpan);
        });
      }
    };
    F.fetch.beforesend = ()=>cs.ajaxStart();
    F.fetch.aftersend = ()=>cs.ajaxEnd();
    cs.e.inputCurrent = cs.e.inputSingle;
    /* Install default settings... */
    Object.keys(cs.settings.defaults).forEach(function(k){
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111


1112

1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
  
  /** Callback for poll() to inject new content into the page.  jx ==
      the response from /chat-poll. If atEnd is true, the message is
      appended to the end of the chat list (for loading older
      messages), else the beginning (the default). */
  const newcontent = function f(jx,atEnd){
    if(!f.processPost){
      /** Array.sort() callback. Expects an array of user names and
          sorts them in last-received message order (newest first). */
      f.sortUsersSeen = function(l,r){
        l = Chat.usersLastSeen[l];
        r = Chat.usersLastSeen[r];
        if(l && r) return r - l;
        else if(l) return -1;
        else if(r) return 1;
        else return 0;
      };
      /** Processes chat message m, placing it either at the start (if
          atEnd is falsy) or end (if atEnd is truthy) of the chat
          history. atEnd should only be true when loading older
          messages. */
      f.processPost = function(m,atEnd){
        ++Chat.totalMessageCount;
        if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid;
        if( !Chat.mnMsg || m.msgid<Chat.mnMsg) Chat.mnMsg = m.msgid;
        if(m.xfrom && m.mtime){
          const d = new Date(m.mtime);
          const uls = Chat.usersLastSeen[m.xfrom];


          if(!uls || uls<d) Chat.usersLastSeen[m.xfrom] = d;

        }
        if( m.mdel ){
          /* A record deletion notice. */
          Chat.deleteMessageElem(m.mdel);
          return;
        }
        if(!Chat._isBatchLoading /*&& Chat.me!==m.xfrom*/ && Chat.playNewMessageSound){
          Chat.playNewMessageSound();
        }
        const row = new Chat.MessageWidget(m);
        Chat.injectMessageElem(row.e.body,atEnd);
        if(m.isError){
          Chat._gotServerError = m;
        }else if(false){
          const users = Object.keys(Chat.usersLastSeen).sort(f.sortUsersSeen);
          console.debug("Users sorted by most recent activity (newest first):", users);
          users.forEach(function(u){
            console.debug(u, Chat.usersLastSeen[u].toISOString());
          });
        }
      }/*processPost()*/;
    }/*end static init*/
    jx.msgs.forEach((m)=>f.processPost(m,atEnd));
    if('visible'===document.visibilityState){
      if(Chat.changesSincePageHidden){
        Chat.changesSincePageHidden = 0;







<
<
<
<
<
<
<
<
<
<











>
>
|
>













|
<
<
<
|
<







1124
1125
1126
1127
1128
1129
1130










1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159



1160

1161
1162
1163
1164
1165
1166
1167
  
  /** Callback for poll() to inject new content into the page.  jx ==
      the response from /chat-poll. If atEnd is true, the message is
      appended to the end of the chat list (for loading older
      messages), else the beginning (the default). */
  const newcontent = function f(jx,atEnd){
    if(!f.processPost){










      /** Processes chat message m, placing it either at the start (if
          atEnd is falsy) or end (if atEnd is truthy) of the chat
          history. atEnd should only be true when loading older
          messages. */
      f.processPost = function(m,atEnd){
        ++Chat.totalMessageCount;
        if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid;
        if( !Chat.mnMsg || m.msgid<Chat.mnMsg) Chat.mnMsg = m.msgid;
        if(m.xfrom && m.mtime){
          const d = new Date(m.mtime);
          const uls = Chat.usersLastSeen[m.xfrom];
          if(!uls || uls<d){
            d.$uColor = m.uclr;
            Chat.usersLastSeen[m.xfrom] = d;
          }
        }
        if( m.mdel ){
          /* A record deletion notice. */
          Chat.deleteMessageElem(m.mdel);
          return;
        }
        if(!Chat._isBatchLoading /*&& Chat.me!==m.xfrom*/ && Chat.playNewMessageSound){
          Chat.playNewMessageSound();
        }
        const row = new Chat.MessageWidget(m);
        Chat.injectMessageElem(row.e.body,atEnd);
        if(m.isError){
          Chat._gotServerError = m;
        }else{



          Chat.updateActiveUserList();

        }
      }/*processPost()*/;
    }/*end static init*/
    jx.msgs.forEach((m)=>f.processPost(m,atEnd));
    if('visible'===document.visibilityState){
      if(Chat.changesSincePageHidden){
        Chat.changesSincePageHidden = 0;
Changes to src/default.css.
1765
1766
1767
1768
1769
1770
1771


















1772
1773
1774
1775
1776
1777
1778
  white-space: pre;
  font-family: monospace;
}

body.chat #chat-drop-details img {
  max-width: 45%;
  max-height: 45%;


















}

input[type="checkbox"].diff-toggle {
  float: right;
}

body.branch .brlist > table > tbody > tr:hover:not(.selected),







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
  white-space: pre;
  font-family: monospace;
}

body.chat #chat-drop-details img {
  max-width: 45%;
  max-height: 45%;
}
body.chat #active-user-list {
  border: 1px inset;
  padding: 0.1em 0.2em;
  border-radius: 0.25em;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  font-size: 80%;
}
body.chat #active-user-list::before {
  content: "Most recently active:";
}
body.chat #active-user-list span.chat-user {
  margin: 0.2em;
  padding: 0.15em;
  border-radius: 0.25em;
}

input[type="checkbox"].diff-toggle {
  float: right;
}

body.branch .brlist > table > tbody > tr:hover:not(.selected),