Fossil SCM

Enable interactive adjustment of the set of wiki pages that are listed on the [/wcontent] page. Corresponding submenu controls can be configured by the administrator to fulfill local needs and constraints.

george 2021-04-21 20:53 trunk
Commit 797134331481cb9c0e89f72511e4355b05ec03ef37cccc3f2d6cca11b248795c
+40
--- src/db.c
+++ src/db.c
@@ -4262,10 +4262,50 @@
42624262
** A shell command used to launch your preferred
42634263
** web browser when given a URL as an argument.
42644264
** Defaults to "start" on windows, "open" on Mac,
42654265
** and "firefox" on Unix.
42664266
*/
4267
+/*
4268
+** SETTING: wiki-classes width=40 block-text
4269
+** Defines classes for wiki pages that are recognized by the list
4270
+** of available wiki pages (displayed by /wcontent web page).
4271
+**
4272
+** Each line defines a single rule for a class using a triplet:
4273
+** Visibility Label Pattern
4274
+**
4275
+** Visibility is one of the following letters:
4276
+**
4277
+** s show on load, may be toggled afterwards
4278
+** h hide on load, may be toggled afterwards
4279
+** d hide permanently, checkbox disabled
4280
+** x exclude completely, no control in the submenu
4281
+**
4282
+** Label is a spaces-free name of the class shown in the submenu.
4283
+** This very same string is used as a value for HTML's class=""
4284
+** atributes on the corresponding rows (thus beware of mangling).
4285
+**
4286
+** Pattern is a GLOB pattern against which wiki page names are
4287
+** matched to distinguish a class. Case-sensitive pattern matching
4288
+** is performed if the Visibility is specified by a lowercase letter,
4289
+** otherwise pattern matching is case-insensitive (in which case
4290
+** the syntax of SQLite's LIKE operator applies).
4291
+** Pattern that consists of just a single * is a special case for
4292
+** catching all wiki pages that do not fall into any other class
4293
+** (this fallback does not depend on the case of the Visibility letter).
4294
+**
4295
+** If several consequtive lines share the same Label then this defines
4296
+** a single class that spans accross several patterns. In that case
4297
+** all Visibilities must also be equal (modulus upper/lower cases).
4298
+**
4299
+** Patterns are matched in the order of their appearance in the list.
4300
+** If a repository manages thousands of wiki pages and the /wcontent
4301
+** page is requested very frequently then server's CPU load may become
4302
+** a concern. In that case an administrator is advised to rearrange
4303
+** classes in the list in such a way that more patterns are matched
4304
+** earlier. In all cases it is advised to keep a special "catch-all"
4305
+** class in the bottom of the list.
4306
+*/
42674307
42684308
/*
42694309
** Look up a control setting by its name. Return a pointer to the Setting
42704310
** object, or NULL if there is no such setting.
42714311
**
42724312
42734313
ADDED src/fossil.page.wcontent.js
--- src/db.c
+++ src/db.c
@@ -4262,10 +4262,50 @@
4262 ** A shell command used to launch your preferred
4263 ** web browser when given a URL as an argument.
4264 ** Defaults to "start" on windows, "open" on Mac,
4265 ** and "firefox" on Unix.
4266 */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4267
4268 /*
4269 ** Look up a control setting by its name. Return a pointer to the Setting
4270 ** object, or NULL if there is no such setting.
4271 **
4272
4273 DDED src/fossil.page.wcontent.js
--- src/db.c
+++ src/db.c
@@ -4262,10 +4262,50 @@
4262 ** A shell command used to launch your preferred
4263 ** web browser when given a URL as an argument.
4264 ** Defaults to "start" on windows, "open" on Mac,
4265 ** and "firefox" on Unix.
4266 */
4267 /*
4268 ** SETTING: wiki-classes width=40 block-text
4269 ** Defines classes for wiki pages that are recognized by the list
4270 ** of available wiki pages (displayed by /wcontent web page).
4271 **
4272 ** Each line defines a single rule for a class using a triplet:
4273 ** Visibility Label Pattern
4274 **
4275 ** Visibility is one of the following letters:
4276 **
4277 ** s show on load, may be toggled afterwards
4278 ** h hide on load, may be toggled afterwards
4279 ** d hide permanently, checkbox disabled
4280 ** x exclude completely, no control in the submenu
4281 **
4282 ** Label is a spaces-free name of the class shown in the submenu.
4283 ** This very same string is used as a value for HTML's class=""
4284 ** atributes on the corresponding rows (thus beware of mangling).
4285 **
4286 ** Pattern is a GLOB pattern against which wiki page names are
4287 ** matched to distinguish a class. Case-sensitive pattern matching
4288 ** is performed if the Visibility is specified by a lowercase letter,
4289 ** otherwise pattern matching is case-insensitive (in which case
4290 ** the syntax of SQLite's LIKE operator applies).
4291 ** Pattern that consists of just a single * is a special case for
4292 ** catching all wiki pages that do not fall into any other class
4293 ** (this fallback does not depend on the case of the Visibility letter).
4294 **
4295 ** If several consequtive lines share the same Label then this defines
4296 ** a single class that spans accross several patterns. In that case
4297 ** all Visibilities must also be equal (modulus upper/lower cases).
4298 **
4299 ** Patterns are matched in the order of their appearance in the list.
4300 ** If a repository manages thousands of wiki pages and the /wcontent
4301 ** page is requested very frequently then server's CPU load may become
4302 ** a concern. In that case an administrator is advised to rearrange
4303 ** classes in the list in such a way that more patterns are matched
4304 ** earlier. In all cases it is advised to keep a special "catch-all"
4305 ** class in the bottom of the list.
4306 */
4307
4308 /*
4309 ** Look up a control setting by its name. Return a pointer to the Setting
4310 ** object, or NULL if there is no such setting.
4311 **
4312
4313 DDED src/fossil.page.wcontent.js
--- a/src/fossil.page.wcontent.js
+++ b/src/fossil.page.wcontent.js
@@ -0,0 +1,49 @@
1
+/* This script implements interactivity of checkboxes that
2
+ * toggle visibilitiy of user-defined classes of wikipage.
3
+ *
4
+ * For the sake of compatibility with ascetic browsers the code tries
5
+ * to avoid modern API and ECMAScript constructs. This makes it less
6
+ * readable and may be reconsidered in the future.
7
+*/
8
+window.addEventListener( 'load', function() {
9
+
10
+var tbody = document.querySelector(
11
+ "body.wiki div.content table.sortable > tbody");
12
+var prc = document.getElementById("page-reload-canary");
13
+if( !tbody || !prc ) return;
14
+
15
+var reloading = prc.checked;
16
+// console.log("Reloading:",reloading);
17
+
18
+var onChange = function(event){
19
+ var display = event.target.checked ? "" : "none";
20
+ var rows = event.target.matchingRows;
21
+ for(var i=0; i<rows.length; i++)
22
+ rows[i].style.display = display;
23
+}
24
+var checkboxes = [];
25
+document.querySelectorAll(
26
+ "body.wiki .submenu > label.submenuckbox > input")
27
+ .forEach(function(cbx){ checkboxes.push(cbx); });
28
+
29
+for(var j=0; j<checkboxes.length; j++){
30
+ var cbx = checkboxes[j];
31
+ var ctrl = cbx.getAttribute("data-ctrl")continue;
32
+ var ctrl = attr.toString();
33
+ var cname = cbx.parentElement.innerText.toString();
34
+ var hidden = ( ctrl == 'h' || ctrl == 'd' );
35
+ if( reloading )
36
+ hidden = !cbx.checked;
37
+ else
38
+ cbx.checked = !hidden;
39
+ cbx.matchingRows = [];
40
+ tbody.querySelectorAll("tr."+cname).forEach(function (tr){
41
+ tr.style.display = ( hidden ? "none" : "" );
42
+ cbx.matchingRows.push(tr);
43
+ });
44
+ cbx.addEventListener("change", onChange );
45
+ // console.log( cbx.matchingRows.length, cname, ctrl );
46
+}
47
+
48
+prc.checked = true;
49
+}); // window.ad
--- a/src/fossil.page.wcontent.js
+++ b/src/fossil.page.wcontent.js
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/src/fossil.page.wcontent.js
+++ b/src/fossil.page.wcontent.js
@@ -0,0 +1,49 @@
1 /* This script implements interactivity of checkboxes that
2 * toggle visibilitiy of user-defined classes of wikipage.
3 *
4 * For the sake of compatibility with ascetic browsers the code tries
5 * to avoid modern API and ECMAScript constructs. This makes it less
6 * readable and may be reconsidered in the future.
7 */
8 window.addEventListener( 'load', function() {
9
10 var tbody = document.querySelector(
11 "body.wiki div.content table.sortable > tbody");
12 var prc = document.getElementById("page-reload-canary");
13 if( !tbody || !prc ) return;
14
15 var reloading = prc.checked;
16 // console.log("Reloading:",reloading);
17
18 var onChange = function(event){
19 var display = event.target.checked ? "" : "none";
20 var rows = event.target.matchingRows;
21 for(var i=0; i<rows.length; i++)
22 rows[i].style.display = display;
23 }
24 var checkboxes = [];
25 document.querySelectorAll(
26 "body.wiki .submenu > label.submenuckbox > input")
27 .forEach(function(cbx){ checkboxes.push(cbx); });
28
29 for(var j=0; j<checkboxes.length; j++){
30 var cbx = checkboxes[j];
31 var ctrl = cbx.getAttribute("data-ctrl")continue;
32 var ctrl = attr.toString();
33 var cname = cbx.parentElement.innerText.toString();
34 var hidden = ( ctrl == 'h' || ctrl == 'd' );
35 if( reloading )
36 hidden = !cbx.checked;
37 else
38 cbx.checked = !hidden;
39 cbx.matchingRows = [];
40 tbody.querySelectorAll("tr."+cname).forEach(function (tr){
41 tr.style.display = ( hidden ? "none" : "" );
42 cbx.matchingRows.push(tr);
43 });
44 cbx.addEventListener("change", onChange );
45 // console.log( cbx.matchingRows.length, cname, ctrl );
46 }
47
48 prc.checked = true;
49 }); // window.ad
--- src/main.mk
+++ src/main.mk
@@ -229,10 +229,11 @@
229229
$(SRCDIR)/fossil.info-diff.js \
230230
$(SRCDIR)/fossil.numbered-lines.js \
231231
$(SRCDIR)/fossil.page.fileedit.js \
232232
$(SRCDIR)/fossil.page.forumpost.js \
233233
$(SRCDIR)/fossil.page.pikchrshow.js \
234
+ $(SRCDIR)/fossil.page.wcontent.js \
234235
$(SRCDIR)/fossil.page.whistory.js \
235236
$(SRCDIR)/fossil.page.wikiedit.js \
236237
$(SRCDIR)/fossil.pikchr.js \
237238
$(SRCDIR)/fossil.popupwidget.js \
238239
$(SRCDIR)/fossil.storage.js \
239240
--- src/main.mk
+++ src/main.mk
@@ -229,10 +229,11 @@
229 $(SRCDIR)/fossil.info-diff.js \
230 $(SRCDIR)/fossil.numbered-lines.js \
231 $(SRCDIR)/fossil.page.fileedit.js \
232 $(SRCDIR)/fossil.page.forumpost.js \
233 $(SRCDIR)/fossil.page.pikchrshow.js \
 
