Fossil SCM

The fossil.XYZ.js-using pages now include all of those APIs when running in bundled JS mode, as that provides far lower aggregate over-the-wire and HTTP request counts. Added ? popup help buttons in wikiedit/fileedit to replace title-attribute hoverhelp (popup positioning can still be improved, though).

stephan 2020-08-25 07:00 trunk merge
Commit 34f7fd72c62dae25a183b4c99cf7c77bb43e71bacf703ce31ed36530e97c1402
+2 -2
--- src/ajax.c
+++ src/ajax.c
@@ -41,11 +41,11 @@
4141
** and symbolic names for use by client-side scripts.
4242
**
4343
** If addScriptTag is true then the output is wrapped in a SCRIPT tag
4444
** with the current nonce, else no SCRIPT tag is emitted.
4545
**
46
-** Requires that style_emit_script_fossil_bootstrap() has already been
46
+** Requires that builtin_emit_script_fossil_bootstrap() has already been
4747
** called in order to initialize the window.fossil.page object.
4848
*/
4949
void ajax_emit_js_preview_modes(int addScriptTag){
5050
if(addScriptTag){
5151
style_emit_script_tag(0,0);
@@ -131,11 +131,11 @@
131131
break;
132132
default:{
133133
const char *zContent = blob_str(pContent);
134134
if(AJAX_PREVIEW_LINE_NUMBERS & flags){
135135
output_text_with_line_numbers(zContent, blob_size(pContent),
136
- zName, "on");
136
+ zName, "on", 0);
137137
}else{
138138
const char *zExt = strrchr(zName,'.');
139139
if(zExt && zExt[1]){
140140
CX("<pre><code class='language-%s'>%h</code></pre>",
141141
zExt+1, zContent);
142142
--- src/ajax.c
+++ src/ajax.c
@@ -41,11 +41,11 @@
41 ** and symbolic names for use by client-side scripts.
42 **
43 ** If addScriptTag is true then the output is wrapped in a SCRIPT tag
44 ** with the current nonce, else no SCRIPT tag is emitted.
45 **
46 ** Requires that style_emit_script_fossil_bootstrap() has already been
47 ** called in order to initialize the window.fossil.page object.
48 */
49 void ajax_emit_js_preview_modes(int addScriptTag){
50 if(addScriptTag){
51 style_emit_script_tag(0,0);
@@ -131,11 +131,11 @@
131 break;
132 default:{
133 const char *zContent = blob_str(pContent);
134 if(AJAX_PREVIEW_LINE_NUMBERS & flags){
135 output_text_with_line_numbers(zContent, blob_size(pContent),
136 zName, "on");
137 }else{
138 const char *zExt = strrchr(zName,'.');
139 if(zExt && zExt[1]){
140 CX("<pre><code class='language-%s'>%h</code></pre>",
141 zExt+1, zContent);
142
--- src/ajax.c
+++ src/ajax.c
@@ -41,11 +41,11 @@
41 ** and symbolic names for use by client-side scripts.
42 **
43 ** If addScriptTag is true then the output is wrapped in a SCRIPT tag
44 ** with the current nonce, else no SCRIPT tag is emitted.
45 **
46 ** Requires that builtin_emit_script_fossil_bootstrap() has already been
47 ** called in order to initialize the window.fossil.page object.
48 */
49 void ajax_emit_js_preview_modes(int addScriptTag){
50 if(addScriptTag){
51 style_emit_script_tag(0,0);
@@ -131,11 +131,11 @@
131 break;
132 default:{
133 const char *zContent = blob_str(pContent);
134 if(AJAX_PREVIEW_LINE_NUMBERS & flags){
135 output_text_with_line_numbers(zContent, blob_size(pContent),
136 zName, "on", 0);
137 }else{
138 const char *zExt = strrchr(zName,'.');
139 if(zExt && zExt[1]){
140 CX("<pre><code class='language-%s'>%h</code></pre>",
141 zExt+1, zContent);
142
+1 -1
--- src/attach.c
+++ src/attach.c
@@ -617,11 +617,11 @@
617617
const char *z;
618618
content_get(ridSrc, &attach);
619619
blob_to_utf8_no_bom(&attach, 0);
620620
z = blob_str(&attach);
621621
if( zLn ){
622
- output_text_with_line_numbers(z, blob_size(&attach), zName, zLn);
622
+ output_text_with_line_numbers(z, blob_size(&attach), zName, zLn, 1);
623623
}else{
624624
@ <pre>
625625
@ %h(z)
626626
@ </pre>
627627
}
628628
--- src/attach.c
+++ src/attach.c
@@ -617,11 +617,11 @@
617 const char *z;
618 content_get(ridSrc, &attach);
619 blob_to_utf8_no_bom(&attach, 0);
620 z = blob_str(&attach);
621 if( zLn ){
622 output_text_with_line_numbers(z, blob_size(&attach), zName, zLn);
623 }else{
624 @ <pre>
625 @ %h(z)
626 @ </pre>
627 }
628
--- src/attach.c
+++ src/attach.c
@@ -617,11 +617,11 @@
617 const char *z;
618 content_get(ridSrc, &attach);
619 blob_to_utf8_no_bom(&attach, 0);
620 z = blob_str(&attach);
621 if( zLn ){
622 output_text_with_line_numbers(z, blob_size(&attach), zName, zLn, 1);
623 }else{
624 @ <pre>
625 @ %h(z)
626 @ </pre>
627 }
628
+190
--- src/builtin.c
+++ src/builtin.c
@@ -258,10 +258,18 @@
258258
}else if( !bSilent ){
259259
fossil_fatal("unknown javascript delivery mode \"%s\" - should be"
260260
" one of: inline separate bundled", zMode);
261261
}
262262
}
263
+
264
+/*
265
+** Returns the current JS delivery mode: one of JS_INLINE,
266
+** JS_SEPARATE, JS_BUNDLED.
267
+*/
268
+int builtin_get_js_delivery_mode(void){
269
+ return builtin.eDelivery;
270
+}
263271
264272
/*
265273
** The caller wants the Javascript file named by zFilename to be
266274
** included in the generated page. Add the file to the queue of
267275
** requested javascript resources, if it is not there already.
@@ -559,5 +567,187 @@
559567
int rc = sqlite3_create_module(db, "builtin", &builtinVtabModule, 0);
560568
return rc;
561569
}
562570
/* End of the builtin virtual table
563571
******************************************************************************/
572
+
573
+
574
+/*
575
+** The first time this is called, it emits code to install and
576
+** bootstrap the window.fossil object, using the built-in file
577
+** fossil.bootstrap.js (not to be confused with bootstrap.js).
578
+**
579
+** Subsequent calls are no-ops.
580
+**
581
+** It emits 2 parts:
582
+**
583
+** 1) window.fossil core object, some of which depends on C-level
584
+** runtime data. That part of the script is always emitted inline. If
585
+** addScriptTag is true then it is wrapped in its own SCRIPT tag, else
586
+** it is assumed that the caller already opened a tag.
587
+**
588
+** 2) Emits the static fossil.bootstrap.js using builtin_request_js().
589
+*/
590
+void builtin_emit_script_fossil_bootstrap(int addScriptTag){
591
+ static int once = 0;
592
+ if(0==once++){
593
+ char * zName;
594
+ /* Set up the generic/app-agnostic parts of window.fossil
595
+ ** which require C-level state... */
596
+ if(addScriptTag!=0){
597
+ style_emit_script_tag(0,0);
598
+ }
599
+ CX("(function(){\n");
600
+ CX(/*MSIE NodeList.forEach polyfill, courtesy of Mozilla:
601
+ https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
602
+ */
603
+ "if(window.NodeList && !NodeList.prototype.forEach){"
604
+ "NodeList.prototype.forEach = Array.prototype.forEach;"
605
+ "}\n");
606
+ CX("if(!window.fossil) window.fossil={};\n"
607
+ "window.fossil.version = %!j;\n"
608
+ /* fossil.rootPath is the top-most CGI/server path,
609
+ ** including a trailing slash. */
610
+ "window.fossil.rootPath = %!j+'/';\n",
611
+ get_version(), g.zTop);
612
+ /* fossil.config = {...various config-level options...} */
613
+ CX("window.fossil.config = {");
614
+ zName = db_get("project-name", "");
615
+ CX("projectName: %!j,\n", zName);
616
+ fossil_free(zName);
617
+ zName = db_get("short-project-name", "");
618
+ CX("shortProjectName: %!j,\n", zName);
619
+ fossil_free(zName);
620
+ zName = db_get("project-code", "");
621
+ CX("projectCode: %!j,\n", zName);
622
+ fossil_free(zName);
623
+ CX("/* Length of UUID hashes for display purposes. */");
624
+ CX("hashDigits: %d, hashDigitsUrl: %d,\n",
625
+ hash_digits(0), hash_digits(1));
626
+ CX("editStateMarkers: {"
627
+ "/*Symbolic markers to denote certain edit states.*/"
628
+ "isNew:'[+]', isModified:'[*]', isDeleted:'[-]'},\n");
629
+ CX("confirmerButtonTicks: 3 "
630
+ "/*default fossil.confirmer tick count.*/\n");
631
+ CX("};\n"/* fossil.config */);
632
+#if 0
633
+ /* Is it safe to emit the CSRF token here? Some pages add it
634
+ ** as a hidden form field. */
635
+ if(g.zCsrfToken[0]!=0){
636
+ CX("window.fossil.csrfToken = %!j;\n",
637
+ g.zCsrfToken);
638
+ }
639
+#endif
640
+ /*
641
+ ** fossil.page holds info about the current page. This is also
642
+ ** where the current page "should" store any of its own
643
+ ** page-specific state, and it is reserved for that purpose.
644
+ */
645
+ CX("window.fossil.page = {"
646
+ "name:\"%T\""
647
+ "};\n", g.zPath);
648
+ CX("})();\n");
649
+ if(addScriptTag!=0){
650
+ style_emit_script_tag(1,0);
651
+ }
652
+ /* The remaining window.fossil bootstrap code is not dependent on
653
+ ** C-runtime state... */
654
+ builtin_request_js("fossil.bootstrap.js");
655
+ }
656
+}
657
+
658
+
659
+/*
660
+** Convenience wrapper which calls builtin_request_js() for a series
661
+** of builtin scripts named fossil.NAME.js. The first time it is
662
+** called, it also calls builtin_emit_script_fossil_bootstrap() to
663
+** initialize the window.fossil JS API. The first argument is the NAME
664
+** part of the first API to emit. All subsequent arguments must be
665
+** strings of the NAME part of additional fossil.NAME.js files,
666
+** followed by a NULL argument to terminate the list.
667
+**
668
+** e.g. pass it ("fetch", "dom", "tabs", 0) to load those 3
669
+** APIs. Do not forget the trailing 0!
670
+**
671
+** In practice it is normally necessary (or preferred) to call
672
+** builtin_fulfill_js_requests() after calling this, before proceeding
673
+** to call builtin_request_js() for page-specific JS, in order to
674
+** improve cachability.
675
+**
676
+** Achtung: the fossil.page.XYZ.js files are page-specific, containing
677
+** the app-level logic for that specific page, and loading more than
678
+** one of them in a single pagee will break that page. Each of those
679
+** expects to "own" the page it is loaded in, and it should be loaded
680
+** as late in the JS-loading process as feasible, ideally bundled (via
681
+** builtin_request_js()) with any other app-/page-specific JS it may
682
+** need.
683
+*/
684
+void builtin_emit_fossil_js_apis( const char * zApi, ... ) {
685
+ static int once = 0;
686
+ const char *zArg;
687
+ char * zName;
688
+ va_list vargs;
689
+
690
+ if(0==once++){
691
+ builtin_emit_script_fossil_bootstrap(1);
692
+ }
693
+ zName = mprintf("fossil.%s.js", zApi);
694
+ builtin_request_js(zName);
695
+ fossil_free(zName);
696
+
697
+ va_start(vargs,zApi);
698
+ while( (zArg = va_arg (vargs, const char *))!=0 ){
699
+ zName = mprintf("fossil.%s.js", zArg);
700
+ builtin_request_js(zName);
701
+ fossil_free(zName);
702
+ }
703
+ va_end(vargs);
704
+}
705
+
706
+/*
707
+** If builtin_get_js_delivery_mode() returns JS_BUNDLED then this
708
+** function emits, via builtin_request_js(), all JS fossil.XYZ APIs
709
+** which are not strictly specific to a single page, and then calls
710
+** builtin_fulfill_js_requests(). The idea is that we can get better
711
+** bundle caching and reduced HTTP requests by including all JS,
712
+** rather than creating separate bundles on a per-page basis. It then
713
+** returns true. As a special case, if this is called more than once
714
+** in bundled mode, subsequent calls are a no-op.
715
+**
716
+** If the current JS delivery mode is *not* JS_BUNDLED then this
717
+** function is a no-op and returns false. The reason is simply because
718
+** bundled mode is the only mode in which this API improves aggregate
719
+** over-the-wire and HTTP request costs. For other modes, reducing the
720
+** inclusion of fossil.XYZ APIs to their bare minimum, provides the
721
+** lowest aggregate costs. For debate and details, see the discussion
722
+** at:
723
+**
724
+** https://fossil-scm.org/forum/forumpost/3fa2633f3e
725
+**
726
+** Minor caveat: the purpose of emitting all of the fossil.XYZ JS APIs
727
+** at once is to reduce over-the-wire transfers by enabling cross-page
728
+** caching, but if there are other JS scripts pending via
729
+** builtin_request_js() when this is called then they will be included
730
+** in the JS request emitted by this routine, resulting in a different
731
+** script URL than if they were not included. Thus, if a given page
732
+** has its own scripts to install via builtin_request_js(), they
733
+** should, if possible, be delayed until after this is called OR the
734
+** page should call builtin_fulfill_js_requests() to flush the request
735
+** queue before calling this routine.
736
+*/
737
+int builtin_bundle_all_fossil_js_apis(void){
738
+ static int bundled = 0;
739
+ if(JS_BUNDLED == builtin_get_js_delivery_mode()){
740
+ if(!bundled){
741
+ bundled = 1;
742
+ builtin_emit_fossil_js_apis("dom", "fetch",
743
+ "storage", "tabs",
744
+ "confirmer", "popupwidget",
745
+ "copybutton", "numbered-lines",
746
+ 0);
747
+ builtin_fulfill_js_requests();
748
+ }
749
+ return 1;
750
+ }else{
751
+ return 0;
752
+ }
753
+}
564754
--- src/builtin.c
+++ src/builtin.c
@@ -258,10 +258,18 @@
258 }else if( !bSilent ){
259 fossil_fatal("unknown javascript delivery mode \"%s\" - should be"
260 " one of: inline separate bundled", zMode);
261 }
262 }
 
 
 
 
 
 
 
 
263
264 /*
265 ** The caller wants the Javascript file named by zFilename to be
266 ** included in the generated page. Add the file to the queue of
267 ** requested javascript resources, if it is not there already.
@@ -559,5 +567,187 @@
559 int rc = sqlite3_create_module(db, "builtin", &builtinVtabModule, 0);
560 return rc;
561 }
562 /* End of the builtin virtual table
563 ******************************************************************************/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
--- src/builtin.c
+++ src/builtin.c
@@ -258,10 +258,18 @@
258 }else if( !bSilent ){
259 fossil_fatal("unknown javascript delivery mode \"%s\" - should be"
260 " one of: inline separate bundled", zMode);
261 }
262 }
263
264 /*
265 ** Returns the current JS delivery mode: one of JS_INLINE,
266 ** JS_SEPARATE, JS_BUNDLED.
267 */
268 int builtin_get_js_delivery_mode(void){
269 return builtin.eDelivery;
270 }
271
272 /*
273 ** The caller wants the Javascript file named by zFilename to be
274 ** included in the generated page. Add the file to the queue of
275 ** requested javascript resources, if it is not there already.
@@ -559,5 +567,187 @@
567 int rc = sqlite3_create_module(db, "builtin", &builtinVtabModule, 0);
568 return rc;
569 }
570 /* End of the builtin virtual table
571 ******************************************************************************/
572
573
574 /*
575 ** The first time this is called, it emits code to install and
576 ** bootstrap the window.fossil object, using the built-in file
577 ** fossil.bootstrap.js (not to be confused with bootstrap.js).
578 **
579 ** Subsequent calls are no-ops.
580 **
581 ** It emits 2 parts:
582 **
583 ** 1) window.fossil core object, some of which depends on C-level
584 ** runtime data. That part of the script is always emitted inline. If
585 ** addScriptTag is true then it is wrapped in its own SCRIPT tag, else
586 ** it is assumed that the caller already opened a tag.
587 **
588 ** 2) Emits the static fossil.bootstrap.js using builtin_request_js().
589 */
590 void builtin_emit_script_fossil_bootstrap(int addScriptTag){
591 static int once = 0;
592 if(0==once++){
593 char * zName;
594 /* Set up the generic/app-agnostic parts of window.fossil
595 ** which require C-level state... */
596 if(addScriptTag!=0){
597 style_emit_script_tag(0,0);
598 }
599 CX("(function(){\n");
600 CX(/*MSIE NodeList.forEach polyfill, courtesy of Mozilla:
601 https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
602 */
603 "if(window.NodeList && !NodeList.prototype.forEach){"
604 "NodeList.prototype.forEach = Array.prototype.forEach;"
605 "}\n");
606 CX("if(!window.fossil) window.fossil={};\n"
607 "window.fossil.version = %!j;\n"
608 /* fossil.rootPath is the top-most CGI/server path,
609 ** including a trailing slash. */
610 "window.fossil.rootPath = %!j+'/';\n",
611 get_version(), g.zTop);
612 /* fossil.config = {...various config-level options...} */
613 CX("window.fossil.config = {");
614 zName = db_get("project-name", "");
615 CX("projectName: %!j,\n", zName);
616 fossil_free(zName);
617 zName = db_get("short-project-name", "");
618 CX("shortProjectName: %!j,\n", zName);
619 fossil_free(zName);
620 zName = db_get("project-code", "");
621 CX("projectCode: %!j,\n", zName);
622 fossil_free(zName);
623 CX("/* Length of UUID hashes for display purposes. */");
624 CX("hashDigits: %d, hashDigitsUrl: %d,\n",
625 hash_digits(0), hash_digits(1));
626 CX("editStateMarkers: {"
627 "/*Symbolic markers to denote certain edit states.*/"
628 "isNew:'[+]', isModified:'[*]', isDeleted:'[-]'},\n");
629 CX("confirmerButtonTicks: 3 "
630 "/*default fossil.confirmer tick count.*/\n");
631 CX("};\n"/* fossil.config */);
632 #if 0
633 /* Is it safe to emit the CSRF token here? Some pages add it
634 ** as a hidden form field. */
635 if(g.zCsrfToken[0]!=0){
636 CX("window.fossil.csrfToken = %!j;\n",
637 g.zCsrfToken);
638 }
639 #endif
640 /*
641 ** fossil.page holds info about the current page. This is also
642 ** where the current page "should" store any of its own
643 ** page-specific state, and it is reserved for that purpose.
644 */
645 CX("window.fossil.page = {"
646 "name:\"%T\""
647 "};\n", g.zPath);
648 CX("})();\n");
649 if(addScriptTag!=0){
650 style_emit_script_tag(1,0);
651 }
652 /* The remaining window.fossil bootstrap code is not dependent on
653 ** C-runtime state... */
654 builtin_request_js("fossil.bootstrap.js");
655 }
656 }
657
658
659 /*
660 ** Convenience wrapper which calls builtin_request_js() for a series
661 ** of builtin scripts named fossil.NAME.js. The first time it is
662 ** called, it also calls builtin_emit_script_fossil_bootstrap() to
663 ** initialize the window.fossil JS API. The first argument is the NAME
664 ** part of the first API to emit. All subsequent arguments must be
665 ** strings of the NAME part of additional fossil.NAME.js files,
666 ** followed by a NULL argument to terminate the list.
667 **
668 ** e.g. pass it ("fetch", "dom", "tabs", 0) to load those 3
669 ** APIs. Do not forget the trailing 0!
670 **
671 ** In practice it is normally necessary (or preferred) to call
672 ** builtin_fulfill_js_requests() after calling this, before proceeding
673 ** to call builtin_request_js() for page-specific JS, in order to
674 ** improve cachability.
675 **
676 ** Achtung: the fossil.page.XYZ.js files are page-specific, containing
677 ** the app-level logic for that specific page, and loading more than
678 ** one of them in a single pagee will break that page. Each of those
679 ** expects to "own" the page it is loaded in, and it should be loaded
680 ** as late in the JS-loading process as feasible, ideally bundled (via
681 ** builtin_request_js()) with any other app-/page-specific JS it may
682 ** need.
683 */
684 void builtin_emit_fossil_js_apis( const char * zApi, ... ) {
685 static int once = 0;
686 const char *zArg;
687 char * zName;
688 va_list vargs;
689
690 if(0==once++){
691 builtin_emit_script_fossil_bootstrap(1);
692 }
693 zName = mprintf("fossil.%s.js", zApi);
694 builtin_request_js(zName);
695 fossil_free(zName);
696
697 va_start(vargs,zApi);
698 while( (zArg = va_arg (vargs, const char *))!=0 ){
699 zName = mprintf("fossil.%s.js", zArg);
700 builtin_request_js(zName);
701 fossil_free(zName);
702 }
703 va_end(vargs);
704 }
705
706 /*
707 ** If builtin_get_js_delivery_mode() returns JS_BUNDLED then this
708 ** function emits, via builtin_request_js(), all JS fossil.XYZ APIs
709 ** which are not strictly specific to a single page, and then calls
710 ** builtin_fulfill_js_requests(). The idea is that we can get better
711 ** bundle caching and reduced HTTP requests by including all JS,
712 ** rather than creating separate bundles on a per-page basis. It then
713 ** returns true. As a special case, if this is called more than once
714 ** in bundled mode, subsequent calls are a no-op.
715 **
716 ** If the current JS delivery mode is *not* JS_BUNDLED then this
717 ** function is a no-op and returns false. The reason is simply because
718 ** bundled mode is the only mode in which this API improves aggregate
719 ** over-the-wire and HTTP request costs. For other modes, reducing the
720 ** inclusion of fossil.XYZ APIs to their bare minimum, provides the
721 ** lowest aggregate costs. For debate and details, see the discussion
722 ** at:
723 **
724 ** https://fossil-scm.org/forum/forumpost/3fa2633f3e
725 **
726 ** Minor caveat: the purpose of emitting all of the fossil.XYZ JS APIs
727 ** at once is to reduce over-the-wire transfers by enabling cross-page
728 ** caching, but if there are other JS scripts pending via
729 ** builtin_request_js() when this is called then they will be included
730 ** in the JS request emitted by this routine, resulting in a different
731 ** script URL than if they were not included. Thus, if a given page
732 ** has its own scripts to install via builtin_request_js(), they
733 ** should, if possible, be delayed until after this is called OR the
734 ** page should call builtin_fulfill_js_requests() to flush the request
735 ** queue before calling this routine.
736 */
737 int builtin_bundle_all_fossil_js_apis(void){
738 static int bundled = 0;
739 if(JS_BUNDLED == builtin_get_js_delivery_mode()){
740 if(!bundled){
741 bundled = 1;
742 builtin_emit_fossil_js_apis("dom", "fetch",
743 "storage", "tabs",
744 "confirmer", "popupwidget",
745 "copybutton", "numbered-lines",
746 0);
747 builtin_fulfill_js_requests();
748 }
749 return 1;
750 }else{
751 return 0;
752 }
753 }
754
+190
--- src/builtin.c
+++ src/builtin.c
@@ -258,10 +258,18 @@
258258
}else if( !bSilent ){
259259
fossil_fatal("unknown javascript delivery mode \"%s\" - should be"
260260
" one of: inline separate bundled", zMode);
261261
}
262262
}
263
+
264
+/*
265
+** Returns the current JS delivery mode: one of JS_INLINE,
266
+** JS_SEPARATE, JS_BUNDLED.
267
+*/
268
+int builtin_get_js_delivery_mode(void){
269
+ return builtin.eDelivery;
270
+}
263271
264272
/*
265273
** The caller wants the Javascript file named by zFilename to be
266274
** included in the generated page. Add the file to the queue of
267275
** requested javascript resources, if it is not there already.
@@ -559,5 +567,187 @@
559567
int rc = sqlite3_create_module(db, "builtin", &builtinVtabModule, 0);
560568
return rc;
561569
}
562570
/* End of the builtin virtual table
563571
******************************************************************************/
572
+
573
+
574
+/*
575
+** The first time this is called, it emits code to install and
576
+** bootstrap the window.fossil object, using the built-in file
577
+** fossil.bootstrap.js (not to be confused with bootstrap.js).
578
+**
579
+** Subsequent calls are no-ops.
580
+**
581
+** It emits 2 parts:
582
+**
583
+** 1) window.fossil core object, some of which depends on C-level
584
+** runtime data. That part of the script is always emitted inline. If
585
+** addScriptTag is true then it is wrapped in its own SCRIPT tag, else
586
+** it is assumed that the caller already opened a tag.
587
+**
588
+** 2) Emits the static fossil.bootstrap.js using builtin_request_js().
589
+*/
590
+void builtin_emit_script_fossil_bootstrap(int addScriptTag){
591
+ static int once = 0;
592
+ if(0==once++){
593
+ char * zName;
594
+ /* Set up the generic/app-agnostic parts of window.fossil
595
+ ** which require C-level state... */
596
+ if(addScriptTag!=0){
597
+ style_emit_script_tag(0,0);
598
+ }
599
+ CX("(function(){\n");
600
+ CX(/*MSIE NodeList.forEach polyfill, courtesy of Mozilla:
601
+ https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
602
+ */
603
+ "if(window.NodeList && !NodeList.prototype.forEach){"
604
+ "NodeList.prototype.forEach = Array.prototype.forEach;"
605
+ "}\n");
606
+ CX("if(!window.fossil) window.fossil={};\n"
607
+ "window.fossil.version = %!j;\n"
608
+ /* fossil.rootPath is the top-most CGI/server path,
609
+ ** including a trailing slash. */
610
+ "window.fossil.rootPath = %!j+'/';\n",
611
+ get_version(), g.zTop);
612
+ /* fossil.config = {...various config-level options...} */
613
+ CX("window.fossil.config = {");
614
+ zName = db_get("project-name", "");
615
+ CX("projectName: %!j,\n", zName);
616
+ fossil_free(zName);
617
+ zName = db_get("short-project-name", "");
618
+ CX("shortProjectName: %!j,\n", zName);
619
+ fossil_free(zName);
620
+ zName = db_get("project-code", "");
621
+ CX("projectCode: %!j,\n", zName);
622
+ fossil_free(zName);
623
+ CX("/* Length of UUID hashes for display purposes. */");
624
+ CX("hashDigits: %d, hashDigitsUrl: %d,\n",
625
+ hash_digits(0), hash_digits(1));
626
+ CX("editStateMarkers: {"
627
+ "/*Symbolic markers to denote certain edit states.*/"
628
+ "isNew:'[+]', isModified:'[*]', isDeleted:'[-]'},\n");
629
+ CX("confirmerButtonTicks: 3 "
630
+ "/*default fossil.confirmer tick count.*/\n");
631
+ CX("};\n"/* fossil.config */);
632
+#if 0
633
+ /* Is it safe to emit the CSRF token here? Some pages add it
634
+ ** as a hidden form field. */
635
+ if(g.zCsrfToken[0]!=0){
636
+ CX("window.fossil.csrfToken = %!j;\n",
637
+ g.zCsrfToken);
638
+ }
639
+#endif
640
+ /*
641
+ ** fossil.page holds info about the current page. This is also
642
+ ** where the current page "should" store any of its own
643
+ ** page-specific state, and it is reserved for that purpose.
644
+ */
645
+ CX("window.fossil.page = {"
646
+ "name:\"%T\""
647
+ "};\n", g.zPath);
648
+ CX("})();\n");
649
+ if(addScriptTag!=0){
650
+ style_emit_script_tag(1,0);
651
+ }
652
+ /* The remaining window.fossil bootstrap code is not dependent on
653
+ ** C-runtime state... */
654
+ builtin_request_js("fossil.bootstrap.js");
655
+ }
656
+}
657
+
658
+
659
+/*
660
+** Convenience wrapper which calls builtin_request_js() for a series
661
+** of builtin scripts named fossil.NAME.js. The first time it is
662
+** called, it also calls builtin_emit_script_fossil_bootstrap() to
663
+** initialize the window.fossil JS API. The first argument is the NAME
664
+** part of the first API to emit. All subsequent arguments must be
665
+** strings of the NAME part of additional fossil.NAME.js files,
666
+** followed by a NULL argument to terminate the list.
667
+**
668
+** e.g. pass it ("fetch", "dom", "tabs", 0) to load those 3
669
+** APIs. Do not forget the trailing 0!
670
+**
671
+** In practice it is normally necessary (or preferred) to call
672
+** builtin_fulfill_js_requests() after calling this, before proceeding
673
+** to call builtin_request_js() for page-specific JS, in order to
674
+** improve cachability.
675
+**
676
+** Achtung: the fossil.page.XYZ.js files are page-specific, containing
677
+** the app-level logic for that specific page, and loading more than
678
+** one of them in a single pagee will break that page. Each of those
679
+** expects to "own" the page it is loaded in, and it should be loaded
680
+** as late in the JS-loading process as feasible, ideally bundled (via
681
+** builtin_request_js()) with any other app-/page-specific JS it may
682
+** need.
683
+*/
684
+void builtin_emit_fossil_js_apis( const char * zApi, ... ) {
685
+ static int once = 0;
686
+ const char *zArg;
687
+ char * zName;
688
+ va_list vargs;
689
+
690
+ if(0==once++){
691
+ builtin_emit_script_fossil_bootstrap(1);
692
+ }
693
+ zName = mprintf("fossil.%s.js", zApi);
694
+ builtin_request_js(zName);
695
+ fossil_free(zName);
696
+
697
+ va_start(vargs,zApi);
698
+ while( (zArg = va_arg (vargs, const char *))!=0 ){
699
+ zName = mprintf("fossil.%s.js", zArg);
700
+ builtin_request_js(zName);
701
+ fossil_free(zName);
702
+ }
703
+ va_end(vargs);
704
+}
705
+
706
+/*
707
+** If builtin_get_js_delivery_mode() returns JS_BUNDLED then this
708
+** function emits, via builtin_request_js(), all JS fossil.XYZ APIs
709
+** which are not strictly specific to a single page, and then calls
710
+** builtin_fulfill_js_requests(). The idea is that we can get better
711
+** bundle caching and reduced HTTP requests by including all JS,
712
+** rather than creating separate bundles on a per-page basis. It then
713
+** returns true. As a special case, if this is called more than once
714
+** in bundled mode, subsequent calls are a no-op.
715
+**
716
+** If the current JS delivery mode is *not* JS_BUNDLED then this
717
+** function is a no-op and returns false. The reason is simply because
718
+** bundled mode is the only mode in which this API improves aggregate
719
+** over-the-wire and HTTP request costs. For other modes, reducing the
720
+** inclusion of fossil.XYZ APIs to their bare minimum, provides the
721
+** lowest aggregate costs. For debate and details, see the discussion
722
+** at:
723
+**
724
+** https://fossil-scm.org/forum/forumpost/3fa2633f3e
725
+**
726
+** Minor caveat: the purpose of emitting all of the fossil.XYZ JS APIs
727
+** at once is to reduce over-the-wire transfers by enabling cross-page
728
+** caching, but if there are other JS scripts pending via
729
+** builtin_request_js() when this is called then they will be included
730
+** in the JS request emitted by this routine, resulting in a different
731
+** script URL than if they were not included. Thus, if a given page
732
+** has its own scripts to install via builtin_request_js(), they
733
+** should, if possible, be delayed until after this is called OR the
734
+** page should call builtin_fulfill_js_requests() to flush the request
735
+** queue before calling this routine.
736
+*/
737
+int builtin_bundle_all_fossil_js_apis(void){
738
+ static int bundled = 0;
739
+ if(JS_BUNDLED == builtin_get_js_delivery_mode()){
740
+ if(!bundled){
741
+ bundled = 1;
742
+ builtin_emit_fossil_js_apis("dom", "fetch",
743
+ "storage", "tabs",
744
+ "confirmer", "popupwidget",
745
+ "copybutton", "numbered-lines",
746
+ 0);
747
+ builtin_fulfill_js_requests();
748
+ }
749
+ return 1;
750
+ }else{
751
+ return 0;
752
+ }
753
+}
564754
--- src/builtin.c
+++ src/builtin.c
@@ -258,10 +258,18 @@
258 }else if( !bSilent ){
259 fossil_fatal("unknown javascript delivery mode \"%s\" - should be"
260 " one of: inline separate bundled", zMode);
261 }
262 }
 
 
 
 
 
 
 
 
263
264 /*
265 ** The caller wants the Javascript file named by zFilename to be
266 ** included in the generated page. Add the file to the queue of
267 ** requested javascript resources, if it is not there already.
@@ -559,5 +567,187 @@
559 int rc = sqlite3_create_module(db, "builtin", &builtinVtabModule, 0);
560 return rc;
561 }
562 /* End of the builtin virtual table
563 ******************************************************************************/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
--- src/builtin.c
+++ src/builtin.c
@@ -258,10 +258,18 @@
258 }else if( !bSilent ){
259 fossil_fatal("unknown javascript delivery mode \"%s\" - should be"
260 " one of: inline separate bundled", zMode);
261 }
262 }
263
264 /*
265 ** Returns the current JS delivery mode: one of JS_INLINE,
266 ** JS_SEPARATE, JS_BUNDLED.
267 */
268 int builtin_get_js_delivery_mode(void){
269 return builtin.eDelivery;
270 }
271
272 /*
273 ** The caller wants the Javascript file named by zFilename to be
274 ** included in the generated page. Add the file to the queue of
275 ** requested javascript resources, if it is not there already.
@@ -559,5 +567,187 @@
567 int rc = sqlite3_create_module(db, "builtin", &builtinVtabModule, 0);
568 return rc;
569 }
570 /* End of the builtin virtual table
571 ******************************************************************************/
572
573
574 /*
575 ** The first time this is called, it emits code to install and
576 ** bootstrap the window.fossil object, using the built-in file
577 ** fossil.bootstrap.js (not to be confused with bootstrap.js).
578 **
579 ** Subsequent calls are no-ops.
580 **
581 ** It emits 2 parts:
582 **
583 ** 1) window.fossil core object, some of which depends on C-level
584 ** runtime data. That part of the script is always emitted inline. If
585 ** addScriptTag is true then it is wrapped in its own SCRIPT tag, else
586 ** it is assumed that the caller already opened a tag.
587 **
588 ** 2) Emits the static fossil.bootstrap.js using builtin_request_js().
589 */
590 void builtin_emit_script_fossil_bootstrap(int addScriptTag){
591 static int once = 0;
592 if(0==once++){
593 char * zName;
594 /* Set up the generic/app-agnostic parts of window.fossil
595 ** which require C-level state... */
596 if(addScriptTag!=0){
597 style_emit_script_tag(0,0);
598 }
599 CX("(function(){\n");
600 CX(/*MSIE NodeList.forEach polyfill, courtesy of Mozilla:
601 https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
602 */
603 "if(window.NodeList && !NodeList.prototype.forEach){"
604 "NodeList.prototype.forEach = Array.prototype.forEach;"
605 "}\n");
606 CX("if(!window.fossil) window.fossil={};\n"
607 "window.fossil.version = %!j;\n"
608 /* fossil.rootPath is the top-most CGI/server path,
609 ** including a trailing slash. */
610 "window.fossil.rootPath = %!j+'/';\n",
611 get_version(), g.zTop);
612 /* fossil.config = {...various config-level options...} */
613 CX("window.fossil.config = {");
614 zName = db_get("project-name", "");
615 CX("projectName: %!j,\n", zName);
616 fossil_free(zName);
617 zName = db_get("short-project-name", "");
618 CX("shortProjectName: %!j,\n", zName);
619 fossil_free(zName);
620 zName = db_get("project-code", "");
621 CX("projectCode: %!j,\n", zName);
622 fossil_free(zName);
623 CX("/* Length of UUID hashes for display purposes. */");
624 CX("hashDigits: %d, hashDigitsUrl: %d,\n",
625 hash_digits(0), hash_digits(1));
626 CX("editStateMarkers: {"
627 "/*Symbolic markers to denote certain edit states.*/"
628 "isNew:'[+]', isModified:'[*]', isDeleted:'[-]'},\n");
629 CX("confirmerButtonTicks: 3 "
630 "/*default fossil.confirmer tick count.*/\n");
631 CX("};\n"/* fossil.config */);
632 #if 0
633 /* Is it safe to emit the CSRF token here? Some pages add it
634 ** as a hidden form field. */
635 if(g.zCsrfToken[0]!=0){
636 CX("window.fossil.csrfToken = %!j;\n",
637 g.zCsrfToken);
638 }
639 #endif
640 /*
641 ** fossil.page holds info about the current page. This is also
642 ** where the current page "should" store any of its own
643 ** page-specific state, and it is reserved for that purpose.
644 */
645 CX("window.fossil.page = {"
646 "name:\"%T\""
647 "};\n", g.zPath);
648 CX("})();\n");
649 if(addScriptTag!=0){
650 style_emit_script_tag(1,0);
651 }
652 /* The remaining window.fossil bootstrap code is not dependent on
653 ** C-runtime state... */
654 builtin_request_js("fossil.bootstrap.js");
655 }
656 }
657
658
659 /*
660 ** Convenience wrapper which calls builtin_request_js() for a series
661 ** of builtin scripts named fossil.NAME.js. The first time it is
662 ** called, it also calls builtin_emit_script_fossil_bootstrap() to
663 ** initialize the window.fossil JS API. The first argument is the NAME
664 ** part of the first API to emit. All subsequent arguments must be
665 ** strings of the NAME part of additional fossil.NAME.js files,
666 ** followed by a NULL argument to terminate the list.
667 **
668 ** e.g. pass it ("fetch", "dom", "tabs", 0) to load those 3
669 ** APIs. Do not forget the trailing 0!
670 **
671 ** In practice it is normally necessary (or preferred) to call
672 ** builtin_fulfill_js_requests() after calling this, before proceeding
673 ** to call builtin_request_js() for page-specific JS, in order to
674 ** improve cachability.
675 **
676 ** Achtung: the fossil.page.XYZ.js files are page-specific, containing
677 ** the app-level logic for that specific page, and loading more than
678 ** one of them in a single pagee will break that page. Each of those
679 ** expects to "own" the page it is loaded in, and it should be loaded
680 ** as late in the JS-loading process as feasible, ideally bundled (via
681 ** builtin_request_js()) with any other app-/page-specific JS it may
682 ** need.
683 */
684 void builtin_emit_fossil_js_apis( const char * zApi, ... ) {
685 static int once = 0;
686 const char *zArg;
687 char * zName;
688 va_list vargs;
689
690 if(0==once++){
691 builtin_emit_script_fossil_bootstrap(1);
692 }
693 zName = mprintf("fossil.%s.js", zApi);
694 builtin_request_js(zName);
695 fossil_free(zName);
696
697 va_start(vargs,zApi);
698 while( (zArg = va_arg (vargs, const char *))!=0 ){
699 zName = mprintf("fossil.%s.js", zArg);
700 builtin_request_js(zName);
701 fossil_free(zName);
702 }
703 va_end(vargs);
704 }
705
706 /*
707 ** If builtin_get_js_delivery_mode() returns JS_BUNDLED then this
708 ** function emits, via builtin_request_js(), all JS fossil.XYZ APIs
709 ** which are not strictly specific to a single page, and then calls
710 ** builtin_fulfill_js_requests(). The idea is that we can get better
711 ** bundle caching and reduced HTTP requests by including all JS,
712 ** rather than creating separate bundles on a per-page basis. It then
713 ** returns true. As a special case, if this is called more than once
714 ** in bundled mode, subsequent calls are a no-op.
715 **
716 ** If the current JS delivery mode is *not* JS_BUNDLED then this
717 ** function is a no-op and returns false. The reason is simply because
718 ** bundled mode is the only mode in which this API improves aggregate
719 ** over-the-wire and HTTP request costs. For other modes, reducing the
720 ** inclusion of fossil.XYZ APIs to their bare minimum, provides the
721 ** lowest aggregate costs. For debate and details, see the discussion
722 ** at:
723 **
724 ** https://fossil-scm.org/forum/forumpost/3fa2633f3e
725 **
726 ** Minor caveat: the purpose of emitting all of the fossil.XYZ JS APIs
727 ** at once is to reduce over-the-wire transfers by enabling cross-page
728 ** caching, but if there are other JS scripts pending via
729 ** builtin_request_js() when this is called then they will be included
730 ** in the JS request emitted by this routine, resulting in a different
731 ** script URL than if they were not included. Thus, if a given page
732 ** has its own scripts to install via builtin_request_js(), they
733 ** should, if possible, be delayed until after this is called OR the
734 ** page should call builtin_fulfill_js_requests() to flush the request
735 ** queue before calling this routine.
736 */
737 int builtin_bundle_all_fossil_js_apis(void){
738 static int bundled = 0;
739 if(JS_BUNDLED == builtin_get_js_delivery_mode()){
740 if(!bundled){
741 bundled = 1;
742 builtin_emit_fossil_js_apis("dom", "fetch",
743 "storage", "tabs",
744 "confirmer", "popupwidget",
745 "copybutton", "numbered-lines",
746 0);
747 builtin_fulfill_js_requests();
748 }
749 return 1;
750 }else{
751 return 0;
752 }
753 }
754
+63 -6
--- src/default.css
+++ src/default.css
@@ -1100,15 +1100,16 @@
11001100
.font-size-200 {
11011101
font-size: 200%;
11021102
}
11031103
11041104
/**
1105
- .input-with-label is intended to be a wrapper element which
1106
- contain both a LABEL tag and an INPUT or SELECT control.
1107
- The wrapper is "necessary", as opposed to placing the INPUT
1108
- in the LABEL, so that we can include multiple INPUT
1109
- elements (e.g. a set of radio buttons).
1105
+ .input-with-label is intended to be a wrapper element which contain
1106
+ both a LABEL tag and an INPUT or SELECT control. The wrapper is
1107
+ "necessary", as opposed to placing the INPUT in the LABEL, so that
1108
+ we can include multiple INPUT elements (e.g. a set of radio
1109
+ buttons). Note that these elements must sometimes be BLOCK elements
1110
+ (e.g. DIV) so that certain nesting constructs are legal.
11101111
*/
11111112
.input-with-label {
11121113
border: 1px inset #808080;
11131114
border-radius: 0.25em;
11141115
padding: 0.25em 0.4em;
@@ -1251,11 +1252,11 @@
12511252
border: 1px solid black;
12521253
border-radius: 0.25em;
12531254
position: absolute;
12541255
display: inline-block;
12551256
z-index: 19/*below default skin's hamburger popup*/;
1256
- box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.75);
1257
+ box-shadow: -0.15em 0.15em 0.2em rgba(0, 0, 0, 0.75);
12571258
background-color: inherit;
12581259
}
12591260
12601261
.fossil-toast-message {
12611262
/* "toast"-style popup message.
@@ -1293,5 +1294,61 @@
12931294
12941295
blockquote.file-content {
12951296
/* file content block in the /file page */
12961297
margin: 0 1em;
12971298
}
1299
+
1300
+
1301
+/**
1302
+ Circular "help" buttons intended to be placed to the right of
1303
+ another element and hold text text for it. These typically get
1304
+ initialized automatically at page startup via
1305
+ fossil.popupwidget.js, and can be manually initialized/created
1306
+ using window.fossil.helpButtonlets.setup/create(). All of their
1307
+ child content (plain text and/or DOM elements) gets moved out of
1308
+ the DOM and shown in a singleton popup when they are clicked. They
1309
+ may be SPAN elements if their children are all inline elements,
1310
+ otherwise they must be DIVs (block elements) so that nesting of
1311
+ block-element content is legal.
1312
+*/
1313
+.help-buttonlet {
1314
+ display: inline-block;
1315
+ min-width: 1rem;
1316
+ max-width: 1rem;
1317
+ min-height: 1rem;
1318
+ max-height: 1rem;
1319
+ font-size: 0.9em;
1320
+ border-radius: 0.5rem;
1321
+ background-color: rgba(54, 54, 255,1);
1322
+ color: rgb(255, 255, 255);
1323
+ cursor: pointer;
1324
+ font-family: monspace;
1325
+ text-align: center;
1326
+ margin: 0 0 0 0.35em;
1327
+ border-width: 1px;
1328
+ border-style: outset;
1329
+ font-weight: 700;
1330
+ overflow: hidden;
1331
+}
1332
+
1333
+.help-buttonlet::before {
1334
+ content: "?";
1335
+}
1336
+
1337
+/**
1338
+ We really want to hide all help text via CSS but CSS cannot select
1339
+ TEXT nodes. Thus we move them out of the way programmatically
1340
+ during initialization.
1341
+*/
1342
+.help-buttonlet > *{}
1343
+
1344
+/**
1345
+ CSS class for PopupWidget which wraps .help-buttonlet content.
1346
+ They also have class fossil-tooltip. We need an overly-exact
1347
+ selector here to be certain that this class's style overrides
1348
+ that of fossil-tooltip.
1349
+*/
1350
+.fossil-tooltip.help-buttonlet-content {
1351
+ cursor: default;
1352
+ text-align: left;
1353
+ border-style: outset;
1354
+}
12981355
--- src/default.css
+++ src/default.css
@@ -1100,15 +1100,16 @@
1100 .font-size-200 {
1101 font-size: 200%;
1102 }
1103
1104 /**
1105 .input-with-label is intended to be a wrapper element which
1106 contain both a LABEL tag and an INPUT or SELECT control.
1107 The wrapper is "necessary", as opposed to placing the INPUT
1108 in the LABEL, so that we can include multiple INPUT
1109 elements (e.g. a set of radio buttons).
 
1110 */
1111 .input-with-label {
1112 border: 1px inset #808080;
1113 border-radius: 0.25em;
1114 padding: 0.25em 0.4em;
@@ -1251,11 +1252,11 @@
1251 border: 1px solid black;
1252 border-radius: 0.25em;
1253 position: absolute;
1254 display: inline-block;
1255 z-index: 19/*below default skin's hamburger popup*/;
1256 box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.75);
1257 background-color: inherit;
1258 }
1259
1260 .fossil-toast-message {
1261 /* "toast"-style popup message.
@@ -1293,5 +1294,61 @@
1293
1294 blockquote.file-content {
1295 /* file content block in the /file page */
1296 margin: 0 1em;
1297 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1298
--- src/default.css
+++ src/default.css
@@ -1100,15 +1100,16 @@
1100 .font-size-200 {
1101 font-size: 200%;
1102 }
1103
1104 /**
1105 .input-with-label is intended to be a wrapper element which contain
1106 both a LABEL tag and an INPUT or SELECT control. The wrapper is
1107 "necessary", as opposed to placing the INPUT in the LABEL, so that
1108 we can include multiple INPUT elements (e.g. a set of radio
1109 buttons). Note that these elements must sometimes be BLOCK elements
1110 (e.g. DIV) so that certain nesting constructs are legal.
1111 */
1112 .input-with-label {
1113 border: 1px inset #808080;
1114 border-radius: 0.25em;
1115 padding: 0.25em 0.4em;
@@ -1251,11 +1252,11 @@
1252 border: 1px solid black;
1253 border-radius: 0.25em;
1254 position: absolute;
1255 display: inline-block;
1256 z-index: 19/*below default skin's hamburger popup*/;
1257 box-shadow: -0.15em 0.15em 0.2em rgba(0, 0, 0, 0.75);
1258 background-color: inherit;
1259 }
1260
1261 .fossil-toast-message {
1262 /* "toast"-style popup message.
@@ -1293,5 +1294,61 @@
1294
1295 blockquote.file-content {
1296 /* file content block in the /file page */
1297 margin: 0 1em;
1298 }
1299
1300
1301 /**
1302 Circular "help" buttons intended to be placed to the right of
1303 another element and hold text text for it. These typically get
1304 initialized automatically at page startup via
1305 fossil.popupwidget.js, and can be manually initialized/created
1306 using window.fossil.helpButtonlets.setup/create(). All of their
1307 child content (plain text and/or DOM elements) gets moved out of
1308 the DOM and shown in a singleton popup when they are clicked. They
1309 may be SPAN elements if their children are all inline elements,
1310 otherwise they must be DIVs (block elements) so that nesting of
1311 block-element content is legal.
1312 */
1313 .help-buttonlet {
1314 display: inline-block;
1315 min-width: 1rem;
1316 max-width: 1rem;
1317 min-height: 1rem;
1318 max-height: 1rem;
1319 font-size: 0.9em;
1320 border-radius: 0.5rem;
1321 background-color: rgba(54, 54, 255,1);
1322 color: rgb(255, 255, 255);
1323 cursor: pointer;
1324 font-family: monspace;
1325 text-align: center;
1326 margin: 0 0 0 0.35em;
1327 border-width: 1px;
1328 border-style: outset;
1329 font-weight: 700;
1330 overflow: hidden;
1331 }
1332
1333 .help-buttonlet::before {
1334 content: "?";
1335 }
1336
1337 /**
1338 We really want to hide all help text via CSS but CSS cannot select
1339 TEXT nodes. Thus we move them out of the way programmatically
1340 during initialization.
1341 */
1342 .help-buttonlet > *{}
1343
1344 /**
1345 CSS class for PopupWidget which wraps .help-buttonlet content.
1346 They also have class fossil-tooltip. We need an overly-exact
1347 selector here to be certain that this class's style overrides
1348 that of fossil-tooltip.
1349 */
1350 .fossil-tooltip.help-buttonlet-content {
1351 cursor: default;
1352 text-align: left;
1353 border-style: outset;
1354 }
1355
+63 -6
--- src/default.css
+++ src/default.css
@@ -1100,15 +1100,16 @@
11001100
.font-size-200 {
11011101
font-size: 200%;
11021102
}
11031103
11041104
/**
1105
- .input-with-label is intended to be a wrapper element which
1106
- contain both a LABEL tag and an INPUT or SELECT control.
1107
- The wrapper is "necessary", as opposed to placing the INPUT
1108
- in the LABEL, so that we can include multiple INPUT
1109
- elements (e.g. a set of radio buttons).
1105
+ .input-with-label is intended to be a wrapper element which contain
1106
+ both a LABEL tag and an INPUT or SELECT control. The wrapper is
1107
+ "necessary", as opposed to placing the INPUT in the LABEL, so that
1108
+ we can include multiple INPUT elements (e.g. a set of radio
1109
+ buttons). Note that these elements must sometimes be BLOCK elements
1110
+ (e.g. DIV) so that certain nesting constructs are legal.
11101111
*/
11111112
.input-with-label {
11121113
border: 1px inset #808080;
11131114
border-radius: 0.25em;
11141115
padding: 0.25em 0.4em;
@@ -1251,11 +1252,11 @@
12511252
border: 1px solid black;
12521253
border-radius: 0.25em;
12531254
position: absolute;
12541255
display: inline-block;
12551256
z-index: 19/*below default skin's hamburger popup*/;
1256
- box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.75);
1257
+ box-shadow: -0.15em 0.15em 0.2em rgba(0, 0, 0, 0.75);
12571258
background-color: inherit;
12581259
}
12591260
12601261
.fossil-toast-message {
12611262
/* "toast"-style popup message.
@@ -1293,5 +1294,61 @@
12931294
12941295
blockquote.file-content {
12951296
/* file content block in the /file page */
12961297
margin: 0 1em;
12971298
}
1299
+
1300
+
1301
+/**
1302
+ Circular "help" buttons intended to be placed to the right of
1303
+ another element and hold text text for it. These typically get
1304
+ initialized automatically at page startup via
1305
+ fossil.popupwidget.js, and can be manually initialized/created
1306
+ using window.fossil.helpButtonlets.setup/create(). All of their
1307
+ child content (plain text and/or DOM elements) gets moved out of
1308
+ the DOM and shown in a singleton popup when they are clicked. They
1309
+ may be SPAN elements if their children are all inline elements,
1310
+ otherwise they must be DIVs (block elements) so that nesting of
1311
+ block-element content is legal.
1312
+*/
1313
+.help-buttonlet {
1314
+ display: inline-block;
1315
+ min-width: 1rem;
1316
+ max-width: 1rem;
1317
+ min-height: 1rem;
1318
+ max-height: 1rem;
1319
+ font-size: 0.9em;
1320
+ border-radius: 0.5rem;
1321
+ background-color: rgba(54, 54, 255,1);
1322
+ color: rgb(255, 255, 255);
1323
+ cursor: pointer;
1324
+ font-family: monspace;
1325
+ text-align: center;
1326
+ margin: 0 0 0 0.35em;
1327
+ border-width: 1px;
1328
+ border-style: outset;
1329
+ font-weight: 700;
1330
+ overflow: hidden;
1331
+}
1332
+
1333
+.help-buttonlet::before {
1334
+ content: "?";
1335
+}
1336
+
1337
+/**
1338
+ We really want to hide all help text via CSS but CSS cannot select
1339
+ TEXT nodes. Thus we move them out of the way programmatically
1340
+ during initialization.
1341
+*/
1342
+.help-buttonlet > *{}
1343
+
1344
+/**
1345
+ CSS class for PopupWidget which wraps .help-buttonlet content.
1346
+ They also have class fossil-tooltip. We need an overly-exact
1347
+ selector here to be certain that this class's style overrides
1348
+ that of fossil-tooltip.
1349
+*/
1350
+.fossil-tooltip.help-buttonlet-content {
1351
+ cursor: default;
1352
+ text-align: left;
1353
+ border-style: outset;
1354
+}
12981355
--- src/default.css
+++ src/default.css
@@ -1100,15 +1100,16 @@
1100 .font-size-200 {
1101 font-size: 200%;
1102 }
1103
1104 /**
1105 .input-with-label is intended to be a wrapper element which
1106 contain both a LABEL tag and an INPUT or SELECT control.
1107 The wrapper is "necessary", as opposed to placing the INPUT
1108 in the LABEL, so that we can include multiple INPUT
1109 elements (e.g. a set of radio buttons).
 
1110 */
1111 .input-with-label {
1112 border: 1px inset #808080;
1113 border-radius: 0.25em;
1114 padding: 0.25em 0.4em;
@@ -1251,11 +1252,11 @@
1251 border: 1px solid black;
1252 border-radius: 0.25em;
1253 position: absolute;
1254 display: inline-block;
1255 z-index: 19/*below default skin's hamburger popup*/;
1256 box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.75);
1257 background-color: inherit;
1258 }
1259
1260 .fossil-toast-message {
1261 /* "toast"-style popup message.
@@ -1293,5 +1294,61 @@
1293
1294 blockquote.file-content {
1295 /* file content block in the /file page */
1296 margin: 0 1em;
1297 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1298
--- src/default.css
+++ src/default.css
@@ -1100,15 +1100,16 @@
1100 .font-size-200 {
1101 font-size: 200%;
1102 }
1103
1104 /**
1105 .input-with-label is intended to be a wrapper element which contain
1106 both a LABEL tag and an INPUT or SELECT control. The wrapper is
1107 "necessary", as opposed to placing the INPUT in the LABEL, so that
1108 we can include multiple INPUT elements (e.g. a set of radio
1109 buttons). Note that these elements must sometimes be BLOCK elements
1110 (e.g. DIV) so that certain nesting constructs are legal.
1111 */
1112 .input-with-label {
1113 border: 1px inset #808080;
1114 border-radius: 0.25em;
1115 padding: 0.25em 0.4em;
@@ -1251,11 +1252,11 @@
1252 border: 1px solid black;
1253 border-radius: 0.25em;
1254 position: absolute;
1255 display: inline-block;
1256 z-index: 19/*below default skin's hamburger popup*/;
1257 box-shadow: -0.15em 0.15em 0.2em rgba(0, 0, 0, 0.75);
1258 background-color: inherit;
1259 }
1260
1261 .fossil-toast-message {
1262 /* "toast"-style popup message.
@@ -1293,5 +1294,61 @@
1294
1295 blockquote.file-content {
1296 /* file content block in the /file page */
1297 margin: 0 1em;
1298 }
1299
1300
1301 /**
1302 Circular "help" buttons intended to be placed to the right of
1303 another element and hold text text for it. These typically get
1304 initialized automatically at page startup via
1305 fossil.popupwidget.js, and can be manually initialized/created
1306 using window.fossil.helpButtonlets.setup/create(). All of their
1307 child content (plain text and/or DOM elements) gets moved out of
1308 the DOM and shown in a singleton popup when they are clicked. They
1309 may be SPAN elements if their children are all inline elements,
1310 otherwise they must be DIVs (block elements) so that nesting of
1311 block-element content is legal.
1312 */
1313 .help-buttonlet {
1314 display: inline-block;
1315 min-width: 1rem;
1316 max-width: 1rem;
1317 min-height: 1rem;
1318 max-height: 1rem;
1319 font-size: 0.9em;
1320 border-radius: 0.5rem;
1321 background-color: rgba(54, 54, 255,1);
1322 color: rgb(255, 255, 255);
1323 cursor: pointer;
1324 font-family: monspace;
1325 text-align: center;
1326 margin: 0 0 0 0.35em;
1327 border-width: 1px;
1328 border-style: outset;
1329 font-weight: 700;
1330 overflow: hidden;
1331 }
1332
1333 .help-buttonlet::before {
1334 content: "?";
1335 }
1336
1337 /**
1338 We really want to hide all help text via CSS but CSS cannot select
1339 TEXT nodes. Thus we move them out of the way programmatically
1340 during initialization.
1341 */
1342 .help-buttonlet > *{}
1343
1344 /**
1345 CSS class for PopupWidget which wraps .help-buttonlet content.
1346 They also have class fossil-tooltip. We need an overly-exact
1347 selector here to be certain that this class's style overrides
1348 that of fossil-tooltip.
1349 */
1350 .fossil-tooltip.help-buttonlet-content {
1351 cursor: default;
1352 text-align: left;
1353 border-style: outset;
1354 }
1355
+22 -14
--- src/fileedit.c
+++ src/fileedit.c
@@ -1705,16 +1705,20 @@
17051705
"data-tab-parent='fileedit-tabs' "
17061706
"data-tab-label='File Content' "
17071707
"class='hidden'"
17081708
">");
17091709
CX("<div class='flex-container flex-row child-gap-small'>");
1710
- CX("<button class='fileedit-content-reload confirmer' "
1711
- "title='Reload the file from the server, discarding "
1710
+ CX("<div class='input-with-label'>"
1711
+ "<button class='fileedit-content-reload confirmer' "
1712
+ ">Discard &amp; Reload</button>"
1713
+ "<div class='help-buttonlet'>"
1714
+ "Reload the file from the server, discarding "
17121715
"any local edits. To help avoid accidental loss of "
17131716
"edits, it requires confirmation (a second click) within "
1714
- "a few seconds or it will not reload.'"
1715
- ">Discard &amp; Reload</button>");
1717
+ "a few seconds or it will not reload."
1718
+ "</div>"
1719
+ "</div>");
17161720
style_select_list_int("select-font-size",
17171721
"editor_font_size", "Editor font size",
17181722
NULL/*tooltip*/,
17191723
100,
17201724
"100%", 100, "125%", 125,
@@ -1746,16 +1750,19 @@
17461750
/* ^^^ fossil.page[methodName](content, callback) */
17471751
"data-f-preview-to='#fileedit-tab-preview-wrapper' "
17481752
/* ^^^ dest elem ID */
17491753
">Refresh</button>");
17501754
/* Toggle auto-update of preview when the Preview tab is selected. */
1751
- style_labeled_checkbox("cb-preview-autoupdate",
1752
- NULL,
1753
- "Auto-refresh?",
1754
- "1", 1,
1755
- "If on, the preview will automatically "
1756
- "refresh when this tab is selected.");
1755
+ CX("<div class='input-with-label'>"
1756
+ "<input type='checkbox' value='1' "
1757
+ "id='cb-preview-autorefresh' checked>"
1758
+ "<label for='cb-preview-autorefresh'>Auto-refresh?</label>"
1759
+ "<div class='help-buttonlet'>"
1760
+ "If on, the preview will automatically "
1761
+ "refresh (if needed) when this tab is selected."
1762
+ "</div>"
1763
+ "</div>");
17571764
17581765
/* Default preview rendering mode selection... */
17591766
previewRenderMode = zFileMime
17601767
? ajax_render_mode_for_mimetype(zFileMime)
17611768
: AJAX_RENDER_GUESS;
@@ -1979,23 +1986,24 @@
19791986
"file/check-in combination are discarded.</li>");
19801987
CX("</ul>");
19811988
}
19821989
CX("</div>"/*#fileedit-tab-help*/);
19831990
1984
- builtin_request_js("sbsdiff.js");
1985
- style_emit_fossil_js_apis(0, "fetch", "dom", "tabs", "confirmer",
1986
- "storage", 0);
1987
- builtin_fulfill_js_requests();
1991
+ if(!builtin_bundle_all_fossil_js_apis()){
1992
+ builtin_emit_fossil_js_apis("fetch", "dom", "tabs", "confirmer",
1993
+ "storage", "popupwidget", 0);
1994
+ }
19881995
/*
19891996
** Set up a JS-side mapping of the AJAX_RENDER_xyz values. This is
19901997
** used for dynamically toggling certain UI components on and off.
19911998
** Must come after window.fossil has been intialized and before
19921999
** fossil.page.fileedit.js. Potential TODO: move this into the
19932000
** window.fossil bootstrapping so that we don't have to "fulfill"
19942001
** the JS multiple times.
19952002
*/
19962003
ajax_emit_js_preview_modes(1);
2004
+ builtin_request_js("sbsdiff.js");
19972005
builtin_request_js("fossil.page.fileedit.js");
19982006
builtin_fulfill_js_requests();
19992007
{
20002008
/* Dynamically populate the editor, display any error in the err
20012009
** blob, and/or switch to tab #0, where the file selector
20022010
--- src/fileedit.c
+++ src/fileedit.c
@@ -1705,16 +1705,20 @@
1705 "data-tab-parent='fileedit-tabs' "
1706 "data-tab-label='File Content' "
1707 "class='hidden'"
1708 ">");
1709 CX("<div class='flex-container flex-row child-gap-small'>");
1710 CX("<button class='fileedit-content-reload confirmer' "
1711 "title='Reload the file from the server, discarding "
 
 
 
1712 "any local edits. To help avoid accidental loss of "
1713 "edits, it requires confirmation (a second click) within "
1714 "a few seconds or it will not reload.'"
1715 ">Discard &amp; Reload</button>");
 
1716 style_select_list_int("select-font-size",
1717 "editor_font_size", "Editor font size",
1718 NULL/*tooltip*/,
1719 100,
1720 "100%", 100, "125%", 125,
@@ -1746,16 +1750,19 @@
1746 /* ^^^ fossil.page[methodName](content, callback) */
1747 "data-f-preview-to='#fileedit-tab-preview-wrapper' "
1748 /* ^^^ dest elem ID */
1749 ">Refresh</button>");
1750 /* Toggle auto-update of preview when the Preview tab is selected. */
1751 style_labeled_checkbox("cb-preview-autoupdate",
1752 NULL,
1753 "Auto-refresh?",
1754 "1", 1,
1755 "If on, the preview will automatically "
1756 "refresh when this tab is selected.");
 
 
 
