Fossil

Check-in [41ef416e77]
Login

Check-in [41ef416e77]

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

Overview
Comment:Fetching of /jchunk lines by clicking on the '...' separator of a diff is now working but the fetched lines still need to be integrated into the UI.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | diff-js-refactoring
Files: files | file ages | folders
SHA3-256: 41ef416e778ea9c3c85e9fa0e335823a600cf6b25b0a8033c8505032f96729ad
User & Date: stephan 2021-09-09 03:23:51.686
Context
2021-09-09
04:58
/jchunk code lines are now injected into the diff view but the line numbers are still TODO. ... (check-in: 49a60a580d user: stephan tags: diff-js-refactoring)
03:23
Fetching of /jchunk lines by clicking on the '...' separator of a diff is now working but the fetched lines still need to be integrated into the UI. ... (check-in: 41ef416e77 user: stephan tags: diff-js-refactoring)
2021-09-08
18:42
/jchunk now always uses a JSON response, even for permissions problems. Doc improvements for the jchunk interface. ... (check-in: 1fec5f4abc user: stephan tags: diff-js-refactoring)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/builtin.c.
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
                        ** entries: all known deps of this one. Each
                        ** REQUIRES an EXPLICIT trailing \0, including
                        ** the final one! */
  } fjs[] = {
  /* This list ordering isn't strictly important. */
  {"confirmer",      0, 0},
  {"copybutton",     0, "dom\0"},
  {"diff",           0, "dom\0fetch\0"},
  {"dom",            0, 0},
  {"fetch",          0, 0},
  {"info-diff",      0, "dom\0"},
  {"numbered-lines", 0, "popupwidget\0copybutton\0"},
  {"pikchr",         0, "dom\0"},
  {"popupwidget",    0, "dom\0"},
  {"storage",        0, 0},







|







700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
                        ** entries: all known deps of this one. Each
                        ** REQUIRES an EXPLICIT trailing \0, including
                        ** the final one! */
  } fjs[] = {
  /* This list ordering isn't strictly important. */
  {"confirmer",      0, 0},
  {"copybutton",     0, "dom\0"},
  {"diff",           0, "dom\0fetch\0popupwidget\0"},
  {"dom",            0, 0},
  {"fetch",          0, 0},
  {"info-diff",      0, "dom\0"},
  {"numbered-lines", 0, "popupwidget\0copybutton\0"},
  {"pikchr",         0, "dom\0"},
  {"popupwidget",    0, "dom\0"},
  {"storage",        0, 0},
Changes to src/default.css.
540
541
542
543
544
545
546







547
548
549
550
551
552
553
  padding: 0 0.5em;
}
table.diff td {
  vertical-align: top;
}
table.diff pre {
  margin: 0 0 0 0;







}
td.diffln {
  width: 1px;
  text-align: right;
  padding: 0 1em 0 0;
}
td.difflne {







>
>
>
>
>
>
>







540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
  padding: 0 0.5em;
}
table.diff td {
  vertical-align: top;
}
table.diff pre {
  margin: 0 0 0 0;
}
tr.diffskip.jchunk:hover {
  /* jchunk gets added from JS to diffskip rows when they are
     plugged into the /jchunk route and removed after that data
     is fetched. */
  background-color: rgba(127,127,127,0.5);
  cursor: pointer;
}
td.diffln {
  width: 1px;
  text-align: right;
  padding: 0 1em 0 0;
}
td.difflne {
Changes to src/fossil.diff.js.
18
19
20
21
22
23
24










25



26
27
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
61
62
63
64
65
66
    }, false);
  };
  document.querySelectorAll('table.diff').forEach(addToggle);
});

window.fossil.onPageLoad(function(){
  const F = window.fossil, D = F.dom;














  /**
     Uses the /jchunk AJAX route to fetch specific lines of a given
     artifact. The first argument must be an Object with these

     properties:

     {
       name: full hash of the target file,
       from: first 1-based line number of the file to fetch (inclusive),
       to: last 1-based line number of the file to fetch (inclusive)
     }




     onload and onerror are optional callback functions to be called




     on success resp. error, as documented for window.fossil.fetch().
     Note that onload is ostensibly optional but this function is not
     of much use without an onload handler. Conversely, the default

     onerror handler is often customized on a per-page basis to send
     the error output somewhere where the user can see it.

     The response, on success, will be an array of strings, each entry
     being one line from the requested artifact. If the 'to' line is
     greater than the length of the file, the array will be shorter
     than (to-from) lines.

     The /jchunk route reports errors via JSON objects with
     an "error" string property describing the problem.

     This is an async operation. Returns this object.
  */







  F.fetchArtifactLines = function(urlParams, onload, onerror){


















    const opt = {urlParams};



















    if(onload) opt.onload = onload;


    if(onerror) opt.onerror = onerror;














































    return this.fetch('jchunk', opt);





  };









});