234 $(SRCDIR)/fossil.page.whistory.js \
235 $(SRCDIR)/fossil.page.wikiedit.js \
236 $(SRCDIR)/fossil.pikchr.js \
237 $(SRCDIR)/fossil.popupwidget.js \
238 $(SRCDIR)/fossil.storage.js \
239
--- src/main.mk
+++ src/main.mk
@@ -229,10 +229,11 @@
229 $(SRCDIR)/fossil.info-diff.js \
230 $(SRCDIR)/fossil.numbered-lines.js \
231 $(SRCDIR)/fossil.page.fileedit.js \
232 $(SRCDIR)/fossil.page.forumpost.js \
233 $(SRCDIR)/fossil.page.pikchrshow.js \
234 $(SRCDIR)/fossil.page.wcontent.js \
235 $(SRCDIR)/fossil.page.whistory.js \
236 $(SRCDIR)/fossil.page.wikiedit.js \
237 $(SRCDIR)/fossil.pikchr.js \
238 $(SRCDIR)/fossil.popupwidget.js \
239 $(SRCDIR)/fossil.storage.js \
240
+167 -4
--- src/wiki.c
+++ src/wiki.c
@@ -21,10 +21,28 @@
2121
#include "config.h"
2222
#include <assert.h>
2323
#include <ctype.h>
2424
#include "wiki.h"
2525
26
+#if INTERFACE
27
+
28
+/*
29
+** WikiClass struct holds information for matching a wiki page name.
30
+** It is constructed by load_wiki_classes() function and represents a
31
+** single well-formed line obtained from the 'wiki-classes' setting.
32
+*/
33
+struct WikiClass {
34
+ const char * zPattern; /* pattern to match against */
35
+ int isCaseIns; /* syntax flag: 0 for GLOB, 1 for LIKE */
36
+ short isAltPat; /* indicates an additional pattern for a class */
37
+
38
+ const char * zVisblty; /* visibility flag, one of: "s" "h" "d" "x" */
39
+ const char * zLabel; /* user-visible class name, same in HTML attrs */
40
+ const char * zParam; /* checkbox attr, like in <input name="..." /> */
41
+};
42
+#endif
43
+
2644
/*
2745
** Return true if the input string is a well-formed wiki page name.
2846
**
2947
** Well-formed wiki page names do not begin or end with whitespace,
3048
** and do not contain tabs or other control characters and do not
@@ -439,10 +457,126 @@
439457
case WIKITYPE_TAG: return "tag";
440458
case WIKITYPE_NORMAL:
441459
default: return "normal";
442460
}
443461
}
462
+
463
+/*
464
+** load_wiki_classes() loads and parses the value of the 'wiki-classes'
465
+** setting. Retrurns either NULL or a dynamically allocated array of
466
+** WikiClasses (thus fossil_free() for the returned value is advised).
467
+*/
468
+WikiClass* load_wiki_classes(
469
+ int *nWC, /* number of well-formed elements in the returned array */
470
+ Blob *wcs /* holds a buffer for strings in the returned WikiClass'es */
471
+){
472
+
473
+ static const char *zPN[] = { /* no malloc() for the common cases */
474
+ "wc0", "wc1", "wc2", "wc3", "wc4", "wc5", "wc6", "wc7",
475
+ "wc8", "wc9","wc10","wc11","wc12","wc13","wc14","wc15",
476
+ "wc16","wc17","wc18","wc19","wc20","wc21","wc22","wc23"
477
+ };
478
+ WikiClass* aWC = 0;
479
+ int n = 0;
480
+ char *zWCS = db_get("wiki-classes",0);
481
+ if( zWCS && strlen(zWCS) >= 5 ){
482
+ Blob line = empty_blob;
483
+ int nAlloc = 0;
484
+ blob_set_dynamic(wcs,zWCS);
485
+ while( blob_line(wcs,&line) > 0 ){
486
+ Blob vis = empty_blob;
487
+ Blob lbl = empty_blob;
488
+ Blob pat = empty_blob;
489
+ WikiClass * wc;
490
+ const char *z;
491
+ int cins = 0;
492
+ char v;
493
+ if( blob_token(&line,&vis) != 1 ){
494
+ continue;
495
+ }
496
+ v = vis.aData[0];
497
+ switch( v ){
498
+ case 'D':
499
+ case 'H':
500
+ case 'S':
501
+ case 'X':
502
+ cins = 1;
503
+ vis.aData[0] = (char)tolower(v);
504
+ case 'd':
505
+ case 'h':
506
+ case 's':
507
+ case 'x':
508
+ break;
509
+ default:
510
+ v = 0;
511
+ }
512
+ if( v == 0 ) continue;
513
+ if( blob_token(&line,&lbl) <= 0 ){
514
+ continue;
515
+ }
516
+ blob_tail(&line,&pat);
517
+ /* blob_to_lf_only(&pre); <-- this is redundant, isn't it ? */
518
+ blob_trim(&pat);
519
+ z = blob_terminate(&pat);
520
+ while(fossil_isspace(z[0])) z++;
521
+ if( z[0] == 0 ){
522
+ continue;
523
+ }
524
+ if( n >= nAlloc ){
525
+ nAlloc += count(zPN);
526
+ aWC = (WikiClass*)( aWC ?
527
+ fossil_realloc(aWC,sizeof(WikiClass)*nAlloc) :
528
+ fossil_malloc_zero(sizeof(WikiClass)*nAlloc) );
529
+ }
530
+ wc = aWC + n;
531
+ wc->zPattern = z;
532
+ wc->isCaseIns = cins;
533
+ wc->zVisblty = blob_terminate(&vis);
534
+ wc->zLabel = blob_terminate(&lbl);
535
+ wc->zParam = ( n < count(zPN) ? zPN[n] : mprintf("wc%d",n) );
536
+ if( n > 0 && strcmp( wc->zLabel, wc[-1].zLabel ) == 0 ){
537
+ if( wc->zVisblty[0] != wc[-1].zVisblty[0] ||
538
+ strcmp( wc->zPattern, wc[-1].zPattern ) == 0 ){
539
+ continue;
540
+ }
541
+ wc->isAltPat = 1;
542
+ }else{
543
+ wc->isAltPat = 0;
544
+ }
545
+ n++;
546
+ }
547
+ }
548
+ if( nWC ) *nWC = n;
549
+ return aWC;
550
+}
551
+/*
552
+** Find and return a WikiClass that matches a name of a wiki page.
553
+** Returns 0 if "fallback" pattern was not provisioned.
554
+*/
555
+const WikiClass* resolve_wiki_class(
556
+ const char *zName, /* name of a wiki page that should be classified */
557
+ const WikiClass *aWC, /* pointer to the array of WikiClass'es */
558
+ int nWC /* number of elements in the above array */
559
+){
560
+ const WikiClass* fallback = 0;
561
+ if( aWC && zName ){
562
+ int i;
563
+ for( i=0; i<nWC; i++ ){
564
+ const WikiClass* wc = aWC + i;
565
+ if( wc->zPattern[0] == '*' && wc->zPattern[1] == 0 ){
566
+ if( !fallback ) fallback = wc;
567
+ }else if( wc->isCaseIns ){
568
+ if( sqlite3_strlike( wc->zPattern, zName, 0 ) == 0 )
569
+ return wc;
570
+ }else{
571
+ if( sqlite3_strglob( wc->zPattern, zName ) == 0 )
572
+ return wc;
573
+ }
574
+ }
575
+ }
576
+ return fallback;
577
+}
444578
445579
/*
446580
** Add an appropriate style_header() for either the /wiki or /wikiedit page
447581
** for zPageName. zExtra is an empty string for /wiki but has the text
448582
** "Edit: " for /wikiedit.
@@ -1773,10 +1907,13 @@
17731907
void wcontent_page(void){
17741908
Stmt q;
17751909
double rNow;
17761910
int showAll = P("all")!=0;
17771911
int showRid = P("showid")!=0;
1912
+ Blob wcs = empty_blob;
1913
+ int i, nWC = 0;
1914
+ WikiClass* aWC;
17781915
17791916
login_check_credentials();
17801917
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
17811918
style_set_current_feature("wiki");
17821919
style_header("Available Wiki Pages");
@@ -1783,12 +1920,20 @@
17831920
if( showAll ){
17841921
style_submenu_element("Active", "%R/wcontent");
17851922
}else{
17861923
style_submenu_element("All", "%R/wcontent?all=1");
17871924
}
1925
+ aWC = load_wiki_classes(&nWC,&wcs);
1926
+ for( i=0; i<nWC; i++ ){
1927
+ const WikiClass * c = aWC + i;
1928
+ if( c->isAltPat || c->zVisblty[0] == 'x' ) continue;
1929
+ style_submenu_checkbox( c->zParam, c->zLabel,
1930
+ c->zVisblty[0]=='d' ? STYLE_DISABLED : STYLE_NORMAL, c->zVisblty);
1931
+ }
17881932
wiki_standard_submenu(W_ALL_BUT(W_LIST));
17891933
db_prepare(&q, listAllWikiPages/*works-like:""*/);
1934
+ @ <input hidden="hidden" id="page-reload-canary" type="checkbox"/>
17901935
@ <div class="brlist">
17911936
@ <table class='sortable' data-column-types='tKN' data-init-sort='1'>
17921937
@ <thead><tr>
17931938
@ <th>Name</th>
17941939
@ <th>Last Change</th>
@@ -1805,22 +1950,37 @@
18051950
double rWmtime = db_column_double(&q, 3);
18061951
sqlite3_int64 iMtime = (sqlite3_int64)(rWmtime*86400.0);
18071952
char *zAge;
18081953
int wcnt = db_column_int(&q, 4);
18091954
char *zWDisplayName;
1955
+ const WikiClass * wc = resolve_wiki_class(zWName,aWC,nWC);
1956
+ if( wc && (wc->zVisblty[0] == 'x' || wc->zVisblty[0] == 'd') ){
1957
+ continue;
1958
+ }
18101959
1811
- if( sqlite3_strglob("checkin/*", zWName)==0 ){
1960
+ if( sqlite3_strglob("checkin/*", zWName)==0 ){ /* --?--> strncmp() */
18121961
zWDisplayName = mprintf("%.25s...", zWName);
18131962
}else{
1814
- zWDisplayName = mprintf("%s", zWName);
1963
+ zWDisplayName = mprintf("%s", zWName); /* --?--> fossil_strdup() */
18151964
}
1965
+
18161966
if( wrid==0 ){
18171967
if( !showAll ) continue;
1818
- @ <tr><td data-sortkey="%h(zSort)">\
1968
+ if(wc){
1969
+ @ <tr class="%h(wc->zLabel)">
1970
+ }else{
1971
+ @ <tr>
1972
+ }
1973
+ @ <td data-sortkey="%h(zSort)">\
18191974
@ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td>
18201975
}else{
1821
- @ <tr><td data-sortkey="%h(zSort)">\
1976
+ if(wc){
1977
+ @ <tr class="%h(wc->zLabel)">
1978
+ }else{
1979
+ @ <tr>
1980
+ }
1981
+ @ <td data-sortkey="%h(zSort)">\
18221982
@ %z(href("%R/wiki?name=%T&p",zWName))%h(zWDisplayName)</a></td>
18231983
}
18241984
zAge = human_readable_age(rNow - rWmtime);
18251985
@ <td data-sortkey="%016llx(iMtime)">%s(zAge)</td>
18261986
fossil_free(zAge);
@@ -1831,12 +1991,15 @@
18311991
@ </tr>
18321992
fossil_free(zWDisplayName);
18331993
}
18341994
@ </tbody></table></div>
18351995
db_finalize(&q);
1996
+ builtin_request_js("fossil.page.wcontent.js");
18361997
style_table_sorter();
18371998
style_finish_page();
1999
+ if(aWC) fossil_free(aWC);
2000
+ blob_reset(&wcs); /* FIXME: it's an analog of fossil_free(), isn't it? */
18382001
}
18392002
18402003
/*
18412004
** WEBPAGE: wfind
18422005
**
18432006
--- src/wiki.c
+++ src/wiki.c
@@ -21,10 +21,28 @@
21 #include "config.h"
22 #include <assert.h>
23 #include <ctype.h>
24 #include "wiki.h"
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26 /*
27 ** Return true if the input string is a well-formed wiki page name.
28 **
29 ** Well-formed wiki page names do not begin or end with whitespace,
30 ** and do not contain tabs or other control characters and do not
@@ -439,10 +457,126 @@
439 case WIKITYPE_TAG: return "tag";
440 case WIKITYPE_NORMAL:
441 default: return "normal";
442 }
443 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
445 /*
446 ** Add an appropriate style_header() for either the /wiki or /wikiedit page
447 ** for zPageName. zExtra is an empty string for /wiki but has the text
448 ** "Edit: " for /wikiedit.
@@ -1773,10 +1907,13 @@
1773 void wcontent_page(void){
1774 Stmt q;
1775 double rNow;
1776 int showAll = P("all")!=0;
1777 int showRid = P("showid")!=0;
 
 
 
1778
1779 login_check_credentials();
1780 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
1781 style_set_current_feature("wiki");
1782 style_header("Available Wiki Pages");
@@ -1783,12 +1920,20 @@
1783 if( showAll ){
1784 style_submenu_element("Active", "%R/wcontent");
1785 }else{
1786 style_submenu_element("All", "%R/wcontent?all=1");
1787 }
 
 
 
 
 
 
 
1788 wiki_standard_submenu(W_ALL_BUT(W_LIST));
1789 db_prepare(&q, listAllWikiPages/*works-like:""*/);
 