1757
1758 /* Default preview rendering mode selection... */
1759 previewRenderMode = zFileMime
1760 ? ajax_render_mode_for_mimetype(zFileMime)
1761 : AJAX_RENDER_GUESS;
@@ -1979,23 +1986,24 @@
1979 "file/check-in combination are discarded.</li>");
1980 CX("</ul>");
1981 }
1982 CX("</div>"/*#fileedit-tab-help*/);
1983
1984 builtin_request_js("sbsdiff.js");
1985 style_emit_fossil_js_apis(0, "fetch", "dom", "tabs", "confirmer",
1986 "storage", 0);
1987 builtin_fulfill_js_requests();
1988 /*
1989 ** Set up a JS-side mapping of the AJAX_RENDER_xyz values. This is
1990 ** used for dynamically toggling certain UI components on and off.
1991 ** Must come after window.fossil has been intialized and before
1992 ** fossil.page.fileedit.js. Potential TODO: move this into the
1993 ** window.fossil bootstrapping so that we don't have to "fulfill"
1994 ** the JS multiple times.
1995 */
1996 ajax_emit_js_preview_modes(1);
 
1997 builtin_request_js("fossil.page.fileedit.js");
1998 builtin_fulfill_js_requests();
1999 {
2000 /* Dynamically populate the editor, display any error in the err
2001 ** blob, and/or switch to tab #0, where the file selector
2002
--- src/fileedit.c
+++ src/fileedit.c
@@ -1705,16 +1705,20 @@
1705 "data-tab-parent='fileedit-tabs' "
1706 "data-tab-label='File Content' "
1707 "class='hidden'"
1708 ">");
1709 CX("<div class='flex-container flex-row child-gap-small'>");
1710 CX("<div class='input-with-label'>"
1711 "<button class='fileedit-content-reload confirmer' "
1712 ">Discard &amp; Reload</button>"
1713 "<div class='help-buttonlet'>"
1714 "Reload the file from the server, discarding "
1715 "any local edits. To help avoid accidental loss of "
1716 "edits, it requires confirmation (a second click) within "
1717 "a few seconds or it will not reload."
1718 "</div>"
1719 "</div>");
1720 style_select_list_int("select-font-size",
1721 "editor_font_size", "Editor font size",
1722 NULL/*tooltip*/,
1723 100,
1724 "100%", 100, "125%", 125,
@@ -1746,16 +1750,19 @@
1750 /* ^^^ fossil.page[methodName](content, callback) */
1751 "data-f-preview-to='#fileedit-tab-preview-wrapper' "
1752 /* ^^^ dest elem ID */
1753 ">Refresh</button>");
1754 /* Toggle auto-update of preview when the Preview tab is selected. */
1755 CX("<div class='input-with-label'>"
1756 "<input type='checkbox' value='1' "
1757 "id='cb-preview-autorefresh' checked>"
1758 "<label for='cb-preview-autorefresh'>Auto-refresh?</label>"
1759 "<div class='help-buttonlet'>"
1760 "If on, the preview will automatically "
1761 "refresh (if needed) when this tab is selected."
1762 "</div>"
1763 "</div>");
1764
1765 /* Default preview rendering mode selection... */
1766 previewRenderMode = zFileMime
1767 ? ajax_render_mode_for_mimetype(zFileMime)
1768 : AJAX_RENDER_GUESS;
@@ -1979,23 +1986,24 @@
1986 "file/check-in combination are discarded.</li>");
1987 CX("</ul>");
1988 }
1989 CX("</div>"/*#fileedit-tab-help*/);
1990
1991 if(!builtin_bundle_all_fossil_js_apis()){
1992 builtin_emit_fossil_js_apis("fetch", "dom", "tabs", "confirmer",
1993 "storage", "popupwidget", 0);
1994 }
1995 /*
1996 ** Set up a JS-side mapping of the AJAX_RENDER_xyz values. This is
1997 ** used for dynamically toggling certain UI components on and off.
1998 ** Must come after window.fossil has been intialized and before
1999 ** fossil.page.fileedit.js. Potential TODO: move this into the
2000 ** window.fossil bootstrapping so that we don't have to "fulfill"
2001 ** the JS multiple times.
2002 */
2003 ajax_emit_js_preview_modes(1);
2004 builtin_request_js("sbsdiff.js");
2005 builtin_request_js("fossil.page.fileedit.js");
2006 builtin_fulfill_js_requests();
2007 {
2008 /* Dynamically populate the editor, display any error in the err
2009 ** blob, and/or switch to tab #0, where the file selector
2010
+1 -1
--- src/forum.c
+++ src/forum.c
@@ -804,11 +804,11 @@
804804
805805
/* Display the thread. */
806806
forum_display_thread(froot, fpid, mode, bUnf, bHist);
807807
808808
/* Emit Forum Javascript. */
809
- style_emit_script_fossil_bootstrap(1);
809
+ builtin_emit_script_fossil_bootstrap(1);
810810
builtin_request_js("forum.js");
811811
builtin_request_js("fossil.dom.js");
812812
builtin_request_js("fossil.page.forumpost.js");
813813
814814
/* Emit the page style. */
815815
--- src/forum.c
+++ src/forum.c
@@ -804,11 +804,11 @@
804
805 /* Display the thread. */
806 forum_display_thread(froot, fpid, mode, bUnf, bHist);
807
808 /* Emit Forum Javascript. */
809 style_emit_script_fossil_bootstrap(1);
810 builtin_request_js("forum.js");
811 builtin_request_js("fossil.dom.js");
812 builtin_request_js("fossil.page.forumpost.js");
813
814 /* Emit the page style. */
815
--- src/forum.c
+++ src/forum.c
@@ -804,11 +804,11 @@
804
805 /* Display the thread. */
806 forum_display_thread(froot, fpid, mode, bUnf, bHist);
807
808 /* Emit Forum Javascript. */
809 builtin_emit_script_fossil_bootstrap(1);
810 builtin_request_js("forum.js");
811 builtin_request_js("fossil.dom.js");
812 builtin_request_js("fossil.page.forumpost.js");
813
814 /* Emit the page style. */
815
+1 -1
--- src/forum.c
+++ src/forum.c
@@ -804,11 +804,11 @@
804804
805805
/* Display the thread. */
806806
forum_display_thread(froot, fpid, mode, bUnf, bHist);
807807
808808
/* Emit Forum Javascript. */
809
- style_emit_script_fossil_bootstrap(1);
809
+ builtin_emit_script_fossil_bootstrap(1);
810810
builtin_request_js("forum.js");
811811
builtin_request_js("fossil.dom.js");
812812
builtin_request_js("fossil.page.forumpost.js");
813813
814814
/* Emit the page style. */
815815
--- src/forum.c
+++ src/forum.c
@@ -804,11 +804,11 @@
804
805 /* Display the thread. */
806 forum_display_thread(froot, fpid, mode, bUnf, bHist);
807
808 /* Emit Forum Javascript. */
809 style_emit_script_fossil_bootstrap(1);
810 builtin_request_js("forum.js");
811 builtin_request_js("fossil.dom.js");
812 builtin_request_js("fossil.page.forumpost.js");
813
814 /* Emit the page style. */
815
--- src/forum.c
+++ src/forum.c
@@ -804,11 +804,11 @@
804
805 /* Display the thread. */
806 forum_display_thread(froot, fpid, mode, bUnf, bHist);
807
808 /* Emit Forum Javascript. */
809 builtin_emit_script_fossil_bootstrap(1);
810 builtin_request_js("forum.js");
811 builtin_request_js("fossil.dom.js");
812 builtin_request_js("fossil.page.forumpost.js");
813
814 /* Emit the page style. */
815
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -5,18 +5,18 @@
55
*/
66
if(typeof window.CustomEvent === "function") return false;
77
window.CustomEvent = function(event, params) {
88
if(!params) params = {bubbles: false, cancelable: false, detail: null};
99
const evt = document.createEvent('CustomEvent');
10
- evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
10
+ evt.initCustomEvent( event, !!params.bubbles, !!params.cancelable, params.detail );
1111
return evt;
1212
};
1313
})();
1414
(function(global){
15
- /* Bootstrapping bits for the global.fossil object. Must be
16
- loaded after style.c:style_emit_script_tag() has initialized
17
- that object.
15
+ /* Bootstrapping bits for the global.fossil object. Must be loaded
16
+ after style.c:builtin_emit_script_fossil_bootstrap() has
17
+ initialized that object.
1818
*/
1919
2020
const F = global.fossil;
2121
2222
/**
@@ -328,10 +328,18 @@
328328
*/
329329
F.onPageLoad = function(callback){
330330
window.addEventListener('load', callback, false);
331331
return this;
332332
};
333
+ /**
334
+ Convenience wrapper which adds a DOMContentLoadedevent listener
335
+ to the window object. Returns this.
336
+ */
337
+ F.onDOMContentLoaded = function(callback){
338
+ window.addEventListener('DOMContentLoaded', callback, false);
339
+ return this;
340
+ };
333341
334342
/**
335343
Assuming name is a repo-style filename, this function returns
336344
a shortened form of that name:
337345
338346
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -5,18 +5,18 @@
5 */
6 if(typeof window.CustomEvent === "function") return false;
7 window.CustomEvent = function(event, params) {
8 if(!params) params = {bubbles: false, cancelable: false, detail: null};
9 const evt = document.createEvent('CustomEvent');
10 evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
11 return evt;
12 };
13 })();
14 (function(global){
15 /* Bootstrapping bits for the global.fossil object. Must be
16 loaded after style.c:style_emit_script_tag() has initialized
17 that object.
18 */
19
20 const F = global.fossil;
21
22 /**
@@ -328,10 +328,18 @@
328 */
329 F.onPageLoad = function(callback){
330 window.addEventListener('load', callback, false);
331 return this;
332 };
 
 
 
 
 
 
 
 
333
334 /**
335 Assuming name is a repo-style filename, this function returns
336 a shortened form of that name:
337
338
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -5,18 +5,18 @@
5 */
6 if(typeof window.CustomEvent === "function") return false;
7 window.CustomEvent = function(event, params) {
8 if(!params) params = {bubbles: false, cancelable: false, detail: null};
9 const evt = document.createEvent('CustomEvent');
10 evt.initCustomEvent( event, !!params.bubbles, !!params.cancelable, params.detail );
11 return evt;
12 };
13 })();
14 (function(global){
15 /* Bootstrapping bits for the global.fossil object. Must be loaded
16 after style.c:builtin_emit_script_fossil_bootstrap() has
17 initialized that object.
18 */
19
20 const F = global.fossil;
21
22 /**
@@ -328,10 +328,18 @@
328 */
329 F.onPageLoad = function(callback){
330 window.addEventListener('load', callback, false);
331 return this;
332 };
333 /**
334 Convenience wrapper which adds a DOMContentLoadedevent listener
335 to the window object. Returns this.
336 */
337 F.onDOMContentLoaded = function(callback){
338 window.addEventListener('DOMContentLoaded', callback, false);
339 return this;
340 };
341
342 /**
343 Assuming name is a repo-style filename, this function returns
344 a shortened form of that name:
345
346
--- src/fossil.numbered-lines.js
+++ src/fossil.numbered-lines.js
@@ -6,19 +6,19 @@
66
77
Requires: fossil.bootstrap, fossil.dom, fossil.popupwidget,
88
fossil.copybutton
99
*/
1010
var tbl = arg || document.querySelectorAll('table.numbered-lines');
11
- if(!tbl) return /* no matching elements */;
12
- else if(!arg){
11
+ if(tbl && !arg){
1312
if(tbl.length>1){ /* multiple query results: recurse */
1413
tbl.forEach( (t)=>callee(t) );
1514
return;
1615
}else{/* single query result */
1716
tbl = tbl[0];
1817
}
1918
}
19
+ if(!tbl) return /* no matching elements */;
2020
const F = window.fossil, D = F.dom;
2121
const tdLn = tbl.querySelector('td.line-numbers');
2222
const lineState = {
2323
urlArgs: (window.location.search||'?')
2424
.replace(/&?\budc=[^&]*/,'') /* "update display prefs cookie" */
2525
--- src/fossil.numbered-lines.js
+++ src/fossil.numbered-lines.js
@@ -6,19 +6,19 @@
6
7 Requires: fossil.bootstrap, fossil.dom, fossil.popupwidget,
8 fossil.copybutton
9 */
10 var tbl = arg || document.querySelectorAll('table.numbered-lines');
11 if(!tbl) return /* no matching elements */;
12 else if(!arg){
13 if(tbl.length>1){ /* multiple query results: recurse */
14 tbl.forEach( (t)=>callee(t) );
15 return;
16 }else{/* single query result */
17 tbl = tbl[0];
18 }
19 }
 
20 const F = window.fossil, D = F.dom;
21 const tdLn = tbl.querySelector('td.line-numbers');
22 const lineState = {
23 urlArgs: (window.location.search||'?')
24 .replace(/&?\budc=[^&]*/,'') /* "update display prefs cookie" */
25
--- src/fossil.numbered-lines.js
+++ src/fossil.numbered-lines.js
@@ -6,19 +6,19 @@
6
7 Requires: fossil.bootstrap, fossil.dom, fossil.popupwidget,
8 fossil.copybutton
9 */
10 var tbl = arg || document.querySelectorAll('table.numbered-lines');
11 if(tbl && !arg){
 
12 if(tbl.length>1){ /* multiple query results: recurse */
13 tbl.forEach( (t)=>callee(t) );
14 return;
15 }else{/* single query result */
16 tbl = tbl[0];
17 }
18 }
19 if(!tbl) return /* no matching elements */;
20 const F = window.fossil, D = F.dom;
21 const tdLn = tbl.querySelector('td.line-numbers');
22 const lineState = {
23 urlArgs: (window.location.search||'?')
24 .replace(/&?\budc=[^&]*/,'') /* "update display prefs cookie" */
25
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -464,23 +464,26 @@
464464
const wrapper = D.addClass(
465465
D.attr(D.div(),'id','fileedit-stash-selector'),
466466
'input-with-label'
467467
);
468468
const sel = this.e.select = D.select();
469
- const btnClear = this.e.btnClear
470
- = D.button("Discard Edits");
469
+ const btnClear = this.e.btnClear = D.button("Discard Edits"),
470
+ btnHelp = D.append(
471
+ D.addClass(D.div(), "help-buttonlet"),
472
+ 'Locally-edited files. Timestamps are the last local edit time. ',
473
+ 'Only the ',P.config.defaultMaxStashSize,' most recent files ',
474
+ 'are retained. Saving or reloading a file removes it from this list. ',
475
+ D.append(D.code(),F.storage.storageImplName()),
476
+ ' = ',F.storage.storageHelpDescription()
477
+ );
478
+
471479
D.append(wrapper, "Local edits (",
472480
D.append(D.code(),
473481
F.storage.storageImplName()),
474482
"):",
475
- sel, btnClear);
476
- D.attr(wrapper, "title", [
477
- 'Locally-edited files. Timestamps are the last local edit time.',
478
- 'Only the',P.config.defaultMaxStashSize,'most recent checkin/file',
479
- 'combinations are retained.',
480
- 'Committing or reloading a file removes it from this list.'
481
- ].join(' '));
483
+ btnHelp, sel, btnClear);
484
+ F.helpButtonlets.setup(btnHelp);
482485
D.option(D.disable(sel), "(empty)");
483486
F.page.addEventListener('fileedit-stash-updated',(e)=>this.updateList(e.detail));
484487
F.page.addEventListener('fileedit-file-loaded',(e)=>this.updateList($stash, e.detail));
485488
sel.addEventListener('change',function(e){
486489
const opt = this.selectedOptions[0];
@@ -492,10 +495,11 @@
492495
"Warning: persistent storage is not available, "+
493496
"so uncomitted edits will not survive a page reload."
494497
));
495498
}
496499
domInsertPoint.parentNode.insertBefore(wrapper, domInsertPoint);
500
+ P.tabs.switchToTab(1/*DOM visibility workaround*/);
497501
F.confirmer(btnClear, {
498502
/* must come after insertion into the DOM for the pinSize option to work. */
499503
pinSize: true,
500504
confirmText: "DISCARD all local edits?",
501505
onconfirm: function(e){
@@ -509,10 +513,11 @@
509513
},
510514
ticks: F.config.confirmerButtonTicks
511515
});
512516
D.addClass(this.e.btnClear,'hidden' /* must not be set until after confirmer is set up!*/);
513517
$stash._fireStashEvent(/*read the page-load-time stash*/);
518
+ P.tabs.switchToTab(0/*DOM visibility workaround*/);
514519
delete this.init;
515520
},
516521
/**
517522
Regenerates the edit selection list.
518523
*/
@@ -654,11 +659,11 @@
654659
selectEolWrap: E('#select-eol-style'),
655660
selectEol: E('#select-eol-style select[name=eol]'),
656661
selectFontSizeWrap: E('#select-font-size'),
657662
selectDiffWS: E('select[name=diff_ws]'),
658663
cbLineNumbersWrap: E('#cb-line-numbers'),
659
- cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'),
664
+ cbAutoPreview: E('#cb-preview-autorefresh'),
660665
previewTarget: E('#fileedit-tab-preview-wrapper'),
661666
manifestTarget: E('#fileedit-manifest'),
662667
diffTarget: E('#fileedit-tab-diff-wrapper'),
663668
cbIsExe: E('input[type=checkbox][name=exec_bit]'),
664669
cbManifest: E('input[type=checkbox][name=include_manifest]'),
@@ -737,10 +742,11 @@
737742
"click",(e)=>P.diff(false), false
738743
);
739744
P.e.btnCommit.addEventListener(
740745
"click",(e)=>P.commit(), false
741746
);
747
+ P.tabs.switchToTab(1/*DOM visibility workaround*/);
742748
F.confirmer(P.e.btnReload, {
743749
pinSize: true,
744750
confirmText: "Really reload, losing edits?",
745751
onconfirm: (e)=>P.unstashContent().loadFile(),
746752
ticks: F.config.confirmerButtonTicks
747753
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -464,23 +464,26 @@
464 const wrapper = D.addClass(
465 D.attr(D.div(),'id','fileedit-stash-selector'),
466 'input-with-label'
467 );
468 const sel = this.e.select = D.select();
469 const btnClear = this.e.btnClear
470 = D.button("Discard Edits");
 
 
 
 
 
 
 
 
471 D.append(wrapper, "Local edits (",
472 D.append(D.code(),
473 F.storage.storageImplName()),
474 "):",
475 sel, btnClear);
476 D.attr(wrapper, "title", [
477 'Locally-edited files. Timestamps are the last local edit time.',
478 'Only the',P.config.defaultMaxStashSize,'most recent checkin/file',
479 'combinations are retained.',
480 'Committing or reloading a file removes it from this list.'
481 ].join(' '));
482 D.option(D.disable(sel), "(empty)");
483 F.page.addEventListener('fileedit-stash-updated',(e)=>this.updateList(e.detail));
484 F.page.addEventListener('fileedit-file-loaded',(e)=>this.updateList($stash, e.detail));
485 sel.addEventListener('change',function(e){
486 const opt = this.selectedOptions[0];
@@ -492,10 +495,11 @@
492 "Warning: persistent storage is not available, "+
493 "so uncomitted edits will not survive a page reload."
494 ));
495 }
496 domInsertPoint.parentNode.insertBefore(wrapper, domInsertPoint);
 
497 F.confirmer(btnClear, {
498 /* must come after insertion into the DOM for the pinSize option to work. */
499 pinSize: true,
500 confirmText: "DISCARD all local edits?",
501 onconfirm: function(e){
@@ -509,10 +513,11 @@
509 },
510 ticks: F.config.confirmerButtonTicks
511 });
512 D.addClass(this.e.btnClear,'hidden' /* must not be set until after confirmer is set up!*/);
513 $stash._fireStashEvent(/*read the page-load-time stash*/);
 
514 delete this.init;
515 },
516 /**
517 Regenerates the edit selection list.
518 */
@@ -654,11 +659,11 @@
654 selectEolWrap: E('#select-eol-style'),
655 selectEol: E('#select-eol-style select[name=eol]'),
656 selectFontSizeWrap: E('#select-font-size'),
657 selectDiffWS: E('select[name=diff_ws]'),
658 cbLineNumbersWrap: E('#cb-line-numbers'),
659 cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'),
660 previewTarget: E('#fileedit-tab-preview-wrapper'),
661 manifestTarget: E('#fileedit-manifest'),
662 diffTarget: E('#fileedit-tab-diff-wrapper'),
663 cbIsExe: E('input[type=checkbox][name=exec_bit]'),
664 cbManifest: E('input[type=checkbox][name=include_manifest]'),
@@ -737,10 +742,11 @@
737 "click",(e)=>P.diff(false), false
738 );
739 P.e.btnCommit.addEventListener(
740 "click",(e)=>P.commit(), false
741 );
 
742 F.confirmer(P.e.btnReload, {
743 pinSize: true,
744 confirmText: "Really reload, losing edits?",
745 onconfirm: (e)=>P.unstashContent().loadFile(),
746 ticks: F.config.confirmerButtonTicks
747
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -464,23 +464,26 @@
464 const wrapper = D.addClass(
465 D.attr(D.div(),'id','fileedit-stash-selector'),
466 'input-with-label'
467 );
468 const sel = this.e.select = D.select();
469 const btnClear = this.e.btnClear = D.button("Discard Edits"),
470 btnHelp = D.append(
471 D.addClass(D.div(), "help-buttonlet"),
472 'Locally-edited files. Timestamps are the last local edit time. ',
473 'Only the ',P.config.defaultMaxStashSize,' most recent files ',
474 'are retained. Saving or reloading a file removes it from this list. ',
475 D.append(D.code(),F.storage.storageImplName()),
476 ' = ',F.storage.storageHelpDescription()
477 );
478
479 D.append(wrapper, "Local edits (",
480 D.append(D.code(),
481 F.storage.storageImplName()),
482 "):",
483 btnHelp, sel, btnClear);
484 F.helpButtonlets.setup(btnHelp);
 
 
 
 
 
485 D.option(D.disable(sel), "(empty)");
486 F.page.addEventListener('fileedit-stash-updated',(e)=>this.updateList(e.detail));
487 F.page.addEventListener('fileedit-file-loaded',(e)=>this.updateList($stash, e.detail));
488 sel.addEventListener('change',function(e){
489 const opt = this.selectedOptions[0];
@@ -492,10 +495,11 @@
495 "Warning: persistent storage is not available, "+
496 "so uncomitted edits will not survive a page reload."
497 ));
498 }
499 domInsertPoint.parentNode.insertBefore(wrapper, domInsertPoint);
500 P.tabs.switchToTab(1/*DOM visibility workaround*/);
501 F.confirmer(btnClear, {
502 /* must come after insertion into the DOM for the pinSize option to work. */
503 pinSize: true,
504 confirmText: "DISCARD all local edits?",
505 onconfirm: function(e){
@@ -509,10 +513,11 @@
513 },
514 ticks: F.config.confirmerButtonTicks
515 });
516 D.addClass(this.e.btnClear,'hidden' /* must not be set until after confirmer is set up!*/);
517 $stash._fireStashEvent(/*read the page-load-time stash*/);
518 P.tabs.switchToTab(0/*DOM visibility workaround*/);
519 delete this.init;
520 },
521 /**
522 Regenerates the edit selection list.
523 */
@@ -654,11 +659,11 @@
659 selectEolWrap: E('#select-eol-style'),
660 selectEol: E('#select-eol-style select[name=eol]'),
661 selectFontSizeWrap: E('#select-font-size'),
662 selectDiffWS: E('select[name=diff_ws]'),
663 cbLineNumbersWrap: E('#cb-line-numbers'),
664 cbAutoPreview: E('#cb-preview-autorefresh'),
665 previewTarget: E('#fileedit-tab-preview-wrapper'),
666 manifestTarget: E('#fileedit-manifest'),
667 diffTarget: E('#fileedit-tab-diff-wrapper'),
668 cbIsExe: E('input[type=checkbox][name=exec_bit]'),
669 cbManifest: E('input[type=checkbox][name=include_manifest]'),
@@ -737,10 +742,11 @@
742 "click",(e)=>P.diff(false), false
743 );
744 P.e.btnCommit.addEventListener(
745 "click",(e)=>P.commit(), false
746 );
747 P.tabs.switchToTab(1/*DOM visibility workaround*/);
748 F.confirmer(P.e.btnReload, {
749 pinSize: true,
750 confirmText: "Really reload, losing edits?",
751 onconfirm: (e)=>P.unstashContent().loadFile(),
752 ticks: F.config.confirmerButtonTicks
753
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -2,11 +2,11 @@
22
"use strict";
33
/**
44
Client-side implementation of the /wikiedit app. Requires that
55
the fossil JS bootstrapping is complete and that several fossil
66
JS APIs have been installed: fossil.fetch, fossil.dom,
7
- fossil.tabs, fossil.storage, fossil.confirmer.
7
+ fossil.tabs, fossil.storage, fossil.confirmer, fossil.popupwidget.
88
99
Custom events which can be listened for via
1010
fossil.page.addEventListener():
1111
1212
- Event 'wiki-page-loaded': passes on information when it
@@ -557,11 +557,11 @@
557557
D.attr(sel, 'size', 12);
558558
D.option(D.disable(D.clearElement(sel)), "Loading...");
559559
560560
/** Set up filter checkboxes for the various types
561561
of wiki pages... */
562
- const fsFilter = D.fieldset("Page types"),
562
+ const fsFilter = D.addClass(D.fieldset("Page types"),"page-types-list"),
563563
fsFilterBody = D.div(),
564564
filters = ['normal', 'branch/...', 'tag/...', 'checkin/...']
565565
;
566566
D.append(fsFilter, fsFilterBody);
567567
D.addClass(fsFilterBody, 'flex-container', 'flex-column', 'stretch');
@@ -597,13 +597,18 @@
597597
'deleted'),
598598
'for', cbId),
599599
cb = D.attr(D.input('checkbox'), 'id', cbId);
600600
cb.checked = false;
601601
D.addClass(parentElem,'hide-deleted');
602
- D.attr(lbl, 'title',
603
- 'Fossil considers empty pages to be "deleted" in some contexts.');
604
- D.append(fsFilterBody, D.append(D.span(), cb, lbl));
602
+ D.attr(lbl);
603
+ const deletedTip = F.helpButtonlets.create(
604
+ D.span(),
605
+ 'Fossil considers empty pages to be "deleted" in some contexts.'
606
+ );
607
+ D.append(fsFilterBody, D.append(
608
+ D.span(), cb, lbl, deletedTip
609
+ ));
605610
cb.addEventListener(
606611
'change',
607612
function(ev){
608613
if(ev.target.checked) D.removeClass(parentElem,'hide-deleted');
609614
else D.addClass(parentElem,'hide-deleted');
@@ -682,22 +687,26 @@
682687
init: function(domInsertPoint/*insert widget BEFORE this element*/){
683688
const wrapper = D.addClass(
684689
D.attr(D.div(),'id','wikiedit-stash-selector'),
685690
'input-with-label'
686691
);
687
- const sel = this.e.select = D.select();
688
- const btnClear = this.e.btnClear = D.button("Discard Edits");
692
+ const sel = this.e.select = D.select(),
693
+ btnClear = this.e.btnClear = D.button("Discard Edits"),
694
+ btnHelp = D.append(
695
+ D.addClass(D.div(), "help-buttonlet"),
696
+ 'Locally-edited wiki pages. Timestamps are the last local edit time. ',
697
+ 'Only the ',P.config.defaultMaxStashSize,' most recent pages ',
698
+ 'are retained. Saving or reloading a file removes it from this list. ',
699
+ D.append(D.code(),F.storage.storageImplName()),
700
+ ' = ',F.storage.storageHelpDescription()
701
+ );
689702
D.append(wrapper, "Local edits (",
690703
D.append(D.code(),
691704
F.storage.storageImplName()),
692705
"):",
693
- sel, btnClear);
694
- D.attr(wrapper, "title", [
695
- 'Locally-edited wiki pages. Timestamps are the last local edit time.',
696
- 'Only the',P.config.defaultMaxStashSize,'most recent pages',
697
- 'are retained. Saving or reloading a file removes it from this list.'
698
- ].join(' '));
706
+ btnHelp, sel, btnClear);
707
+ F.helpButtonlets.setup(btnHelp);
699708
D.option(D.disable(sel), "(empty)");
700709
P.addEventListener('wiki-stash-updated',(e)=>this.updateList(e.detail));
701710
P.addEventListener('wiki-page-loaded',(e)=>this.updateList($stash, e.detail));
702711
sel.addEventListener('change',function(e){
703712
const opt = this.selectedOptions[0];
@@ -835,17 +844,15 @@
835844
P.base.originalHref = P.base.tag.href;
836845
P.e = { /* various DOM elements we work with... */
837846
taEditor: E('#wikiedit-content-editor'),
838847
btnReload: E("#wikiedit-tab-content button.wikiedit-content-reload"),
839848
btnSave: E("button.wikiedit-save"),
840
- btnSaveClose: D.attr(E("button.wikiedit-save-close"),
841
- 'title',
842
- 'Save changes and return to the wiki reader.'),
849
+ btnSaveClose: E("button.wikiedit-save-close"),
843850
selectMimetype: E('select[name=mimetype]'),
844851
selectFontSizeWrap: E('#select-font-size'),
845852
// selectDiffWS: E('select[name=diff_ws]'),
846
- cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'),
853
+ cbAutoPreview: E('#cb-preview-autorefresh'),
847854
previewTarget: E('#wikiedit-tab-preview-wrapper'),
848855
diffTarget: E('#wikiedit-tab-diff-wrapper'),
849856
editStatus: E('#wikiedit-edit-status'),
850857
tabContainer: E('#wikiedit-tabs'),
851858
tabs:{
@@ -870,12 +877,12 @@
870877
'before-switch-to', function(ev){
871878
const theTab = ev.detail, btnSlot = theTab.querySelector('.save-button-slot');
872879
if(btnSlot){
873880
/* Several places make sense for a save button, so we'll
874881
move that button around to those tabs where it makes sense. */
875
- btnSlot.parentNode.insertBefore( P.e.btnSave, btnSlot );
876
- btnSlot.parentNode.insertBefore( P.e.btnSaveClose, btnSlot );
882
+ btnSlot.parentNode.insertBefore( P.e.btnSave.parentNode, btnSlot );
883
+ btnSlot.parentNode.insertBefore( P.e.btnSaveClose.parentNode, btnSlot );
877884
P.updateSaveButton();
878885
}
879886
if(theTab===P.e.tabs.preview){
880887
P.baseHrefForWiki();
881888
if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview();
@@ -958,10 +965,11 @@
958965
F.message("Discarded new page ["+w.name+"].");
959966
}
960967
};
961968
962969
if(P.config.useConfirmerButtons.reload){
970
+ P.tabs.switchToTab(1/*DOM visibility workaround*/);
963971
F.confirmer(P.e.btnReload, {
964972
pinSize: true,
965973
confirmText: "Really reload, losing edits?",
966974
onconfirm: doReload,
967975
ticks: F.config.confirmerButtonTicks
@@ -968,10 +976,11 @@
968976
});
969977
}else{
970978
P.e.btnReload.addEventListener('click', doReload, false);
971979
}
972980
if(P.config.useConfirmerButtons.save){
981
+ P.tabs.switchToTab(1/*DOM visibility workaround*/);
973982
F.confirmer(P.e.btnSave, {
974983
pinSize: true,
975984
confirmText: "Really save changes?",
976985
onconfirm: ()=>doSave(),
977986
ticks: F.config.confirmerButtonTicks
@@ -1069,13 +1078,19 @@
10691078
}
10701079
P.updatePageTitle().updateSaveButton(/* b/c save() routes through here */);
10711080
},
10721081
false
10731082
);
1074
- /* These init()s need to come after P's event handlers are registered */
1083
+ /* These init()s need to come after P's event handlers are registered.
1084
+ The tab-switching is a workaround for the pinSize option of the confirmer widgets:
1085
+ it does not work if the confirmer button being initialized is in a hidden
1086
+ part of the DOM :/. */
1087
+ P.tabs.switchToTab(0);
10751088
WikiList.init( P.e.tabs.pageList.firstElementChild );
1089
+ P.tabs.switchToTab(1);
10761090
P.stashWidget.init(P.e.tabs.content.lastElementChild);
1091
+ P.tabs.switchToTab(0);
10771092
//P.$wikiList = WikiList/*only for testing/debugging*/;
10781093
}/*F.onPageLoad()*/);
10791094
10801095
/**
10811096
Returns true if fossil.page.winfo is set, indicating that a page
10821097
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -2,11 +2,11 @@
2 "use strict";
3 /**
4 Client-side implementation of the /wikiedit app. Requires that
5 the fossil JS bootstrapping is complete and that several fossil
6 JS APIs have been installed: fossil.fetch, fossil.dom,
7 fossil.tabs, fossil.storage, fossil.confirmer.
8
9 Custom events which can be listened for via
10 fossil.page.addEventListener():
11
12 - Event 'wiki-page-loaded': passes on information when it
@@ -557,11 +557,11 @@
557 D.attr(sel, 'size', 12);
558 D.option(D.disable(D.clearElement(sel)), "Loading...");
559
560 /** Set up filter checkboxes for the various types
561 of wiki pages... */
562 const fsFilter = D.fieldset("Page types"),
563 fsFilterBody = D.div(),
564 filters = ['normal', 'branch/...', 'tag/...', 'checkin/...']
565 ;
566 D.append(fsFilter, fsFilterBody);
567 D.addClass(fsFilterBody, 'flex-container', 'flex-column', 'stretch');
@@ -597,13 +597,18 @@
597 'deleted'),
598 'for', cbId),
599 cb = D.attr(D.input('checkbox'), 'id', cbId);
600 cb.checked = false;
601 D.addClass(parentElem,'hide-deleted');
602 D.attr(lbl, 'title',
603 'Fossil considers empty pages to be "deleted" in some contexts.');
604 D.append(fsFilterBody, D.append(D.span(), cb, lbl));
 
 
 
 
 
605 cb.addEventListener(
606 'change',
607 function(ev){
608 if(ev.target.checked) D.removeClass(parentElem,'hide-deleted');
609 else D.addClass(parentElem,'hide-deleted');
@@ -682,22 +687,26 @@
682 init: function(domInsertPoint/*insert widget BEFORE this element*/){
683 const wrapper = D.addClass(
684 D.attr(D.div(),'id','wikiedit-stash-selector'),
685 'input-with-label'
686 );
687 const sel = this.e.select = D.select();
688 const btnClear = this.e.btnClear = D.button("Discard Edits");
 
 
 
 
 
 
 
 
689 D.append(wrapper, "Local edits (",
690 D.append(D.code(),
691 F.storage.storageImplName()),
692 "):",
693 sel, btnClear);
694 D.attr(wrapper, "title", [
695 'Locally-edited wiki pages. Timestamps are the last local edit time.',
696 'Only the',P.config.defaultMaxStashSize,'most recent pages',
697 'are retained. Saving or reloading a file removes it from this list.'
698 ].join(' '));
699 D.option(D.disable(sel), "(empty)");
700 P.addEventListener('wiki-stash-updated',(e)=>this.updateList(e.detail));
701 P.addEventListener('wiki-page-loaded',(e)=>this.updateList($stash, e.detail));
702 sel.addEventListener('change',function(e){
703 const opt = this.selectedOptions[0];
@@ -835,17 +844,15 @@
835 P.base.originalHref = P.base.tag.href;
836 P.e = { /* various DOM elements we work with... */
837 taEditor: E('#wikiedit-content-editor'),
838 btnReload: E("#wikiedit-tab-content button.wikiedit-content-reload"),
839 btnSave: E("button.wikiedit-save"),
840 btnSaveClose: D.attr(E("button.wikiedit-save-close"),
841 'title',
842 'Save changes and return to the wiki reader.'),
843 selectMimetype: E('select[name=mimetype]'),
844 selectFontSizeWrap: E('#select-font-size'),
845 // selectDiffWS: E('select[name=diff_ws]'),
846 cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'),
847 previewTarget: E('#wikiedit-tab-preview-wrapper'),
848 diffTarget: E('#wikiedit-tab-diff-wrapper'),
849 editStatus: E('#wikiedit-edit-status'),
850 tabContainer: E('#wikiedit-tabs'),
851 tabs:{
@@ -870,12 +877,12 @@
870 'before-switch-to', function(ev){
871 const theTab = ev.detail, btnSlot = theTab.querySelector('.save-button-slot');
872 if(btnSlot){
873 /* Several places make sense for a save button, so we'll
874 move that button around to those tabs where it makes sense. */
875 btnSlot.parentNode.insertBefore( P.e.btnSave, btnSlot );
876 btnSlot.parentNode.insertBefore( P.e.btnSaveClose, btnSlot );
877 P.updateSaveButton();
878 }
879 if(theTab===P.e.tabs.preview){
880 P.baseHrefForWiki();
881 if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview();
@@ -958,10 +965,11 @@
958 F.message("Discarded new page ["+w.name+"].");
959 }
960 };
961
962 if(P.config.useConfirmerButtons.reload){
 
963 F.confirmer(P.e.btnReload, {
964 pinSize: true,
965 confirmText: "Really reload, losing edits?",
966 onconfirm: doReload,
967 ticks: F.config.confirmerButtonTicks
@@ -968,10 +976,11 @@
968 });
969 }else{
970 P.e.btnReload.addEventListener('click', doReload, false);
971 }
972 if(P.config.useConfirmerButtons.save){
 
973 F.confirmer(P.e.btnSave, {
974 pinSize: true,
975 confirmText: "Really save changes?",
976 onconfirm: ()=>doSave(),
977 ticks: F.config.confirmerButtonTicks
@@ -1069,13 +1078,19 @@
1069 }
1070 P.updatePageTitle().updateSaveButton(/* b/c save() routes through here */);
1071 },
1072 false
1073 );
1074 /* These init()s need to come after P's event handlers are registered */
 
 
 
 
1075 WikiList.init( P.e.tabs.pageList.firstElementChild );
 
1076 P.stashWidget.init(P.e.tabs.content.lastElementChild);
 
1077 //P.$wikiList = WikiList/*only for testing/debugging*/;
1078 }/*F.onPageLoad()*/);
1079
1080 /**
1081 Returns true if fossil.page.winfo is set, indicating that a page
1082
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -2,11 +2,11 @@
2 "use strict";
3 /**
4 Client-side implementation of the /wikiedit app. Requires that
5 the fossil JS bootstrapping is complete and that several fossil
6 JS APIs have been installed: fossil.fetch, fossil.dom,
7 fossil.tabs, fossil.storage, fossil.confirmer, fossil.popupwidget.
8
9 Custom events which can be listened for via
10 fossil.page.addEventListener():
11
12 - Event 'wiki-page-loaded': passes on information when it
@@ -557,11 +557,11 @@
557 D.attr(sel, 'size', 12);
558 D.option(D.disable(D.clearElement(sel)), "Loading...");
559
560 /** Set up filter checkboxes for the various types
561 of wiki pages... */
562 const fsFilter = D.addClass(D.fieldset("Page types"),"page-types-list"),
563 fsFilterBody = D.div(),
564 filters = ['normal', 'branch/...', 'tag/...', 'checkin/...']
565 ;
566 D.append(fsFilter, fsFilterBody);
567 D.addClass(fsFilterBody, 'flex-container', 'flex-column', 'stretch');
@@ -597,13 +597,18 @@
597 'deleted'),
598 'for', cbId),
599 cb = D.attr(D.input('checkbox'), 'id', cbId);
600 cb.checked = false;
601 D.addClass(parentElem,'hide-deleted');
602 D.attr(lbl);
603 const deletedTip = F.helpButtonlets.create(
604 D.span(),
605 'Fossil considers empty pages to be "deleted" in some contexts.'
606 );
607 D.append(fsFilterBody, D.append(
608 D.span(), cb, lbl, deletedTip
609 ));
610 cb.addEventListener(
611 'change',
612 function(ev){
613 if(ev.target.checked) D.removeClass(parentElem,'hide-deleted');
614 else D.addClass(parentElem,'hide-deleted');
@@ -682,22 +687,26 @@
687 init: function(domInsertPoint/*insert widget BEFORE this element*/){
688 const wrapper = D.addClass(
689 D.attr(D.div(),'id','wikiedit-stash-selector'),
690 'input-with-label'
691 );
692 const sel = this.e.select = D.select(),
693 btnClear = this.e.btnClear = D.button("Discard Edits"),
694 btnHelp = D.append(
695 D.addClass(D.div(), "help-buttonlet"),
696 'Locally-edited wiki pages. Timestamps are the last local edit time. ',
697 'Only the ',P.config.defaultMaxStashSize,' most recent pages ',
698 'are retained. Saving or reloading a file removes it from this list. ',
699 D.append(D.code(),F.storage.storageImplName()),
700 ' = ',F.storage.storageHelpDescription()
701 );
702 D.append(wrapper, "Local edits (",
703 D.append(D.code(),
704 F.storage.storageImplName()),
705 "):",
706 btnHelp, sel, btnClear);
707 F.helpButtonlets.setup(btnHelp);
 
 
 
 
708 D.option(D.disable(sel), "(empty)");
709 P.addEventListener('wiki-stash-updated',(e)=>this.updateList(e.detail));
710 P.addEventListener('wiki-page-loaded',(e)=>this.updateList($stash, e.detail));
711 sel.addEventListener('change',function(e){
712 const opt = this.selectedOptions[0];
@@ -835,17 +844,15 @@
844 P.base.originalHref = P.base.tag.href;
845 P.e = { /* various DOM elements we work with... */
846 taEditor: E('#wikiedit-content-editor'),
847 btnReload: E("#wikiedit-tab-content button.wikiedit-content-reload"),
848 btnSave: E("button.wikiedit-save"),
849 btnSaveClose: E("button.wikiedit-save-close"),
 
 
850 selectMimetype: E('select[name=mimetype]'),
851 selectFontSizeWrap: E('#select-font-size'),
852 // selectDiffWS: E('select[name=diff_ws]'),
853 cbAutoPreview: E('#cb-preview-autorefresh'),
854 previewTarget: E('#wikiedit-tab-preview-wrapper'),
855 diffTarget: E('#wikiedit-tab-diff-wrapper'),
856 editStatus: E('#wikiedit-edit-status'),
857 tabContainer: E('#wikiedit-tabs'),
858 tabs:{
@@ -870,12 +877,12 @@
877 'before-switch-to', function(ev){
878 const theTab = ev.detail, btnSlot = theTab.querySelector('.save-button-slot');
879 if(btnSlot){
880 /* Several places make sense for a save button, so we'll
881 move that button around to those tabs where it makes sense. */
882 btnSlot.parentNode.insertBefore( P.e.btnSave.parentNode, btnSlot );
883 btnSlot.parentNode.insertBefore( P.e.btnSaveClose.parentNode, btnSlot );
884 P.updateSaveButton();
885 }
886 if(theTab===P.e.tabs.preview){
887 P.baseHrefForWiki();
888 if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview();
@@ -958,10 +965,11 @@
965 F.message("Discarded new page ["+w.name+"].");
966 }
967 };
968
969 if(P.config.useConfirmerButtons.reload){
970 P.tabs.switchToTab(1/*DOM visibility workaround*/);
971 F.confirmer(P.e.btnReload, {
972 pinSize: true,
973 confirmText: "Really reload, losing edits?",
974 onconfirm: doReload,
975 ticks: F.config.confirmerButtonTicks
@@ -968,10 +976,11 @@
976 });
977 }else{
978 P.e.btnReload.addEventListener('click', doReload, false);
979 }
980 if(P.config.useConfirmerButtons.save){
981 P.tabs.switchToTab(1/*DOM visibility workaround*/);
982 F.confirmer(P.e.btnSave, {
983 pinSize: true,
984 confirmText: "Really save changes?",
985 onconfirm: ()=>doSave(),
986 ticks: F.config.confirmerButtonTicks
@@ -1069,13 +1078,19 @@
1078 }
1079 P.updatePageTitle().updateSaveButton(/* b/c save() routes through here */);
1080 },
1081 false
1082 );
1083 /* These init()s need to come after P's event handlers are registered.
1084 The tab-switching is a workaround for the pinSize option of the confirmer widgets:
1085 it does not work if the confirmer button being initialized is in a hidden
1086 part of the DOM :/. */
1087 P.tabs.switchToTab(0);
1088 WikiList.init( P.e.tabs.pageList.firstElementChild );
1089 P.tabs.switchToTab(1);
1090 P.stashWidget.init(P.e.tabs.content.lastElementChild);
1091 P.tabs.switchToTab(0);
1092 //P.$wikiList = WikiList/*only for testing/debugging*/;
1093 }/*F.onPageLoad()*/);
1094
1095 /**
1096 Returns true if fossil.page.winfo is set, indicating that a page
1097
--- src/fossil.popupwidget.js
+++ src/fossil.popupwidget.js
@@ -264,6 +264,131 @@
264264
error: function(/*...*/){
265265
return toastImpl('error',2,arguments);
266266
}
267267
}/*F.toast*/;
268268
269
+
270
+ F.helpButtonlets = {
271
+ /**
272
+ Initializes one or more "help buttonlets". It may be passed any of:
273
+
274
+ - A string: CSS selector (multiple matches are legal)
275
+
276
+ - A single DOM element.
277
+
278
+ - A forEach-compatible container of DOM elements.
279
+
280
+ - No arguments, which is equivalent to passing the string
281
+ ".help-buttonlet:not(.processed)".
282
+
283
+ Passing the same element(s) more than once is a no-op: during
284
+ initialization, each elements get the class'processed' added to
285
+ it, and any elements with that class are skipped.
286
+
287
+ All child nodes of a help buttonlet are removed from the button
288
+ during initialization and stashed away for use in a PopupWidget
289
+ when the botton is clicked.
290
+
291
+ */
292
+ setup: function f(){
293
+ if(!f.hasOwnProperty('clickHandler')){
294
+ f.clickHandler = function fch(ev){
295
+ if(!fch.popup){
296
+ fch.popup = new F.PopupWidget({
297
+ cssClass: ['fossil-tooltip', 'help-buttonlet-content'],
298
+ refresh: function(){
299
+ }
300
+ });
301
+ fch.popup.e.style.maxWidth = '80%'/*of body*/;
302
+ const hide = ()=>fch.popup.hide();
303
+ fch.popup.e.addEventListener('click', hide, false);
304
+ document.body.addEventListener('click', hide, true);
305
+ document.body.addEventListener('keydown', function(ev){
306
+ if(fch.popup.isShown() && 27===ev.which){
307
+ fch.popup.hide();
308
+ }
309
+ }, true);
310
+ }
311
+ D.append(D.clearElement(fch.popup.e), ev.target.$helpContent);
312
+ var popupRect = ev.target.getClientRects()[0];
313
+ var x = popupRect.left, y = popupRect.top;
314
+ if(x<0) x = 0;
315
+ if(y<0) y = 0;
316
+ /* Shift the help around a bit to "better" fit the
317
+ screen. However, fch.popup.e.getClientRects() is empty
318
+ until the popup is shown, so we have to show it,
319
+ calculate the resulting size, then move and/or resize it.
320
+
321
+ This algorithm/these heuristics can certainly be improved
322
+ upon.
323
+ */
324
+ fch.popup.show(x, y);
325
+ x = popupRect.left, y = popupRect.top;
326
+ popupRect = fch.popup.e.getBoundingClientRect();
327
+ const rectBody = document.body.getClientRects()[0];
328
+ if(popupRect.right > rectBody.right){
329
+ x -= (popupRect.right - rectBody.right);
330
+ }
331
+ if(x + popupRect.width > rectBody.right){
332
+ x = rectBody.x + (rectBody.width*0.1);
333
+ fch.popup.e.style.minWidth = '70%';
334
+ }else{
335
+ fch.popup.e.style.removeProperty('min-width');
336
+ x -= popupRect.width/2;
337
+ }
338
+ if(x<0) x = 0;
339
+ //console.debug("dimensions",x,y, popupRect, rectBody);
340
+ fch.popup.show(x, y);
341
+ };
342
+ f.foreachElement = function(e){
343
+ if(e.classList.contains('processed')) return;
344
+ e.classList.add('processed');
345
+ e.$helpContent = [];
346
+ /* We have to move all child nodes out of the way because we
347
+ cannot hide TEXT nodes via CSS (which cannot select TEXT
348
+ nodes). We have to do it in two steps to avoid invaliding
349
+ the list during traversal. */
350
+ e.childNodes.forEach((ch)=>e.$helpContent.push(ch));
351
+ e.$helpContent.forEach((ch)=>ch.remove());
352
+ e.addEventListener('click', f.clickHandler, false);
353
+ };
354
+ }/*static init*/
355
+ var elems;
356
+ if(!arguments.length){
357
+ arguments[0] = '.help-buttonlet:not(.processed)';
358
+ arguments.length = 1;
359
+ }
360
+ if(arguments.length){
361
+ if('string'===typeof arguments[0]){
362
+ elems = document.querySelectorAll(arguments[0]);
363
+ }else if(arguments[0] instanceof HTMLElement){
364
+ elems = [arguments[0]];
365
+ }else if(arguments[0].forEach){/* assume DOM element list or array */
366
+ elems = arguments[0];
367
+ }
368
+ }
369
+ if(elems) elems.forEach(f.foreachElement);
370
+ },
371
+
372
+ /**
373
+ Sets up the given element as a "help buttonlet", adding the CSS
374
+ class help-buttonlet to it. Any (optional) arguments after the
375
+ first are appended to the element using fossil.dom.append(), so
376
+ that they become the content for the buttonlet's popup help.
377
+
378
+ The element is then passed to this.setup() before it
379
+ is returned from this function.
380
+ */
381
+ create: function(elem/*...body*/){
382
+ D.addClass(elem, 'help-buttonlet');
383
+ if(arguments.length>1){
384
+ const args = Array.prototype.slice.call(arguments,1);
385
+ D.append(elem, args);
386
+ }
387
+ this.setup(elem);
388
+ return elem;
389
+ }
390
+ }/*helpButtonlets*/;
391
+
392
+ F.onDOMContentLoaded( ()=>F.helpButtonlets.setup() );
393
+
269394
})(window.fossil);
270395
--- src/fossil.popupwidget.js
+++ src/fossil.popupwidget.js
@@ -264,6 +264,131 @@
264 error: function(/*...*/){
265 return toastImpl('error',2,arguments);
266 }
267 }/*F.toast*/;
268
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269 })(window.fossil);
270
--- src/fossil.popupwidget.js
+++ src/fossil.popupwidget.js
@@ -264,6 +264,131 @@
264 error: function(/*...*/){
265 return toastImpl('error',2,arguments);
266 }
267 }/*F.toast*/;
268
269
270 F.helpButtonlets = {
271 /**
272 Initializes one or more "help buttonlets". It may be passed any of:
273
274 - A string: CSS selector (multiple matches are legal)
275
276 - A single DOM element.
277
278 - A forEach-compatible container of DOM elements.
279
280 - No arguments, which is equivalent to passing the string
281 ".help-buttonlet:not(.processed)".
282
283 Passing the same element(s) more than once is a no-op: during
284 initialization, each elements get the class'processed' added to
285 it, and any elements with that class are skipped.
286
287 All child nodes of a help buttonlet are removed from the button
288 during initialization and stashed away for use in a PopupWidget
289 when the botton is clicked.
290
291 */
292 setup: function f(){
293 if(!f.hasOwnProperty('clickHandler')){
294 f.clickHandler = function fch(ev){
295 if(!fch.popup){
296 fch.popup = new F.PopupWidget({
297 cssClass: ['fossil-tooltip', 'help-buttonlet-content'],
298 refresh: function(){
299 }
300 });
301 fch.popup.e.style.maxWidth = '80%'/*of body*/;
302 const hide = ()=>fch.popup.hide();
303 fch.popup.e.addEventListener('click', hide, false);
304 document.body.addEventListener('click', hide, true);
305 document.body.addEventListener('keydown', function(ev){
306 if(fch.popup.isShown() && 27===ev.which){
307 fch.popup.hide();
308 }
309 }, true);
310 }
311 D.append(D.clearElement(fch.popup.e), ev.target.$helpContent);
312 var popupRect = ev.target.getClientRects()[0];
313 var x = popupRect.left, y = popupRect.top;
314 if(x<0) x = 0;
315 if(y<0) y = 0;
316 /* Shift the help around a bit to "better" fit the
317 screen. However, fch.popup.e.getClientRects() is empty
318 until the popup is shown, so we have to show it,
319 calculate the resulting size, then move and/or resize it.
320
321 This algorithm/these heuristics can certainly be improved
322 upon.
323 */
324 fch.popup.show(x, y);
325 x = popupRect.left, y = popupRect.top;
326 popupRect = fch.popup.e.getBoundingClientRect();
327 const rectBody = document.body.getClientRects()[0];
328 if(popupRect.right > rectBody.right){
329 x -= (popupRect.right - rectBody.right);
330 }
331 if(x + popupRect.width > rectBody.right){
332 x = rectBody.x + (rectBody.width*0.1);
333 fch.popup.e.style.minWidth = '70%';
334 }else{
335 fch.popup.e.style.removeProperty('min-width');
336 x -= popupRect.width/2;
337 }
338 if(x<0) x = 0;
339 //console.debug("dimensions",x,y, popupRect, rectBody);
340 fch.popup.show(x, y);
341 };
342 f.foreachElement = function(e){
343 if(e.classList.contains('processed')) return;
344 e.classList.add('processed');
345 e.$helpContent = [];
346 /* We have to move all child nodes out of the way because we
347 cannot hide TEXT nodes via CSS (which cannot select TEXT
348 nodes). We have to do it in two steps to avoid invaliding
349 the list during traversal. */
350 e.childNodes.forEach((ch)=>e.$helpContent.push(ch));
351 e.$helpContent.forEach((ch)=>ch.remove());
352 e.addEventListener('click', f.clickHandler, false);
353 };
354 }/*static init*/
355 var elems;
356 if(!arguments.length){
357 arguments[0] = '.help-buttonlet:not(.processed)';
358 arguments.length = 1;
359 }
360 if(arguments.length){
361 if('string'===typeof arguments[0]){
362 elems = document.querySelectorAll(arguments[0]);
363 }else if(arguments[0] instanceof HTMLElement){
364 elems = [arguments[0]];
365 }else if(arguments[0].forEach){/* assume DOM element list or array */
366 elems = arguments[0];
367 }
368 }
369 if(elems) elems.forEach(f.foreachElement);
370 },
371
372 /**
373 Sets up the given element as a "help buttonlet", adding the CSS
374 class help-buttonlet to it. Any (optional) arguments after the
375 first are appended to the element using fossil.dom.append(), so
376 that they become the content for the buttonlet's popup help.
377
378 The element is then passed to this.setup() before it
379 is returned from this function.
380 */
381 create: function(elem/*...body*/){
382 D.addClass(elem, 'help-buttonlet');
383 if(arguments.length>1){
384 const args = Array.prototype.slice.call(arguments,1);
385 D.append(elem, args);
386 }
387 this.setup(elem);
388 return elem;
389 }
390 }/*helpButtonlets*/;
391
392 F.onDOMContentLoaded( ()=>F.helpButtonlets.setup() );
393
394 })(window.fossil);
395
--- src/fossil.popupwidget.js
+++ src/fossil.popupwidget.js
@@ -264,6 +264,131 @@
264264
error: function(/*...*/){
265265
return toastImpl('error',2,arguments);
266266
}
267267
}/*F.toast*/;
268268
269
+
270
+ F.helpButtonlets = {
271
+ /**
272
+ Initializes one or more "help buttonlets". It may be passed any of:
273
+
274
+ - A string: CSS selector (multiple matches are legal)
275
+
276
+ - A single DOM element.
277
+
278
+ - A forEach-compatible container of DOM elements.
279
+
280
+ - No arguments, which is equivalent to passing the string
281
+ ".help-buttonlet:not(.processed)".
282
+
283
+ Passing the same element(s) more than once is a no-op: during
284
+ initialization, each elements get the class'processed' added to
285
+ it, and any elements with that class are skipped.
286
+
287
+ All child nodes of a help buttonlet are removed from the button
288
+ during initialization and stashed away for use in a PopupWidget
289
+ when the botton is clicked.
290
+
291
+ */
292
+ setup: function f(){
293
+ if(!f.hasOwnProperty('clickHandler')){
294
+ f.clickHandler = function fch(ev){
295
+ if(!fch.popup){
296
+ fch.popup = new F.PopupWidget({
297
+ cssClass: ['fossil-tooltip', 'help-buttonlet-content'],
298
+ refresh: function(){
299
+ }
300
+ });
301
+ fch.popup.e.style.maxWidth = '80%'/*of body*/;
302
+ const hide = ()=>fch.popup.hide();
303
+ fch.popup.e.addEventListener('click', hide, false);
304
+ document.body.addEventListener('click', hide, true);
305
+ document.body.addEventListener('keydown', function(ev){
306
+ if(fch.popup.isShown() && 27===ev.which){
307
+ fch.popup.hide();
308
+ }
309
+ }, true);
310
+ }
311
+ D.append(D.clearElement(fch.popup.e), ev.target.$helpContent);
312
+ var popupRect = ev.target.getClientRects()[0];
313
+ var x = popupRect.left, y = popupRect.top;
314
+ if(x<0) x = 0;
315
+ if(y<0) y = 0;
316
+ /* Shift the help around a bit to "better" fit the
317
+ screen. However, fch.popup.e.getClientRects() is empty
318
+ until the popup is shown, so we have to show it,
319
+ calculate the resulting size, then move and/or resize it.
320
+
321
+ This algorithm/these heuristics can certainly be improved
322
+ upon.
323
+ */
324
+ fch.popup.show(x, y);
325
+ x = popupRect.left, y = popupRect.top;
326
+ popupRect = fch.popup.e.getBoundingClientRect();
327
+ const rectBody = document.body.getClientRects()[0];
328
+ if(popupRect.right > rectBody.right){
329
+ x -= (popupRect.right - rectBody.right);
330
+ }
331
+ if(x + popupRect.width > rectBody.right){
332
+ x = rectBody.x + (rectBody.width*0.1);
333
+ fch.popup.e.style.minWidth = '70%';
334
+ }else{
335
+ fch.popup.e.style.removeProperty('min-width');
336
+ x -= popupRect.width/2;
337
+ }
338
+ if(x<0) x = 0;
339
+ //console.debug("dimensions",x,y, popupRect, rectBody);
340
+ fch.popup.show(x, y);
341
+ };
342
+ f.foreachElement = function(e){
343
+ if(e.classList.contains('processed')) return;
344
+ e.classList.add('processed');
345
+ e.$helpContent = [];
346
+ /* We have to move all child nodes out of the way because we
347
+ cannot hide TEXT nodes via CSS (which cannot select TEXT
348
+ nodes). We have to do it in two steps to avoid invaliding
349
+ the list during traversal. */
350
+ e.childNodes.forEach((ch)=>e.$helpContent.push(ch));
351
+ e.$helpContent.forEach((ch)=>ch.remove());
352
+ e.addEventListener('click', f.clickHandler, false);
353
+ };
354
+ }/*static init*/
355
+ var elems;
356
+ if(!arguments.length){
357
+ arguments[0] = '.help-buttonlet:not(.processed)';
358
+ arguments.length = 1;
359
+ }
360
+ if(arguments.length){
361
+ if('string'===typeof arguments[0]){
362
+ elems = document.querySelectorAll(arguments[0]);
363
+ }else if(arguments[0] instanceof HTMLElement){
364
+ elems = [arguments[0]];
365
+ }else if(arguments[0].forEach){/* assume DOM element list or array */
366
+ elems = arguments[0];
367
+ }
368
+ }
369
+ if(elems) elems.forEach(f.foreachElement);
370
+ },
371
+
372
+ /**
373
+ Sets up the given element as a "help buttonlet", adding the CSS
374
+ class help-buttonlet to it. Any (optional) arguments after the
375
+ first are appended to the element using fossil.dom.append(), so
376
+ that they become the content for the buttonlet's popup help.
377
+
378
+ The element is then passed to this.setup() before it
379
+ is returned from this function.
380
+ */
381
+ create: function(elem/*...body*/){
382
+ D.addClass(elem, 'help-buttonlet');
383
+ if(arguments.length>1){
384
+ const args = Array.prototype.slice.call(arguments,1);
385
+ D.append(elem, args);
386
+ }
387
+ this.setup(elem);
388
+ return elem;
389
+ }
390
+ }/*helpButtonlets*/;
391
+
392
+ F.onDOMContentLoaded( ()=>F.helpButtonlets.setup() );
393
+
269394
})(window.fossil);
270395
--- src/fossil.popupwidget.js
+++ src/fossil.popupwidget.js
@@ -264,6 +264,131 @@
264 error: function(/*...*/){
265 return toastImpl('error',2,arguments);
266 }
267 }/*F.toast*/;
268
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269 })(window.fossil);
270
--- src/fossil.popupwidget.js
+++ src/fossil.popupwidget.js
@@ -264,6 +264,131 @@
264 error: function(/*...*/){
265 return toastImpl('error',2,arguments);
266 }
267 }/*F.toast*/;
268
269
270 F.helpButtonlets = {
271 /**
272 Initializes one or more "help buttonlets". It may be passed any of:
273
274 - A string: CSS selector (multiple matches are legal)
275
276 - A single DOM element.
277
278 - A forEach-compatible container of DOM elements.
279
280 - No arguments, which is equivalent to passing the string
281 ".help-buttonlet:not(.processed)".
282
283 Passing the same element(s) more than once is a no-op: during
284 initialization, each elements get the class'processed' added to
285 it, and any elements with that class are skipped.
286
287 All child nodes of a help buttonlet are removed from the button
288 during initialization and stashed away for use in a PopupWidget
289 when the botton is clicked.
290
291 */
292 setup: function f(){
293 if(!f.hasOwnProperty('clickHandler')){
294 f.clickHandler = function fch(ev){
295 if(!fch.popup){
296 fch.popup = new F.PopupWidget({
297 cssClass: ['fossil-tooltip', 'help-buttonlet-content'],
298 refresh: function(){
299 }
300 });
301 fch.popup.e.style.maxWidth = '80%'/*of body*/;
302 const hide = ()=>fch.popup.hide();
303 fch.popup.e.addEventListener('click', hide, false);
304 document.body.addEventListener('click', hide, true);
305 document.body.addEventListener('keydown', function(ev){
306 if(fch.popup.isShown() && 27===ev.which){
307 fch.popup.hide();
308 }
309 }, true);
310 }
311 D.append(D.clearElement(fch.popup.e), ev.target.$helpContent);
312 var popupRect = ev.target.getClientRects()[0];
313 var x = popupRect.left, y = popupRect.top;
314 if(x<0) x = 0;
315 if(y<0) y = 0;
316 /* Shift the help around a bit to "better" fit the
317 screen. However, fch.popup.e.getClientRects() is empty
318 until the popup is shown, so we have to show it,
319 calculate the resulting size, then move and/or resize it.
320
321 This algorithm/these heuristics can certainly be improved
322 upon.
323 */
324 fch.popup.show(x, y);
325 x = popupRect.left, y = popupRect.top;
326 popupRect = fch.popup.e.getBoundingClientRect();
327 const rectBody = document.body.getClientRects()[0];
328 if(popupRect.right > rectBody.right){
329 x -= (popupRect.right - rectBody.right);
330 }
331 if(x + popupRect.width > rectBody.right){
332 x = rectBody.x + (rectBody.width*0.1);
333 fch.popup.e.style.minWidth = '70%';
334 }else{
335 fch.popup.e.style.removeProperty('min-width');
336 x -= popupRect.width/2;
337 }
338 if(x<0) x = 0;
339 //console.debug("dimensions",x,y, popupRect, rectBody);
340 fch.popup.show(x, y);
341 };
342 f.foreachElement = function(e){
343 if(e.classList.contains('processed')) return;
344 e.classList.add('processed');
345 e.$helpContent = [];
346 /* We have to move all child nodes out of the way because we
347 cannot hide TEXT nodes via CSS (which cannot select TEXT
348 nodes). We have to do it in two steps to avoid invaliding
349 the list during traversal. */
350 e.childNodes.forEach((ch)=>e.$helpContent.push(ch));
351 e.$helpContent.forEach((ch)=>ch.remove());
352 e.addEventListener('click', f.clickHandler, false);
353 };
354 }/*static init*/
355 var elems;
356 if(!arguments.length){
357 arguments[0] = '.help-buttonlet:not(.processed)';
358 arguments.length = 1;
359 }
360 if(arguments.length){
361 if('string'===typeof arguments[0]){
362 elems = document.querySelectorAll(arguments[0]);
363 }else if(arguments[0] instanceof HTMLElement){
364 elems = [arguments[0]];
365 }else if(arguments[0].forEach){/* assume DOM element list or array */
366 elems = arguments[0];
367 }
368 }
369 if(elems) elems.forEach(f.foreachElement);
370 },
371
372 /**
373 Sets up the given element as a "help buttonlet", adding the CSS
374 class help-buttonlet to it. Any (optional) arguments after the
375 first are appended to the element using fossil.dom.append(), so
376 that they become the content for the buttonlet's popup help.
377
378 The element is then passed to this.setup() before it
379 is returned from this function.
380 */
381 create: function(elem/*...body*/){
382 D.addClass(elem, 'help-buttonlet');
383 if(arguments.length>1){
384 const args = Array.prototype.slice.call(arguments,1);
385 D.append(elem, args);
386 }
387 this.setup(elem);
388 return elem;
389 }
390 }/*helpButtonlets*/;
391
392 F.onDOMContentLoaded( ()=>F.helpButtonlets.setup() );
393
394 })(window.fossil);
395
--- src/fossil.storage.js
+++ src/fossil.storage.js
@@ -135,9 +135,24 @@
135135
/** Returns a symbolic name for the current storage mechanism. */
136136
storageImplName: function(){
137137
if($storage===window.localStorage) return 'localStorage';
138138
else if($storage===window.sessionStorage) return 'sessionStorage';
139139
else return 'transient';
140
+ },
141
+
142
+ /**
143
+ Returns a brief help text string for the currently-selected
144
+ storage type.
145
+ */
146
+ storageHelpDescription: function(){
147
+ return {
148
+ localStorage: "Browser-local persistent storage with an "+
149
+ "unspecified long-term lifetime (survives closing the browser, "+
150
+ "but maybe not a browser upgrade).",
151
+ sessionStorage: "Storage local to this browser tab, "+
152
+ "lost if this tab is closed.",
153
+ "transient": "Transient storage local to this invocation of this page."
154
+ }[this.storageImplName()];
140155
}
141156
};
142157
143158
})(window.fossil);
144159
--- src/fossil.storage.js
+++ src/fossil.storage.js
@@ -135,9 +135,24 @@
135 /** Returns a symbolic name for the current storage mechanism. */
136 storageImplName: function(){
137 if($storage===window.localStorage) return 'localStorage';
138 else if($storage===window.sessionStorage) return 'sessionStorage';
139 else return 'transient';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140 }
141 };
142
143 })(window.fossil);
144
--- src/fossil.storage.js
+++ src/fossil.storage.js
@@ -135,9 +135,24 @@
135 /** Returns a symbolic name for the current storage mechanism. */
136 storageImplName: function(){
137 if($storage===window.localStorage) return 'localStorage';
138 else if($storage===window.sessionStorage) return 'sessionStorage';
139 else return 'transient';
140 },
141
142 /**
143 Returns a brief help text string for the currently-selected
144 storage type.
145 */
146 storageHelpDescription: function(){
147 return {
148 localStorage: "Browser-local persistent storage with an "+
149 "unspecified long-term lifetime (survives closing the browser, "+
150 "but maybe not a browser upgrade).",
151 sessionStorage: "Storage local to this browser tab, "+
152 "lost if this tab is closed.",
153 "transient": "Transient storage local to this invocation of this page."
154 }[this.storageImplName()];
155 }
156 };
157
158 })(window.fossil);
159
+21 -7
--- src/info.c
+++ src/info.c
@@ -2024,24 +2024,33 @@
20242024
** zLn is the ?ln= parameter for the HTTP query. If there is an argument,
20252025
** then highlight that line number and scroll to it once the page loads.
20262026
** If there are two line numbers, highlight the range of lines.
20272027
** Multiple ranges can be highlighed by adding additional line numbers
20282028
** separated by a non-digit character (also not one of [-,.]).
2029
+**
2030
+** If includeJS is true then the JS code associated with line
2031
+** numbering is also emitted, else it is not. If this routine is
2032
+** called multiple times in a single app run, the JS is emitted only
2033
+** once. Note that when using this routine to emit Ajax responses, the
2034
+** JS should be not be included, as it will not get imported properly
2035
+** into the response's rendering.
20292036
*/
20302037
void output_text_with_line_numbers(
20312038
const char *z,
20322039
int nZ,
20332040
const char *zName,
2034
- const char *zLn
2041
+ const char *zLn,
2042
+ int includeJS
20352043
){
20362044
int iStart, iEnd; /* Start and end of region to highlight */
20372045
int n = 0; /* Current line number */
20382046
int i = 0; /* Loop index */
20392047
int iTop = 0; /* Scroll so that this line is on top of screen. */
20402048
int nLine = 0; /* content line count */
20412049
int nSpans = 0; /* number of distinct zLn spans */
20422050
const char *zExt = file_extension(zName);
2051
+ static int emittedJS = 0; /* emitted shared JS yet? */
20432052
Stmt q;
20442053
20452054
iStart = iEnd = atoi(zLn);
20462055
db_multi_exec(
20472056
"CREATE TEMP TABLE lnos(iStart INTEGER PRIMARY KEY, iEnd INTEGER)");
@@ -2115,15 +2124,20 @@
21152124
}else{
21162125
cgi_append_content("<code>", -1);
21172126
}
21182127
cgi_printf("%z", htmlize(z, nZ));
21192128
CX("</code></pre></td></tr></tbody></table>\n");
2120
- if( db_int(0, "SELECT EXISTS(SELECT 1 FROM lnos)") ){
2121
- builtin_request_js("scroll.js");
2129
+ if(includeJS && !emittedJS){
2130
+ emittedJS = 1;
2131
+ if( db_int(0, "SELECT EXISTS(SELECT 1 FROM lnos)") ){
2132
+ builtin_request_js("scroll.js");
2133
+ }
2134
+ if(!builtin_bundle_all_fossil_js_apis()){
2135
+ builtin_emit_fossil_js_apis("dom", "copybutton", "popupwidget",
2136
+ "numbered-lines", 0);
2137
+ }
21222138
}
2123
- style_emit_fossil_js_apis(0, "dom", "copybutton", "popupwidget",
2124
- "numbered-lines", 0);
21252139
}
21262140
21272141
/*
21282142
** COMMAND: test-line-numbers
21292143
**
@@ -2144,11 +2158,11 @@
21442158
zFilename = g.argv[2];
21452159
fossil_print("%s %s\n", zFilename, zLn);
21462160
21472161
blob_read_from_file(&content, zFilename, ExtFILE);
21482162
output_text_with_line_numbers(blob_str(&content), blob_size(&content),
2149
- zFilename, zLn);
2163
+ zFilename, zLn, 0);
21502164
blob_reset(&content);
21512165
fossil_print("%b\n", cgi_output_blob());
21522166
}
21532167
21542168
/*
@@ -2456,11 +2470,11 @@
24562470
" AND mlink.fid=%d",
24572471
rid);
24582472
zExt = zFileName ? file_extension(zFileName) : 0;
24592473
if( zLn ){
24602474
output_text_with_line_numbers(z, blob_size(&content),
2461
- zFileName, zLn);
2475
+ zFileName, zLn, 1);
24622476
}else if( zExt && zExt[1] ){
24632477
@ <pre>
24642478
@ <code class="language-%s(zExt)">%h(z)</code>
24652479
@ </pre>
24662480
}else{
24672481
--- src/info.c
+++ src/info.c
@@ -2024,24 +2024,33 @@
2024 ** zLn is the ?ln= parameter for the HTTP query. If there is an argument,
2025 ** then highlight that line number and scroll to it once the page loads.
2026 ** If there are two line numbers, highlight the range of lines.
2027 ** Multiple ranges can be highlighed by adding additional line numbers
2028 ** separated by a non-digit character (also not one of [-,.]).
 
 
 
 
 
 
 
2029 */
2030 void output_text_with_line_numbers(
2031 const char *z,
2032 int nZ,
2033 const char *zName,
2034 const char *zLn
 
2035 ){
2036 int iStart, iEnd; /* Start and end of region to highlight */
2037 int n = 0; /* Current line number */
2038 int i = 0; /* Loop index */
2039 int iTop = 0; /* Scroll so that this line is on top of screen. */
2040 int nLine = 0; /* content line count */
2041 int nSpans = 0; /* number of distinct zLn spans */
2042 const char *zExt = file_extension(zName);
 
2043 Stmt q;
2044
2045 iStart = iEnd = atoi(zLn);
2046 db_multi_exec(
2047 "CREATE TEMP TABLE lnos(iStart INTEGER PRIMARY KEY, iEnd INTEGER)");
@@ -2115,15 +2124,20 @@
2115 }else{
2116 cgi_append_content("<code>", -1);
2117 }
2118 cgi_printf("%z", htmlize(z, nZ));
2119 CX("</code></pre></td></tr></tbody></table>\n");
2120 if( db_int(0, "SELECT EXISTS(SELECT 1 FROM lnos)") ){
2121 builtin_request_js("scroll.js");
 
 
 
 
 
 
 
2122 }
2123 style_emit_fossil_js_apis(0, "dom", "copybutton", "popupwidget",
2124 "numbered-lines", 0);
2125 }
2126
2127 /*
2128 ** COMMAND: test-line-numbers
2129 **
@@ -2144,11 +2158,11 @@
2144 zFilename = g.argv[2];
2145 fossil_print("%s %s\n", zFilename, zLn);
2146
2147 blob_read_from_file(&content, zFilename, ExtFILE);
2148 output_text_with_line_numbers(blob_str(&content), blob_size(&content),
2149 zFilename, zLn);
2150 blob_reset(&content);
2151 fossil_print("%b\n", cgi_output_blob());
2152 }
2153
2154 /*
@@ -2456,11 +2470,11 @@
2456 " AND mlink.fid=%d",
2457 rid);
2458 zExt = zFileName ? file_extension(zFileName) : 0;
2459 if( zLn ){
2460 output_text_with_line_numbers(z, blob_size(&content),
2461 zFileName, zLn);
2462 }else if( zExt && zExt[1] ){
2463 @ <pre>
2464 @ <code class="language-%s(zExt)">%h(z)</code>
2465 @ </pre>
2466 }else{
2467
--- src/info.c
+++ src/info.c
@@ -2024,24 +2024,33 @@
2024 ** zLn is the ?ln= parameter for the HTTP query. If there is an argument,
2025 ** then highlight that line number and scroll to it once the page loads.
2026 ** If there are two line numbers, highlight the range of lines.
2027 ** Multiple ranges can be highlighed by adding additional line numbers
2028 ** separated by a non-digit character (also not one of [-,.]).
2029 **
2030 ** If includeJS is true then the JS code associated with line
2031 ** numbering is also emitted, else it is not. If this routine is
2032 ** called multiple times in a single app run, the JS is emitted only
2033 ** once. Note that when using this routine to emit Ajax responses, the
2034 ** JS should be not be included, as it will not get imported properly
2035 ** into the response's rendering.
2036 */
2037 void output_text_with_line_numbers(
2038 const char *z,
2039 int nZ,
2040 const char *zName,
2041 const char *zLn,
2042 int includeJS
2043 ){
2044 int iStart, iEnd; /* Start and end of region to highlight */
2045 int n = 0; /* Current line number */
2046 int i = 0; /* Loop index */
2047 int iTop = 0; /* Scroll so that this line is on top of screen. */
2048 int nLine = 0; /* content line count */
2049 int nSpans = 0; /* number of distinct zLn spans */
2050 const char *zExt = file_extension(zName);
2051 static int emittedJS = 0; /* emitted shared JS yet? */
2052 Stmt q;
2053
2054 iStart = iEnd = atoi(zLn);
2055 db_multi_exec(
2056 "CREATE TEMP TABLE lnos(iStart INTEGER PRIMARY KEY, iEnd INTEGER)");
@@ -2115,15 +2124,20 @@
2124 }else{
2125 cgi_append_content("<code>", -1);
2126 }
2127 cgi_printf("%z", htmlize(z, nZ));
2128 CX("</code></pre></td></tr></tbody></table>\n");
2129 if(includeJS && !emittedJS){
2130 emittedJS = 1;
2131 if( db_int(0, "SELECT EXISTS(SELECT 1 FROM lnos)") ){
2132 builtin_request_js("scroll.js");
2133 }
2134 if(!builtin_bundle_all_fossil_js_apis()){
2135 builtin_emit_fossil_js_apis("dom", "copybutton", "popupwidget",
2136 "numbered-lines", 0);
2137 }
2138 }
 
 
2139 }
2140
2141 /*
2142 ** COMMAND: test-line-numbers
2143 **
@@ -2144,11 +2158,11 @@
2158 zFilename = g.argv[2];
2159 fossil_print("%s %s\n", zFilename, zLn);
2160
2161 blob_read_from_file(&content, zFilename, ExtFILE);
2162 output_text_with_line_numbers(blob_str(&content), blob_size(&content),
2163 zFilename, zLn, 0);
2164 blob_reset(&content);
2165 fossil_print("%b\n", cgi_output_blob());
2166 }
2167
2168 /*
@@ -2456,11 +2470,11 @@
2470 " AND mlink.fid=%d",
2471 rid);
2472 zExt = zFileName ? file_extension(zFileName) : 0;
2473 if( zLn ){
2474 output_text_with_line_numbers(z, blob_size(&content),
2475 zFileName, zLn, 1);
2476 }else if( zExt && zExt[1] ){
2477 @ <pre>
2478 @ <code class="language-%s(zExt)">%h(z)</code>
2479 @ </pre>
2480 }else{
2481
+10 -125
--- src/style.c
+++ src/style.c
@@ -1230,16 +1230,16 @@
12301230
** element. If isChecked is true, the checkbox gets the "checked"
12311231
** attribute set, else it is not.
12321232
**
12331233
** Resulting structure:
12341234
**
1235
-** <span class='input-with-label' title={{zTip}} id={{zWrapperId}}>
1235
+** <div class='input-with-label' title={{zTip}} id={{zWrapperId}}>
12361236
** <input type='checkbox' name={{zFieldName}} value={{zValue}}
12371237
** id='A RANDOM VALUE'
12381238
** {{isChecked ? " checked : ""}}/>
12391239
** <label for='ID OF THE INPUT FIELD'>{{zLabel}}</label>
1240
-** </span>
1240
+** </div>
12411241
**
12421242
** zLabel, and zValue are required. zFieldName, zWrapperId, and zTip
12431243
** are may be NULL or empty.
12441244
**
12451245
** Be sure that the input-with-label CSS class is defined sensibly, in
@@ -1249,11 +1249,11 @@
12491249
void style_labeled_checkbox(const char * zWrapperId,
12501250
const char *zFieldName, const char * zLabel,
12511251
const char * zValue, int isChecked,
12521252
const char * zTip){
12531253
char * zLabelID = style_next_input_id();
1254
- CX("<span class='input-with-label'");
1254
+ CX("<div class='input-with-label'");
12551255
if(zTip && *zTip){
12561256
CX(" title='%h'", zTip);
12571257
}
12581258
if(zWrapperId && *zWrapperId){
12591259
CX(" id='%s'",zWrapperId);
@@ -1262,11 +1262,11 @@
12621262
if(zFieldName && *zFieldName){
12631263
CX("name='%s' ",zFieldName);
12641264
}
12651265
CX("value='%T'%s/>",
12661266
zValue ? zValue : "", isChecked ? " checked" : "");
1267
- CX("<label for='%s'>%h</label></span>", zLabelID, zLabel);
1267
+ CX("<label for='%s'>%h</label></div>", zLabelID, zLabel);
12681268
fossil_free(zLabelID);
12691269
}
12701270
12711271
/*
12721272
** Outputs a SELECT list from a compile-time list of integers.
@@ -1296,14 +1296,14 @@
12961296
**
12971297
** zTooltip is an optional value for the SELECT's title attribute.
12981298
**
12991299
** The structure of the emitted HTML is:
13001300
**
1301
-** <span class='input-with-label' title={{zToolTip}} id={{zWrapperId}}>
1301
+** <div class='input-with-label' title={{zToolTip}} id={{zWrapperId}}>
13021302
** <label for='SELECT ELEMENT ID'>{{zLabel}}</label>
13031303
** <select id='RANDOM ID' name={{zFieldName}}>...</select>
1304
-** </span>
1304
+** </div>
13051305
**
13061306
** Example:
13071307
**
13081308
** style_select_list_int("my-grapes", "my_grapes", "Grapes",
13091309
** "Select the number of grapes",
@@ -1318,11 +1318,11 @@
13181318
... ){
13191319
char * zLabelID = style_next_input_id();
13201320
va_list vargs;
13211321
13221322
va_start(vargs,selectedVal);
1323
- CX("<span class='input-with-label'");
1323
+ CX("<div class='input-with-label'");
13241324
if(zToolTip && *zToolTip){
13251325
CX(" title='%h'",zToolTip);
13261326
}
13271327
if(zWrapperId && *zWrapperId){
13281328
CX(" id='%s'",zWrapperId);
@@ -1347,11 +1347,11 @@
13471347
CX("%d",v);
13481348
}
13491349
CX("</option>\n");
13501350
}
13511351
CX("</select>\n");
1352
- CX("</span>\n");
1352
+ CX("</div>\n");
13531353
va_end(vargs);
13541354
fossil_free(zLabelID);
13551355
}
13561356
13571357
/*
@@ -1382,11 +1382,11 @@
13821382
13831383
va_start(vargs,zSelectedVal);
13841384
if(!zSelectedVal){
13851385
zSelectedVal = __FILE__/*some string we'll never match*/;
13861386
}
1387
- CX("<span class='input-with-label'");
1387
+ CX("<div class='input-with-label'");
13881388
if(zToolTip && *zToolTip){
13891389
CX(" title='%h'",zToolTip);
13901390
}
13911391
if(zWrapperId && *zWrapperId){
13921392
CX(" id='%s'",zWrapperId);
@@ -1411,100 +1411,15 @@
14111411
CX("%h",zVal);
14121412
}
14131413
CX("</option>\n");
14141414
}
14151415
CX("</select>\n");
1416
- CX("</span>\n");
1416
+ CX("</div>\n");
14171417
va_end(vargs);
14181418
fossil_free(zLabelID);
14191419
}
14201420
1421
-
1422
-/*
1423
-** The first time this is called, it emits code to install and
1424
-** bootstrap the window.fossil object, using the built-in file
1425
-** fossil.bootstrap.js (not to be confused with bootstrap.js).
1426
-**
1427
-** Subsequent calls are no-ops.
1428
-**
1429
-** It emits 2 parts:
1430
-**
1431
-** 1) window.fossil core object, some of which depends on C-level
1432
-** runtime data. That part of the script is always emitted inline. If
1433
-** addScriptTag is true then it is wrapped in its own SCRIPT tag, else
1434
-** it is assumed that the caller already opened a tag.
1435
-**
1436
-** 2) Emits the static fossil.bootstrap.js using builtin_request_js().
1437
-*/
1438
-void style_emit_script_fossil_bootstrap(int addScriptTag){
1439
- static int once = 0;
1440
- if(0==once++){
1441
- char * zName;
1442
- /* Set up the generic/app-agnostic parts of window.fossil
1443
- ** which require C-level state... */
1444
- if(addScriptTag!=0){
1445
- style_emit_script_tag(0,0);
1446
- }
1447
- CX("(function(){\n");
1448
- CX(/*MSIE NodeList.forEach polyfill, courtesy of Mozilla:
1449
- https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
1450
- */
1451
- "if(window.NodeList && !NodeList.prototype.forEach){"
1452
- "NodeList.prototype.forEach = Array.prototype.forEach;"
1453
- "}\n");
1454
- CX("if(!window.fossil) window.fossil={};\n"
1455
- "window.fossil.version = %!j;\n"
1456
- /* fossil.rootPath is the top-most CGI/server path,
1457
- ** including a trailing slash. */
1458
- "window.fossil.rootPath = %!j+'/';\n",
1459
- get_version(), g.zTop);
1460
- /* fossil.config = {...various config-level options...} */
1461
- CX("window.fossil.config = {");
1462
- zName = db_get("project-name", "");
1463
- CX("projectName: %!j,\n", zName);
1464
- fossil_free(zName);
1465
- zName = db_get("short-project-name", "");
1466
- CX("shortProjectName: %!j,\n", zName);
1467
- fossil_free(zName);
1468
- zName = db_get("project-code", "");
1469
- CX("projectCode: %!j,\n", zName);
1470
- fossil_free(zName);
1471
- CX("/* Length of UUID hashes for display purposes. */");
1472
- CX("hashDigits: %d, hashDigitsUrl: %d,\n",
1473
- hash_digits(0), hash_digits(1));
1474
- CX("editStateMarkers: {"
1475
- "/*Symbolic markers to denote certain edit states.*/"
1476
- "isNew:'[+]', isModified:'[*]', isDeleted:'[-]'},\n");
1477
- CX("confirmerButtonTicks: 3 "
1478
- "/*default fossil.confirmer tick count.*/\n");
1479
- CX("};\n"/* fossil.config */);
1480
-#if 0
1481
- /* Is it safe to emit the CSRF token here? Some pages add it
1482
- ** as a hidden form field. */
1483
- if(g.zCsrfToken[0]!=0){
1484
- CX("window.fossil.csrfToken = %!j;\n",
1485
- g.zCsrfToken);
1486
- }
1487
-#endif
1488
- /*
1489
- ** fossil.page holds info about the current page. This is also
1490
- ** where the current page "should" store any of its own
1491
- ** page-specific state, and it is reserved for that purpose.
1492
- */
1493
- CX("window.fossil.page = {"
1494
- "name:\"%T\""
1495
- "};\n", g.zPath);
1496
- CX("})();\n");
1497
- if(addScriptTag!=0){
1498
- style_emit_script_tag(1,0);
1499
- }
1500
- /* The remaining window.fossil bootstrap code is not dependent on
1501
- ** C-runtime state... */
1502
- builtin_request_js("fossil.bootstrap.js");
1503
- }
1504
-}
1505
-
15061421
/*
15071422
** If passed 0 as its first argument, it emits a script opener tag
15081423
** with this request's nonce. If passed non-0 it emits a script
15091424
** closing tag. Mnemonic for remembering the order in which to pass 0
15101425
** or 1 as the first argument to this function: 0 comes before 1.
@@ -1530,35 +1445,5 @@
15301445
}
15311446
}else{
15321447
CX("</script>\n");
15331448
}
15341449
}
1535
-
1536
-/*
1537
-** Convenience wrapper which calls builtin_request_js() for a series
1538
-** of builtin scripts named fossil.NAME.js. The first time it is
1539
-** called, it also calls style_emit_script_fossil_bootstrap() to
1540
-** initialize the window.fossil JS API. The first argument is a
1541
-** no-meaning dummy required by the va_start() interface. All
1542
-** subsequent arguments must be strings of the NAME part of
1543
-** fossil.NAME.js, followed by a NULL argument to terminate the list.
1544
-**
1545
-** e.g. pass it (0, "fetch", "dom", "tabs", 0) to load those 3
1546
-** APIs. Do not forget the trailing 0!
1547
-*/
1548
-void style_emit_fossil_js_apis( int dummy, ... ) {
1549
- static int once = 0;
1550
- const char *zArg;
1551
- char * zName;
1552
- va_list vargs;
1553
-
1554
- if(0==once++){
1555
- style_emit_script_fossil_bootstrap(1);
1556
- }
1557
- va_start(vargs,dummy);
1558
- while( (zArg = va_arg (vargs, const char *))!=0 ){
1559
- zName = mprintf("fossil.%s.js", zArg);
1560
- builtin_request_js(zName);
1561
- fossil_free(zName);
1562
- }
1563
- va_end(vargs);
1564
-}
15651450
--- src/style.c
+++ src/style.c
@@ -1230,16 +1230,16 @@
1230 ** element. If isChecked is true, the checkbox gets the "checked"
1231 ** attribute set, else it is not.
1232 **
1233 ** Resulting structure:
1234 **
1235 ** <span class='input-with-label' title={{zTip}} id={{zWrapperId}}>
1236 ** <input type='checkbox' name={{zFieldName}} value={{zValue}}
1237 ** id='A RANDOM VALUE'
1238 ** {{isChecked ? " checked : ""}}/>
1239 ** <label for='ID OF THE INPUT FIELD'>{{zLabel}}</label>
1240 ** </span>
1241 **
1242 ** zLabel, and zValue are required. zFieldName, zWrapperId, and zTip
1243 ** are may be NULL or empty.
1244 **
1245 ** Be sure that the input-with-label CSS class is defined sensibly, in
@@ -1249,11 +1249,11 @@
1249 void style_labeled_checkbox(const char * zWrapperId,
1250 const char *zFieldName, const char * zLabel,
1251 const char * zValue, int isChecked,
1252 const char * zTip){
1253 char * zLabelID = style_next_input_id();
1254 CX("<span class='input-with-label'");
1255 if(zTip && *zTip){
1256 CX(" title='%h'", zTip);
1257 }
1258 if(zWrapperId && *zWrapperId){
1259 CX(" id='%s'",zWrapperId);
@@ -1262,11 +1262,11 @@
1262 if(zFieldName && *zFieldName){
1263 CX("name='%s' ",zFieldName);
1264 }
1265 CX("value='%T'%s/>",
1266 zValue ? zValue : "", isChecked ? " checked" : "");
1267 CX("<label for='%s'>%h</label></span>", zLabelID, zLabel);
1268 fossil_free(zLabelID);
1269 }
1270
1271 /*
1272 ** Outputs a SELECT list from a compile-time list of integers.
@@ -1296,14 +1296,14 @@
1296 **
1297 ** zTooltip is an optional value for the SELECT's title attribute.
1298 **
1299 ** The structure of the emitted HTML is:
1300 **
1301 ** <span class='input-with-label' title={{zToolTip}} id={{zWrapperId}}>
1302 ** <label for='SELECT ELEMENT ID'>{{zLabel}}</label>
1303 ** <select id='RANDOM ID' name={{zFieldName}}>...</select>
1304 ** </span>
1305 **
1306 ** Example:
1307 **
1308 ** style_select_list_int("my-grapes", "my_grapes", "Grapes",
1309 ** "Select the number of grapes",
@@ -1318,11 +1318,11 @@
1318 ... ){
1319 char * zLabelID = style_next_input_id();
1320 va_list vargs;
1321
1322 va_start(vargs,selectedVal);
1323 CX("<span class='input-with-label'");
1324 if(zToolTip && *zToolTip){
1325 CX(" title='%h'",zToolTip);
1326 }
1327 if(zWrapperId && *zWrapperId){
1328 CX(" id='%s'",zWrapperId);
@@ -1347,11 +1347,11 @@
1347 CX("%d",v);
1348 }
1349 CX("</option>\n");
1350 }
1351 CX("</select>\n");
1352 CX("</span>\n");
1353 va_end(vargs);
1354 fossil_free(zLabelID);
1355 }
1356
1357 /*
@@ -1382,11 +1382,11 @@
1382
1383 va_start(vargs,zSelectedVal);
1384 if(!zSelectedVal){
1385 zSelectedVal = __FILE__/*some string we'll never match*/;
1386 }
1387 CX("<span class='input-with-label'");
1388 if(zToolTip && *zToolTip){
1389 CX(" title='%h'",zToolTip);
1390 }
1391 if(zWrapperId && *zWrapperId){
1392 CX(" id='%s'",zWrapperId);
@@ -1411,100 +1411,15 @@
1411 CX("%h",zVal);
1412 }
1413 CX("</option>\n");
1414 }
1415 CX("</select>\n");
1416 CX("</span>\n");
1417 va_end(vargs);
1418 fossil_free(zLabelID);
1419 }
1420
1421
1422 /*
1423 ** The first time this is called, it emits code to install and
1424 ** bootstrap the window.fossil object, using the built-in file
1425 ** fossil.bootstrap.js (not to be confused with bootstrap.js).
1426 **
1427 ** Subsequent calls are no-ops.
1428 **
1429 ** It emits 2 parts:
1430 **
1431 ** 1) window.fossil core object, some of which depends on C-level
1432 ** runtime data. That part of the script is always emitted inline. If
1433 ** addScriptTag is true then it is wrapped in its own SCRIPT tag, else
1434 ** it is assumed that the caller already opened a tag.
1435 **
1436 ** 2) Emits the static fossil.bootstrap.js using builtin_request_js().
1437 */
1438 void style_emit_script_fossil_bootstrap(int addScriptTag){
1439 static int once = 0;
1440 if(0==once++){
1441 char * zName;
1442 /* Set up the generic/app-agnostic parts of window.fossil
1443 ** which require C-level state... */
1444 if(addScriptTag!=0){
1445 style_emit_script_tag(0,0);
1446 }
1447 CX("(function(){\n");
1448 CX(/*MSIE NodeList.forEach polyfill, courtesy of Mozilla:
1449 https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
1450 */
1451 "if(window.NodeList && !NodeList.prototype.forEach){"
1452 "NodeList.prototype.forEach = Array.prototype.forEach;"
1453 "}\n");
1454 CX("if(!window.fossil) window.fossil={};\n"
1455 "window.fossil.version = %!j;\n"
1456 /* fossil.rootPath is the top-most CGI/server path,
1457 ** including a trailing slash. */
1458 "window.fossil.rootPath = %!j+'/';\n",
1459 get_version(), g.zTop);
1460 /* fossil.config = {...various config-level options...} */
1461 CX("window.fossil.config = {");
1462 zName = db_get("project-name", "");
1463 CX("projectName: %!j,\n", zName);
1464 fossil_free(zName);
1465 zName = db_get("short-project-name", "");
1466 CX("shortProjectName: %!j,\n", zName);
1467 fossil_free(zName);
1468 zName = db_get("project-code", "");
1469 CX("projectCode: %!j,\n", zName);
1470 fossil_free(zName);
1471 CX("/* Length of UUID hashes for display purposes. */");
1472 CX("hashDigits: %d, hashDigitsUrl: %d,\n",
1473 hash_digits(0), hash_digits(1));
1474 CX("editStateMarkers: {"
1475 "/*Symbolic markers to denote certain edit states.*/"
1476 "isNew:'[+]', isModified:'[*]', isDeleted:'[-]'},\n");
1477 CX("confirmerButtonTicks: 3 "
1478 "/*default fossil.confirmer tick count.*/\n");
1479 CX("};\n"/* fossil.config */);
1480 #if 0
1481 /* Is it safe to emit the CSRF token here? Some pages add it
1482 ** as a hidden form field. */
1483 if(g.zCsrfToken[0]!=0){
1484 CX("window.fossil.csrfToken = %!j;\n",
1485 g.zCsrfToken);
1486 }
1487 #endif
1488 /*
1489 ** fossil.page holds info about the current page. This is also
1490 ** where the current page "should" store any of its own
1491 ** page-specific state, and it is reserved for that purpose.
1492 */
1493 CX("window.fossil.page = {"
1494 "name:\"%T\""
1495 "};\n", g.zPath);
1496 CX("})();\n");
1497 if(addScriptTag!=0){
1498 style_emit_script_tag(1,0);
1499 }
1500 /* The remaining window.fossil bootstrap code is not dependent on
1501 ** C-runtime state... */
1502 builtin_request_js("fossil.bootstrap.js");
1503 }
1504 }
1505
1506 /*
1507 ** If passed 0 as its first argument, it emits a script opener tag
1508 ** with this request's nonce. If passed non-0 it emits a script
1509 ** closing tag. Mnemonic for remembering the order in which to pass 0
1510 ** or 1 as the first argument to this function: 0 comes before 1.
@@ -1530,35 +1445,5 @@
1530 }
1531 }else{
1532 CX("</script>\n");
1533 }
1534 }
1535
1536 /*
1537 ** Convenience wrapper which calls builtin_request_js() for a series
1538 ** of builtin scripts named fossil.NAME.js. The first time it is
1539 ** called, it also calls style_emit_script_fossil_bootstrap() to
1540 ** initialize the window.fossil JS API. The first argument is a
1541 ** no-meaning dummy required by the va_start() interface. All
1542 ** subsequent arguments must be strings of the NAME part of
1543 ** fossil.NAME.js, followed by a NULL argument to terminate the list.
1544 **
1545 ** e.g. pass it (0, "fetch", "dom", "tabs", 0) to load those 3
1546 ** APIs. Do not forget the trailing 0!
1547 */
1548 void style_emit_fossil_js_apis( int dummy, ... ) {
1549 static int once = 0;
1550 const char *zArg;
1551 char * zName;
1552 va_list vargs;
1553
1554 if(0==once++){
1555 style_emit_script_fossil_bootstrap(1);
1556 }
1557 va_start(vargs,dummy);
1558 while( (zArg = va_arg (vargs, const char *))!=0 ){
1559 zName = mprintf("fossil.%s.js", zArg);
1560 builtin_request_js(zName);
1561 fossil_free(zName);
1562 }
1563 va_end(vargs);
1564 }
1565
--- src/style.c
+++ src/style.c
@@ -1230,16 +1230,16 @@
1230 ** element. If isChecked is true, the checkbox gets the "checked"
1231 ** attribute set, else it is not.
1232 **
1233 ** Resulting structure:
1234 **
1235 ** <div class='input-with-label' title={{zTip}} id={{zWrapperId}}>
1236 ** <input type='checkbox' name={{zFieldName}} value={{zValue}}
1237 ** id='A RANDOM VALUE'
1238 ** {{isChecked ? " checked : ""}}/>
1239 ** <label for='ID OF THE INPUT FIELD'>{{zLabel}}</label>
1240 ** </div>
1241 **
1242 ** zLabel, and zValue are required. zFieldName, zWrapperId, and zTip
1243 ** are may be NULL or empty.
1244 **
1245 ** Be sure that the input-with-label CSS class is defined sensibly, in
@@ -1249,11 +1249,11 @@
1249 void style_labeled_checkbox(const char * zWrapperId,
1250 const char *zFieldName, const char * zLabel,
1251 const char * zValue, int isChecked,
1252 const char * zTip){
1253 char * zLabelID = style_next_input_id();
1254 CX("<div class='input-with-label'");
1255 if(zTip && *zTip){
1256 CX(" title='%h'", zTip);
1257 }
1258 if(zWrapperId && *zWrapperId){
1259 CX(" id='%s'",zWrapperId);
@@ -1262,11 +1262,11 @@
1262 if(zFieldName && *zFieldName){
1263 CX("name='%s' ",zFieldName);
1264 }
1265 CX("value='%T'%s/>",
1266 zValue ? zValue : "", isChecked ? " checked" : "");
1267 CX("<label for='%s'>%h</label></div>", zLabelID, zLabel);
1268 fossil_free(zLabelID);
1269 }
1270
1271 /*
1272 ** Outputs a SELECT list from a compile-time list of integers.
@@ -1296,14 +1296,14 @@
1296 **
1297 ** zTooltip is an optional value for the SELECT's title attribute.
1298 **
1299 ** The structure of the emitted HTML is:
1300 **
1301 ** <div class='input-with-label' title={{zToolTip}} id={{zWrapperId}}>
1302 ** <label for='SELECT ELEMENT ID'>{{zLabel}}</label>
1303 ** <select id='RANDOM ID' name={{zFieldName}}>...</select>
1304 ** </div>
1305 **
1306 ** Example:
1307 **
1308 ** style_select_list_int("my-grapes", "my_grapes", "Grapes",
1309 ** "Select the number of grapes",
@@ -1318,11 +1318,11 @@
1318 ... ){
1319 char * zLabelID = style_next_input_id();
1320 va_list vargs;
1321
1322 va_start(vargs,selectedVal);
1323 CX("<div class='input-with-label'");
1324 if(zToolTip && *zToolTip){
1325 CX(" title='%h'",zToolTip);
1326 }
1327 if(zWrapperId && *zWrapperId){
1328 CX(" id='%s'",zWrapperId);
@@ -1347,11 +1347,11 @@
1347 CX("%d",v);
1348 }
1349 CX("</option>\n");
1350 }
1351 CX("</select>\n");
1352 CX("</div>\n");
1353 va_end(vargs);
1354 fossil_free(zLabelID);
1355 }
1356
1357 /*
@@ -1382,11 +1382,11 @@
1382
1383 va_start(vargs,zSelectedVal);
1384 if(!zSelectedVal){
1385 zSelectedVal = __FILE__/*some string we'll never match*/;
1386 }
1387 CX("<div class='input-with-label'");
1388 if(zToolTip && *zToolTip){
1389 CX(" title='%h'",zToolTip);
1390 }
1391 if(zWrapperId && *zWrapperId){
1392 CX(" id='%s'",zWrapperId);
@@ -1411,100 +1411,15 @@
1411 CX("%h",zVal);
1412 }
1413 CX("</option>\n");
1414 }
1415 CX("</select>\n");
1416 CX("</div>\n");
1417 va_end(vargs);
1418 fossil_free(zLabelID);
1419 }
1420
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1421 /*
1422 ** If passed 0 as its first argument, it emits a script opener tag
1423 ** with this request's nonce. If passed non-0 it emits a script
1424 ** closing tag. Mnemonic for remembering the order in which to pass 0
1425 ** or 1 as the first argument to this function: 0 comes before 1.
@@ -1530,35 +1445,5 @@
1445 }
1446 }else{
1447 CX("</script>\n");
1448 }
1449 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1450
--- src/style.fileedit.css
+++ src/style.fileedit.css
@@ -220,6 +220,15 @@
220220
body.fileedit #fileedit-edit-status span.links > *::before {
221221
content: "[";
222222
}
223223
body.fileedit #fileedit-edit-status span.links > *::after {
224224
content: "]";
225
+}
226
+/* JS selection of line numbers cannot work in preview mode,
227
+ so disable the UI indications which imply that it does
228
+ something... */
229
+body.fileedit table.numbered-lines td.line-numbers > span {
230
+ cursor: unset;
231
+}
232
+body.fileedit table.numbered-lines td.line-numbers > span:hover {
233
+ background-color: inherit;
225234
}
226235
--- src/style.fileedit.css
+++ src/style.fileedit.css
@@ -220,6 +220,15 @@
220 body.fileedit #fileedit-edit-status span.links > *::before {
221 content: "[";
222 }
223 body.fileedit #fileedit-edit-status span.links > *::after {
224 content: "]";
 
 
 
 
 
 
 
 
 
