Fossil

Check-in [ee175636aa]
Login

Check-in [ee175636aa]

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

Overview
Comment:Several small style tweaks. Changed style_labeled_checkbox() parameter order for better readability. TabManager class now supports events alerting before/after a tab is switched to. Added auto-refresh of preview when the preview tab is activated, with a checkbox to disable it for slow connections and/or large documents (the refresh button still works as before).
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | fileedit-ajaxify
Files: files | file ages | folders
SHA3-256: ee175636aa9a8862b2ae2e7d42c3365efe798a5f4e9bfb8bf4e194e0ce17fb41
User & Date: stephan 2020-05-10 08:47:53.144
Context
2020-05-10
09:16
Minor efficiency hack in style_emit_script_builtin() and swapped a potentially problematic ordering of two JS includes. ... (check-in: c60ad868b4 user: stephan tags: fileedit-ajaxify)
08:47
Several small style tweaks. Changed style_labeled_checkbox() parameter order for better readability. TabManager class now supports events alerting before/after a tab is switched to. Added auto-refresh of preview when the preview tab is activated, with a checkbox to disable it for slow connections and/or large documents (the refresh button still works as before). ... (check-in: ee175636aa user: stephan tags: fileedit-ajaxify)
2020-05-09
13:56
Disable rendering of the comment mime type selection list, as it's not honored/implemented where comments are rendered. ... (check-in: 875b60bccd user: stephan tags: fileedit-ajaxify)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/default_css.txt.
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
  padding: 0;
}
#fileedit-comment {
  width: 100%;
  font-family: monospace;
}
.tab-container > .tabs > .tab-panel > .fileedit-options {
  margin-top: 0.25em 0;
  border: none;
  border-radius: 0;
  border-bottom-width: 1px;
  border-bottom-style: dotted;
}
.tab-container > .tabs > .tab-panel > .fileedit-options > button {
  vertical-align: middle;
  margin: 0.5em;








}
////////////////////////////////////////////////////////////////////
// Styles developed for /fileedit but which have wider
// applicability:
.flex-container {
    display: flex;
}
.flex-container.row {
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
}
.fileedit-options.flex-container.row {
  align-items: first baseline;



}
.flex-container.row.stretch {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: stretch;
  margin: 0;
}
.flex-container.column {
  flex-direction: column;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
}
.flex-container.column.stretch {
  align-items: stretch;
  margin: 0;



}
.font-size-100 {
  font-size: 100%;
}
.font-size-125 {
  font-size: 125%;
}







|








>
>
>
>
>
>
>
>















>
>
>
















>
>
>







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
  padding: 0;
}
#fileedit-comment {
  width: 100%;
  font-family: monospace;
}
.tab-container > .tabs > .tab-panel > .fileedit-options {
  margin-top: 0;
  border: none;
  border-radius: 0;
  border-bottom-width: 1px;
  border-bottom-style: dotted;
}
.tab-container > .tabs > .tab-panel > .fileedit-options > button {
  vertical-align: middle;
  margin: 0.5em;
}
.tab-container > .tabs > .tab-panel > .fileedit-options > input {
  vertical-align: middle;
  margin: 0.5em;
}
.tab-container > .tabs > .tab-panel > .fileedit-options > .input-with-label {
  vertical-align: middle;
  margin: 0.5em;
}
////////////////////////////////////////////////////////////////////
// Styles developed for /fileedit but which have wider
// applicability:
.flex-container {
    display: flex;
}
.flex-container.row {
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
}
.fileedit-options.flex-container.row {
  align-items: first baseline;
}
.fileedit-options > div > * {
  margin: 0.25em;
}
.flex-container.row.stretch {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: stretch;
  margin: 0;
}
.flex-container.column {
  flex-direction: column;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
}
.flex-container.column.stretch {
  align-items: stretch;
  margin: 0;
}
.flex-container.child-gap-small > * {
  margin: 0.25em;
}
.font-size-100 {
  font-size: 100%;
}
.font-size-125 {
  font-size: 125%;
}
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
// contains a SPAN label and an INPUT control.
.input-with-label {
  border: 1px inset #808080;
  border-radius: 0.5em;
  padding: 0.25em 0.4em;
  margin: 0 0.5em;
  display: inline-block;
  cursor: pointer;
}
.input-with-label > * {
  vertical-align: middle;
}
.input-with-label > input {
  margin: 0;
}







