Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | 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. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | wcontent-subsets |
| Files: | files | file ages | folders |
| SHA3-256: |
797134331481cb9c0e89f72511e4355b |
| User & Date: | george 2021-04-21 20:53:05.736 |
Context
|
2021-06-15
| ||
| 21:37 | Merge from trunk check-in: cea36e6c86 user: george tags: wcontent-subsets | |
|
2021-04-21
| ||
| 20:53 | 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. check-in: 7971343314 user: george tags: wcontent-subsets | |
| 16:48 | Remove extraneous and incomplete <span> tag from the /finfo output. check-in: d115594b2f user: drh tags: trunk | |
Changes
Changes to src/db.c.
| ︙ | ︙ | |||
4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 | /* ** SETTING: web-browser width=30 sensitive ** A shell command used to launch your preferred ** web browser when given a URL as an argument. ** Defaults to "start" on windows, "open" on Mac, ** and "firefox" on Unix. */ /* ** Look up a control setting by its name. Return a pointer to the Setting ** object, or NULL if there is no such setting. ** ** If allowPrefix is true, then the Setting returned is the first one for ** which zName is a prefix of the Setting name. | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 | /* ** SETTING: web-browser width=30 sensitive ** A shell command used to launch your preferred ** web browser when given a URL as an argument. ** Defaults to "start" on windows, "open" on Mac, ** and "firefox" on Unix. */ /* ** SETTING: wiki-classes width=40 block-text ** Defines classes for wiki pages that are recognized by the list ** of available wiki pages (displayed by /wcontent web page). ** ** Each line defines a single rule for a class using a triplet: ** Visibility Label Pattern ** ** Visibility is one of the following letters: ** ** s show on load, may be toggled afterwards ** h hide on load, may be toggled afterwards ** d hide permanently, checkbox disabled ** x exclude completely, no control in the submenu ** ** Label is a spaces-free name of the class shown in the submenu. ** This very same string is used as a value for HTML's class="" ** atributes on the corresponding rows (thus beware of mangling). ** ** Pattern is a GLOB pattern against which wiki page names are ** matched to distinguish a class. Case-sensitive pattern matching ** is performed if the Visibility is specified by a lowercase letter, ** otherwise pattern matching is case-insensitive (in which case ** the syntax of SQLite's LIKE operator applies). ** Pattern that consists of just a single * is a special case for ** catching all wiki pages that do not fall into any other class ** (this fallback does not depend on the case of the Visibility letter). ** ** If several consequtive lines share the same Label then this defines ** a single class that spans accross several patterns. In that case ** all Visibilities must also be equal (modulus upper/lower cases). ** ** Patterns are matched in the order of their appearance in the list. ** If a repository manages thousands of wiki pages and the /wcontent ** page is requested very frequently then server's CPU load may become ** a concern. In that case an administrator is advised to rearrange ** classes in the list in such a way that more patterns are matched ** earlier. In all cases it is advised to keep a special "catch-all" ** class in the bottom of the list. */ /* ** Look up a control setting by its name. Return a pointer to the Setting ** object, or NULL if there is no such setting. ** ** If allowPrefix is true, then the Setting returned is the first one for ** which zName is a prefix of the Setting name. |
| ︙ | ︙ |
Added src/fossil.page.wcontent.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 |
/* This script implements interactivity of checkboxes that
* toggle visibilitiy of user-defined classes of wikipage.
*
* For the sake of compatibility with ascetic browsers the code tries
* to avoid modern API and ECMAScript constructs. This makes it less
* readable and may be reconsidered in the future.
*/
window.addEventListener( 'load', function() {
var tbody = document.querySelector(
"body.wiki div.content table.sortable > tbody");
var prc = document.getElementById("page-reload-canary");
if( !tbody || !prc ) return;
var reloading = prc.checked;
// console.log("Reloading:",reloading);
var onChange = function(event){
var display = event.target.checked ? "" : "none";
var rows = event.target.matchingRows;
for(var i=0; i<rows.length; i++)
rows[i].style.display = display;
}
var checkboxes = [];
document.querySelectorAll(
"body.wiki .submenu > label.submenuckbox > input")
.forEach(function(cbx){ checkboxes.push(cbx); });
for(var j=0; j<checkboxes.length; j++){
var cbx = checkboxes[j];
var ctrl = cbx.getAttribute("data-ctrl").toString();
var cname = cbx.parentElement.innerText.toString();
var hidden = ( ctrl == 'h' || ctrl == 'd' );
if( reloading )
hidden = !cbx.checked;
else
cbx.checked = !hidden;
cbx.matchingRows = [];
tbody.querySelectorAll("tr."+cname).forEach(function (tr){
tr.style.display = ( hidden ? "none" : "" );
cbx.matchingRows.push(tr);
});
cbx.addEventListener("change", onChange );
// console.log( cbx.matchingRows.length, cname, ctrl );
}
prc.checked = true;
}); // window.addEventListener( 'load' ...
|
Changes to src/main.mk.
| ︙ | ︙ | |||
227 228 229 230 231 232 233 234 235 236 237 238 239 240 | $(SRCDIR)/fossil.dom.js \ $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.info-diff.js \ $(SRCDIR)/fossil.numbered-lines.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.page.forumpost.js \ $(SRCDIR)/fossil.page.pikchrshow.js \ $(SRCDIR)/fossil.page.whistory.js \ $(SRCDIR)/fossil.page.wikiedit.js \ $(SRCDIR)/fossil.pikchr.js \ $(SRCDIR)/fossil.popupwidget.js \ $(SRCDIR)/fossil.storage.js \ $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/fossil.wikiedit-wysiwyg.js \ | > | 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | $(SRCDIR)/fossil.dom.js \ $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.info-diff.js \ $(SRCDIR)/fossil.numbered-lines.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.page.forumpost.js \ $(SRCDIR)/fossil.page.pikchrshow.js \ $(SRCDIR)/fossil.page.wcontent.js \ $(SRCDIR)/fossil.page.whistory.js \ $(SRCDIR)/fossil.page.wikiedit.js \ $(SRCDIR)/fossil.pikchr.js \ $(SRCDIR)/fossil.popupwidget.js \ $(SRCDIR)/fossil.storage.js \ $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/fossil.wikiedit-wysiwyg.js \ |
| ︙ | ︙ |
Changes to src/wiki.c.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 26 27 28 29 30 31 32 | ** This file contains code to do formatting of wiki text. */ #include "config.h" #include <assert.h> #include <ctype.h> #include "wiki.h" /* ** Return true if the input string is a well-formed wiki page name. ** ** Well-formed wiki page names do not begin or end with whitespace, ** and do not contain tabs or other control characters and do not ** contain more than a single space character in a row. Well-formed ** names must be between 1 and 100 characters in length, inclusive. | > > > > > > > > > > > > > > > > > > | 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 |
** This file contains code to do formatting of wiki text.
*/
#include "config.h"
#include <assert.h>
#include <ctype.h>
#include "wiki.h"
#if INTERFACE
/*
** WikiClass struct holds information for matching a wiki page name.
** It is constructed by load_wiki_classes() function and represents a
** single well-formed line obtained from the 'wiki-classes' setting.
*/
struct WikiClass {
const char * zPattern; /* pattern to match against */
int isCaseIns; /* syntax flag: 0 for GLOB, 1 for LIKE */
short isAltPat; /* indicates an additional pattern for a class */
const char * zVisblty; /* visibility flag, one of: "s" "h" "d" "x" */
const char * zLabel; /* user-visible class name, same in HTML attrs */
const char * zParam; /* checkbox attr, like in <input name="..." /> */
};
#endif
/*
** Return true if the input string is a well-formed wiki page name.
**
** Well-formed wiki page names do not begin or end with whitespace,
** and do not contain tabs or other control characters and do not
** contain more than a single space character in a row. Well-formed
** names must be between 1 and 100 characters in length, inclusive.
|
| ︙ | ︙ | |||
437 438 439 440 441 442 443 444 445 446 447 448 449 450 |
case WIKITYPE_CHECKIN: return "checkin";
case WIKITYPE_BRANCH: return "branch";
case WIKITYPE_TAG: return "tag";
case WIKITYPE_NORMAL:
default: return "normal";
}
}
/*
** Add an appropriate style_header() for either the /wiki or /wikiedit page
** for zPageName. zExtra is an empty string for /wiki but has the text
** "Edit: " for /wikiedit.
**
** If the page is /wiki and the page is one of the special times (check-in,
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 |
case WIKITYPE_CHECKIN: return "checkin";
case WIKITYPE_BRANCH: return "branch";
case WIKITYPE_TAG: return "tag";
case WIKITYPE_NORMAL:
default: return "normal";
}
}
/*
** load_wiki_classes() loads and parses the value of the 'wiki-classes'
** setting. Retrurns either NULL or a dynamically allocated array of
** WikiClasses (thus fossil_free() for the returned value is advised).
*/
WikiClass* load_wiki_classes(
int *nWC, /* number of well-formed elements in the returned array */
Blob *wcs /* holds a buffer for strings in the returned WikiClass'es */
){
static const char *zPN[] = { /* no malloc() for the common cases */
"wc0", "wc1", "wc2", "wc3", "wc4", "wc5", "wc6", "wc7",
"wc8", "wc9","wc10","wc11","wc12","wc13","wc14","wc15",
"wc16","wc17","wc18","wc19","wc20","wc21","wc22","wc23"
};
WikiClass* aWC = 0;
int n = 0;
char *zWCS = db_get("wiki-classes",0);
if( zWCS && strlen(zWCS) >= 5 ){
Blob line = empty_blob;
int nAlloc = 0;
blob_set_dynamic(wcs,zWCS);
while( blob_line(wcs,&line) > 0 ){
Blob vis = empty_blob;
Blob lbl = empty_blob;
Blob pat = empty_blob;
WikiClass * wc;
const char *z;
int cins = 0;
char v;
if( blob_token(&line,&vis) != 1 ){
continue;
}
v = vis.aData[0];
switch( v ){
case 'D':
case 'H':
case 'S':
case 'X':
cins = 1;
vis.aData[0] = (char)tolower(v);
case 'd':
case 'h':
case 's':
case 'x':
break;
default:
v = 0;
}
if( v == 0 ) continue;
if( blob_token(&line,&lbl) <= 0 ){
continue;
}
blob_tail(&line,&pat);
/* blob_to_lf_only(&pre); <-- this is redundant, isn't it ? */
blob_trim(&pat);
z = blob_terminate(&pat);
while(fossil_isspace(z[0])) z++;
if( z[0] == 0 ){
continue;
}
if( n >= nAlloc ){
nAlloc += count(zPN);
aWC = (WikiClass*)( aWC ?
fossil_realloc(aWC,sizeof(WikiClass)*nAlloc) :
fossil_malloc_zero(sizeof(WikiClass)*nAlloc) );
}
wc = aWC + n;
wc->zPattern = z;
wc->isCaseIns = cins;
wc->zVisblty = blob_terminate(&vis);
wc->zLabel = blob_terminate(&lbl);
wc->zParam = ( n < count(zPN) ? zPN[n] : mprintf("wc%d",n) );
if( n > 0 && strcmp( wc->zLabel, wc[-1].zLabel ) == 0 ){
if( wc->zVisblty[0] != wc[-1].zVisblty[0] ||
strcmp( wc->zPattern, wc[-1].zPattern ) == 0 ){
continue;
}
wc->isAltPat = 1;
}else{
wc->isAltPat = 0;
}
n++;
}
}
if( nWC ) *nWC = n;
return aWC;
}
/*
** Find and return a WikiClass that matches a name of a wiki page.
** Returns 0 if "fallback" pattern was not provisioned.
*/
const WikiClass* resolve_wiki_class(
const char *zName, /* name of a wiki page that should be classified */
const WikiClass *aWC, /* pointer to the array of WikiClass'es */
int nWC /* number of elements in the above array */
){
const WikiClass* fallback = 0;
if( aWC && zName ){
int i;
for( i=0; i<nWC; i++ ){
const WikiClass* wc = aWC + i;
if( wc->zPattern[0] == '*' && wc->zPattern[1] == 0 ){
if( !fallback ) fallback = wc;
}else if( wc->isCaseIns ){
if( sqlite3_strlike( wc->zPattern, zName, 0 ) == 0 )
return wc;
}else{
if( sqlite3_strglob( wc->zPattern, zName ) == 0 )
return wc;
}
}
}
return fallback;
}
/*
** Add an appropriate style_header() for either the /wiki or /wikiedit page
** for zPageName. zExtra is an empty string for /wiki but has the text
** "Edit: " for /wikiedit.
**
** If the page is /wiki and the page is one of the special times (check-in,
|
| ︙ | ︙ | |||
1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 |
** List all available wiki pages with date created and last modified.
*/
void wcontent_page(void){
Stmt q;
double rNow;
int showAll = P("all")!=0;
int showRid = P("showid")!=0;
login_check_credentials();
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
style_set_current_feature("wiki");
style_header("Available Wiki Pages");
if( showAll ){
style_submenu_element("Active", "%R/wcontent");
}else{
style_submenu_element("All", "%R/wcontent?all=1");
}
wiki_standard_submenu(W_ALL_BUT(W_LIST));
db_prepare(&q, listAllWikiPages/*works-like:""*/);
@ <div class="brlist">
@ <table class='sortable' data-column-types='tKN' data-init-sort='1'>
@ <thead><tr>
@ <th>Name</th>
@ <th>Last Change</th>
@ <th>Versions</th>
if( showRid ){
@ <th>RID</th>
}
@ </tr></thead><tbody>
rNow = db_double(0.0, "SELECT julianday('now')");
while( db_step(&q)==SQLITE_ROW ){
const char *zWName = db_column_text(&q, 0);
const char *zSort = db_column_text(&q, 1);
int wrid = db_column_int(&q, 2);
double rWmtime = db_column_double(&q, 3);
sqlite3_int64 iMtime = (sqlite3_int64)(rWmtime*86400.0);
char *zAge;
int wcnt = db_column_int(&q, 4);
char *zWDisplayName;
| > > > > > > > > > > > > > > | > | | > > > > > > | > > > > > | > > > | 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 |
** List all available wiki pages with date created and last modified.
*/
void wcontent_page(void){
Stmt q;
double rNow;
int showAll = P("all")!=0;
int showRid = P("showid")!=0;
Blob wcs = empty_blob;
int i, nWC = 0;
WikiClass* aWC;
login_check_credentials();
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
style_set_current_feature("wiki");
style_header("Available Wiki Pages");
if( showAll ){
style_submenu_element("Active", "%R/wcontent");
}else{
style_submenu_element("All", "%R/wcontent?all=1");
}
aWC = load_wiki_classes(&nWC,&wcs);
for( i=0; i<nWC; i++ ){
const WikiClass * c = aWC + i;
if( c->isAltPat || c->zVisblty[0] == 'x' ) continue;
style_submenu_checkbox( c->zParam, c->zLabel,
c->zVisblty[0]=='d' ? STYLE_DISABLED : STYLE_NORMAL, c->zVisblty);
}
wiki_standard_submenu(W_ALL_BUT(W_LIST));
db_prepare(&q, listAllWikiPages/*works-like:""*/);
@ <input hidden="hidden" id="page-reload-canary" type="checkbox"/>
@ <div class="brlist">
@ <table class='sortable' data-column-types='tKN' data-init-sort='1'>
@ <thead><tr>
@ <th>Name</th>
@ <th>Last Change</th>
@ <th>Versions</th>
if( showRid ){
@ <th>RID</th>
}
@ </tr></thead><tbody>
rNow = db_double(0.0, "SELECT julianday('now')");
while( db_step(&q)==SQLITE_ROW ){
const char *zWName = db_column_text(&q, 0);
const char *zSort = db_column_text(&q, 1);
int wrid = db_column_int(&q, 2);
double rWmtime = db_column_double(&q, 3);
sqlite3_int64 iMtime = (sqlite3_int64)(rWmtime*86400.0);
char *zAge;
int wcnt = db_column_int(&q, 4);
char *zWDisplayName;
const WikiClass * wc = resolve_wiki_class(zWName,aWC,nWC);
if( wc && (wc->zVisblty[0] == 'x' || wc->zVisblty[0] == 'd') ){
continue;
}
if( sqlite3_strglob("checkin/*", zWName)==0 ){ /* --?--> strncmp() */
zWDisplayName = mprintf("%.25s...", zWName);
}else{
zWDisplayName = mprintf("%s", zWName); /* --?--> fossil_strdup() */
}
if( wrid==0 ){
if( !showAll ) continue;
if(wc){
@ <tr class="%h(wc->zLabel)">
}else{
@ <tr>
}
@ <td data-sortkey="%h(zSort)">\
@ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td>
}else{
if(wc){
@ <tr class="%h(wc->zLabel)">
}else{
@ <tr>
}
@ <td data-sortkey="%h(zSort)">\
@ %z(href("%R/wiki?name=%T&p",zWName))%h(zWDisplayName)</a></td>
}
zAge = human_readable_age(rNow - rWmtime);
@ <td data-sortkey="%016llx(iMtime)">%s(zAge)</td>
fossil_free(zAge);
@ <td>%z(href("%R/whistory?name=%T",zWName))%d(wcnt)</a></td>
if( showRid ){
@ <td>%d(wrid)</td>
}
@ </tr>
fossil_free(zWDisplayName);
}
@ </tbody></table></div>
db_finalize(&q);
builtin_request_js("fossil.page.wcontent.js");
style_table_sorter();
style_finish_page();
if(aWC) fossil_free(aWC);
blob_reset(&wcs); /* FIXME: it's an analog of fossil_free(), isn't it? */
}
/*
** WEBPAGE: wfind
**
** URL: /wfind?title=TITLE
** List all wiki pages whose titles contain the search text
|
| ︙ | ︙ |
Changes to www/changes.wiki.
1 2 3 4 5 6 7 8 9 |
<title>Change Log</title>
<a name='v2_15'></a>
<h2>Changes for Version 2.15 (2021-03-26) and Patch 2.15.1 on (2021-04-07)</h2>
* <b>Patch 2.15.1:</b> Fix a data exfiltration bug in the server. <b>Upgrading to
the patch is recommended.</b><p>
* The [./defcsp.md|default CSP] has been relaxed slightly to allow
images to be loaded from any URL. All other resources are still
locked down by default.
| > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<title>Change Log</title>
<a name='v2_16'></a>
<h2>Changes for Version 2.16 (pending)</h2>
* [/wcontent|Listing] of the available wiki pages
[/timeline?r=wcontent-subsets|gained] the ability to
[./javascript.md#wcontent|interactively] adjust a subset of
wiki pages that are shown; this is based on the
classification of wiki page names according to the
[/help?cmd=wiki-classes|configurable] glob patterns.
<a name='v2_15'></a>
<h2>Changes for Version 2.15 (2021-03-26) and Patch 2.15.1 on (2021-04-07)</h2>
* <b>Patch 2.15.1:</b> Fix a data exfiltration bug in the server. <b>Upgrading to
the patch is recommended.</b><p>
* The [./defcsp.md|default CSP] has been relaxed slightly to allow
images to be loaded from any URL. All other resources are still
locked down by default.
|
| ︙ | ︙ |
Changes to www/javascript.md.
| ︙ | ︙ | |||
568 569 570 571 572 573 574 575 576 577 578 579 580 581 |
SELECT xfrom, xmsg FROM chat WHERE msgid > 1234;
…would pull the messages submitted since the last poll. Making the
gateway bidirectional should be possible as well, as long as it properly
uses SQLite transactions.
----
## <a id="future"></a>Future Plans for JavaScript in Fossil
As of mid-2020, the informal provisional plan is to increase Fossil
UI's use of JavaScript considerably compared to its historically minimal
uses. To that end, a framework of Fossil-centric APIs is being developed
| > > > > > > > > > | 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 |
SELECT xfrom, xmsg FROM chat WHERE msgid > 1234;
…would pull the messages submitted since the last poll. Making the
gateway bidirectional should be possible as well, as long as it properly
uses SQLite transactions.
### <a id="wcontent"></a>Wiki content listing
[Since](/timeline?r=wcontent-subsets) version 2.16 it is possible to
add [configurable](/help?cmd=wiki-classes) checkbox controls to the
submenu of [available wiki pages](/wcontent) for the interactive
adjustment of a subset of wiki pages that are shown.
Client-side script is used to toggle visibility of the corresponding
rows according to the state of these checkboxes.
----
## <a id="future"></a>Future Plans for JavaScript in Fossil
As of mid-2020, the informal provisional plan is to increase Fossil
UI's use of JavaScript considerably compared to its historically minimal
uses. To that end, a framework of Fossil-centric APIs is being developed
|
| ︙ | ︙ |