225 }
226
--- src/style.fileedit.css
+++ src/style.fileedit.css
@@ -220,6 +220,15 @@
220 body.fileedit #fileedit-edit-status span.links > *::before {
221 content: "[";
222 }
223 body.fileedit #fileedit-edit-status span.links > *::after {
224 content: "]";
225 }
226 /* JS selection of line numbers cannot work in preview mode,
227 so disable the UI indications which imply that it does
228 something... */
229 body.fileedit table.numbered-lines td.line-numbers > span {
230 cursor: unset;
231 }
232 body.fileedit table.numbered-lines td.line-numbers > span:hover {
233 background-color: inherit;
234 }
235
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -184,11 +184,16 @@
184184
flex-direction: row;
185185
flex-wrap: wrap;
186186
align-items: baseline;
187187
}
188188
body.wikiedit #wikiedit-stash-selector select {
189
- margin: 0 1em;
189
+ margin: 0 1em 0 0.5em;
190190
height: initial;
191191
font-family: monospace;
192192
flex: 10 1 auto;
193193
}
194
-
194
+body.wikiedit fieldset.page-types-list > div > span {
195
+ display: flex;
196
+ flex-direction: row;
197
+ flex-wrap: nowrap;
198
+ align-items: center;
199
+}
195200
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -184,11 +184,16 @@
184 flex-direction: row;
185 flex-wrap: wrap;
186 align-items: baseline;
187 }
188 body.wikiedit #wikiedit-stash-selector select {
189 margin: 0 1em;
190 height: initial;
191 font-family: monospace;
192 flex: 10 1 auto;
193 }
194
 
 
 
 
 
