Fossil

Check-in [6f5e04b340]
Login

Check-in [6f5e04b340]

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

Overview
Comment:Replace /chat config popup with a friendlier and more flexible widget. Reintroduces ability to select from multiple alerts. Seems to work but needs more testing. [forum:d97c869900 | Forum post d97c869900].
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | chat-config-options
Files: files | file ages | folders
SHA3-256: 6f5e04b340dd4863c8e3600992f9fc94229e92643a1fd8113c129316c05601b8
User & Date: stephan 2021-09-17 23:21:06.950
Context
2021-09-17
23:24
Removed dev-mode-only automatic toggle of config area. ... (check-in: 563ce12aca user: stephan tags: chat-config-options)
23:21
Replace /chat config popup with a friendlier and more flexible widget. Reintroduces ability to select from multiple alerts. Seems to work but needs more testing. [forum:d97c869900 | Forum post d97c869900]. ... (check-in: 6f5e04b340 user: stephan tags: chat-config-options)
19:48
Formatting improvements on the change log. Improvements to the help text for the "fossil ui" command. ... (check-in: 76f65b4362 user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/chat.c.
49
50
51
52
53
54
55
56


57
58
59
60
61
62
63
** alert-sounds/\*.{mp3,ogg,wav} are included.
*/
static void chat_emit_alert_list(void){
  /*Stmt q = empty_Stmt;*/
  unsigned int i;
  const char * azBuiltins[] = {
  "builtin/alerts/plunk.wav",
  "builtin/alerts/b-flat.wav"


  };
  CX("window.fossil.config.chat.alerts = [\n");
  for(i=0; i < sizeof(azBuiltins)/sizeof(azBuiltins[0]); ++i){
    CX("%s%!j", i ? ", " : "", azBuiltins[i]);
  }
#if 0
  /*







|
>
>







49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
** alert-sounds/\*.{mp3,ogg,wav} are included.
*/
static void chat_emit_alert_list(void){
  /*Stmt q = empty_Stmt;*/
  unsigned int i;
  const char * azBuiltins[] = {
  "builtin/alerts/plunk.wav",
  "builtin/alerts/bflat2.wav",
  "builtin/alerts/bflat3.wav",
  "builtin/alerts/bloop.wav"
  };
  CX("window.fossil.config.chat.alerts = [\n");
  for(i=0; i < sizeof(azBuiltins)/sizeof(azBuiltins[0]); ++i){
    CX("%s%!j", i ? ", " : "", azBuiltins[i]);
  }
#if 0
  /*
179
180
181
182
183
184
185





186
187
188
189
190
191
192
  @       </div>
  @       <input type="file" name="file" id="chat-input-file">
  @     </div>
  @     <div id="chat-drop-details"></div>
  @   </div>
  @ </div>
  @ </form>





  @ <div id='chat-messages-wrapper'>
  /* New chat messages get inserted immediately after this element */
  @ <span id='message-inject-point'></span>
  @ </div>
  fossil_free(zProjectName);
  builtin_fossil_js_bundle_or("popupwidget", "storage", "fetch", NULL);
  /* Always in-line the javascript for the chat page */







>
>
>
>
>







181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
  @       </div>
  @       <input type="file" name="file" id="chat-input-file">
  @     </div>
  @     <div id="chat-drop-details"></div>
  @   </div>
  @ </div>
  @ </form>
  @ <div id='chat-config' class='hidden'>
  @ <div id='chat-config-options'></div>
    /* ^^^populated client-side */
  @ <button>Close</button>
  @ </div>
  @ <div id='chat-messages-wrapper'>
  /* New chat messages get inserted immediately after this element */
  @ <span id='message-inject-point'></span>
  @ </div>
  fossil_free(zProjectName);
  builtin_fossil_js_bundle_or("popupwidget", "storage", "fetch", NULL);
  /* Always in-line the javascript for the chat page */
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'),
        configArea: E1('#chat-config')
      },
      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',
948
949
950
951
952
953
954
955
956
957
958


959
960
961
962
963
964
965
  const iso8601ish = function(d){
    return d.toISOString()
      .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' zulu');
  };

  (function(){/*Set up #chat-settings-button */
    const settingsButton = document.querySelector('#chat-settings-button');
    var popupSize = undefined/*placement workaround*/;
    const settingsPopup = new F.PopupWidget({
      cssClass: ['fossil-tooltip', 'chat-settings-popup']
    });


    /* Settings menu entries... */
    const settingsOps = [{
      label: "Multi-line input",
      boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
      persistentSetting: 'edit-multiline',
      callback: function(){
        Chat.inputToggleSingleMulti();







|
|
|
|
>
>







949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
  const iso8601ish = function(d){
    return d.toISOString()
      .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' zulu');
  };

  (function(){/*Set up #chat-settings-button */
    const settingsButton = document.querySelector('#chat-settings-button');
    const optionsMenu = E1('#chat-config-options');
    const cbToggle = function(){
      D.toggleClass([Chat.e.messagesWrapper, Chat.e.configArea], 'hidden');
    };
    D.attr(settingsButton, 'role', 'button').addEventListener('click', cbToggle, false);
    Chat.e.configArea.querySelector('button').addEventListener('click', cbToggle, false);
    /* Settings menu entries... */
    const settingsOps = [{
      label: "Multi-line input",
      boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
      persistentSetting: 'edit-multiline',
      callback: function(){
        Chat.inputToggleSingleMulti();
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014




1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047


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
1078
1079
1080
1081
1082
1083
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
      callback: function(){
        const v = Chat.settings.toggle('images-inline');
        F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
      }
    }];

    /** Set up selection list of notification sounds. */
    if(true/*flip this to false to enable selection of audio files*/){
      settingsOps.push({
        label: "Audible alerts",
        boolValue: ()=>Chat.settings.getBool('audible-alert'),
        callback: function(){
          const v = Chat.settings.toggle('audible-alert');
          Chat.setNewMessageSound(v ? F.config.chat.alertSound : false);
          if(v) setTimeout(()=>Chat.playNewMessageSound(), 50);
          F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
        }
      });
      Chat.setNewMessageSound(
        Chat.settings.getBool('audible-alert') ? F.config.chat.alertSound : false
      );
    }else{
      /* Disabled per chatroom discussion: selection list of audio files for
         chat notification. */
      const selectSound = settingsOps.selectSound = D.addClass(D.select(), 'menu-entry');




      D.disable(D.option(selectSound, "0", "Audible alert..."));
      D.option(selectSound, "", "(no audio)");
      F.config.chat.alerts.forEach(function(a){
        D.option(selectSound, a);
      });
      if(true===Chat.settings.getBool('audible-alert')){
        selectSound.selectedIndex = 2/*first audio file in the list*/;
      }else{
        selectSound.value = Chat.settings.get('audible-alert','');
        if(selectSound.selectedIndex<0){
          /*Missing file - removed after this setting was applied. Fall back
            to the first sound in the list. */
          selectSound.selectedIndex = 2;
        }
      }
      selectSound.addEventListener('change',function(){
        const v = this.selectedIndex>1 ? this.value : '';
        Chat.setNewMessageSound(v);
        F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
        if(v) setTimeout(()=>Chat.playNewMessageSound(), 50);
        settingsPopup.hide();
      }, false);
      Chat.setNewMessageSound(selectSound.value);
    }/*audio notification config*/
    /**
       Rebuild the menu each time it's shown so that the toggles can
       show their current values.
    */
    settingsPopup.options.refresh = function(){
      D.clearElement(this.e);
      settingsOps.forEach(function(op){
        const line = D.addClass(D.span(), 'menu-entry');
        const btn = D.append(D.addClass(D.span(), 'button'), op.label);


        const callback = function(ev){
          settingsPopup.hide();
          op.callback(ev);
          if(op.persistentSetting){
            Chat.settings.set(op.persistentSetting, op.boolValue());
          }
        };
        D.append(line, btn);
        if(op.hasOwnProperty('boolValue')){


          const check = D.attr(D.checkbox(1, op.boolValue()),
                                          'aria-label', op.label);




          D.append(line, check);



        }
        D.append(settingsPopup.e, line);
        line.addEventListener('click', callback);
      });
      if(settingsOps.selectSound){
        D.append(settingsPopup.e, settingsOps.selectSound);
      }
    };
    settingsPopup.installHideHandlers(
      false, settingsOps.selectSound ? false : true,
      true)
    /** Reminder: click-to-hide interferes with "?" embedded within
        the popup, so cannot be used together with those. Enabling
        this means, however, that tapping the menu button to toggle
        the menu cannot work because tapping the menu button while the
        menu is opened will, because of the click-to-hide handler,
        hide the menu before the button gets an event saying to toggle
        it.

        Reminder: because we need a SELECT element for the audio file
        selection (since that list can be arbitrarily long), we have
        to disable tap-outside-the-popup-to-close-it via passing false
        as the 2nd argument to installHideHandlers(). If we don't,
        tapping on the select element is unreliable on desktop
        browsers and doesn't seem to work at all on mobile. */;
    D.attr(settingsButton, 'role', 'button');
    settingsButton.addEventListener('click',function(ev){
      //ev.preventDefault();
      if(settingsPopup.isShown()) settingsPopup.hide();
      else settingsPopup.show(settingsButton);
      /* Reminder: we cannot toggle the visibility from her
       */
    }, false);

    /* Find an ideal X/Y position for the popup, directly above the settings
       button, based on the size of the popup... */
    settingsPopup.show(document.body);
    popupSize = settingsPopup.e.getBoundingClientRect();
    settingsPopup.hide();
    settingsPopup.options.adjustX = function(x){
      const rect = settingsButton.getBoundingClientRect();
      return rect.right - popupSize.width;
    };
    settingsPopup.options.adjustY = function(y){
      const rect = settingsButton.getBoundingClientRect();
      return rect.top - popupSize.height -2;
    };
  })()/*#chat-settings-button setup*/;
  
  /** 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){







|
















|
>
>
>
>



















|
<




|
<

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







993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041

1042
1043
1044
1045
1046

1047


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
1078











1079




























1080
1081
1082
1083
1084
1085
1086
      callback: function(){
        const v = Chat.settings.toggle('images-inline');
        F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
      }
    }];

    /** Set up selection list of notification sounds. */
    if(false/*flip this to false to enable selection of audio files*/){
      settingsOps.push({
        label: "Audible alerts",
        boolValue: ()=>Chat.settings.getBool('audible-alert'),
        callback: function(){
          const v = Chat.settings.toggle('audible-alert');
          Chat.setNewMessageSound(v ? F.config.chat.alertSound : false);
          if(v) setTimeout(()=>Chat.playNewMessageSound(), 50);
          F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
        }
      });
      Chat.setNewMessageSound(
        Chat.settings.getBool('audible-alert') ? F.config.chat.alertSound : false
      );
    }else{
      /* Disabled per chatroom discussion: selection list of audio files for
         chat notification. */
      settingsOps.selectSound = D.addClass(D.div(), 'menu-entry');
      const selectSound = D.select();
      D.append(settingsOps.selectSound,
               D.append(D.span(),"Audio alert"),
               selectSound);
      D.disable(D.option(selectSound, "0", "Audible alert..."));
      D.option(selectSound, "", "(no audio)");
      F.config.chat.alerts.forEach(function(a){
        D.option(selectSound, a);
      });
      if(true===Chat.settings.getBool('audible-alert')){
        selectSound.selectedIndex = 2/*first audio file in the list*/;
      }else{
        selectSound.value = Chat.settings.get('audible-alert','');
        if(selectSound.selectedIndex<0){
          /*Missing file - removed after this setting was applied. Fall back
            to the first sound in the list. */
          selectSound.selectedIndex = 2;
        }
      }
      selectSound.addEventListener('change',function(){
        const v = this.selectedIndex>1 ? this.value : '';
        Chat.setNewMessageSound(v);
        F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
        if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);

      }, false);
      Chat.setNewMessageSound(selectSound.value);
    }/*audio notification config*/
    /**
       Build list of options...

    */


    settingsOps.forEach(function f(op){
      const line = D.addClass(D.div(), 'menu-entry');
      const btn = D.append(
        D.addClass(D.label(), 'cbutton'/*bootstrap skin hijacks 'button'*/),
        op.label);
      const callback = function(ev){

        op.callback(ev);
        if(op.persistentSetting){
          Chat.settings.set(op.persistentSetting, op.boolValue());
        }
      };

      if(op.hasOwnProperty('boolValue')){
        if(undefined === f.$id) f.$id = 0;
        ++f.$id;
        const check = D.attr(D.checkbox(1, op.boolValue()),
                             'aria-label', op.label);
        const id = 'cfgopt'+f.$id;
        if(op.boolValue()) check.checked = true;
        D.attr(check, 'id', id);
        D.attr(btn, 'for', id);
        D.append(line, check);
        check.addEventListener('change', callback);
      }else{
        line.addEventListener('click', callback);
      }
      D.append(line, btn);
      D.append(optionsMenu, line);
    });
    if(settingsOps.selectSound){
      D.append(optionsMenu, settingsOps.selectSound);
    }











    settingsButton.click()/*for for development*/;




























  })()/*#chat-settings-button setup*/;
  
  /** 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){
Changes to src/default.css.
1895
1896
1897
1898
1899
1900
1901








1902






















1903
1904
1905
1906
1907
1908
1909
  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),
body.branch .brlist > table > tbody > tr.selected {
  background-color: #ffc;







>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
  font-family: monospace;
}

body.chat #chat-drop-details img {
  max-width: 45%;
  max-height: 45%;
}
body.chat #chat-config {
  /* /chat configuration widget */
  display: flex;
  flex-direction: column;
  flex: 1 0 auto;
  overflow: auto;
  flex: 2 1 auto;
  padding: 0 0.25em;
}
body.chat #chat-config > button {
  padding: 0.5em;
  flex: 0 1 auto;
  margin: 0.25em 0;
}
body.chat #chat-config #chat-config-options {
  /* /chat config options go here */
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  overflow: auto;
}
body.chat #chat-config #chat-config-options .menu-entry {
  display: flex;
  align-items: center;
  flex-direction: row;
  flex-wrap: nowrap;
  padding: 1em;
}
body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
  margin-right: 1em;
}
input[type="checkbox"].diff-toggle {
  float: right;
}

body.branch .brlist > table > tbody > tr:hover:not(.selected),
body.branch .brlist > table > tbody > tr.selected {
  background-color: #ffc;