/**
   2021-09-07: refactoring the following for use in the higher-level
   fossil.*.js framework is pending. For now it's a copy/paste copy
   of diff.js.
*/







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


|
>
|







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









|

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







18
19
20
21
22
23
24
25
26
27
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
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
    }, false);
  };
  document.querySelectorAll('table.diff').forEach(addToggle);
});

window.fossil.onPageLoad(function(){
  const F = window.fossil, D = F.dom;
  const Diff = F.diff = {
    config: {
      chunkLoadLines: 20,
      chunkFetch: {
        /* Default callack handlers for Diff.fetchArtifactChunk(),
           unless overridden by options passeed to that function. */
        beforesend: function(){},
        aftersend: function(){},
        onerror: function(e){
          F.toast.error("XHR error: ",e.message);
        }
      }
    }
  };
  /**
     Uses the /jchunk AJAX route to fetch specific lines of a given
     artifact. The argument must be an Object suitable for passing as
     the second argument to fossil.fetch(). Its urlParams property
     must be an object with these properties:

     {
       name: full hash of the target file,
       from: first 1-based line number of the file to fetch (inclusive),
       to: last 1-based line number of the file to fetch (inclusive)
     }

     The fetchOpt object is NOT cloned for use by the call: it is used
     as-is and may be modified by this call. Thus callers "really
     should" pass a temporary object, not a long-lived one.

     If fetchOpt does not define any of the (beforesend, aftersend,
     onerror) callbacks, the defaults from fossil.diff.config.chunkFetch
     are used, so any given client page may override those to provide
     page-level default handling.

     Note that onload callback is ostensibly optional but this
     function is not of much use without an onload
     handler. Conversely, the default onerror handler is often
     customized on a per-page basis to send the error output somewhere
     where the user can see it.

     The response, on success, will be an array of strings, each entry
     being one line from the requested artifact. If the 'to' line is
     greater than the length of the file, the array will be shorter
     than (to-from) lines.

     The /jchunk route reports errors via JSON objects with
     an "error" string property describing the problem.

     This is an async operation. Returns the fossil object.
  */
  Diff.fetchArtifactChunk = function(fetchOpt){
    if(!fetchOpt.beforesend) fetchOpt.beforesend = Diff.config.chunkFetch.beforesend;
    if(!fetchOpt.aftersend) fetchOpt.aftersend = Diff.config.chunkFetch.aftersend;
    if(!fetchOpt.onerror) fetchOpt.onerror = Diff.config.chunkFetch.onerror;
    fetchOpt.responseType = 'json';
    return F.fetch('jchunk', fetchOpt);
  };

  /**
     Fetches /jchunk for the given TR element then replaces the TR's
     contents with data from the result of that request.
  */
  const fetchTrChunk = function(tr){
    if(tr.dataset.xfer /* already being fetched */) return;
    const table = tr.parentElement.parentElement;
    const hash = table.dataset.lefthash;
    if(!hash) return;
    const isSbs = table.classList.contains('splitdiff')/*else udiff*/;
    tr.dataset.xfer = 1 /* sentinel against multiple concurrent ajax requests */;
    const lineTo = +tr.dataset.endln;
    var lnFrom = +tr.dataset.startln;
    /* TODO: for the time being, for simplicity, we'll read the whole
       [startln, endln] chunk. "Later on" we'll maybe want to read it in
       chunks of, say, 20 lines or so, adjusting lnFrom to be 1 if it would
       be less than 1. */
    Diff.fetchArtifactChunk({
      urlParams:{
        name: hash,
        from: lnFrom,
        to: lineTo
      },
      aftersend: function(){
        delete tr.dataset.xfer;
        Diff.config.chunkFetch.aftersend.apply(
          this, Array.prototype.slice.call(arguments,0)
        );
      },
      onload: function(result){
        console.debug("Chunk result: ",result);
        D.clearElement(tr);
        D.append(
          D.attr(D.td(tr), 'colspan', isSbs ? 5 : 4),
          "Fetched chunk of ",result.length," line(s). TODO: insert it here."
        );
        /*
          At this point we need to:

          - Read the previous TR, if any, to get the preceeding LHS/RHS
          line numbers so that we know where to start counting.

          - If there is no previous TR, we're at the top and we
          instead need to get the LHS/RHS line numbers from the
          following TR's children.

          - D.clearElement(tr) and insert columns appropriate for the
          parent table's diff type.

          We can fish the line numbers out of the PRE columns with something
          like this inefficient but effective hack:

          theElement.innerText.split(/\n+/)

          (need /\n+/ instead of '\n' b/c of INS/DEL elements)

          Noting that the result array will end with an empty element
          due to the trailing \n character, so a call to pop() will be
          needed.

          SBS diff col layout:
            <td><pre>...LHS line numbers...</pre></td>
            <td>...code lines...</td>
            <td></td> empty for this case.
            <td><pre>...RHS line numbers...</pre></td>
            <td>...dupe of col 2</td>

          Unified diff col layout:
            <td>LHS line numbers</td>
            <td>RHS line numbers</td>
            <td>blank in this case</td>
            <td>code line</td>
         */
      }
    });
  };
  
  Diff.addDiffSkipHandlers = function(){
    const tables = document.querySelectorAll('table.diff[data-lefthash]');
    if(!tables.length) return F;
    const addDiffSkipToTr = function f(tr){
      D.addClass(tr, 'jchunk');
      if(!f._handler){
        f._handler = function ff(event){
          var e = event.target;
          while(e && 'TR' !== e.tagName) e = e.parentElement;
          if(!e){
            console.error("Internal event-handling error: didn't find TR target.");
            return;
          }
          e.removeEventListener('click',ff);
          D.removeClass(e, 'jchunk');
          //console.debug("addDiffSkipToTr() Event:",e, event);
          fetchTrChunk(e);
        };
      }
      tr.addEventListener('click', f._handler, false);
    };
    tables.forEach(function(t){
      t.querySelectorAll('tr.diffskip[data-startln]').forEach(addDiffSkipToTr);
    });
  };

  F.diff.addDiffSkipHandlers();
});