|







1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
// contains a SPAN label and an INPUT control.
.input-with-label {
  border: 1px inset #808080;
  border-radius: 0.5em;
  padding: 0.25em 0.4em;
  margin: 0 0.5em;
  display: inline-block;
  cursor: default;
}
.input-with-label > * {
  vertical-align: middle;
}
.input-with-label > input {
  margin: 0;
}
Changes to src/fileedit.c.
1604
1605
1606
1607
1608
1609
1610
1611

1612
1613
1614
1615
1616
1617
1618
  
  /******* Content tab *******/
  {
    CX("<div id='fileedit-tab-content' "
       "data-tab-parent='fileedit-tabs' "
       "data-tab-label='File Content'"
       ">");
    CX("<div class='fileedit-options flex-container row'>");

    if(1){
      /* Discard/reload button. Leave this out until we have a
      ** nice way of offering confirmation, e.g. like the old
      ** jQuery.confirmer plugin which required a 2nd click of the
      ** button within X seconds to confirm. Right now it's simply
      ** to easy to tap by accident. */
      CX("<button class='fileedit-content-reload confirmer' "







|
>







1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
  
  /******* Content tab *******/
  {
    CX("<div id='fileedit-tab-content' "
       "data-tab-parent='fileedit-tabs' "
       "data-tab-label='File Content'"
       ">");
    CX("<div class='fileedit-options "
       "flex-container row child-gap-small'>");
    if(1){
      /* Discard/reload button. Leave this out until we have a
      ** nice way of offering confirmation, e.g. like the old
      ** jQuery.confirmer plugin which required a 2nd click of the
      ** button within X seconds to confirm. Right now it's simply
      ** to easy to tap by accident. */
      CX("<button class='fileedit-content-reload confirmer' "
1652
1653
1654
1655
1656
1657
1658










1659
1660
1661
1662
1663
1664
1665
       "data-f-preview-from='fileedit-content-editor' "
       /* ^^^ text source elem ID*/
       "data-f-preview-via='_postPreview' "
       /* ^^^ fossil.page[methodName](content, callback) */
       "data-f-preview-to='fileedit-tab-preview-wrapper' "
       /* ^^^ dest elem ID */
       ">Refresh</button>");










    /* Default preview rendering mode selection... */
    previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime);
    style_select_list_int("select-preview-mode",
                          "preview_render_mode",
                          "Preview Mode",
                          "Preview mode format.",
                          previewRenderMode,







>
>
>
>
>
>
>
>
>
>







1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
       "data-f-preview-from='fileedit-content-editor' "
       /* ^^^ text source elem ID*/
       "data-f-preview-via='_postPreview' "
       /* ^^^ fossil.page[methodName](content, callback) */
       "data-f-preview-to='fileedit-tab-preview-wrapper' "
       /* ^^^ dest elem ID */
       ">Refresh</button>");
    /* Toggle auto-update of preview when the Preview tab is selected. */
    style_labeled_checkbox("cb-preview-autoupdate",
                           NULL,
                           "Auto-refresh?",
                           "1", 1,
                           "If on, the preview will automatically "
                           "refresh when this tab is selected. Not "
                           "recommended for large files or slow "
                           "connections.");

    /* Default preview rendering mode selection... */
    previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime);
    style_select_list_int("select-preview-mode",
                          "preview_render_mode",
                          "Preview Mode",
                          "Preview mode format.",
                          previewRenderMode,
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
                          "", 20, "", 40,
                          "", 60, "", 80,
                          "", 100, NULL);
    /* Selection of line numbers for text preview */
    style_labeled_checkbox("cb-line-numbers",
                           "preview_ln",
                           "Add line numbers to plain-text previews?",
                           "1",
                           "If on, plain-text files (only) will get "
                           "line numbers added to the preview.",
                           P("preview_ln")!=0);
    CX("</div>"/*.fileedit-options*/);
    CX("<div id='fileedit-tab-preview-wrapper'></div>");
    CX("</div>"/*#fileedit-tab-preview*/);
  }

  /****** Diff tab ******/
  {







|

|
<







1709
1710
1711
1712
1713
1714
1715
1716
1717
1718

1719
1720
1721
1722
1723
1724
1725
                          "", 20, "", 40,
                          "", 60, "", 80,
                          "", 100, NULL);
    /* Selection of line numbers for text preview */
    style_labeled_checkbox("cb-line-numbers",
                           "preview_ln",
                           "Add line numbers to plain-text previews?",
                           "1", P("preview_ln")!=0,
                           "If on, plain-text files (only) will get "
                           "line numbers added to the preview.");

    CX("</div>"/*.fileedit-options*/);
    CX("<div id='fileedit-tab-preview-wrapper'></div>");
    CX("</div>"/*#fileedit-tab-preview*/);
  }

  /****** Diff tab ******/
  {
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750

1751
1752
1753
1754

1755
1756
1757
1758
1759
1760
1761

1762
1763
1764

1765
1766
1767
1768
1769
1770
1771

1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
     "data-tab-label='Commit'"
     ">");

  {
    /******* Commit flags/options *******/
    CX("<div class='fileedit-options flex-container row'>");
    style_labeled_checkbox("cb-dry-run",
                           "dry_run", "Dry-run?", "1",
                           "In dry-run mode, the Save button performs "
                           "all work needed for saving but then rolls "
                           "back the transaction, and thus does not "
                           "really save.",
                           1);
    style_labeled_checkbox("cb-allow-fork",
                           "allow_fork", "Allow fork?", "1",

                           "Allow saving to create a fork?",
                           cimi.flags & CIMINI_ALLOW_FORK);
    style_labeled_checkbox("cb-allow-older",
                           "allow_older", "Allow older?", "1",

                           "Allow saving against a parent version "
                           "which has a newer timestamp?",
                           cimi.flags & CIMINI_ALLOW_OLDER);
    style_labeled_checkbox("cb-exec-bit",
                           "exec_bit", "Executable?", "1",
                           "Set the executable bit?",
                           PERM_EXE==cimi.filePerm);

    style_labeled_checkbox("cb-allow-merge-conflict",
                           "allow_merge_conflict",
                           "Allow merge conflict markers?", "1",

                           "Allow saving even if the content contains "
                           "what appear to be fossil merge conflict "
                           "markers?",
                           cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
    style_labeled_checkbox("cb-prefer-delta",
                           "prefer_delta",
                           "Prefer delta manifest?", "1",

                           "Will create a delta manifest, instead of "
                           "baseline, if conditions are favorable to "
                           "do so. This option is only a suggestion.",
                           cimi.flags & CIMINI_PREFER_DELTA);
    style_select_list_int("select-eol-style",
                          "eol", "EOL Style",
                          "EOL conversion policy, noting that "
                          "form-processing may implicitly change the "
                          "line endings of the input.",
                          (cimi.flags & CIMINI_CONVERT_EOL_UNIX)
                          ? 1 : (cimi.flags & CIMINI_CONVERT_EOL_WINDOWS







|



|
<


>
|
<


>

|
<


<
|
>



>


|
<



>


|
<







1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757

1758
1759
1760
1761

1762
1763
1764
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
     "data-tab-label='Commit'"
     ">");

  {
    /******* Commit flags/options *******/
    CX("<div class='fileedit-options flex-container row'>");
    style_labeled_checkbox("cb-dry-run",
                           "dry_run", "Dry-run?", "1", 1,
                           "In dry-run mode, the Save button performs "
                           "all work needed for saving but then rolls "
                           "back the transaction, and thus does not "
                           "really save.");

    style_labeled_checkbox("cb-allow-fork",
                           "allow_fork", "Allow fork?", "1",
                           cimi.flags & CIMINI_ALLOW_FORK,
                           "Allow saving to create a fork?");

    style_labeled_checkbox("cb-allow-older",
                           "allow_older", "Allow older?", "1",
                           cimi.flags & CIMINI_ALLOW_OLDER,
                           "Allow saving against a parent version "
                           "which has a newer timestamp?");

    style_labeled_checkbox("cb-exec-bit",
                           "exec_bit", "Executable?", "1",

                           PERM_EXE==cimi.filePerm,
                           "Set the executable bit?");
    style_labeled_checkbox("cb-allow-merge-conflict",
                           "allow_merge_conflict",
                           "Allow merge conflict markers?", "1",
                           cimi.flags & CIMINI_ALLOW_MERGE_MARKER,
                           "Allow saving even if the content contains "
                           "what appear to be fossil merge conflict "
                           "markers?");

    style_labeled_checkbox("cb-prefer-delta",
                           "prefer_delta",
                           "Prefer delta manifest?", "1",
                           cimi.flags & CIMINI_PREFER_DELTA,
                           "Will create a delta manifest, instead of "
                           "baseline, if conditions are favorable to "
                           "do so. This option is only a suggestion.");

    style_select_list_int("select-eol-style",
                          "eol", "EOL Style",
                          "EOL conversion policy, noting that "
                          "form-processing may implicitly change the "
                          "line endings of the input.",
                          (cimi.flags & CIMINI_CONVERT_EOL_UNIX)
                          ? 1 : (cimi.flags & CIMINI_CONVERT_EOL_WINDOWS
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
    ** mode. JS code sets up the ability to toggle between single-
    ** and multi-line modes. */
    CX("<input type='text' name='comment' "
       "id='fileedit-comment'></input>");
    CX("<textarea name='commentBig' class='hidden' "
       "rows='5' id='fileedit-comment-big'></textarea>\n");
    { /* comment options... */
      CX("<div class='fileedit-options flex-container column'>");
      CX("<button id='comment-toggle' "
         "title='Toggle between single- and multi-line comment mode, "
         "noting that switching from multi- to single-line will cause "
         "newlines to get stripped.'"
         ">toggle single-/multi-line</button> ");
      if(0){
        /* Manifests support an N-card (comment mime type) but it has
        ** yet to be honored where comments are rendered, so we don't
        ** currently offer it as an option here:
        ** https://fossil-scm.org/forum/forumpost/662da045a1
        **
        ** If/when it's ever implemented, simply enable this block and







|




|







1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
    ** mode. JS code sets up the ability to toggle between single-
    ** and multi-line modes. */
    CX("<input type='text' name='comment' "
       "id='fileedit-comment'></input>");
    CX("<textarea name='commentBig' class='hidden' "
       "rows='5' id='fileedit-comment-big'></textarea>\n");
    { /* comment options... */
      CX("<div class='flex-container column child-gap-small'>");
      CX("<button id='comment-toggle' "
         "title='Toggle between single- and multi-line comment mode, "
         "noting that switching from multi- to single-line will cause "
         "newlines to get stripped.'"
         ">Toggle single-/multi-line</button> ");
      if(0){
        /* Manifests support an N-card (comment mime type) but it has
        ** yet to be honored where comments are rendered, so we don't
        ** currently offer it as an option here:
        ** https://fossil-scm.org/forum/forumpost/662da045a1
        **
        ** If/when it's ever implemented, simply enable this block and
Changes to src/fossil.page.fileedit.js.
17
18
19
20
21
22
23

24
25
26
27
28
29
30
      btnCommit: E("#fileedit-btn-commit"),
      btnReload: E("#fileedit-tab-content > .fileedit-options > "
                   +"button.fileedit-content-reload"),
      selectPreviewModeWrap: E('#select-preview-mode'),
      selectHtmlEmsWrap: E('#select-preview-html-ems'),
      selectEolWrap:  E('#select-preview-html-ems'),
      cbLineNumbersWrap: E('#cb-line-numbers'),

      tabs:{
        content: E('#fileedit-tab-content'),
        preview: E('#fileedit-tab-preview'),
        diff: E('#fileedit-tab-diff'),
        commit: E('#fileedit-tab-commit')
      }
    };







>







17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
      btnCommit: E("#fileedit-btn-commit"),
      btnReload: E("#fileedit-tab-content > .fileedit-options > "
                   +"button.fileedit-content-reload"),
      selectPreviewModeWrap: E('#select-preview-mode'),
      selectHtmlEmsWrap: E('#select-preview-html-ems'),
      selectEolWrap:  E('#select-preview-html-ems'),
      cbLineNumbersWrap: E('#cb-line-numbers'),
      cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'),
      tabs:{
        content: E('#fileedit-tab-content'),
        preview: E('#fileedit-tab-preview'),
        diff: E('#fileedit-tab-diff'),
        commit: E('#fileedit-tab-commit')
      }
    };
45
46
47
48
49
50
51


52
53
54

55
56
57


58
59
60
61

62
63
64
65
66
67
68
    P.tabs.e.container.insertBefore(
      /* Move the status bar between the tab buttons and
         tab panels. Seems to be the best fit in terms of
         functionality and visibility. */
      E('#fossil-status-bar'), P.tabs.e.tabs
    );



    const stopEvent = function(e){
      //e.preventDefault();
      //e.stopPropagation();

      return P;
    };
      


    //P.tabs.getButtonForTab(P.e.tabs.preview)
    F.connectPagePreviewers(
      P.e.tabs.preview.querySelector(
        'button'

      )
    );

    const diffButtons = E('#fileedit-tab-diff-buttons');
    diffButtons.querySelector('button.sbs').addEventListener(
      "click",(e)=>P.diff(true), false
    );







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


<
>







46
47
48
49
50
51
52
53
54
55
56

57
58

59
60
61
62
63
64

65
66
67
68
69
70
71
72
    P.tabs.e.container.insertBefore(
      /* Move the status bar between the tab buttons and
         tab panels. Seems to be the best fit in terms of
         functionality and visibility. */
      E('#fossil-status-bar'), P.tabs.e.tabs
    );

    P.tabs.addEventListener(
      /* Set up auto-refresh of the preview tab... */
      'before-switch-to', function(ev){
        if(ev.detail===P.e.tabs.preview

           && P.e.cbAutoPreview.checked){
          P.preview();

        }
      }
    );

    F.connectPagePreviewers(
      P.e.tabs.preview.querySelector(

        '#btn-preview-refresh'
      )
    );

    const diffButtons = E('#fileedit-tab-diff-buttons');
    diffButtons.querySelector('button.sbs').addEventListener(
      "click",(e)=>P.diff(true), false
    );
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238

239
240
241

242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270


271
272
273
274
275
276
277
278
279
280
281

282
283
284
285
286
287
288
289
290
291
292
293
294
295
296


297
298
299
300

301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
    F.message("Loading content...");
    F.fetch('fileedit_content',{
      urlParams: {filename:file,checkin:rev},
      onload:(r)=>{
        F.message('Loaded content.');
        self.e.taEditor.value = r;
        self.updateVersion(file,rev);
        self.preview();
        self.tabs.switchToTab(self.e.tabs.content);
      }
    });
    return this;
  };

  /**
     Fetches the page preview based on the contents and settings of
     this page's input fields, and updates the UI with with the
     preview.

     Returns this object, noting that the operation is async.
  */
  P.preview = function(switchToTab){
    if(!this.finfo){
      F.error("No content is loaded.");
      return this;
    }

    const target = this.e.tabs.preview.querySelector(
      '#fileedit-tab-preview-wrapper'
    );

    const self = this;
    const updateView = function(c){
      D.clearElement(target);
      if('string'===typeof c) target.innerHTML = c;
      if(switchToTab) self.tabs.switchToTab(self.e.tabs.preview);
    };
    const content = this.e.taEditor.value;
    if(!content){
      updateView('');
      return this;
    }
    this._postPreview(content, updateView);
    return this;
  };

  /**
     Callback for use with F.connectPagePreviewers()
  */
  P._postPreview = function(content,callback){
    if(!content){
      callback(content);
      return;
    }
    const fd = new FormData();
    fd.append('render_mode',E('select[name=preview_render_mode]').value);
    fd.append('filename',this.finfo.filename);
    fd.append('ln',E('[name=preview_ln]').checked ? 1 : 0);
    fd.append('iframe_height', E('[name=preview_html_ems]').value);
    fd.append('content',content || '');


    fossil.fetch('fileedit_preview',{
      payload: fd,
      onload: (r)=>{
        callback(r);
        F.message('Updated preview.');
      },
      onerror: (e)=>{
        fossil.fetch.onerror(e);
        callback("Error fetching preview: "+e);
      }
    });

  };

  
  /**
     Fetches the content diff based on the contents and settings of this
     page's input fields, and updates the UI with the diff view.

     Returns this object, noting that the operation is async.
  */
  P.diff = function(sbs){
    if(!this.finfo){
      F.error("No content is loaded.");
      return this;
    }
    const content = this.e.taEditor.value,


          target = this.e.tabs.diff.querySelector(
            '#fileedit-tab-diff-wrapper'
          ),
          self = this;

    const fd = new FormData();
    fd.append('filename',this.finfo.filename);
    fd.append('checkin', this.finfo.checkin);
    fd.append('sbs', sbs ? 1 : 0);
    fd.append('content',content);
    F.message(
      "Fetching diff..."
    ).fetch('fileedit_diff',{
      payload: fd,
      onload: function(c){
        target.innerHTML = [
          "<div>Diff <code>[",
          self.finfo.checkin,
          "]</code> &rarr; Local Edits</div>",
          c||'No changes.'
        ].join('');
        F.message('Updated diff.');
        self.tabs.switchToTab(self.e.tabs.diff);







<













|




>
|
|
|
>


|
|


<
<
<
|
<
<
<








|







>
>
|










>









|





>
>
|
|
|
<
>










|







217
218
219
220
221
222
223

224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252



253



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303

304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
    F.message("Loading content...");
    F.fetch('fileedit_content',{
      urlParams: {filename:file,checkin:rev},
      onload:(r)=>{
        F.message('Loaded content.');
        self.e.taEditor.value = r;
        self.updateVersion(file,rev);

        self.tabs.switchToTab(self.e.tabs.content);
      }
    });
    return this;
  };

  /**
     Fetches the page preview based on the contents and settings of
     this page's input fields, and updates the UI with with the
     preview.

     Returns this object, noting that the operation is async.
  */
  P.preview = function f(switchToTab){
    if(!this.finfo){
      F.error("No content is loaded.");
      return this;
    }
    if(!f.target){
      f.target = this.e.tabs.preview.querySelector(
        '#fileedit-tab-preview-wrapper'
      );
    }
    const self = this;
    const updateView = function(c){
      D.clearElement(f.target);
      if('string'===typeof c) f.target.innerHTML = c;
      if(switchToTab) self.tabs.switchToTab(self.e.tabs.preview);
    };



    return this._postPreview(this.e.taEditor.value, updateView);



  };

  /**
     Callback for use with F.connectPagePreviewers()
  */
  P._postPreview = function(content,callback){
    if(!content){
      callback(content);
      return this;
    }
    const fd = new FormData();
    fd.append('render_mode',E('select[name=preview_render_mode]').value);
    fd.append('filename',this.finfo.filename);
    fd.append('ln',E('[name=preview_ln]').checked ? 1 : 0);
    fd.append('iframe_height', E('[name=preview_html_ems]').value);
    fd.append('content',content || '');
    F.message(
      "Fetching preview..."
    ).fetch('fileedit_preview',{
      payload: fd,
      onload: (r)=>{
        callback(r);
        F.message('Updated preview.');
      },
      onerror: (e)=>{
        fossil.fetch.onerror(e);
        callback("Error fetching preview: "+e);
      }
    });
    return this;
  };

  
  /**
     Fetches the content diff based on the contents and settings of this
     page's input fields, and updates the UI with the diff view.

     Returns this object, noting that the operation is async.
  */
  P.diff = function f(sbs){
    if(!this.finfo){
      F.error("No content is loaded.");
      return this;
    }
    const content = this.e.taEditor.value,
          self = this;
    if(!f.target){
      f.target = this.e.tabs.diff.querySelector(
        '#fileedit-tab-diff-wrapper'
      );

    }
    const fd = new FormData();
    fd.append('filename',this.finfo.filename);
    fd.append('checkin', this.finfo.checkin);
    fd.append('sbs', sbs ? 1 : 0);
    fd.append('content',content);
    F.message(
      "Fetching diff..."
    ).fetch('fileedit_diff',{
      payload: fd,
      onload: function(c){
        f.target.innerHTML = [
          "<div>Diff <code>[",
          self.finfo.checkin,
          "]</code> &rarr; Local Edits</div>",
          c||'No changes.'
        ].join('');
        F.message('Updated diff.');
        self.tabs.switchToTab(self.e.tabs.diff);
Changes to src/fossil.tabs.js.
28
29
30
31
32
33
34
35



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
  const setVisible = function(e,yes){
    D[yes ? 'removeClass' : 'addClass'](e, 'hidden');
  };

  TabManager.prototype = {
    /**
       Initializes the tabs associated with the given tab container
       (DOM element or selector for a single element).




       The tab container must have an 'id' attribute. This function
       looks through the DOM for all elements which have
       data-tab-parent=thatId. For each one it creates a button to
       switch to that tab and moves the element into this.e.tabs.

       The label for each tab is set by the data-tab-label attribute
       of each element, defaulting to something not terribly useful.

       When it's done, it auto-selects the first tab unless a tab has
       a truthy numeric value in its data-tab-select attribute, in
       which case the last tab to have such a property is selected.

       This method must only be called once per instance. TabManagers
       may be nested but may not share any tabs instances.

       Returns this object.

       DOM elements of potential interest to users:

       this.e.container = the outermost container element.








|
>
>
>














|







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
  const setVisible = function(e,yes){
    D[yes ? 'removeClass' : 'addClass'](e, 'hidden');
  };

  TabManager.prototype = {
    /**
       Initializes the tabs associated with the given tab container
       (DOM element or selector for a single element). This must be
       called once before using any other member functions of a given
       instance, noting that the constructor will call this if it is
       passed an argument.       

       The tab container must have an 'id' attribute. This function
       looks through the DOM for all elements which have
       data-tab-parent=thatId. For each one it creates a button to
       switch to that tab and moves the element into this.e.tabs.

       The label for each tab is set by the data-tab-label attribute
       of each element, defaulting to something not terribly useful.

       When it's done, it auto-selects the first tab unless a tab has
       a truthy numeric value in its data-tab-select attribute, in
       which case the last tab to have such a property is selected.

       This method must only be called once per instance. TabManagers
       may be nested but must not share any tabs instances.

       Returns this object.

       DOM elements of potential interest to users:

       this.e.container = the outermost container element.

84
85
86
87
88
89
90

91
92
93
94
95
96
97
      let selectIndex = 0;
      EA('[data-tab-parent='+cID+']').forEach((c,n)=>{
        if(+c.dataset.tabSelect) selectIndex=n;
        this.addTab(c);
      });
      return this.switchToTab(selectIndex);
    },

    /**
       For the given tab element, unique selector string, or integer
       (0-based tab number), returns the button associated with that
       tab, or undefined if the argument does not match any current
       tab.
    */
    getButtonForTab: function(tab){







>







87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
      let selectIndex = 0;
      EA('[data-tab-parent='+cID+']').forEach((c,n)=>{
        if(+c.dataset.tabSelect) selectIndex=n;
        this.addTab(c);
      });
      return this.switchToTab(selectIndex);
    },

    /**
       For the given tab element, unique selector string, or integer
       (0-based tab number), returns the button associated with that
       tab, or undefined if the argument does not match any current
       tab.
    */
    getButtonForTab: function(tab){
120
121
122
123
124
125
126






































127
128
129
130
131
132
133
134
135
136
137




138
139

140



141
142
143
144
145
146
147
148
149
150
      const btn = D.addClass(D.append(D.span(), lbl), 'tab-button');
      D.append(this.e.tabBar,btn);
      btn.$manager = this;
      btn.$tab = tab;
      btn.addEventListener('click', f.click, false);
      return this;
    },






































    /**
       If the given DOM element, unique selector, or integer (0-based
       tab number) is one of this object's tabs, the UI makes that tab
       the currently-visible one. Returns this object.
    */
    switchToTab: function(tab){
      tab = tabArg(tab,this);
      const self = this;
      this.e.tabs.childNodes.forEach((e,ndx)=>{
        const btn = this.e.tabBar.childNodes[ndx];
        if(e===tab){




          setVisible(e, true);
          D.addClass(btn,'selected');

        }else{



          setVisible(e, false);
          D.removeClass(btn,'selected');
        }
      });
      return this;
    }
  };

  F.TabManager = TabManager;
})(window.fossil);







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











>
>
>
>


>

>
>
>










124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
      const btn = D.addClass(D.append(D.span(), lbl), 'tab-button');
      D.append(this.e.tabBar,btn);
      btn.$manager = this;
      btn.$tab = tab;
      btn.addEventListener('click', f.click, false);
      return this;
    },

    /**
       Internal. Fires a new CustomEvent to all listeners which have
       registered via this.addEventListener().
     */
    _dispatchEvent: function(name, detail){
      try{
        this.e.container.dispatchEvent(
          new CustomEvent(name, {detail: detail})
        );
      }catch(e){
        /* ignore */
      }
      return this;
    },

    /**
       Registers an event listener for this object's custom events.
       The callback gets a CustomEvent object with a 'detail'
       propertly holding any tab-related state for the event. The events
       are:

       - 'before-switch-to' is emitted immediately before a new tab is
       switched to.  detail = the tab element.

       - 'after-switch-to' is emitted immediately after a new tab is
       switched to.  detail = the tab element.

       Any exceptions thrown by listeners are caught and ignored, to
       avoid that they knock the tab state out of sync.

       Returns this object.
    */
    addEventListener: function(eventName, callback){
      this.e.container.addEventListener(eventName, callback, false);
      return this;
    },

    /**
       If the given DOM element, unique selector, or integer (0-based
       tab number) is one of this object's tabs, the UI makes that tab
       the currently-visible one. Returns this object.
    */
    switchToTab: function(tab){
      tab = tabArg(tab,this);
      const self = this;
      this.e.tabs.childNodes.forEach((e,ndx)=>{
        const btn = this.e.tabBar.childNodes[ndx];
        if(e===tab){
          if(D.hasClass(e,'selected')){
            return;
          }
          self._dispatchEvent('before-switch-to',tab);
          setVisible(e, true);
          D.addClass(btn,'selected');
          self._dispatchEvent('after-switch-to',tab);
        }else{
          if(D.hasClass(e,'selected')){
            return;
          }
          setVisible(e, false);
          D.removeClass(btn,'selected');
        }
      });
      return this;
    }
  };

  F.TabManager = TabManager;
})(window.fossil);
Changes to src/style.c.
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336

1337


1338
1339
1340
1341
1342
1343
1344
**
** <span class='input-with-label' title={{zTip}} id={{zWrapperId}}>
**   <input type='checkbox' name={{zFieldName}} value={{zValue}}
**          {{isChecked ? " checked : ""}}/>
**   <span>{{zLabel}}</span>
** </span>
**
** zFieldName, zLabel, and zValue are required. zWrapperId and zTip
** are optional.
**
** Be sure that the input-with-label CSS class is defined sensibly, in
** particular, having its display:inline-block is useful for alignment
** purposes.
*/
void style_labeled_checkbox(const char * zWrapperId,
                            const char *zFieldName, const char * zLabel,
                            const char * zValue, const char * zTip,
                            int isChecked){
  CX("<span class='input-with-label'");
  if(zTip && *zTip){
    CX(" title='%h'", zTip);
  }
  if(zWrapperId && *zWrapperId){
    CX(" id='%s'",zWrapperId);
  }
  CX("><input type='checkbox' name='%s' value='%T'%s/>",

     zFieldName,


     zValue ? zValue : "", isChecked ? " checked" : "");
  CX("<span>%h</span></span>", zLabel);
}

/*
** Outputs a SELECT list from a compile-time list of integers.
** The vargs must be a list of (const char *, int) pairs, terminated







|
|







|
|







|
>
|
>
>







1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
**
** <span class='input-with-label' title={{zTip}} id={{zWrapperId}}>
**   <input type='checkbox' name={{zFieldName}} value={{zValue}}
**          {{isChecked ? " checked : ""}}/>
**   <span>{{zLabel}}</span>
** </span>
**
** zLabel, and zValue are required. zFieldName, zWrapperId, and zTip
** are may be NULL or empty.
**
** Be sure that the input-with-label CSS class is defined sensibly, in
** particular, having its display:inline-block is useful for alignment
** purposes.
*/
void style_labeled_checkbox(const char * zWrapperId,
                            const char *zFieldName, const char * zLabel,
                            const char * zValue, int isChecked,
                            const char * zTip){
  CX("<span class='input-with-label'");
  if(zTip && *zTip){
    CX(" title='%h'", zTip);
  }
  if(zWrapperId && *zWrapperId){
    CX(" id='%s'",zWrapperId);
  }
  CX("><input type='checkbox' ");
  if(zFieldName && *zFieldName){
    CX("name='%s' ",zFieldName);
  }
  CX("value='%T'%s/>",
     zValue ? zValue : "", isChecked ? " checked" : "");
  CX("<span>%h</span></span>", zLabel);
}

/*
** Outputs a SELECT list from a compile-time list of integers.
** The vargs must be a list of (const char *, int) pairs, terminated