195
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -184,11 +184,16 @@
184 flex-direction: row;
185 flex-wrap: wrap;
186 align-items: baseline;
187 }
188 body.wikiedit #wikiedit-stash-selector select {
189 margin: 0 1em 0 0.5em;
190 height: initial;
191 font-family: monospace;
192 flex: 10 1 auto;
193 }
194 body.wikiedit fieldset.page-types-list > div > span {
195 display: flex;
196 flex-direction: row;
197 flex-wrap: nowrap;
198 align-items: center;
199 }
200
+36 -18
--- src/wiki.c
+++ src/wiki.c
@@ -1162,33 +1162,45 @@
11621162
"data-tab-parent='wikiedit-tabs' "
11631163
"data-tab-label='Editor' "
11641164
"class='hidden'"
11651165
">");
11661166
CX("<div class='flex-container flex-row child-gap-small'>");
1167
- CX("<span class='input-with-label'>"
1167
+ CX("<div class='input-with-label'>"
11681168
"<label>Mime type</label>");
11691169
mimetype_option_menu(0);
1170
- CX("</span>");
1170
+ CX("</div>");
11711171
style_select_list_int("select-font-size",
11721172
"editor_font_size", "Editor font size",
11731173
NULL/*tooltip*/,
11741174
100,
11751175
"100%", 100, "125%", 125,
11761176
"150%", 150, "175%", 175,
11771177
"200%", 200, NULL);
1178
- CX("<button class='wikiedit-save'>"
1178
+ CX("<div class='input-with-label'>"
1179
+ "<button class='wikiedit-save'>"
11791180
"Save</button>"
1180
- /*will get moved around dynamically*/);
1181
- CX("<button class='wikiedit-save-close'>"
1182
- "Save &amp; Close</button>"/*will get moved around dynamically*/);
1181
+ "</div>" /*will get moved around dynamically*/);
1182
+ CX("<div class='input-with-label'>"
1183
+ "<button class='wikiedit-save-close'>"
1184
+ "Save &amp; Close</button>"
1185
+ "<div class='help-buttonlet'>"
1186
+ "Save edits to this page and returns to the wiki page viewer."
1187
+ "</div>"
1188
+ "</div>" /*will get moved around dynamically*/);
11831189
CX("<span class='save-button-slot'></span>");
1184
- CX("<button class='wikiedit-content-reload' "
1185
- "title='Reload the file from the server, discarding "
1190
+
1191
+ CX("<div class='input-with-label'>"
1192
+ "<button class='wikiedit-content-reload' "
1193
+ ">Discard &amp; Reload</button>"
1194
+ "<div class='help-buttonlet'>"
1195
+ "Reload the file from the server, discarding "
11861196
"any local edits. To help avoid accidental loss of "
11871197
"edits, it requires confirmation (a second click) within "
1188
- "a few seconds or it will not reload.'"
1189
- ">Discard &amp; Reload</button>");
1198
+ "a few seconds or it will not reload."
1199
+ "</div>"
1200
+ "</div>");
1201
+
11901202
CX("</div>");
11911203
CX("<div class='flex-container flex-column stretch'>");
11921204
CX("<textarea name='content' id='wikiedit-content-editor' "
11931205
"class='wikiedit' rows='25'>");
11941206
CX("</textarea>");
@@ -1213,16 +1225,19 @@
12131225
/* ^^^ fossil.page[methodName](content, callback) */
12141226
"data-f-preview-to='#wikiedit-tab-preview-wrapper' "
12151227
/* ^^^ dest elem ID */
12161228
">Refresh</button>");
12171229
/* Toggle auto-update of preview when the Preview tab is selected. */
1218
- style_labeled_checkbox("cb-preview-autoupdate",
1219
- NULL,
1220
- "Auto-refresh?",
1221
- "1", 1,
1222
- "If on, the preview will automatically "
1223
- "refresh when this tab is selected.");
1230
+ CX("<div class='input-with-label'>"
1231
+ "<input type='checkbox' value='1' "
1232
+ "id='cb-preview-autorefresh' checked>"
1233
+ "<label for='cb-preview-autorefresh'>Auto-refresh?</label>"
1234
+ "<div class='help-buttonlet'>"
1235
+ "If on, the preview will automatically "
1236
+ "refresh (if needed) when this tab is selected."
1237
+ "</div>"
1238
+ "</div>");
12241239
CX("<span class='save-button-slot'></span>");
12251240
CX("</div>"/*.wikiedit-options*/);
12261241
CX("<div id='wikiedit-tab-preview-wrapper'></div>");
12271242
CX("</div>"/*#wikiedit-tab-preview*/);
12281243
}
@@ -1270,13 +1285,16 @@
12701285
CX("<h2>Wiki Name Rules</h2>");
12711286
well_formed_wiki_name_rules();
12721287
CX("</div>"/*#wikiedit-tab-save*/);
12731288
}
12741289
1290
+ if(!builtin_bundle_all_fossil_js_apis()){
1291
+ builtin_emit_fossil_js_apis("fetch", "dom", "tabs", "confirmer",
1292
+ "storage", "popupwidget", 0);
1293
+ }
12751294
builtin_request_js("sbsdiff.js");
1276
- style_emit_fossil_js_apis(0, "fetch", "dom", "tabs", "confirmer",
1277
- "storage", "page.wikiedit", 0);
1295
+ builtin_request_js("fossil.page.wikiedit.js");
12781296
builtin_fulfill_js_requests();
12791297
/* Dynamically populate the editor... */
12801298
style_emit_script_tag(0,0);
12811299
{
12821300
/* Render the current page list to save us an XHR request
12831301
--- src/wiki.c
+++ src/wiki.c
@@ -1162,33 +1162,45 @@
1162 "data-tab-parent='wikiedit-tabs' "
1163 "data-tab-label='Editor' "
1164 "class='hidden'"
1165 ">");
1166 CX("<div class='flex-container flex-row child-gap-small'>");
1167 CX("<span class='input-with-label'>"
1168 "<label>Mime type</label>");
1169 mimetype_option_menu(0);
1170 CX("</span>");
1171 style_select_list_int("select-font-size",
1172 "editor_font_size", "Editor font size",
1173 NULL/*tooltip*/,
1174 100,
1175 "100%", 100, "125%", 125,
1176 "150%", 150, "175%", 175,
1177 "200%", 200, NULL);
1178 CX("<button class='wikiedit-save'>"
 
1179 "Save</button>"
1180 /*will get moved around dynamically*/);
1181 CX("<button class='wikiedit-save-close'>"
1182 "Save &amp; Close</button>"/*will get moved around dynamically*/);
 
 
 
 
 
