Fossil SCM

Added filter checkboxes to show/hide to filter the wiki page list by page type(s). Related internal API additions.

stephan 2020-07-31 01:09 ajax-wiki-editor
Commit 9edf5e7dd68f85c89d346cd36d145f6dcd04fa2276ffe59a43476029a8bf2d0c
+10
--- src/ajax.c
+++ src/ajax.c
@@ -163,10 +163,20 @@
163163
}else{
164164
CX("<pre class='udiff'>%b</pre>",&out);
165165
}
166166
blob_reset(&out);
167167
}
168
+
169
+/*
170
+** Uses P(zKey) to fetch a CGI environment variable. If that var is
171
+** NULL or starts with '0' or 'f' then this function returns false,
172
+** else it returns true.
173
+*/
174
+int ajax_p_bool(char const *zKey){
175
+ const * zVal = P(zKey);
176
+ return (!zVal || '0'==*zVal || 'f'==*zVal) ? 0 : 1;
177
+}
168178
169179
/*
170180
** Helper for /ajax routes. Clears the CGI content buffer, sets an
171181
** HTTP error status code, and queues up a JSON response in the form
172182
** of an object:
173183
--- src/ajax.c
+++ src/ajax.c
@@ -163,10 +163,20 @@
163 }else{
164 CX("<pre class='udiff'>%b</pre>",&out);
165 }
166 blob_reset(&out);
167 }
 
 
 
 
 
 
 
 
 
 
168
169 /*
170 ** Helper for /ajax routes. Clears the CGI content buffer, sets an
171 ** HTTP error status code, and queues up a JSON response in the form
172 ** of an object:
173
--- src/ajax.c
+++ src/ajax.c
@@ -163,10 +163,20 @@
163 }else{
164 CX("<pre class='udiff'>%b</pre>",&out);
165 }
166 blob_reset(&out);
167 }
168
169 /*
170 ** Uses P(zKey) to fetch a CGI environment variable. If that var is
171 ** NULL or starts with '0' or 'f' then this function returns false,
172 ** else it returns true.
173 */
174 int ajax_p_bool(char const *zKey){
175 const * zVal = P(zKey);
176 return (!zVal || '0'==*zVal || 'f'==*zVal) ? 0 : 1;
177 }
178
179 /*
180 ** Helper for /ajax routes. Clears the CGI content buffer, sets an
181 ** HTTP error status code, and queues up a JSON response in the form
182 ** of an object:
183
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -262,11 +262,16 @@
262262
s.dispatchEvent(new Event('change',{target:s}));
263263
}
264264
};
265265
266266
const WikiList = {
267
- e: {},
267
+ e: {
268
+ filterCheckboxes: {
269
+ /*map of wiki page type to checkbox for list filtering purposes,
270
+ except for "sandbox" type, which is assumed to be covered by
271
+ the "normal" type filter. */}
272
+ },
268273
/** Updates OPTION elements to reflect whether the page has
269274
local changes or is new/unsaved. */
270275
refreshStashMarks: function(){
271276
const sel = this.e.select;
272277
Object.keys(sel.options).forEach(function(key){
@@ -293,10 +298,11 @@
293298
sel.selectedIndex = ndx;
294299
},
295300
296301
/** Loads the page list and populates the selection list. */
297302
loadList: function callee(){
303
+ delete this.pageMap;
298304
if(!callee.sorticase){
299305
callee.sorticase = function(l,r){
300306
l = l.toLowerCase();
301307
r = r.toLowerCase();
302308
return l<=r ? -1 : 1;
@@ -307,25 +313,33 @@
307313
pages into the list of existing pages... We use a map
308314
as an intermediary in order to filter out any local-stash
309315
dupes from server-side copies. */
310316
const map = {}, ndx = $stash.getIndex(), sel = self.e.select;
311317
D.clearElement(sel);
312
- list.forEach((name)=>map[name] = true);
318
+ list.forEach((winfo)=>map[winfo.name] = winfo);
313319
Object.keys(ndx).forEach(function(key){
314320
const winfo = ndx[key];
315
- if(!winfo.version/*new page*/) map[winfo.name] = true;
321
+ if(!winfo.version/*new page*/) map[winfo.name] = winfo;
316322
});
317323
Object.keys(map)
318324
.sort(callee.sorticase)
319
- .forEach((name)=>D.option(sel, name));
325
+ .forEach(function(name){
326
+ const winfo = map[name];
327
+ const opt = D.option(sel, winfo.name);
328
+ const wtype = opt.dataset.wtype =
329
+ winfo.type==='sandbox' ? 'normal' : winfo.type;
330
+ const cb = self.e.filterCheckboxes[wtype];
331
+ if(cb && !cb.checked) D.addClass(opt, 'hidden');
332
+ });
320333
D.enable(sel);
321334
if(P.winfo) sel.value = P.winfo.name;
322335
self.refreshStashMarks();
323336
F.message("Loaded page list.");
324337
};
325338
}
326339
F.fetch('wikiajax/list',{
340
+ urlParams:{verbose:true},
327341
responseType: 'json',
328342
onload: callee.onload
329343
});
330344
return this;
331345
},
@@ -339,32 +353,72 @@
339353
D.addClass(parentElem, 'wikiedit-page-list-wrapper');
340354
D.clearElement(parentElem);
341355
D.append(
342356
parentElem,
343357
D.append(D.span(), "Select a page to edit:"),
344
- sel,
345
- D.append(D.span(), "[*] = page has local edits"),
346
- D.append(D.span(), "[+] = page is new/unsaved"),
347
- btn
358
+ sel
348359
);
349
- D.attr(sel, 'size', 10);
360
+ D.attr(sel, 'size', 15);
350361
D.option(D.disable(D.clearElement(sel)), "Loading...");
362
+
363
+ /** Set up filter checkboxes for the various types
364
+ of wiki pages... */
365
+ const fsFilter = D.fieldset("Wiki page types"),
366
+ fsFilterBody = D.div(),
367
+ filters = ['normal', 'branch', 'checkin', 'tag']
368
+ ;
369
+ D.append(fsFilter, fsFilterBody);
370
+ D.addClass(fsFilterBody, 'flex-container', 'flex-column', 'stretch');
371
+ const filterSelection = function(wtype, show){
372
+ sel.querySelectorAll('option[data-wtype='+wtype+']').forEach(function(opt){
373
+ if(show) opt.classList.remove('hidden');
374
+ else opt.classList.add('hidden');
375
+ });
376
+ };
351377
const self = this;
352
- btn.addEventListener(
353
- 'click', ()=>this.loadList(), false
354
- );
355
- this.loadList();
356
- sel.addEventListener(
357
- 'change',
358
- (e)=>P.loadPage(e.target.value),
359
- false
360
- );
361
- F.page.addEventListener(
362
- 'wiki-stash-updated',
363
- ()=>this.refreshStashMarks(),
364
- false
365
- );
378
+ filters.forEach(function(wtype){
379
+ const cbId = 'wtype-filter-'+wtype,
380
+ lbl = D.attr(D.append(D.label(),wtype),
381
+ 'for', cbId),
382
+ cb = D.attr(D.input('checkbox'), 'id', cbId),
383
+ span = D.append(D.span(), cb, lbl);
384
+ self.e.filterCheckboxes[wtype] = cb;
385
+ cb.checked = true;
386
+ filterSelection(wtype, cb.checked);
387
+ cb.addEventListener(
388
+ 'change',
389
+ function(ev){filterSelection(wtype, ev.target.checked)},
390
+ false
391
+ );
392
+ D.append(fsFilterBody, span);
393
+ });
394
+
395
+ /* A legend of the meanings of the symbols we use in
396
+ the OPTION elements to denote certain state. Note that
397
+ the symbols themselves are *actually* defined in CSS, so if
398
+ they're changed there they also need to be changed here.*/
399
+ const fsLegend = D.fieldset("Edit status"),
400
+ fsLegendBody = D.div();
401
+ D.append(fsLegend, fsLegendBody);
402
+ D.addClass(fsLegendBody, 'flex-container', 'flex-column', 'stretch');
403
+ D.append(
404
+ fsLegendBody,
405
+ D.append(D.span(), "[*] = page has local edits"),
406
+ D.append(D.span(), "[+] = page is new/unsaved")
407
+ );
408
+
409
+ D.append(
410
+ parentElem,
411
+ D.append(D.addClass(D.div(), 'fieldset-wrapper'),
412
+ fsFilter, fsLegend)
413
+ );
414
+
415
+ D.append(parentElem, btn);
416
+ btn.addEventListener('click', ()=>this.loadList(), false);
417
+ this.loadList();
418
+ sel.addEventListener('change', (e)=>P.loadPage(e.target.value), false);
419
+ F.page.addEventListener('wiki-stash-updated', ()=>this.refreshStashMarks(), false);
366420
delete this.init;
367421
}
368422
};
369423
370424
/**
371425
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -262,11 +262,16 @@
262 s.dispatchEvent(new Event('change',{target:s}));
263 }
264 };
265
266 const WikiList = {
267 e: {},
 
 
 
 
 
268 /** Updates OPTION elements to reflect whether the page has
269 local changes or is new/unsaved. */
270 refreshStashMarks: function(){
271 const sel = this.e.select;
272 Object.keys(sel.options).forEach(function(key){
@@ -293,10 +298,11 @@
293 sel.selectedIndex = ndx;
294 },
295
296 /** Loads the page list and populates the selection list. */
297 loadList: function callee(){
 
298 if(!callee.sorticase){
299 callee.sorticase = function(l,r){
300 l = l.toLowerCase();
301 r = r.toLowerCase();
302 return l<=r ? -1 : 1;
@@ -307,25 +313,33 @@
307 pages into the list of existing pages... We use a map
308 as an intermediary in order to filter out any local-stash
309 dupes from server-side copies. */
310 const map = {}, ndx = $stash.getIndex(), sel = self.e.select;
311 D.clearElement(sel);
312 list.forEach((name)=>map[name] = true);
313 Object.keys(ndx).forEach(function(key){
314 const winfo = ndx[key];
315 if(!winfo.version/*new page*/) map[winfo.name] = true;
316 });
317 Object.keys(map)
318 .sort(callee.sorticase)
319 .forEach((name)=>D.option(sel, name));
 
 
 
 
 
 
 
320 D.enable(sel);
321 if(P.winfo) sel.value = P.winfo.name;
322 self.refreshStashMarks();
323 F.message("Loaded page list.");
324 };
325 }
326 F.fetch('wikiajax/list',{
 
327 responseType: 'json',
328 onload: callee.onload
329 });
330 return this;
331 },
@@ -339,32 +353,72 @@
339 D.addClass(parentElem, 'wikiedit-page-list-wrapper');
340 D.clearElement(parentElem);
341 D.append(
342 parentElem,
343 D.append(D.span(), "Select a page to edit:"),
344 sel,
345 D.append(D.span(), "[*] = page has local edits"),
346 D.append(D.span(), "[+] = page is new/unsaved"),
347 btn
348 );
349 D.attr(sel, 'size', 10);
350 D.option(D.disable(D.clearElement(sel)), "Loading...");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351 const self = this;
352 btn.addEventListener(
353 'click', ()=>this.loadList(), false
354 );
355 this.loadList();
356 sel.addEventListener(
357 'change',
358 (e)=>P.loadPage(e.target.value),
359 false
360 );
361 F.page.addEventListener(
362 'wiki-stash-updated',
363 ()=>this.refreshStashMarks(),
364 false
365 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366 delete this.init;
367 }
368 };
369
370 /**
371
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -262,11 +262,16 @@
262 s.dispatchEvent(new Event('change',{target:s}));
263 }
264 };
265
266 const WikiList = {
267 e: {
268 filterCheckboxes: {
269 /*map of wiki page type to checkbox for list filtering purposes,
270 except for "sandbox" type, which is assumed to be covered by
271 the "normal" type filter. */}
272 },
273 /** Updates OPTION elements to reflect whether the page has
274 local changes or is new/unsaved. */
275 refreshStashMarks: function(){
276 const sel = this.e.select;
277 Object.keys(sel.options).forEach(function(key){
@@ -293,10 +298,11 @@
298 sel.selectedIndex = ndx;
299 },
300
301 /** Loads the page list and populates the selection list. */
302 loadList: function callee(){
303 delete this.pageMap;
304 if(!callee.sorticase){
305 callee.sorticase = function(l,r){
306 l = l.toLowerCase();
307 r = r.toLowerCase();
308 return l<=r ? -1 : 1;
@@ -307,25 +313,33 @@
313 pages into the list of existing pages... We use a map
314 as an intermediary in order to filter out any local-stash
315 dupes from server-side copies. */
316 const map = {}, ndx = $stash.getIndex(), sel = self.e.select;
317 D.clearElement(sel);
318 list.forEach((winfo)=>map[winfo.name] = winfo);
319 Object.keys(ndx).forEach(function(key){
320 const winfo = ndx[key];
321 if(!winfo.version/*new page*/) map[winfo.name] = winfo;
322 });
323 Object.keys(map)
324 .sort(callee.sorticase)
325 .forEach(function(name){
326 const winfo = map[name];
327 const opt = D.option(sel, winfo.name);
328 const wtype = opt.dataset.wtype =
329 winfo.type==='sandbox' ? 'normal' : winfo.type;
330 const cb = self.e.filterCheckboxes[wtype];
331 if(cb && !cb.checked) D.addClass(opt, 'hidden');
332 });
333 D.enable(sel);
334 if(P.winfo) sel.value = P.winfo.name;
335 self.refreshStashMarks();
336 F.message("Loaded page list.");
337 };
338 }
339 F.fetch('wikiajax/list',{
340 urlParams:{verbose:true},
341 responseType: 'json',
342 onload: callee.onload
343 });
344 return this;
345 },
@@ -339,32 +353,72 @@
353 D.addClass(parentElem, 'wikiedit-page-list-wrapper');
354 D.clearElement(parentElem);
355 D.append(
356 parentElem,
357 D.append(D.span(), "Select a page to edit:"),
358 sel
 
 
 
359 );
360 D.attr(sel, 'size', 15);
361 D.option(D.disable(D.clearElement(sel)), "Loading...");
362
363 /** Set up filter checkboxes for the various types
364 of wiki pages... */
365 const fsFilter = D.fieldset("Wiki page types"),
366 fsFilterBody = D.div(),
367 filters = ['normal', 'branch', 'checkin', 'tag']
368 ;
369 D.append(fsFilter, fsFilterBody);
370 D.addClass(fsFilterBody, 'flex-container', 'flex-column', 'stretch');
371 const filterSelection = function(wtype, show){
372 sel.querySelectorAll('option[data-wtype='+wtype+']').forEach(function(opt){
373 if(show) opt.classList.remove('hidden');
374 else opt.classList.add('hidden');
375 });
376 };
377 const self = this;
378 filters.forEach(function(wtype){
379 const cbId = 'wtype-filter-'+wtype,
380 lbl = D.attr(D.append(D.label(),wtype),
381 'for', cbId),
382 cb = D.attr(D.input('checkbox'), 'id', cbId),
383 span = D.append(D.span(), cb, lbl);
384 self.e.filterCheckboxes[wtype] = cb;
385 cb.checked = true;
386 filterSelection(wtype, cb.checked);
387 cb.addEventListener(
388 'change',
389 function(ev){filterSelection(wtype, ev.target.checked)},
390 false
391 );
392 D.append(fsFilterBody, span);
393 });
394
395 /* A legend of the meanings of the symbols we use in
396 the OPTION elements to denote certain state. Note that
397 the symbols themselves are *actually* defined in CSS, so if
398 they're changed there they also need to be changed here.*/
399 const fsLegend = D.fieldset("Edit status"),
400 fsLegendBody = D.div();
401 D.append(fsLegend, fsLegendBody);
402 D.addClass(fsLegendBody, 'flex-container', 'flex-column', 'stretch');
403 D.append(
404 fsLegendBody,
405 D.append(D.span(), "[*] = page has local edits"),
406 D.append(D.span(), "[+] = page is new/unsaved")
407 );
408
409 D.append(
410 parentElem,
411 D.append(D.addClass(D.div(), 'fieldset-wrapper'),
412 fsFilter, fsLegend)
413 );
414
415 D.append(parentElem, btn);
416 btn.addEventListener('click', ()=>this.loadList(), false);
417 this.loadList();
418 sel.addEventListener('change', (e)=>P.loadPage(e.target.value), false);
419 F.page.addEventListener('wiki-stash-updated', ()=>this.refreshStashMarks(), false);
420 delete this.init;
421 }
422 };
423
424 /**
425
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -54,10 +54,14 @@
5454
}
5555
body.wikiedit .wikiedit-page-list-wrapper select option {
5656
margin: 0.5em 0;
5757
}
5858
body.wikiedit .wikiedit-page-list-wrapper select option.stashed::before {
59
+/* Maintenance reminder: the option.stashed/stashed-new "content" values
60
+ are duplicated in fossil.page.wikiedit.js and need to be changed there
61
+ if they are changed here. i.e. they're not "really" customizable
62
+ by client code. */
5963
content: "[*] ";
6064
}
6165
body.wikiedit .wikiedit-page-list-wrapper select option.stashed-new::before {
6266
content: "[+] ";
6367
}
@@ -65,6 +69,14 @@
6569
max-width: calc(100% - 1em);
6670
}
6771
6872
body.wikiedit .tabs .tab-panel {
6973
overflow: auto;
74
+}
75
+body.wikiedit .wikiedit-page-list-wrapper .fieldset-wrapper {
76
+ display: flex;
77
+ flex-direction: row;
78
+ flex-wrap: wrap;
79
+ align-items: stretch;
80
+ justify-content: stretch;
81
+ margin: 0;
7082
}
7183
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -54,10 +54,14 @@
54 }
55 body.wikiedit .wikiedit-page-list-wrapper select option {
56 margin: 0.5em 0;
57 }
58 body.wikiedit .wikiedit-page-list-wrapper select option.stashed::before {
 
 
 
 
59 content: "[*] ";
60 }
61 body.wikiedit .wikiedit-page-list-wrapper select option.stashed-new::before {
62 content: "[+] ";
63 }
@@ -65,6 +69,14 @@
65 max-width: calc(100% - 1em);
66 }
67
68 body.wikiedit .tabs .tab-panel {
69 overflow: auto;
 
 
 
 
 
 
 
 
70 }
71
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -54,10 +54,14 @@
54 }
55 body.wikiedit .wikiedit-page-list-wrapper select option {
56 margin: 0.5em 0;
57 }
58 body.wikiedit .wikiedit-page-list-wrapper select option.stashed::before {
59 /* Maintenance reminder: the option.stashed/stashed-new "content" values
60 are duplicated in fossil.page.wikiedit.js and need to be changed there
61 if they are changed here. i.e. they're not "really" customizable
62 by client code. */
63 content: "[*] ";
64 }
65 body.wikiedit .wikiedit-page-list-wrapper select option.stashed-new::before {
66 content: "[+] ";
67 }
@@ -65,6 +69,14 @@
69 max-width: calc(100% - 1em);
70 }
71
72 body.wikiedit .tabs .tab-panel {
73 overflow: auto;
74 }
75 body.wikiedit .wikiedit-page-list-wrapper .fieldset-wrapper {
76 display: flex;
77 flex-direction: row;
78 flex-wrap: wrap;
79 align-items: stretch;
80 justify-content: stretch;
81 margin: 0;
82 }
83
+46 -18
--- src/wiki.c
+++ src/wiki.c
@@ -742,24 +742,31 @@
742742
** mimetype: "mime type",
743743
** version: UUID string or null for a sandbox page,
744744
** parent: "parent uuid" or null if no parent,
745745
** content: "page content"
746746
** }
747
+**
748
+** If includeContent is false then the content member is elided.
747749
*/
748
-static int wiki_ajax_emit_page_object(const char *zPageName){
750
+static int wiki_ajax_emit_page_object(const char *zPageName,
751
+ int includeContent){
749752
Manifest * pWiki = 0;
750753
char * zUuid;
751754
752755
cgi_set_content_type("application/json");
753756
if( is_sandbox(zPageName) ){
754757
char * zMimetype =
755758
db_get("sandbox-mimetype","text/x-fossil-wiki");
756759
char * zBody = db_get("sandbox","");
757760
CX("{\"name\": %!j, \"type\": \"sandbox\", "
758
- "\"mimetype\": %!j, \"version\": null, \"parent\": null, "
759
- "\"content\": %!j}",
760
- zPageName, zMimetype, zBody);
761
+ "\"mimetype\": %!j, \"version\": null, \"parent\": null",
762
+ zPageName, zMimetype);
763
+ if(includeContent){
764
+ CX(", \"content\": %!j",
765
+ zBody);
766
+ }
767
+ CX("}");
761768
fossil_free(zMimetype);
762769
fossil_free(zBody);
763770
return 1;
764771
}else if( !wiki_fetch_by_name(zPageName, 0, 0, &pWiki) ){
765772
ajax_route_error(404, "Wiki page could not be loaded: %s",
@@ -774,18 +781,21 @@
774781
wiki_page_type_name(pWiki->zWikiTitle),
775782
zUuid,
776783
pWiki->zMimetype ? pWiki->zMimetype : "text/x-fossil-wiki");
777784
CX("\"parent\": ");
778785
if(pWiki->nParent){
779
- CX("%!j, ", pWiki->azParent[0]);
786
+ CX("%!j", pWiki->azParent[0]);
780787
}else{
781
- CX("null, ");
788
+ CX("null");
789
+ }
790
+ if(includeContent){
791
+ CX(", \"content\": %!j", pWiki->zWiki);
782792
}
783
- CX("\"content\": %!j}", pWiki->zWiki);
793
+ CX("}");
784794
fossil_free(zUuid);
785795
manifest_destroy(pWiki);
786
- return 1;
796
+ return 2;
787797
}
788798
}
789799
790800
/*
791801
** Ajax route handler for /wikiajax/save.
@@ -847,11 +857,11 @@
847857
}
848858
849859
blob_init(&content, zContent ? zContent : "", -1);
850860
db_begin_transaction();
851861
wiki_cmd_commit(zPageName, parentRid, &content, zMimetype, 0);
852
- rollback = wiki_ajax_emit_page_object(zPageName) ? 0 : 1;
862
+ rollback = wiki_ajax_emit_page_object(zPageName, 1) ? 0 : 1;
853863
db_end_transaction(rollback);
854864
}
855865
856866
/*
857867
** Ajax route handler for /wikiajax/fetch.
@@ -869,11 +879,11 @@
869879
870880
if( zPageName==0 || zPageName[0]==0 ){
871881
ajax_route_error(400,"Missing page name.");
872882
return;
873883
}
874
- wiki_ajax_emit_page_object(zPageName);
884
+ wiki_ajax_emit_page_object(zPageName, 1);
875885
}
876886
877887
/*
878888
** Ajax route handler for /wikiajax/diff.
879889
**
@@ -951,34 +961,52 @@
951961
}
952962
}
953963
954964
/*
955965
** Ajax route handler for /wikiajax/list.
966
+**
967
+** Optional parameters: verbose, includeContent (see below).
956968
**
957969
** Responds with JSON. On error, an object in the form documented by
958
-** ajax_route_error(). On success, an array of strings (page names)
959
-** sorted case-insensitively. The result list contains an entry
970
+** ajax_route_error().
971
+**
972
+** On success, it emits an array of strings (page names) sorted
973
+** case-insensitively. If the "verbose" parameter is passed in then
974
+** the result list contains objects in the format documented for
975
+** wiki_ajax_emit_page_object(). The content of each object is elided
976
+** unless the "includeContent" parameter is passed on.
977
+**
978
+** The result list always contains an entry
960979
** named "sandbox" which represents the sandbox pseudo-page.
961980
*/
962981
static void wiki_ajax_route_list(void){
963982
Stmt q = empty_Stmt;
964983
int n = 0;
984
+ const int verbose = ajax_p_bool("verbose");
985
+ const int includeContent = ajax_p_bool("includeContent");
965986
966987
cgi_set_content_type("application/json");
988
+ db_begin_transaction();
967989
db_prepare(&q, "SELECT"
968990
" substr(tagname,6) AS name"
969991
" FROM tag WHERE tagname GLOB 'wiki-*'"
970992
" UNION SELECT 'sandbox' AS name"
971993
" ORDER BY name COLLATE NOCASE");
972994
CX("[");
973995
while( SQLITE_ROW==db_step(&q) ){
996
+ char const * zName = db_column_text(&q,0);
974997
if(n++){
975998
CX(",");
976999
}
977
- CX("%!j", db_column_text(&q,0));
1000
+ if(verbose==0){
1001
+ CX("%!j", zName);
1002
+ }else{
1003
+ wiki_ajax_emit_page_object(zName, includeContent);
1004
+ }
9781005
}
9791006
db_finalize(&q);
1007
+ db_end_transaction(0);
9801008
CX("]");
9811009
}
9821010
9831011
9841012
/*
@@ -1090,21 +1118,21 @@
10901118
10911119
/******* Page list *******/
10921120
{
10931121
CX("<div id='wikiedit-tab-pages' "
10941122
"data-tab-parent='wikiedit-tabs' "
1095
- "data-tab-label='Page List'"
1123
+ "data-tab-label='Wiki Page List'"
10961124
">");
10971125
CX("<div>Loading wiki pages list...</div>");
10981126
CX("</div>"/*#wikiedit-tab-pages*/);
10991127
}
11001128
11011129
/******* Content tab *******/
11021130
{
11031131
CX("<div id='wikiedit-tab-content' "
11041132
"data-tab-parent='wikiedit-tabs' "
1105
- "data-tab-label='Page Editor'"
1133
+ "data-tab-label='Wiki Editor'"
11061134
">");
11071135
CX("<div class='flex-container flex-row child-gap-small'>");
11081136
mimetype_option_menu(0);
11091137
CX("<button class='wikiedit-content-reload' "
11101138
"title='Reload the file from the server, discarding "
@@ -1121,11 +1149,11 @@
11211149
"200%", 200, NULL);
11221150
CX("</div>");
11231151
CX("<div class='flex-container flex-column stretch'>");
11241152
CX("<textarea name='content' id='wikiedit-content-editor' "
11251153
"class='wikiedit' "
1126
- "rows='20' cols='80'>");
1154
+ "rows='25' cols='80'>");
11271155
CX("</textarea>");
11281156
CX("</div>"/*textarea wrapper*/);
11291157
CX("</div>"/*#tab-file-content*/);
11301158
}
11311159
/****** Preview tab ******/
@@ -1191,15 +1219,15 @@
11911219
}
11921220
11931221
{
11941222
CX("<div id='wikiedit-tab-save' "
11951223
"data-tab-parent='wikiedit-tabs' "
1196
- "data-tab-label='Save &amp; Help'"
1224
+ "data-tab-label='Save, etc.'"
11971225
">");
11981226
CX("<button class='wikiedit-save'>Save</button>");
11991227
CX("<hr>");
1200
- CX("The wiki formatting rules can be found at:");
1228
+ CX("Wiki formatting rules:");
12011229
CX("<ul>");
12021230
CX("<li><a href='%R/wiki_rules'>Fossil wiki format</a></li>");
12031231
CX("<li><a href='%R/md_rules'>Markdown format</a></li>");
12041232
CX("</ul>");
12051233
CX("<hr>Attachments:");
12061234
--- src/wiki.c
+++ src/wiki.c
@@ -742,24 +742,31 @@
742 ** mimetype: "mime type",
743 ** version: UUID string or null for a sandbox page,
744 ** parent: "parent uuid" or null if no parent,
745 ** content: "page content"
746 ** }
 
 
747 */
748 static int wiki_ajax_emit_page_object(const char *zPageName){
 
749 Manifest * pWiki = 0;
750 char * zUuid;
751
752 cgi_set_content_type("application/json");
753 if( is_sandbox(zPageName) ){
754 char * zMimetype =
755 db_get("sandbox-mimetype","text/x-fossil-wiki");
756 char * zBody = db_get("sandbox","");
757 CX("{\"name\": %!j, \"type\": \"sandbox\", "
758 "\"mimetype\": %!j, \"version\": null, \"parent\": null, "
759 "\"content\": %!j}",
760 zPageName, zMimetype, zBody);
 
 
 
 
761 fossil_free(zMimetype);
762 fossil_free(zBody);
763 return 1;
764 }else if( !wiki_fetch_by_name(zPageName, 0, 0, &pWiki) ){
765 ajax_route_error(404, "Wiki page could not be loaded: %s",
@@ -774,18 +781,21 @@
774 wiki_page_type_name(pWiki->zWikiTitle),
775 zUuid,
776 pWiki->zMimetype ? pWiki->zMimetype : "text/x-fossil-wiki");
777 CX("\"parent\": ");
778 if(pWiki->nParent){
779 CX("%!j, ", pWiki->azParent[0]);
780 }else{
781 CX("null, ");
 
 
 
782 }
783 CX("\"content\": %!j}", pWiki->zWiki);
784 fossil_free(zUuid);
785 manifest_destroy(pWiki);
786 return 1;
787 }
788 }
789
790 /*
791 ** Ajax route handler for /wikiajax/save.
@@ -847,11 +857,11 @@
847 }
848
849 blob_init(&content, zContent ? zContent : "", -1);
850 db_begin_transaction();
851 wiki_cmd_commit(zPageName, parentRid, &content, zMimetype, 0);
852 rollback = wiki_ajax_emit_page_object(zPageName) ? 0 : 1;
853 db_end_transaction(rollback);
854 }
855
856 /*
857 ** Ajax route handler for /wikiajax/fetch.
@@ -869,11 +879,11 @@
869
870 if( zPageName==0 || zPageName[0]==0 ){
871 ajax_route_error(400,"Missing page name.");
872 return;
873 }
874 wiki_ajax_emit_page_object(zPageName);
875 }
876
877 /*
878 ** Ajax route handler for /wikiajax/diff.
879 **
@@ -951,34 +961,52 @@
951 }
952 }
953
954 /*
955 ** Ajax route handler for /wikiajax/list.
 
 
956 **
957 ** Responds with JSON. On error, an object in the form documented by
958 ** ajax_route_error(). On success, an array of strings (page names)
959 ** sorted case-insensitively. The result list contains an entry
 
 
 
 
 
 
 
960 ** named "sandbox" which represents the sandbox pseudo-page.
961 */
962 static void wiki_ajax_route_list(void){
963 Stmt q = empty_Stmt;
964 int n = 0;
 
 
965
966 cgi_set_content_type("application/json");
 
967 db_prepare(&q, "SELECT"
968 " substr(tagname,6) AS name"
969 " FROM tag WHERE tagname GLOB 'wiki-*'"
970 " UNION SELECT 'sandbox' AS name"
971 " ORDER BY name COLLATE NOCASE");
972 CX("[");
973 while( SQLITE_ROW==db_step(&q) ){
 
974 if(n++){
975 CX(",");
976 }
977 CX("%!j", db_column_text(&q,0));
 
 
 
 
978 }
979 db_finalize(&q);
 
980 CX("]");
981 }
982
983
984 /*
@@ -1090,21 +1118,21 @@
1090
1091 /******* Page list *******/
1092 {
1093 CX("<div id='wikiedit-tab-pages' "
1094 "data-tab-parent='wikiedit-tabs' "
1095 "data-tab-label='Page List'"
1096 ">");
1097 CX("<div>Loading wiki pages list...</div>");
1098 CX("</div>"/*#wikiedit-tab-pages*/);
1099 }
1100
1101 /******* Content tab *******/
1102 {
1103 CX("<div id='wikiedit-tab-content' "
1104 "data-tab-parent='wikiedit-tabs' "
1105 "data-tab-label='Page Editor'"
1106 ">");
1107 CX("<div class='flex-container flex-row child-gap-small'>");
1108 mimetype_option_menu(0);
1109 CX("<button class='wikiedit-content-reload' "
1110 "title='Reload the file from the server, discarding "
@@ -1121,11 +1149,11 @@
1121 "200%", 200, NULL);
1122 CX("</div>");
1123 CX("<div class='flex-container flex-column stretch'>");
1124 CX("<textarea name='content' id='wikiedit-content-editor' "
1125 "class='wikiedit' "
1126 "rows='20' cols='80'>");
1127 CX("</textarea>");
1128 CX("</div>"/*textarea wrapper*/);
1129 CX("</div>"/*#tab-file-content*/);
1130 }
1131 /****** Preview tab ******/
@@ -1191,15 +1219,15 @@
1191 }
1192
1193 {
1194 CX("<div id='wikiedit-tab-save' "
1195 "data-tab-parent='wikiedit-tabs' "
1196 "data-tab-label='Save &amp; Help'"
1197 ">");
1198 CX("<button class='wikiedit-save'>Save</button>");
1199 CX("<hr>");
1200 CX("The wiki formatting rules can be found at:");
1201 CX("<ul>");
1202 CX("<li><a href='%R/wiki_rules'>Fossil wiki format</a></li>");
1203 CX("<li><a href='%R/md_rules'>Markdown format</a></li>");
1204 CX("</ul>");
1205 CX("<hr>Attachments:");
1206
--- src/wiki.c
+++ src/wiki.c
@@ -742,24 +742,31 @@
742 ** mimetype: "mime type",
743 ** version: UUID string or null for a sandbox page,
744 ** parent: "parent uuid" or null if no parent,
745 ** content: "page content"
746 ** }
747 **
748 ** If includeContent is false then the content member is elided.
749 */
750 static int wiki_ajax_emit_page_object(const char *zPageName,
751 int includeContent){
752 Manifest * pWiki = 0;
753 char * zUuid;
754
755 cgi_set_content_type("application/json");
756 if( is_sandbox(zPageName) ){
757 char * zMimetype =
758 db_get("sandbox-mimetype","text/x-fossil-wiki");
759 char * zBody = db_get("sandbox","");
760 CX("{\"name\": %!j, \"type\": \"sandbox\", "
761 "\"mimetype\": %!j, \"version\": null, \"parent\": null",
762 zPageName, zMimetype);
763 if(includeContent){
764 CX(", \"content\": %!j",
765 zBody);
766 }
767 CX("}");
768 fossil_free(zMimetype);
769 fossil_free(zBody);
770 return 1;
771 }else if( !wiki_fetch_by_name(zPageName, 0, 0, &pWiki) ){
772 ajax_route_error(404, "Wiki page could not be loaded: %s",
@@ -774,18 +781,21 @@
781 wiki_page_type_name(pWiki->zWikiTitle),
782 zUuid,
783 pWiki->zMimetype ? pWiki->zMimetype : "text/x-fossil-wiki");
784 CX("\"parent\": ");
785 if(pWiki->nParent){
786 CX("%!j", pWiki->azParent[0]);
787 }else{
788 CX("null");
789 }
790 if(includeContent){
791 CX(", \"content\": %!j", pWiki->zWiki);
792 }
793 CX("}");
794 fossil_free(zUuid);
795 manifest_destroy(pWiki);
796 return 2;
797 }
798 }
799
800 /*
801 ** Ajax route handler for /wikiajax/save.
@@ -847,11 +857,11 @@
857 }
858
859 blob_init(&content, zContent ? zContent : "", -1);
860 db_begin_transaction();
861 wiki_cmd_commit(zPageName, parentRid, &content, zMimetype, 0);
862 rollback = wiki_ajax_emit_page_object(zPageName, 1) ? 0 : 1;
863 db_end_transaction(rollback);
864 }
865
866 /*
867 ** Ajax route handler for /wikiajax/fetch.
@@ -869,11 +879,11 @@
879
880 if( zPageName==0 || zPageName[0]==0 ){
881 ajax_route_error(400,"Missing page name.");
882 return;
883 }
884 wiki_ajax_emit_page_object(zPageName, 1);
885 }
886
887 /*
888 ** Ajax route handler for /wikiajax/diff.
889 **
@@ -951,34 +961,52 @@
961 }
962 }
963
964 /*
965 ** Ajax route handler for /wikiajax/list.
966 **
967 ** Optional parameters: verbose, includeContent (see below).
968 **
969 ** Responds with JSON. On error, an object in the form documented by
970 ** ajax_route_error().
971 **
972 ** On success, it emits an array of strings (page names) sorted
973 ** case-insensitively. If the "verbose" parameter is passed in then
974 ** the result list contains objects in the format documented for
975 ** wiki_ajax_emit_page_object(). The content of each object is elided
976 ** unless the "includeContent" parameter is passed on.
977 **
978 ** The result list always contains an entry
979 ** named "sandbox" which represents the sandbox pseudo-page.
980 */
981 static void wiki_ajax_route_list(void){
982 Stmt q = empty_Stmt;
983 int n = 0;
984 const int verbose = ajax_p_bool("verbose");
985 const int includeContent = ajax_p_bool("includeContent");
986
987 cgi_set_content_type("application/json");
988 db_begin_transaction();
989 db_prepare(&q, "SELECT"
990 " substr(tagname,6) AS name"
991 " FROM tag WHERE tagname GLOB 'wiki-*'"
992 " UNION SELECT 'sandbox' AS name"
993 " ORDER BY name COLLATE NOCASE");
994 CX("[");
995 while( SQLITE_ROW==db_step(&q) ){
996 char const * zName = db_column_text(&q,0);
997 if(n++){
998 CX(",");
999 }
1000 if(verbose==0){
1001 CX("%!j", zName);
1002 }else{
1003 wiki_ajax_emit_page_object(zName, includeContent);
1004 }
1005 }
1006 db_finalize(&q);
1007 db_end_transaction(0);
1008 CX("]");
1009 }
1010
1011
1012 /*
@@ -1090,21 +1118,21 @@
1118
1119 /******* Page list *******/
1120 {
1121 CX("<div id='wikiedit-tab-pages' "
1122 "data-tab-parent='wikiedit-tabs' "
1123 "data-tab-label='Wiki Page List'"
1124 ">");
1125 CX("<div>Loading wiki pages list...</div>");
1126 CX("</div>"/*#wikiedit-tab-pages*/);
1127 }
1128
1129 /******* Content tab *******/
1130 {
1131 CX("<div id='wikiedit-tab-content' "
1132 "data-tab-parent='wikiedit-tabs' "
1133 "data-tab-label='Wiki Editor'"
1134 ">");
1135 CX("<div class='flex-container flex-row child-gap-small'>");
1136 mimetype_option_menu(0);
1137 CX("<button class='wikiedit-content-reload' "
1138 "title='Reload the file from the server, discarding "
@@ -1121,11 +1149,11 @@
1149 "200%", 200, NULL);
1150 CX("</div>");
1151 CX("<div class='flex-container flex-column stretch'>");
1152 CX("<textarea name='content' id='wikiedit-content-editor' "
1153 "class='wikiedit' "
1154 "rows='25' cols='80'>");
1155 CX("</textarea>");
1156 CX("</div>"/*textarea wrapper*/);
1157 CX("</div>"/*#tab-file-content*/);
1158 }
1159 /****** Preview tab ******/
@@ -1191,15 +1219,15 @@
1219 }
1220
1221 {
1222 CX("<div id='wikiedit-tab-save' "
1223 "data-tab-parent='wikiedit-tabs' "
1224 "data-tab-label='Save, etc.'"
1225 ">");
1226 CX("<button class='wikiedit-save'>Save</button>");
1227 CX("<hr>");
1228 CX("Wiki formatting rules:");
1229 CX("<ul>");
1230 CX("<li><a href='%R/wiki_rules'>Fossil wiki format</a></li>");
1231 CX("<li><a href='%R/md_rules'>Markdown format</a></li>");
1232 CX("</ul>");
1233 CX("<hr>Attachments:");
1234

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button