Fossil

Check-in [29567e6e7e]
Login

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

Overview
Comment:Added hash_digits() info to fossil.config object and added fossil.hashDigits(). Factored out fileedit JS use of innerHTML where possible. Reworked the Version Info tab a bit.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | fileedit-ajaxify
Files: files | file ages | folders
SHA3-256: 29567e6e7ef1b05adaa9b5b4b33115c5b3c59f08c5fae04f076304b7e227412e
User & Date: stephan 2020-05-07 01:19:09.770
Context
2020-05-07
02:36
Merged in trunk. ... (check-in: 087c5d1f3e user: stephan tags: fileedit-ajaxify)
01:19
Added hash_digits() info to fossil.config object and added fossil.hashDigits(). Factored out fileedit JS use of innerHTML where possible. Reworked the Version Info tab a bit. ... (check-in: 29567e6e7e user: stephan tags: fileedit-ajaxify)
2020-05-06
23:53
Added a 'tick' mode to fossil.confirmer to more easily allow the triggering element to be visibly updated to reflect the countdown state. The editor's discard/reload button now visibly counts down from 3 if clicked. ... (check-in: 3da4b94c44 user: stephan tags: fileedit-ajaxify)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/fileedit.c.
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561

1562
1563
1564
1565
1566
1567
1568
  /***** File/version info tab *****/
  {
    CX("<div id='fileedit-tab-version' "
       "data-tab-parent='fileedit-tabs' "
       "data-tab-label='Version Info'"
       ">");
    CX("File: "
       "[<a id='finfo-link' href='#'>/finfo</a>] "
       /* %R/finfo?name=%T&m=%!S */
       "<code id='finfo-file-name'>(loading)</code><br>");
    CX("Checkin Version: "

       "[<a id='r-link' href='#'>/info</a>] "
       /* %R/info/%!S */
       "<code id='r-label'>(loading...)</code><br>"
       );
    CX("Permalink: <code>"
       "<a id='permalink' href='#'>(loading...)</a></code><br>"
       "(Clicking the permalink will reload the page and discard "







<
<


>







1551
1552
1553
1554
1555
1556
1557


1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
  /***** File/version info tab *****/
  {
    CX("<div id='fileedit-tab-version' "
       "data-tab-parent='fileedit-tabs' "
       "data-tab-label='Version Info'"
       ">");
    CX("File: "


       "<code id='finfo-file-name'>(loading)</code><br>");
    CX("Checkin Version: "
       "[<a id='timeline-link' href='#'>/timeline</a>] "
       "[<a id='r-link' href='#'>/info</a>] "
       /* %R/info/%!S */
       "<code id='r-label'>(loading...)</code><br>"
       );
    CX("Permalink: <code>"
       "<a id='permalink' href='#'>(loading...)</a></code><br>"
       "(Clicking the permalink will reload the page and discard "
Changes to src/fossil.bootstrap.js.
139
140
141
142
143
144
145

















146
147
148
149
150
151
152
      if(!F.isObject(o = arguments[i])) continue;
      for( k in o ){
        if(o.hasOwnProperty(k)) rc[k] = o[k];
      }
    }
    return rc;
  };


















  /**
     Sets up pseudo-automatic content preview handling between a
     source element (typically a TEXTAREA) and a target rendering
     element (typically a DIV). The selector argument must be one of:

     - A single DOM element







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







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
      if(!F.isObject(o = arguments[i])) continue;
      for( k in o ){
        if(o.hasOwnProperty(k)) rc[k] = o[k];
      }
    }
    return rc;
  };

  /**
     Expects to be passed as hash code as its first argument. It
     returns a "shortened" form of hash, with a length which depends
     on the 2nd argument: truthy = fossil.config.hashDigitsUrl, falsy
     = fossil.config.hashDigits. Both of those values are derived from
     the 'hash-digits' repo-level config setting or the
     FOSSIL_HASH_DIGITS_URL/FOSSIL_HASH_DIGITS compile-time options.

     If its first arugment is a non-string, that value is returned
     as-is.
  */
  F.hashDigits = function(hash,forUrl){
    return ('string'==typeof hash ? hash.substr(
      0, F.config[forUrl ? 'hashDigitsUrl' : 'hashDigits']
    ) : hash);
  };

  /**
     Sets up pseudo-automatic content preview handling between a
     source element (typically a TEXTAREA) and a target rendering
     element (typically a DIV). The selector argument must be one of:

     - A single DOM element
248
249
250
251
252
253
254
255
256
257
            (r)=>eTo[asText ? 'textContent' : 'innerHTML'] = r||''
          );
        }, false
      );
    });
    return this;
  };


})(window);








<

265
266
267
268
269
270
271
272

273
            (r)=>eTo[asText ? 'textContent' : 'innerHTML'] = r||''
          );
        }, false
      );
    });
    return this;
  };


})(window);
Changes to src/fossil.dom.js.
1
2
3
4
5


6
7
8
9
10
11
12
"use strict";
(function(F/*fossil object*/){
  /**
     A collection of HTML DOM utilities to simplify, a bit,
     using the DOM API.


  */
  const argsToArray = (a)=>Array.prototype.slice.call(a,0);
  const isArray = (v)=>v instanceof Array;

  const dom = {
    create: function(elemType){
      return document.createElement(elemType);



|
|
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
"use strict";
(function(F/*fossil object*/){
  /**
     A collection of HTML DOM utilities to simplify, a bit, using the
     DOM API. It is focused on manipulation of the DOM, but one of its
     core mantras is "No innerHTML." Using innerHTML in this code, in
     particular assigning to it, is absolutely verboten.
  */
  const argsToArray = (a)=>Array.prototype.slice.call(a,0);
  const isArray = (v)=>v instanceof Array;

  const dom = {
    create: function(elemType){
      return document.createElement(elemType);
Changes to src/fossil.page.fileedit.js.
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
    );
    diffButtons.querySelector('button.unified').addEventListener(
      "click",(e)=>P.diff(false), false
    );
    P.e.btnCommit.addEventListener(
      "click",(e)=>P.commit(), false
    );
    if(P.e.btnReload){
      const label = "Really reload, losing edits?";
      F.confirmer(P.e.btnReload, {
        confirmText: label,
        onconfirm: (e)=>P.loadFile(),
        ticks: 3
      });
    }
    /**
       Cosmetic: jump through some hoops to enable/disable
       certain preview options depending on the current
       preview mode...
    */
    const selectPreviewMode =
          P.e.selectPreviewModeWrap.querySelector('select');







<
<
|
|
|
|
|
<







51
52
53
54
55
56
57


58
59
60
61
62

63
64
65
66
67
68
69
    );
    diffButtons.querySelector('button.unified').addEventListener(
      "click",(e)=>P.diff(false), false
    );
    P.e.btnCommit.addEventListener(
      "click",(e)=>P.commit(), false
    );


    F.confirmer(P.e.btnReload, {
      confirmText: "Really reload, losing edits?",
      onconfirm: (e)=>P.loadFile(),
      ticks: 3
    });

    /**
       Cosmetic: jump through some hoops to enable/disable
       certain preview options depending on the current
       preview mode...
    */
    const selectPreviewMode =
          P.e.selectPreviewModeWrap.querySelector('select');
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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
        }, false
      );
      selectFontSize.dispatchEvent(
        // Force UI update
        new Event('change',{target:selectFontSize})
      );
    }

  }, false)/*onload event handler*/;
  
  /**
     updateVersion() updates the filename and version in various UI
     elements...

     Returns this object.
  */
  P.updateVersion = function(file,rev){
    this.finfo = {filename:file,checkin:rev};
    const E = (s)=>document.querySelector(s),
          euc = encodeURIComponent,
          rShort = rev.substr(0,16);


    E('#r-label').innerText=rev;



    E('#finfo-link').setAttribute(
      'href',



      F.repoUrl('finfo',{name:file, m:rShort})
    );
    E('#finfo-file-name').innerText=file;
    E('#r-link').setAttribute(
      'href',
      F.repoUrl('info/'+rev)
    );
    E('#r-label').innerText = rev;

    const purlArgs = F.encodeUrlArgs({
      filename: this.finfo.filename,
      checkin: this.finfo.checkin
    },false,true);
    const purl = F.repoUrl('fileedit',purlArgs);
    const e = E('#permalink');
    e.innerText='fileedit?'+purlArgs;
    e.setAttribute('href',purl);
    return this;
  };

  /**
     loadFile() loads (file,checkinVersion) and updates the relevant
     UI elements to reflect the loaded state.








<












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

<
|
<
|
<
|
>


|


|
|
<







100
101
102
103
104
105
106

107
108
109
110
111
112
113
114
115
116
117
118
119
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
        }, false
      );
      selectFontSize.dispatchEvent(
        // Force UI update
        new Event('change',{target:selectFontSize})
      );
    }

  }, false)/*onload event handler*/;
  
  /**
     updateVersion() updates the filename and version in various UI
     elements...

     Returns this object.
  */
  P.updateVersion = function(file,rev){
    this.finfo = {filename:file,checkin:rev};
    const E = (s)=>document.querySelector(s),
          euc = encodeURIComponent,
          rHuman = F.hashDigits(rev),
          rUrl = F.hashDigits(rev,true);
    D.append(
      D.clearElement(E('#r-label')),
      rHuman
    );
    var e;
    e = E('#timeline-link');
    D.attr(e, 'href',F.repoUrl('timeline',{c:rUrl}));
    e = E('#finfo-file-name');
    D.append(
      D.clearElement(e),
      D.a(F.repoUrl('finfo',{name:file, m:rUrl}), file)
    );

    e = E('#r-link');

    D.attr(e, 'href', F.repoUrl('info/'+rUrl));

    e = E('#r-label');
    D.append(D.clearElement(e),rHuman);
    const purlArgs = F.encodeUrlArgs({
      filename: this.finfo.filename,
      checkin: rUrl
    },false,true);
    const purl = F.repoUrl('fileedit',purlArgs);
    e = E('#permalink');
    D.attr(D.append(D.clearElement(e),'?'+purlArgs),'href', purl);

    return this;
  };

  /**
     loadFile() loads (file,checkinVersion) and updates the relevant
     UI elements to reflect the loaded state.

185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
    }
    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;
    }







|
<
<







186
187
188
189
190
191
192
193


194
195
196
197
198
199
200
    }
    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;
    }
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
          isDryRun = cbDryRun.checked,
          filename = this.finfo.filename;
    if(!f.updateView){
      f.updateView = function(c){
        target.innerHTML = [
          "<h3>Manifest",
          (c.dryRun?" (dry run)":""),
          ": ", c.uuid.substring(0,16),"</h3>",
          "<code class='fileedit-manifest'>",
          c.manifest,
          "</code></pre>"
        ].join('');
        const msg = [
          'Committed',
          c.dryRun ? '(dry run)' : '',
          '[', c.uuid,'].'
        ];
        if(!c.dryRun){
          msg.push('Re-activating dry-run mode.');
          self.e.taComment.value = '';
          cbDryRun.checked = true;
          P.updateVersion(filename, c.uuid);
        }







|







|







287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
          isDryRun = cbDryRun.checked,
          filename = this.finfo.filename;
    if(!f.updateView){
      f.updateView = function(c){
        target.innerHTML = [
          "<h3>Manifest",
          (c.dryRun?" (dry run)":""),
          ": ", F.hashDigits(c.uuid),"</h3>",
          "<code class='fileedit-manifest'>",
          c.manifest,
          "</code></pre>"
        ].join('');
        const msg = [
          'Committed',
          c.dryRun ? '(dry run)' : '',
          '[', F.hashDigits(c.uuid) ,'].'
        ];
        if(!c.dryRun){
          msg.push('Re-activating dry-run mode.');
          self.e.taComment.value = '';
          cbDryRun.checked = true;
          P.updateVersion(filename, c.uuid);
        }
Changes to src/style.c.
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454




1455
1456
1457
1458
1459
1460
1461
1462
1463

1464
1465
1466
1467
1468
1469
1470
1471
    /* Set up the generic/app-agnostic parts of window.fossil
    ** which require C-level state... */
    style_emit_script_tag(0,0);
    CX("(function(){\n"
       "if(!window.fossil) window.fossil={};\n"
       "window.fossil.version = \"%j\";\n"
    /* fossil.rootPath is the top-most CGI/server path,
       including a trailing slash. */
       "window.fossil.rootPath = \"%j\"+'/';\n",
       get_version(), g.zTop);




    /*
    ** fossil.page holds info about the current page. This is
    ** also where the current page "should" store any of its
    ** own page-specific state.
    */
    CX("window.fossil.page = {"
       "page:\"%T\""
       "};\n", g.zPath);
    CX("})();\n");

    /* The remaining code is not dependent on C-runtime state... */
    if(asInline){
      CX("%s\n", builtin_text("fossil.bootstrap.js"));
    }
    style_emit_script_tag(1,0);
    if(asInline==0){
      style_emit_script_tag(0,"builtin/fossil.bootstrap.js");
    }







|


>
>
>
>

|
|
|





>
|







1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
    /* Set up the generic/app-agnostic parts of window.fossil
    ** which require C-level state... */
    style_emit_script_tag(0,0);
    CX("(function(){\n"
       "if(!window.fossil) window.fossil={};\n"
       "window.fossil.version = \"%j\";\n"
    /* fossil.rootPath is the top-most CGI/server path,
    ** including a trailing slash. */
       "window.fossil.rootPath = \"%j\"+'/';\n",
       get_version(), g.zTop);
    /* fossil.config = {...various config-level options...} */
    CX("window.fossil.config = {"
       "hashDigits: %d, hashDigitsUrl: %d"
       "};\n", hash_digits(0), hash_digits(1));
    /*
    ** fossil.page holds info about the current page. This is also
    ** where the current page "should" store any of its own
    ** page-specific state, and it is reserved for that purpose.
    */
    CX("window.fossil.page = {"
       "page:\"%T\""
       "};\n", g.zPath);
    CX("})();\n");
    /* The remaining fossil object bootstrap code is not dependent on
    ** C-runtime state... */
    if(asInline){
      CX("%s\n", builtin_text("fossil.bootstrap.js"));
    }
    style_emit_script_tag(1,0);
    if(asInline==0){
      style_emit_script_tag(0,"builtin/fossil.bootstrap.js");
    }