1183 CX("<span class='save-button-slot'></span>");
1184 CX("<button class='wikiedit-content-reload' "
1185 "title='Reload the file from the server, discarding "
 
 
 
 
1186 "any local edits. To help avoid accidental loss of "
1187 "edits, it requires confirmation (a second click) within "
1188 "a few seconds or it will not reload.'"
1189 ">Discard &amp; Reload</button>");
 
 
1190 CX("</div>");
1191 CX("<div class='flex-container flex-column stretch'>");
1192 CX("<textarea name='content' id='wikiedit-content-editor' "
1193 "class='wikiedit' rows='25'>");
1194 CX("</textarea>");
@@ -1213,16 +1225,19 @@
1213 /* ^^^ fossil.page[methodName](content, callback) */
1214 "data-f-preview-to='#wikiedit-tab-preview-wrapper' "
1215 /* ^^^ dest elem ID */
1216 ">Refresh</button>");
1217 /* Toggle auto-update of preview when the Preview tab is selected. */
1218 style_labeled_checkbox("cb-preview-autoupdate",
1219 NULL,
1220 "Auto-refresh?",
1221 "1", 1,
1222 "If on, the preview will automatically "
1223 "refresh when this tab is selected.");
 
 
 
1224 CX("<span class='save-button-slot'></span>");
1225 CX("</div>"/*.wikiedit-options*/);
1226 CX("<div id='wikiedit-tab-preview-wrapper'></div>");
1227 CX("</div>"/*#wikiedit-tab-preview*/);
1228 }
@@ -1270,13 +1285,16 @@
1270 CX("<h2>Wiki Name Rules</h2>");
1271 well_formed_wiki_name_rules();
1272 CX("</div>"/*#wikiedit-tab-save*/);
1273 }
1274
 
 
 
 
1275 builtin_request_js("sbsdiff.js");
1276 style_emit_fossil_js_apis(0, "fetch", "dom", "tabs", "confirmer",
1277 "storage", "page.wikiedit", 0);
1278 builtin_fulfill_js_requests();
1279 /* Dynamically populate the editor... */
1280 style_emit_script_tag(0,0);
1281 {
1282 /* Render the current page list to save us an XHR request
1283
--- src/wiki.c
+++ src/wiki.c
@@ -1162,33 +1162,45 @@
1162 "data-tab-parent='wikiedit-tabs' "
1163 "data-tab-label='Editor' "
1164 "class='hidden'"
1165 ">");
1166 CX("<div class='flex-container flex-row child-gap-small'>");
1167 CX("<div class='input-with-label'>"
1168 "<label>Mime type</label>");
1169 mimetype_option_menu(0);
1170 CX("</div>");
1171 style_select_list_int("select-font-size",
1172 "editor_font_size", "Editor font size",
1173 NULL/*tooltip*/,
1174 100,
1175 "100%", 100, "125%", 125,
1176 "150%", 150, "175%", 175,
1177 "200%", 200, NULL);
1178 CX("<div class='input-with-label'>"
1179 "<button class='wikiedit-save'>"
1180 "Save</button>"
1181 "</div>" /*will get moved around dynamically*/);
1182 CX("<div class='input-with-label'>"
1183 "<button class='wikiedit-save-close'>"
1184 "Save &amp; Close</button>"
1185 "<div class='help-buttonlet'>"
1186 "Save edits to this page and returns to the wiki page viewer."
1187 "</div>"
1188 "</div>" /*will get moved around dynamically*/);
1189 CX("<span class='save-button-slot'></span>");
1190
1191 CX("<div class='input-with-label'>"
1192 "<button class='wikiedit-content-reload' "
1193 ">Discard &amp; Reload</button>"
1194 "<div class='help-buttonlet'>"
1195 "Reload the file from the server, discarding "
1196 "any local edits. To help avoid accidental loss of "
1197 "edits, it requires confirmation (a second click) within "
1198 "a few seconds or it will not reload."
1199 "</div>"
1200 "</div>");
1201
1202 CX("</div>");
1203 CX("<div class='flex-container flex-column stretch'>");
1204 CX("<textarea name='content' id='wikiedit-content-editor' "
1205 "class='wikiedit' rows='25'>");
1206 CX("</textarea>");
@@ -1213,16 +1225,19 @@
1225 /* ^^^ fossil.page[methodName](content, callback) */
1226 "data-f-preview-to='#wikiedit-tab-preview-wrapper' "
1227 /* ^^^ dest elem ID */
1228 ">Refresh</button>");
1229 /* Toggle auto-update of preview when the Preview tab is selected. */
1230 CX("<div class='input-with-label'>"
1231 "<input type='checkbox' value='1' "
1232 "id='cb-preview-autorefresh' checked>"
1233 "<label for='cb-preview-autorefresh'>Auto-refresh?</label>"
1234 "<div class='help-buttonlet'>"
1235 "If on, the preview will automatically "
1236 "refresh (if needed) when this tab is selected."
1237 "</div>"
1238 "</div>");
1239 CX("<span class='save-button-slot'></span>");
1240 CX("</div>"/*.wikiedit-options*/);
1241 CX("<div id='wikiedit-tab-preview-wrapper'></div>");
1242 CX("</div>"/*#wikiedit-tab-preview*/);
1243 }
@@ -1270,13 +1285,16 @@
1285 CX("<h2>Wiki Name Rules</h2>");
1286 well_formed_wiki_name_rules();
1287 CX("</div>"/*#wikiedit-tab-save*/);
1288 }
1289
1290 if(!builtin_bundle_all_fossil_js_apis()){
1291 builtin_emit_fossil_js_apis("fetch", "dom", "tabs", "confirmer",
1292 "storage", "popupwidget", 0);
1293 }
1294 builtin_request_js("sbsdiff.js");
1295 builtin_request_js("fossil.page.wikiedit.js");
 
1296 builtin_fulfill_js_requests();
1297 /* Dynamically populate the editor... */
1298 style_emit_script_tag(0,0);
1299 {
1300 /* Render the current page list to save us an XHR request
1301

Keyboard Shortcuts

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