1790 @ <div class="brlist">
1791 @ <table class='sortable' data-column-types='tKN' data-init-sort='1'>
1792 @ <thead><tr>
1793 @ <th>Name</th>
1794 @ <th>Last Change</th>
@@ -1805,22 +1950,37 @@
1805 double rWmtime = db_column_double(&q, 3);
1806 sqlite3_int64 iMtime = (sqlite3_int64)(rWmtime*86400.0);
1807 char *zAge;
1808 int wcnt = db_column_int(&q, 4);
1809 char *zWDisplayName;
 
 
 
 
1810
1811 if( sqlite3_strglob("checkin/*", zWName)==0 ){
1812 zWDisplayName = mprintf("%.25s...", zWName);
1813 }else{
1814 zWDisplayName = mprintf("%s", zWName);
1815 }
 
1816 if( wrid==0 ){
1817 if( !showAll ) continue;
1818 @ <tr><td data-sortkey="%h(zSort)">\
 
 
 
 
 
1819 @ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td>
1820 }else{
1821 @ <tr><td data-sortkey="%h(zSort)">\
 
 
 
 
 
1822 @ %z(href("%R/wiki?name=%T&p",zWName))%h(zWDisplayName)</a></td>
1823 }
1824 zAge = human_readable_age(rNow - rWmtime);
1825 @ <td data-sortkey="%016llx(iMtime)">%s(zAge)</td>
1826 fossil_free(zAge);
@@ -1831,12 +1991,15 @@
1831 @ </tr>
1832 fossil_free(zWDisplayName);
1833 }
1834 @ </tbody></table></div>
1835 db_finalize(&q);
 