/**
   2021-09-07: refactoring the following for use in the higher-level
   fossil.*.js framework is pending. For now it's a copy/paste copy
   of diff.js.
*/
Changes to src/info.c.
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
**
** This page is intended to be used in an XHR from javascript on a
** diff page, to return unseen context to fill in additional context
** when the user clicks on the appropriate button. The response is
** always in JSON form and errors are reported as documented for
** ajax_route_error().
*/
void jtext_page(void){
  int rid = 0;
  const char *zName = PD("name", "");
  int iFrom = atoi(PD("from","0"));
  int iTo = atoi(PD("to","0"));
  int ln;
  int go = 1;
  const char *zSep;
  Blob content;
  Blob line;
  Blob *pOut;






  login_check_credentials();
  if( !g.perm.Read ){
    ajax_route_error(403, "Access requires Read permissions.");
    return;
  }
#if 0
  /* Re-enable this block once this code is integrated somewhere into







|











>
>
>
>
>







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
**
** This page is intended to be used in an XHR from javascript on a
** diff page, to return unseen context to fill in additional context
** when the user clicks on the appropriate button. The response is
** always in JSON form and errors are reported as documented for
** ajax_route_error().
*/
void jchunk_page(void){
  int rid = 0;
  const char *zName = PD("name", "");
  int iFrom = atoi(PD("from","0"));
  int iTo = atoi(PD("to","0"));
  int ln;
  int go = 1;
  const char *zSep;
  Blob content;
  Blob line;
  Blob *pOut;

  if(0){
    ajax_route_error(400, "Just testing client-side error handling.");
    return;
  }
  
  login_check_credentials();
  if( !g.perm.Read ){
    ajax_route_error(403, "Access requires Read permissions.");
    return;
  }
#if 0
  /* Re-enable this block once this code is integrated somewhere into