1836 style_table_sorter();
1837 style_finish_page();
 
 
1838 }
1839
1840 /*
1841 ** WEBPAGE: wfind
1842 **
1843
--- src/wiki.c
+++ src/wiki.c
@@ -21,10 +21,28 @@
21 #include "config.h"
22 #include <assert.h>
23 #include <ctype.h>
24 #include "wiki.h"
25
26 #if INTERFACE
27
28 /*
29 ** WikiClass struct holds information for matching a wiki page name.
30 ** It is constructed by load_wiki_classes() function and represents a
31 ** single well-formed line obtained from the 'wiki-classes' setting.
32 */
33 struct WikiClass {
34 const char * zPattern; /* pattern to match against */
35 int isCaseIns; /* syntax flag: 0 for GLOB, 1 for LIKE */
36 short isAltPat; /* indicates an additional pattern for a class */
37
38 const char * zVisblty; /* visibility flag, one of: "s" "h" "d" "x" */
39 const char * zLabel; /* user-visible class name, same in HTML attrs */
40 const char * zParam; /* checkbox attr, like in <input name="..." /> */
41 };
42 #endif
43
44 /*
45 ** Return true if the input string is a well-formed wiki page name.
46 **
47 ** Well-formed wiki page names do not begin or end with whitespace,
48 ** and do not contain tabs or other control characters and do not
@@ -439,10 +457,126 @@
457 case WIKITYPE_TAG: return "tag";
458 case WIKITYPE_NORMAL:
459 default: return "normal";
460 }
461 }
462
463 /*
464 ** load_wiki_classes() loads and parses the value of the 'wiki-classes'
465 ** setting. Retrurns either NULL or a dynamically allocated array of
466 ** WikiClasses (thus fossil_free() for the returned value is advised).
467 */
468 WikiClass* load_wiki_classes(
469 int *nWC, /* number of well-formed elements in the returned array */
470 Blob *wcs /* holds a buffer for strings in the returned WikiClass'es */
471 ){
472
473 static const char *zPN[] = { /* no malloc() for the common cases */
474 "wc0", "wc1", "wc2", "wc3", "wc4", "wc5", "wc6", "wc7",
475 "wc8", "wc9","wc10","wc11","wc12","wc13","wc14","wc15",
476 "wc16","wc17","wc18","wc19","wc20","wc21","wc22","wc23"
477 };
478 WikiClass* aWC = 0;
479 int n = 0;
480 char *zWCS = db_get("wiki-classes",0);
481 if( zWCS && strlen(zWCS) >= 5 ){
482 Blob line = empty_blob;
483 int nAlloc = 0;
484 blob_set_dynamic(wcs,zWCS);
485 while( blob_line(wcs,&line) > 0 ){
486 Blob vis = empty_blob;
487 Blob lbl = empty_blob;
488 Blob pat = empty_blob;
489 WikiClass * wc;
490 const char *z;
491 int cins = 0;
492 char v;
493 if( blob_token(&line,&vis) != 1 ){
494 continue;
495 }
496 v = vis.aData[0];
497 switch( v ){
498 case 'D':
499 case 'H':
500 case 'S':
501 case 'X':
502 cins = 1;
503 vis.aData[0] = (char)tolower(v);
504 case 'd':
505 case 'h':
506 case 's':
507 case 'x':
508 break;
509 default:
510 v = 0;
511 }
512 if( v == 0 ) continue;
513 if( blob_token(&line,&lbl) <= 0 ){
514 continue;
515 }
516 blob_tail(&line,&pat);
517 /* blob_to_lf_only(&pre); <-- this is redundant, isn't it ? */
518 blob_trim(&pat);
519 z = blob_terminate(&pat);
520 while(fossil_isspace(z[0])) z++;
521 if( z[0] == 0 ){
522 continue;
523 }
524 if( n >= nAlloc ){
525 nAlloc += count(zPN);
526 aWC = (WikiClass*)( aWC ?
527 fossil_realloc(aWC,sizeof(WikiClass)*nAlloc) :
528 fossil_malloc_zero(sizeof(WikiClass)*nAlloc) );
529 }
530 wc = aWC + n;
531 wc->zPattern = z;
532 wc->isCaseIns = cins;
533 wc->zVisblty = blob_terminate(&vis);
534 wc->zLabel = blob_terminate(&lbl);
535 wc->zParam = ( n < count(zPN) ? zPN[n] : mprintf("wc%d",n) );
536 if( n > 0 && strcmp( wc->zLabel, wc[-1].zLabel ) == 0 ){
537 if( wc->zVisblty[0] != wc[-1].zVisblty[0] ||
538 strcmp( wc->zPattern, wc[-1].zPattern ) == 0 ){
539 continue;
540 }
541 wc->isAltPat = 1;
542 }else{
543 wc->isAltPat = 0;
544 }
545 n++;
546 }
547 }
548 if( nWC ) *nWC = n;
549 return aWC;
550 }
551 /*
552 ** Find and return a WikiClass that matches a name of a wiki page.
553 ** Returns 0 if "fallback" pattern was not provisioned.
554 */
555 const WikiClass* resolve_wiki_class(
556 const char *zName, /* name of a wiki page that should be classified */
557 const WikiClass *aWC, /* pointer to the array of WikiClass'es */
558 int nWC /* number of elements in the above array */
559 ){
560 const WikiClass* fallback = 0;
561 if( aWC && zName ){
562 int i;
563 for( i=0; i<nWC; i++ ){
564 const WikiClass* wc = aWC + i;
565 if( wc->zPattern[0] == '*' && wc->zPattern[1] == 0 ){
566 if( !fallback ) fallback = wc;
567 }else if( wc->isCaseIns ){
568 if( sqlite3_strlike( wc->zPattern, zName, 0 ) == 0 )
569 return wc;
570 }else{
571 if( sqlite3_strglob( wc->zPattern, zName ) == 0 )
572 return wc;
573 }
574 }
575 }
576 return fallback;
577 }
578
579 /*
580 ** Add an appropriate style_header() for either the /wiki or /wikiedit page
581 ** for zPageName. zExtra is an empty string for /wiki but has the text
582 ** "Edit: " for /wikiedit.
@@ -1773,10 +1907,13 @@
1907 void wcontent_page(void){
1908 Stmt q;
1909 double rNow;
1910 int showAll = P("all")!=0;
1911 int showRid = P("showid")!=0;
1912 Blob wcs = empty_blob;
1913 int i, nWC = 0;
1914 WikiClass* aWC;
1915
1916 login_check_credentials();
1917 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
1918 style_set_current_feature("wiki");
1919 style_header("Available Wiki Pages");
@@ -1783,12 +1920,20 @@
1920 if( showAll ){
1921 style_submenu_element("Active", "%R/wcontent");
1922 }else{
1923 style_submenu_element("All", "%R/wcontent?all=1");
1924 }
1925 aWC = load_wiki_classes(&nWC,&wcs);
1926 for( i=0; i<nWC; i++ ){
1927 const WikiClass * c = aWC + i;
1928 if( c->isAltPat || c->zVisblty[0] == 'x' ) continue;
1929 style_submenu_checkbox( c->zParam, c->zLabel,
1930 c->zVisblty[0]=='d' ? STYLE_DISABLED : STYLE_NORMAL, c->zVisblty);
1931 }
1932 wiki_standard_submenu(W_ALL_BUT(W_LIST));
1933 db_prepare(&q, listAllWikiPages/*works-like:""*/);
1934 @ <input hidden="hidden" id="page-reload-canary" type="checkbox"/>
1935 @ <div class="brlist">
1936 @ <table class='sortable' data-column-types='tKN' data-init-sort='1'>
1937 @ <thead><tr>
1938 @ <th>Name</th>
1939 @ <th>Last Change</th>
@@ -1805,22 +1950,37 @@
1950 double rWmtime = db_column_double(&q, 3);
1951 sqlite3_int64 iMtime = (sqlite3_int64)(rWmtime*86400.0);
1952 char *zAge;
1953 int wcnt = db_column_int(&q, 4);
1954 char *zWDisplayName;
1955 const WikiClass * wc = resolve_wiki_class(zWName,aWC,nWC);
1956 if( wc && (wc->zVisblty[0] == 'x' || wc->zVisblty[0] == 'd') ){
1957 continue;
1958 }
1959
1960 if( sqlite3_strglob("checkin/*", zWName)==0 ){ /* --?--> strncmp() */
1961 zWDisplayName = mprintf("%.25s...", zWName);
1962 }else{
1963 zWDisplayName = mprintf("%s", zWName); /* --?--> fossil_strdup() */
1964 }
1965
1966 if( wrid==0 ){
1967 if( !showAll ) continue;
1968 if(wc){
1969 @ <tr class="%h(wc->zLabel)">
1970 }else{
1971 @ <tr>
1972 }
1973 @ <td data-sortkey="%h(zSort)">\
1974 @ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td>
1975 }else{
1976 if(wc){
1977 @ <tr class="%h(wc->zLabel)">
1978 }else{
1979 @ <tr>
1980 }
1981 @ <td data-sortkey="%h(zSort)">\
1982 @ %z(href("%R/wiki?name=%T&p",zWName))%h(zWDisplayName)</a></td>
1983 }
1984 zAge = human_readable_age(rNow - rWmtime);
1985 @ <td data-sortkey="%016llx(iMtime)">%s(zAge)</td>
1986 fossil_free(zAge);
@@ -1831,12 +1991,15 @@
1991 @ </tr>
1992 fossil_free(zWDisplayName);
1993 }
1994 @ </tbody></table></div>
1995 db_finalize(&q);
1996 builtin_request_js("fossil.page.wcontent.js");
1997 style_table_sorter();
1998 style_finish_page();
1999 if(aWC) fossil_free(aWC);
2000 blob_reset(&wcs); /* FIXME: it's an analog of fossil_free(), isn't it? */
2001 }
2002
2003 /*
2004 ** WEBPAGE: wfind
2005 **
2006
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,16 @@
11
<title>Change Log</title>
22
3
+<a name='v2_16'></a>
4
+<h2>Changes for Version 2.16 (pending)</h2>
5
+ * [/wcontent|Listing] of the available wiki pages
6
+ [/timeline?r=wcontent-subsets|gained] the ability to
7
+ [./javascript.md#wcontent|interactively] adjust a subset of
8
+ wiki pages that are shown; this is based on the
9
+ classification of wiki page names according to the
10
+ [/help?cmd=wiki-classes|configurable] glob patterns.
11
+
312
<a name='v2_15'></a>
413
<h2>Changes for Version 2.15 (2021-03-26) and Patch 2.15.1 on (2021-04-07)</h2>
514
* <b>Patch 2.15.1:</b> Fix a data exfiltration bug in the server. <b>Upgrading to
615
the patch is recommended.</b><p>
716
* The [./defcsp.md|default CSP] has been relaxed slightly to allow
817
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,16 @@
1 <title>Change Log</title>
2
 
 
 
 
 
 
 
 
 
3 <a name='v2_15'></a>
4 <h2>Changes for Version 2.15 (2021-03-26) and Patch 2.15.1 on (2021-04-07)</h2>
5 * <b>Patch 2.15.1:</b> Fix a data exfiltration bug in the server. <b>Upgrading to
6 the patch is recommended.</b><p>
7 * The [./defcsp.md|default CSP] has been relaxed slightly to allow
8
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,16 @@
1 <title>Change Log</title>
2
3 <a name='v2_16'></a>
4 <h2>Changes for Version 2.16 (pending)</h2>
5 * [/wcontent|Listing] of the available wiki pages
6 [/timeline?r=wcontent-subsets|gained] the ability to
7 [./javascript.md#wcontent|interactively] adjust a subset of
8 wiki pages that are shown; this is based on the
9 classification of wiki page names according to the
10 [/help?cmd=wiki-classes|configurable] glob patterns.
11
12 <a name='v2_15'></a>
13 <h2>Changes for Version 2.15 (2021-03-26) and Patch 2.15.1 on (2021-04-07)</h2>
14 * <b>Patch 2.15.1:</b> Fix a data exfiltration bug in the server. <b>Upgrading to
15 the patch is recommended.</b><p>
16 * The [./defcsp.md|default CSP] has been relaxed slightly to allow
17
--- www/javascript.md
+++ www/javascript.md
@@ -570,10 +570,19 @@
570570
571571
…would pull the messages submitted since the last poll. Making the
572572
gateway bidirectional should be possible as well, as long as it properly
573573
uses SQLite transactions.
574574
575
+### <a id="wcontent"></a>Wiki content listing
576
+
577
+[Since](/timeline?r=wcontent-subsets) version 2.16 it is possible to
578
+add [configurable](/help?cmd=wiki-classes) checkbox controls to the
579
+submenu of [available wiki pages](/wcontent) for the interactive
580
+adjustment of a subset of wiki pages that are shown.
581
+Client-side script is used to toggle visibility of the corresponding
582
+rows according to the state of these checkboxes.
583
+
575584
----
576585
577586
## <a id="future"></a>Future Plans for JavaScript in Fossil
578587
579588
As of mid-2020, the informal provisional plan is to increase Fossil
580589
--- www/javascript.md
+++ www/javascript.md
@@ -570,10 +570,19 @@
570
571 …would pull the messages submitted since the last poll. Making the
572 gateway bidirectional should be possible as well, as long as it properly
573 uses SQLite transactions.
574
 
 
 
 
 
 
 
 
 
575 ----
576
577 ## <a id="future"></a>Future Plans for JavaScript in Fossil
578
579 As of mid-2020, the informal provisional plan is to increase Fossil
580
--- www/javascript.md
+++ www/javascript.md
@@ -570,10 +570,19 @@
570
571 …would pull the messages submitted since the last poll. Making the
572 gateway bidirectional should be possible as well, as long as it properly
573 uses SQLite transactions.
574
575 ### <a id="wcontent"></a>Wiki content listing
576
577 [Since](/timeline?r=wcontent-subsets) version 2.16 it is possible to
578 add [configurable](/help?cmd=wiki-classes) checkbox controls to the
579 submenu of [available wiki pages](/wcontent) for the interactive
580 adjustment of a subset of wiki pages that are shown.
581 Client-side script is used to toggle visibility of the corresponding
582 rows according to the state of these checkboxes.
583
584 ----
585
586 ## <a id="future"></a>Future Plans for JavaScript in Fossil
587
588 As of mid-2020, the informal provisional plan is to increase Fossil
589

Keyboard Shortcuts

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