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).
Commit
34f7fd72c62dae25a183b4c99cf7c77bb43e71bacf703ce31ed36530e97c1402
Parent
11384f1874f245a…
21 files changed
+2
-2
+1
-1
+190
+190
+63
-6
+63
-6
+22
-14
+1
-1
+1
-1
+12
-4
+2
-2
+16
-10
+35
-20
+125
+125
+15
+21
-7
+10
-125
+9
+7
-2
+36
-18
~
src/ajax.c
~
src/attach.c
~
src/builtin.c
~
src/builtin.c
~
src/default.css
~
src/default.css
~
src/fileedit.c
~
src/forum.c
~
src/forum.c
~
src/fossil.bootstrap.js
~
src/fossil.numbered-lines.js
~
src/fossil.page.fileedit.js
~
src/fossil.page.wikiedit.js
~
src/fossil.popupwidget.js
~
src/fossil.popupwidget.js
~
src/fossil.storage.js
~
src/info.c
~
src/style.c
~
src/style.fileedit.css
~
src/style.wikiedit.css
~
src/wiki.c
+2
-2
| --- src/ajax.c | ||
| +++ src/ajax.c | ||
| @@ -41,11 +41,11 @@ | ||
| 41 | 41 | ** and symbolic names for use by client-side scripts. |
| 42 | 42 | ** |
| 43 | 43 | ** If addScriptTag is true then the output is wrapped in a SCRIPT tag |
| 44 | 44 | ** with the current nonce, else no SCRIPT tag is emitted. |
| 45 | 45 | ** |
| 46 | -** Requires that style_emit_script_fossil_bootstrap() has already been | |
| 46 | +** Requires that builtin_emit_script_fossil_bootstrap() has already been | |
| 47 | 47 | ** called in order to initialize the window.fossil.page object. |
| 48 | 48 | */ |
| 49 | 49 | void ajax_emit_js_preview_modes(int addScriptTag){ |
| 50 | 50 | if(addScriptTag){ |
| 51 | 51 | style_emit_script_tag(0,0); |
| @@ -131,11 +131,11 @@ | ||
| 131 | 131 | break; |
| 132 | 132 | default:{ |
| 133 | 133 | const char *zContent = blob_str(pContent); |
| 134 | 134 | if(AJAX_PREVIEW_LINE_NUMBERS & flags){ |
| 135 | 135 | output_text_with_line_numbers(zContent, blob_size(pContent), |
| 136 | - zName, "on"); | |
| 136 | + zName, "on", 0); | |
| 137 | 137 | }else{ |
| 138 | 138 | const char *zExt = strrchr(zName,'.'); |
| 139 | 139 | if(zExt && zExt[1]){ |
| 140 | 140 | CX("<pre><code class='language-%s'>%h</code></pre>", |
| 141 | 141 | zExt+1, zContent); |
| 142 | 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 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 @@ | ||
| 617 | 617 | const char *z; |
| 618 | 618 | content_get(ridSrc, &attach); |
| 619 | 619 | blob_to_utf8_no_bom(&attach, 0); |
| 620 | 620 | z = blob_str(&attach); |
| 621 | 621 | 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); | |
| 623 | 623 | }else{ |
| 624 | 624 | @ <pre> |
| 625 | 625 | @ %h(z) |
| 626 | 626 | @ </pre> |
| 627 | 627 | } |
| 628 | 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); |
| 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 @@ | ||
| 258 | 258 | }else if( !bSilent ){ |
| 259 | 259 | fossil_fatal("unknown javascript delivery mode \"%s\" - should be" |
| 260 | 260 | " one of: inline separate bundled", zMode); |
| 261 | 261 | } |
| 262 | 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 | +} | |
| 263 | 271 | |
| 264 | 272 | /* |
| 265 | 273 | ** The caller wants the Javascript file named by zFilename to be |
| 266 | 274 | ** included in the generated page. Add the file to the queue of |
| 267 | 275 | ** requested javascript resources, if it is not there already. |
| @@ -559,5 +567,187 @@ | ||
| 559 | 567 | int rc = sqlite3_create_module(db, "builtin", &builtinVtabModule, 0); |
| 560 | 568 | return rc; |
| 561 | 569 | } |
| 562 | 570 | /* End of the builtin virtual table |
| 563 | 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 | +} | |
| 564 | 754 |
| --- 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 @@ | ||
| 258 | 258 | }else if( !bSilent ){ |
| 259 | 259 | fossil_fatal("unknown javascript delivery mode \"%s\" - should be" |
| 260 | 260 | " one of: inline separate bundled", zMode); |
| 261 | 261 | } |
| 262 | 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 | +} | |
| 263 | 271 | |
| 264 | 272 | /* |
| 265 | 273 | ** The caller wants the Javascript file named by zFilename to be |
| 266 | 274 | ** included in the generated page. Add the file to the queue of |
| 267 | 275 | ** requested javascript resources, if it is not there already. |
| @@ -559,5 +567,187 @@ | ||
| 559 | 567 | int rc = sqlite3_create_module(db, "builtin", &builtinVtabModule, 0); |
| 560 | 568 | return rc; |
| 561 | 569 | } |
| 562 | 570 | /* End of the builtin virtual table |
| 563 | 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 | +} | |
| 564 | 754 |
| --- 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 @@ | ||
| 1100 | 1100 | .font-size-200 { |
| 1101 | 1101 | font-size: 200%; |
| 1102 | 1102 | } |
| 1103 | 1103 | |
| 1104 | 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). | |
| 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. | |
| 1110 | 1111 | */ |
| 1111 | 1112 | .input-with-label { |
| 1112 | 1113 | border: 1px inset #808080; |
| 1113 | 1114 | border-radius: 0.25em; |
| 1114 | 1115 | padding: 0.25em 0.4em; |
| @@ -1251,11 +1252,11 @@ | ||
| 1251 | 1252 | border: 1px solid black; |
| 1252 | 1253 | border-radius: 0.25em; |
| 1253 | 1254 | position: absolute; |
| 1254 | 1255 | display: inline-block; |
| 1255 | 1256 | 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); | |
| 1257 | 1258 | background-color: inherit; |
| 1258 | 1259 | } |
| 1259 | 1260 | |
| 1260 | 1261 | .fossil-toast-message { |
| 1261 | 1262 | /* "toast"-style popup message. |
| @@ -1293,5 +1294,61 @@ | ||
| 1293 | 1294 | |
| 1294 | 1295 | blockquote.file-content { |
| 1295 | 1296 | /* file content block in the /file page */ |
| 1296 | 1297 | margin: 0 1em; |
| 1297 | 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 | +} | |
| 1298 | 1355 |
| --- 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 @@ | ||
| 1100 | 1100 | .font-size-200 { |
| 1101 | 1101 | font-size: 200%; |
| 1102 | 1102 | } |
| 1103 | 1103 | |
| 1104 | 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). | |
| 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. | |
| 1110 | 1111 | */ |
| 1111 | 1112 | .input-with-label { |
| 1112 | 1113 | border: 1px inset #808080; |
| 1113 | 1114 | border-radius: 0.25em; |
| 1114 | 1115 | padding: 0.25em 0.4em; |
| @@ -1251,11 +1252,11 @@ | ||
| 1251 | 1252 | border: 1px solid black; |
| 1252 | 1253 | border-radius: 0.25em; |
| 1253 | 1254 | position: absolute; |
| 1254 | 1255 | display: inline-block; |
| 1255 | 1256 | 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); | |
| 1257 | 1258 | background-color: inherit; |
| 1258 | 1259 | } |
| 1259 | 1260 | |
| 1260 | 1261 | .fossil-toast-message { |
| 1261 | 1262 | /* "toast"-style popup message. |
| @@ -1293,5 +1294,61 @@ | ||
| 1293 | 1294 | |
| 1294 | 1295 | blockquote.file-content { |
| 1295 | 1296 | /* file content block in the /file page */ |
| 1296 | 1297 | margin: 0 1em; |
| 1297 | 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 | +} | |
| 1298 | 1355 |
| --- 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 @@ | ||
| 1705 | 1705 | "data-tab-parent='fileedit-tabs' " |
| 1706 | 1706 | "data-tab-label='File Content' " |
| 1707 | 1707 | "class='hidden'" |
| 1708 | 1708 | ">"); |
| 1709 | 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 " | |
| 1710 | + CX("<div class='input-with-label'>" | |
| 1711 | + "<button class='fileedit-content-reload confirmer' " | |
| 1712 | + ">Discard & Reload</button>" | |
| 1713 | + "<div class='help-buttonlet'>" | |
| 1714 | + "Reload the file from the server, discarding " | |
| 1712 | 1715 | "any local edits. To help avoid accidental loss of " |
| 1713 | 1716 | "edits, it requires confirmation (a second click) within " |
| 1714 | - "a few seconds or it will not reload.'" | |
| 1715 | - ">Discard & Reload</button>"); | |
| 1717 | + "a few seconds or it will not reload." | |
| 1718 | + "</div>" | |
| 1719 | + "</div>"); | |
| 1716 | 1720 | style_select_list_int("select-font-size", |
| 1717 | 1721 | "editor_font_size", "Editor font size", |
| 1718 | 1722 | NULL/*tooltip*/, |
| 1719 | 1723 | 100, |
| 1720 | 1724 | "100%", 100, "125%", 125, |
| @@ -1746,16 +1750,19 @@ | ||
| 1746 | 1750 | /* ^^^ fossil.page[methodName](content, callback) */ |
| 1747 | 1751 | "data-f-preview-to='#fileedit-tab-preview-wrapper' " |
| 1748 | 1752 | /* ^^^ dest elem ID */ |
| 1749 | 1753 | ">Refresh</button>"); |
| 1750 | 1754 | /* 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>"); | |
| 1757 | 1764 | |
| 1758 | 1765 | /* Default preview rendering mode selection... */ |
| 1759 | 1766 | previewRenderMode = zFileMime |
| 1760 | 1767 | ? ajax_render_mode_for_mimetype(zFileMime) |
| 1761 | 1768 | : AJAX_RENDER_GUESS; |
| @@ -1979,23 +1986,24 @@ | ||
| 1979 | 1986 | "file/check-in combination are discarded.</li>"); |
| 1980 | 1987 | CX("</ul>"); |
| 1981 | 1988 | } |
| 1982 | 1989 | CX("</div>"/*#fileedit-tab-help*/); |
| 1983 | 1990 | |
| 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 | + } | |
| 1988 | 1995 | /* |
| 1989 | 1996 | ** Set up a JS-side mapping of the AJAX_RENDER_xyz values. This is |
| 1990 | 1997 | ** used for dynamically toggling certain UI components on and off. |
| 1991 | 1998 | ** Must come after window.fossil has been intialized and before |
| 1992 | 1999 | ** fossil.page.fileedit.js. Potential TODO: move this into the |
| 1993 | 2000 | ** window.fossil bootstrapping so that we don't have to "fulfill" |
| 1994 | 2001 | ** the JS multiple times. |
| 1995 | 2002 | */ |
| 1996 | 2003 | ajax_emit_js_preview_modes(1); |
| 2004 | + builtin_request_js("sbsdiff.js"); | |
| 1997 | 2005 | builtin_request_js("fossil.page.fileedit.js"); |
| 1998 | 2006 | builtin_fulfill_js_requests(); |
| 1999 | 2007 | { |
| 2000 | 2008 | /* Dynamically populate the editor, display any error in the err |
| 2001 | 2009 | ** blob, and/or switch to tab #0, where the file selector |
| 2002 | 2010 |
| --- 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 & 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 & 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 @@ | ||
| 804 | 804 | |
| 805 | 805 | /* Display the thread. */ |
| 806 | 806 | forum_display_thread(froot, fpid, mode, bUnf, bHist); |
| 807 | 807 | |
| 808 | 808 | /* Emit Forum Javascript. */ |
| 809 | - style_emit_script_fossil_bootstrap(1); | |
| 809 | + builtin_emit_script_fossil_bootstrap(1); | |
| 810 | 810 | builtin_request_js("forum.js"); |
| 811 | 811 | builtin_request_js("fossil.dom.js"); |
| 812 | 812 | builtin_request_js("fossil.page.forumpost.js"); |
| 813 | 813 | |
| 814 | 814 | /* Emit the page style. */ |
| 815 | 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 | 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 @@ | ||
| 804 | 804 | |
| 805 | 805 | /* Display the thread. */ |
| 806 | 806 | forum_display_thread(froot, fpid, mode, bUnf, bHist); |
| 807 | 807 | |
| 808 | 808 | /* Emit Forum Javascript. */ |
| 809 | - style_emit_script_fossil_bootstrap(1); | |
| 809 | + builtin_emit_script_fossil_bootstrap(1); | |
| 810 | 810 | builtin_request_js("forum.js"); |
| 811 | 811 | builtin_request_js("fossil.dom.js"); |
| 812 | 812 | builtin_request_js("fossil.page.forumpost.js"); |
| 813 | 813 | |
| 814 | 814 | /* Emit the page style. */ |
| 815 | 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 | 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 |
+12
-4
| --- src/fossil.bootstrap.js | ||
| +++ src/fossil.bootstrap.js | ||
| @@ -5,18 +5,18 @@ | ||
| 5 | 5 | */ |
| 6 | 6 | if(typeof window.CustomEvent === "function") return false; |
| 7 | 7 | window.CustomEvent = function(event, params) { |
| 8 | 8 | if(!params) params = {bubbles: false, cancelable: false, detail: null}; |
| 9 | 9 | 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 ); | |
| 11 | 11 | return evt; |
| 12 | 12 | }; |
| 13 | 13 | })(); |
| 14 | 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. | |
| 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 | 18 | */ |
| 19 | 19 | |
| 20 | 20 | const F = global.fossil; |
| 21 | 21 | |
| 22 | 22 | /** |
| @@ -328,10 +328,18 @@ | ||
| 328 | 328 | */ |
| 329 | 329 | F.onPageLoad = function(callback){ |
| 330 | 330 | window.addEventListener('load', callback, false); |
| 331 | 331 | return this; |
| 332 | 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 | + }; | |
| 333 | 341 | |
| 334 | 342 | /** |
| 335 | 343 | Assuming name is a repo-style filename, this function returns |
| 336 | 344 | a shortened form of that name: |
| 337 | 345 | |
| 338 | 346 |
| --- 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 |
+2
-2
| --- src/fossil.numbered-lines.js | ||
| +++ src/fossil.numbered-lines.js | ||
| @@ -6,19 +6,19 @@ | ||
| 6 | 6 | |
| 7 | 7 | Requires: fossil.bootstrap, fossil.dom, fossil.popupwidget, |
| 8 | 8 | fossil.copybutton |
| 9 | 9 | */ |
| 10 | 10 | var tbl = arg || document.querySelectorAll('table.numbered-lines'); |
| 11 | - if(!tbl) return /* no matching elements */; | |
| 12 | - else if(!arg){ | |
| 11 | + if(tbl && !arg){ | |
| 13 | 12 | if(tbl.length>1){ /* multiple query results: recurse */ |
| 14 | 13 | tbl.forEach( (t)=>callee(t) ); |
| 15 | 14 | return; |
| 16 | 15 | }else{/* single query result */ |
| 17 | 16 | tbl = tbl[0]; |
| 18 | 17 | } |
| 19 | 18 | } |
| 19 | + if(!tbl) return /* no matching elements */; | |
| 20 | 20 | const F = window.fossil, D = F.dom; |
| 21 | 21 | const tdLn = tbl.querySelector('td.line-numbers'); |
| 22 | 22 | const lineState = { |
| 23 | 23 | urlArgs: (window.location.search||'?') |
| 24 | 24 | .replace(/&?\budc=[^&]*/,'') /* "update display prefs cookie" */ |
| 25 | 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) 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 |
+16
-10
| --- src/fossil.page.fileedit.js | ||
| +++ src/fossil.page.fileedit.js | ||
| @@ -464,23 +464,26 @@ | ||
| 464 | 464 | const wrapper = D.addClass( |
| 465 | 465 | D.attr(D.div(),'id','fileedit-stash-selector'), |
| 466 | 466 | 'input-with-label' |
| 467 | 467 | ); |
| 468 | 468 | 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 | + | |
| 471 | 479 | D.append(wrapper, "Local edits (", |
| 472 | 480 | D.append(D.code(), |
| 473 | 481 | F.storage.storageImplName()), |
| 474 | 482 | "):", |
| 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); | |
| 482 | 485 | D.option(D.disable(sel), "(empty)"); |
| 483 | 486 | F.page.addEventListener('fileedit-stash-updated',(e)=>this.updateList(e.detail)); |
| 484 | 487 | F.page.addEventListener('fileedit-file-loaded',(e)=>this.updateList($stash, e.detail)); |
| 485 | 488 | sel.addEventListener('change',function(e){ |
| 486 | 489 | const opt = this.selectedOptions[0]; |
| @@ -492,10 +495,11 @@ | ||
| 492 | 495 | "Warning: persistent storage is not available, "+ |
| 493 | 496 | "so uncomitted edits will not survive a page reload." |
| 494 | 497 | )); |
| 495 | 498 | } |
| 496 | 499 | domInsertPoint.parentNode.insertBefore(wrapper, domInsertPoint); |
| 500 | + P.tabs.switchToTab(1/*DOM visibility workaround*/); | |
| 497 | 501 | F.confirmer(btnClear, { |
| 498 | 502 | /* must come after insertion into the DOM for the pinSize option to work. */ |
| 499 | 503 | pinSize: true, |
| 500 | 504 | confirmText: "DISCARD all local edits?", |
| 501 | 505 | onconfirm: function(e){ |
| @@ -509,10 +513,11 @@ | ||
| 509 | 513 | }, |
| 510 | 514 | ticks: F.config.confirmerButtonTicks |
| 511 | 515 | }); |
| 512 | 516 | D.addClass(this.e.btnClear,'hidden' /* must not be set until after confirmer is set up!*/); |
| 513 | 517 | $stash._fireStashEvent(/*read the page-load-time stash*/); |
| 518 | + P.tabs.switchToTab(0/*DOM visibility workaround*/); | |
| 514 | 519 | delete this.init; |
| 515 | 520 | }, |
| 516 | 521 | /** |
| 517 | 522 | Regenerates the edit selection list. |
| 518 | 523 | */ |
| @@ -654,11 +659,11 @@ | ||
| 654 | 659 | selectEolWrap: E('#select-eol-style'), |
| 655 | 660 | selectEol: E('#select-eol-style select[name=eol]'), |
| 656 | 661 | selectFontSizeWrap: E('#select-font-size'), |
| 657 | 662 | selectDiffWS: E('select[name=diff_ws]'), |
| 658 | 663 | cbLineNumbersWrap: E('#cb-line-numbers'), |
| 659 | - cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'), | |
| 664 | + cbAutoPreview: E('#cb-preview-autorefresh'), | |
| 660 | 665 | previewTarget: E('#fileedit-tab-preview-wrapper'), |
| 661 | 666 | manifestTarget: E('#fileedit-manifest'), |
| 662 | 667 | diffTarget: E('#fileedit-tab-diff-wrapper'), |
| 663 | 668 | cbIsExe: E('input[type=checkbox][name=exec_bit]'), |
| 664 | 669 | cbManifest: E('input[type=checkbox][name=include_manifest]'), |
| @@ -737,10 +742,11 @@ | ||
| 737 | 742 | "click",(e)=>P.diff(false), false |
| 738 | 743 | ); |
| 739 | 744 | P.e.btnCommit.addEventListener( |
| 740 | 745 | "click",(e)=>P.commit(), false |
| 741 | 746 | ); |
| 747 | + P.tabs.switchToTab(1/*DOM visibility workaround*/); | |
| 742 | 748 | F.confirmer(P.e.btnReload, { |
| 743 | 749 | pinSize: true, |
| 744 | 750 | confirmText: "Really reload, losing edits?", |
| 745 | 751 | onconfirm: (e)=>P.unstashContent().loadFile(), |
| 746 | 752 | ticks: F.config.confirmerButtonTicks |
| 747 | 753 |
| --- 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 |
+35
-20
| --- src/fossil.page.wikiedit.js | ||
| +++ src/fossil.page.wikiedit.js | ||
| @@ -2,11 +2,11 @@ | ||
| 2 | 2 | "use strict"; |
| 3 | 3 | /** |
| 4 | 4 | Client-side implementation of the /wikiedit app. Requires that |
| 5 | 5 | the fossil JS bootstrapping is complete and that several fossil |
| 6 | 6 | 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. | |
| 8 | 8 | |
| 9 | 9 | Custom events which can be listened for via |
| 10 | 10 | fossil.page.addEventListener(): |
| 11 | 11 | |
| 12 | 12 | - Event 'wiki-page-loaded': passes on information when it |
| @@ -557,11 +557,11 @@ | ||
| 557 | 557 | D.attr(sel, 'size', 12); |
| 558 | 558 | D.option(D.disable(D.clearElement(sel)), "Loading..."); |
| 559 | 559 | |
| 560 | 560 | /** Set up filter checkboxes for the various types |
| 561 | 561 | of wiki pages... */ |
| 562 | - const fsFilter = D.fieldset("Page types"), | |
| 562 | + const fsFilter = D.addClass(D.fieldset("Page types"),"page-types-list"), | |
| 563 | 563 | fsFilterBody = D.div(), |
| 564 | 564 | filters = ['normal', 'branch/...', 'tag/...', 'checkin/...'] |
| 565 | 565 | ; |
| 566 | 566 | D.append(fsFilter, fsFilterBody); |
| 567 | 567 | D.addClass(fsFilterBody, 'flex-container', 'flex-column', 'stretch'); |
| @@ -597,13 +597,18 @@ | ||
| 597 | 597 | 'deleted'), |
| 598 | 598 | 'for', cbId), |
| 599 | 599 | cb = D.attr(D.input('checkbox'), 'id', cbId); |
| 600 | 600 | cb.checked = false; |
| 601 | 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)); | |
| 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 | + )); | |
| 605 | 610 | cb.addEventListener( |
| 606 | 611 | 'change', |
| 607 | 612 | function(ev){ |
| 608 | 613 | if(ev.target.checked) D.removeClass(parentElem,'hide-deleted'); |
| 609 | 614 | else D.addClass(parentElem,'hide-deleted'); |
| @@ -682,22 +687,26 @@ | ||
| 682 | 687 | init: function(domInsertPoint/*insert widget BEFORE this element*/){ |
| 683 | 688 | const wrapper = D.addClass( |
| 684 | 689 | D.attr(D.div(),'id','wikiedit-stash-selector'), |
| 685 | 690 | 'input-with-label' |
| 686 | 691 | ); |
| 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 | + ); | |
| 689 | 702 | D.append(wrapper, "Local edits (", |
| 690 | 703 | D.append(D.code(), |
| 691 | 704 | F.storage.storageImplName()), |
| 692 | 705 | "):", |
| 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); | |
| 699 | 708 | D.option(D.disable(sel), "(empty)"); |
| 700 | 709 | P.addEventListener('wiki-stash-updated',(e)=>this.updateList(e.detail)); |
| 701 | 710 | P.addEventListener('wiki-page-loaded',(e)=>this.updateList($stash, e.detail)); |
| 702 | 711 | sel.addEventListener('change',function(e){ |
| 703 | 712 | const opt = this.selectedOptions[0]; |
| @@ -835,17 +844,15 @@ | ||
| 835 | 844 | P.base.originalHref = P.base.tag.href; |
| 836 | 845 | P.e = { /* various DOM elements we work with... */ |
| 837 | 846 | taEditor: E('#wikiedit-content-editor'), |
| 838 | 847 | btnReload: E("#wikiedit-tab-content button.wikiedit-content-reload"), |
| 839 | 848 | 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"), | |
| 843 | 850 | selectMimetype: E('select[name=mimetype]'), |
| 844 | 851 | selectFontSizeWrap: E('#select-font-size'), |
| 845 | 852 | // selectDiffWS: E('select[name=diff_ws]'), |
| 846 | - cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'), | |
| 853 | + cbAutoPreview: E('#cb-preview-autorefresh'), | |
| 847 | 854 | previewTarget: E('#wikiedit-tab-preview-wrapper'), |
| 848 | 855 | diffTarget: E('#wikiedit-tab-diff-wrapper'), |
| 849 | 856 | editStatus: E('#wikiedit-edit-status'), |
| 850 | 857 | tabContainer: E('#wikiedit-tabs'), |
| 851 | 858 | tabs:{ |
| @@ -870,12 +877,12 @@ | ||
| 870 | 877 | 'before-switch-to', function(ev){ |
| 871 | 878 | const theTab = ev.detail, btnSlot = theTab.querySelector('.save-button-slot'); |
| 872 | 879 | if(btnSlot){ |
| 873 | 880 | /* Several places make sense for a save button, so we'll |
| 874 | 881 | 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 ); | |
| 877 | 884 | P.updateSaveButton(); |
| 878 | 885 | } |
| 879 | 886 | if(theTab===P.e.tabs.preview){ |
| 880 | 887 | P.baseHrefForWiki(); |
| 881 | 888 | if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview(); |
| @@ -958,10 +965,11 @@ | ||
| 958 | 965 | F.message("Discarded new page ["+w.name+"]."); |
| 959 | 966 | } |
| 960 | 967 | }; |
| 961 | 968 | |
| 962 | 969 | if(P.config.useConfirmerButtons.reload){ |
| 970 | + P.tabs.switchToTab(1/*DOM visibility workaround*/); | |
| 963 | 971 | F.confirmer(P.e.btnReload, { |
| 964 | 972 | pinSize: true, |
| 965 | 973 | confirmText: "Really reload, losing edits?", |
| 966 | 974 | onconfirm: doReload, |
| 967 | 975 | ticks: F.config.confirmerButtonTicks |
| @@ -968,10 +976,11 @@ | ||
| 968 | 976 | }); |
| 969 | 977 | }else{ |
| 970 | 978 | P.e.btnReload.addEventListener('click', doReload, false); |
| 971 | 979 | } |
| 972 | 980 | if(P.config.useConfirmerButtons.save){ |
| 981 | + P.tabs.switchToTab(1/*DOM visibility workaround*/); | |
| 973 | 982 | F.confirmer(P.e.btnSave, { |
| 974 | 983 | pinSize: true, |
| 975 | 984 | confirmText: "Really save changes?", |
| 976 | 985 | onconfirm: ()=>doSave(), |
| 977 | 986 | ticks: F.config.confirmerButtonTicks |
| @@ -1069,13 +1078,19 @@ | ||
| 1069 | 1078 | } |
| 1070 | 1079 | P.updatePageTitle().updateSaveButton(/* b/c save() routes through here */); |
| 1071 | 1080 | }, |
| 1072 | 1081 | false |
| 1073 | 1082 | ); |
| 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); | |
| 1075 | 1088 | WikiList.init( P.e.tabs.pageList.firstElementChild ); |
| 1089 | + P.tabs.switchToTab(1); | |
| 1076 | 1090 | P.stashWidget.init(P.e.tabs.content.lastElementChild); |
| 1091 | + P.tabs.switchToTab(0); | |
| 1077 | 1092 | //P.$wikiList = WikiList/*only for testing/debugging*/; |
| 1078 | 1093 | }/*F.onPageLoad()*/); |
| 1079 | 1094 | |
| 1080 | 1095 | /** |
| 1081 | 1096 | Returns true if fossil.page.winfo is set, indicating that a page |
| 1082 | 1097 |
| --- 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 |
+125
| --- src/fossil.popupwidget.js | ||
| +++ src/fossil.popupwidget.js | ||
| @@ -264,6 +264,131 @@ | ||
| 264 | 264 | error: function(/*...*/){ |
| 265 | 265 | return toastImpl('error',2,arguments); |
| 266 | 266 | } |
| 267 | 267 | }/*F.toast*/; |
| 268 | 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 | + | |
| 269 | 394 | })(window.fossil); |
| 270 | 395 |
| --- 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 |
+125
| --- src/fossil.popupwidget.js | ||
| +++ src/fossil.popupwidget.js | ||
| @@ -264,6 +264,131 @@ | ||
| 264 | 264 | error: function(/*...*/){ |
| 265 | 265 | return toastImpl('error',2,arguments); |
| 266 | 266 | } |
| 267 | 267 | }/*F.toast*/; |
| 268 | 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 | + | |
| 269 | 394 | })(window.fossil); |
| 270 | 395 |
| --- 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 |
+15
| --- src/fossil.storage.js | ||
| +++ src/fossil.storage.js | ||
| @@ -135,9 +135,24 @@ | ||
| 135 | 135 | /** Returns a symbolic name for the current storage mechanism. */ |
| 136 | 136 | storageImplName: function(){ |
| 137 | 137 | if($storage===window.localStorage) return 'localStorage'; |
| 138 | 138 | else if($storage===window.sessionStorage) return 'sessionStorage'; |
| 139 | 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()]; | |
| 140 | 155 | } |
| 141 | 156 | }; |
| 142 | 157 | |
| 143 | 158 | })(window.fossil); |
| 144 | 159 |
| --- 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 @@ | ||
| 2024 | 2024 | ** zLn is the ?ln= parameter for the HTTP query. If there is an argument, |
| 2025 | 2025 | ** then highlight that line number and scroll to it once the page loads. |
| 2026 | 2026 | ** If there are two line numbers, highlight the range of lines. |
| 2027 | 2027 | ** Multiple ranges can be highlighed by adding additional line numbers |
| 2028 | 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. | |
| 2029 | 2036 | */ |
| 2030 | 2037 | void output_text_with_line_numbers( |
| 2031 | 2038 | const char *z, |
| 2032 | 2039 | int nZ, |
| 2033 | 2040 | const char *zName, |
| 2034 | - const char *zLn | |
| 2041 | + const char *zLn, | |
| 2042 | + int includeJS | |
| 2035 | 2043 | ){ |
| 2036 | 2044 | int iStart, iEnd; /* Start and end of region to highlight */ |
| 2037 | 2045 | int n = 0; /* Current line number */ |
| 2038 | 2046 | int i = 0; /* Loop index */ |
| 2039 | 2047 | int iTop = 0; /* Scroll so that this line is on top of screen. */ |
| 2040 | 2048 | int nLine = 0; /* content line count */ |
| 2041 | 2049 | int nSpans = 0; /* number of distinct zLn spans */ |
| 2042 | 2050 | const char *zExt = file_extension(zName); |
| 2051 | + static int emittedJS = 0; /* emitted shared JS yet? */ | |
| 2043 | 2052 | Stmt q; |
| 2044 | 2053 | |
| 2045 | 2054 | iStart = iEnd = atoi(zLn); |
| 2046 | 2055 | db_multi_exec( |
| 2047 | 2056 | "CREATE TEMP TABLE lnos(iStart INTEGER PRIMARY KEY, iEnd INTEGER)"); |
| @@ -2115,15 +2124,20 @@ | ||
| 2115 | 2124 | }else{ |
| 2116 | 2125 | cgi_append_content("<code>", -1); |
| 2117 | 2126 | } |
| 2118 | 2127 | cgi_printf("%z", htmlize(z, nZ)); |
| 2119 | 2128 | 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 | + } | |
| 2122 | 2138 | } |
| 2123 | - style_emit_fossil_js_apis(0, "dom", "copybutton", "popupwidget", | |
| 2124 | - "numbered-lines", 0); | |
| 2125 | 2139 | } |
| 2126 | 2140 | |
| 2127 | 2141 | /* |
| 2128 | 2142 | ** COMMAND: test-line-numbers |
| 2129 | 2143 | ** |
| @@ -2144,11 +2158,11 @@ | ||
| 2144 | 2158 | zFilename = g.argv[2]; |
| 2145 | 2159 | fossil_print("%s %s\n", zFilename, zLn); |
| 2146 | 2160 | |
| 2147 | 2161 | blob_read_from_file(&content, zFilename, ExtFILE); |
| 2148 | 2162 | output_text_with_line_numbers(blob_str(&content), blob_size(&content), |
| 2149 | - zFilename, zLn); | |
| 2163 | + zFilename, zLn, 0); | |
| 2150 | 2164 | blob_reset(&content); |
| 2151 | 2165 | fossil_print("%b\n", cgi_output_blob()); |
| 2152 | 2166 | } |
| 2153 | 2167 | |
| 2154 | 2168 | /* |
| @@ -2456,11 +2470,11 @@ | ||
| 2456 | 2470 | " AND mlink.fid=%d", |
| 2457 | 2471 | rid); |
| 2458 | 2472 | zExt = zFileName ? file_extension(zFileName) : 0; |
| 2459 | 2473 | if( zLn ){ |
| 2460 | 2474 | output_text_with_line_numbers(z, blob_size(&content), |
| 2461 | - zFileName, zLn); | |
| 2475 | + zFileName, zLn, 1); | |
| 2462 | 2476 | }else if( zExt && zExt[1] ){ |
| 2463 | 2477 | @ <pre> |
| 2464 | 2478 | @ <code class="language-%s(zExt)">%h(z)</code> |
| 2465 | 2479 | @ </pre> |
| 2466 | 2480 | }else{ |
| 2467 | 2481 |
| --- 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 @@ | ||
| 1230 | 1230 | ** element. If isChecked is true, the checkbox gets the "checked" |
| 1231 | 1231 | ** attribute set, else it is not. |
| 1232 | 1232 | ** |
| 1233 | 1233 | ** Resulting structure: |
| 1234 | 1234 | ** |
| 1235 | -** <span class='input-with-label' title={{zTip}} id={{zWrapperId}}> | |
| 1235 | +** <div class='input-with-label' title={{zTip}} id={{zWrapperId}}> | |
| 1236 | 1236 | ** <input type='checkbox' name={{zFieldName}} value={{zValue}} |
| 1237 | 1237 | ** id='A RANDOM VALUE' |
| 1238 | 1238 | ** {{isChecked ? " checked : ""}}/> |
| 1239 | 1239 | ** <label for='ID OF THE INPUT FIELD'>{{zLabel}}</label> |
| 1240 | -** </span> | |
| 1240 | +** </div> | |
| 1241 | 1241 | ** |
| 1242 | 1242 | ** zLabel, and zValue are required. zFieldName, zWrapperId, and zTip |
| 1243 | 1243 | ** are may be NULL or empty. |
| 1244 | 1244 | ** |
| 1245 | 1245 | ** Be sure that the input-with-label CSS class is defined sensibly, in |
| @@ -1249,11 +1249,11 @@ | ||
| 1249 | 1249 | void style_labeled_checkbox(const char * zWrapperId, |
| 1250 | 1250 | const char *zFieldName, const char * zLabel, |
| 1251 | 1251 | const char * zValue, int isChecked, |
| 1252 | 1252 | const char * zTip){ |
| 1253 | 1253 | char * zLabelID = style_next_input_id(); |
| 1254 | - CX("<span class='input-with-label'"); | |
| 1254 | + CX("<div class='input-with-label'"); | |
| 1255 | 1255 | if(zTip && *zTip){ |
| 1256 | 1256 | CX(" title='%h'", zTip); |
| 1257 | 1257 | } |
| 1258 | 1258 | if(zWrapperId && *zWrapperId){ |
| 1259 | 1259 | CX(" id='%s'",zWrapperId); |
| @@ -1262,11 +1262,11 @@ | ||
| 1262 | 1262 | if(zFieldName && *zFieldName){ |
| 1263 | 1263 | CX("name='%s' ",zFieldName); |
| 1264 | 1264 | } |
| 1265 | 1265 | CX("value='%T'%s/>", |
| 1266 | 1266 | zValue ? zValue : "", isChecked ? " checked" : ""); |
| 1267 | - CX("<label for='%s'>%h</label></span>", zLabelID, zLabel); | |
| 1267 | + CX("<label for='%s'>%h</label></div>", zLabelID, zLabel); | |
| 1268 | 1268 | fossil_free(zLabelID); |
| 1269 | 1269 | } |
| 1270 | 1270 | |
| 1271 | 1271 | /* |
| 1272 | 1272 | ** Outputs a SELECT list from a compile-time list of integers. |
| @@ -1296,14 +1296,14 @@ | ||
| 1296 | 1296 | ** |
| 1297 | 1297 | ** zTooltip is an optional value for the SELECT's title attribute. |
| 1298 | 1298 | ** |
| 1299 | 1299 | ** The structure of the emitted HTML is: |
| 1300 | 1300 | ** |
| 1301 | -** <span class='input-with-label' title={{zToolTip}} id={{zWrapperId}}> | |
| 1301 | +** <div class='input-with-label' title={{zToolTip}} id={{zWrapperId}}> | |
| 1302 | 1302 | ** <label for='SELECT ELEMENT ID'>{{zLabel}}</label> |
| 1303 | 1303 | ** <select id='RANDOM ID' name={{zFieldName}}>...</select> |
| 1304 | -** </span> | |
| 1304 | +** </div> | |
| 1305 | 1305 | ** |
| 1306 | 1306 | ** Example: |
| 1307 | 1307 | ** |
| 1308 | 1308 | ** style_select_list_int("my-grapes", "my_grapes", "Grapes", |
| 1309 | 1309 | ** "Select the number of grapes", |
| @@ -1318,11 +1318,11 @@ | ||
| 1318 | 1318 | ... ){ |
| 1319 | 1319 | char * zLabelID = style_next_input_id(); |
| 1320 | 1320 | va_list vargs; |
| 1321 | 1321 | |
| 1322 | 1322 | va_start(vargs,selectedVal); |
| 1323 | - CX("<span class='input-with-label'"); | |
| 1323 | + CX("<div class='input-with-label'"); | |
| 1324 | 1324 | if(zToolTip && *zToolTip){ |
| 1325 | 1325 | CX(" title='%h'",zToolTip); |
| 1326 | 1326 | } |
| 1327 | 1327 | if(zWrapperId && *zWrapperId){ |
| 1328 | 1328 | CX(" id='%s'",zWrapperId); |
| @@ -1347,11 +1347,11 @@ | ||
| 1347 | 1347 | CX("%d",v); |
| 1348 | 1348 | } |
| 1349 | 1349 | CX("</option>\n"); |
| 1350 | 1350 | } |
| 1351 | 1351 | CX("</select>\n"); |
| 1352 | - CX("</span>\n"); | |
| 1352 | + CX("</div>\n"); | |
| 1353 | 1353 | va_end(vargs); |
| 1354 | 1354 | fossil_free(zLabelID); |
| 1355 | 1355 | } |
| 1356 | 1356 | |
| 1357 | 1357 | /* |
| @@ -1382,11 +1382,11 @@ | ||
| 1382 | 1382 | |
| 1383 | 1383 | va_start(vargs,zSelectedVal); |
| 1384 | 1384 | if(!zSelectedVal){ |
| 1385 | 1385 | zSelectedVal = __FILE__/*some string we'll never match*/; |
| 1386 | 1386 | } |
| 1387 | - CX("<span class='input-with-label'"); | |
| 1387 | + CX("<div class='input-with-label'"); | |
| 1388 | 1388 | if(zToolTip && *zToolTip){ |
| 1389 | 1389 | CX(" title='%h'",zToolTip); |
| 1390 | 1390 | } |
| 1391 | 1391 | if(zWrapperId && *zWrapperId){ |
| 1392 | 1392 | CX(" id='%s'",zWrapperId); |
| @@ -1411,100 +1411,15 @@ | ||
| 1411 | 1411 | CX("%h",zVal); |
| 1412 | 1412 | } |
| 1413 | 1413 | CX("</option>\n"); |
| 1414 | 1414 | } |
| 1415 | 1415 | CX("</select>\n"); |
| 1416 | - CX("</span>\n"); | |
| 1416 | + CX("</div>\n"); | |
| 1417 | 1417 | va_end(vargs); |
| 1418 | 1418 | fossil_free(zLabelID); |
| 1419 | 1419 | } |
| 1420 | 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 | 1421 | /* |
| 1507 | 1422 | ** If passed 0 as its first argument, it emits a script opener tag |
| 1508 | 1423 | ** with this request's nonce. If passed non-0 it emits a script |
| 1509 | 1424 | ** closing tag. Mnemonic for remembering the order in which to pass 0 |
| 1510 | 1425 | ** or 1 as the first argument to this function: 0 comes before 1. |
| @@ -1530,35 +1445,5 @@ | ||
| 1530 | 1445 | } |
| 1531 | 1446 | }else{ |
| 1532 | 1447 | CX("</script>\n"); |
| 1533 | 1448 | } |
| 1534 | 1449 | } |
| 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 | 1450 |
| --- 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 @@ | ||
| 220 | 220 | body.fileedit #fileedit-edit-status span.links > *::before { |
| 221 | 221 | content: "["; |
| 222 | 222 | } |
| 223 | 223 | body.fileedit #fileedit-edit-status span.links > *::after { |
| 224 | 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; | |
| 225 | 234 | } |
| 226 | 235 |
| --- 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 |
+7
-2
| --- src/style.wikiedit.css | ||
| +++ src/style.wikiedit.css | ||
| @@ -184,11 +184,16 @@ | ||
| 184 | 184 | flex-direction: row; |
| 185 | 185 | flex-wrap: wrap; |
| 186 | 186 | align-items: baseline; |
| 187 | 187 | } |
| 188 | 188 | body.wikiedit #wikiedit-stash-selector select { |
| 189 | - margin: 0 1em; | |
| 189 | + margin: 0 1em 0 0.5em; | |
| 190 | 190 | height: initial; |
| 191 | 191 | font-family: monospace; |
| 192 | 192 | flex: 10 1 auto; |
| 193 | 193 | } |
| 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 | +} | |
| 195 | 200 |
| --- 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 @@ | ||
| 1162 | 1162 | "data-tab-parent='wikiedit-tabs' " |
| 1163 | 1163 | "data-tab-label='Editor' " |
| 1164 | 1164 | "class='hidden'" |
| 1165 | 1165 | ">"); |
| 1166 | 1166 | CX("<div class='flex-container flex-row child-gap-small'>"); |
| 1167 | - CX("<span class='input-with-label'>" | |
| 1167 | + CX("<div class='input-with-label'>" | |
| 1168 | 1168 | "<label>Mime type</label>"); |
| 1169 | 1169 | mimetype_option_menu(0); |
| 1170 | - CX("</span>"); | |
| 1170 | + CX("</div>"); | |
| 1171 | 1171 | style_select_list_int("select-font-size", |
| 1172 | 1172 | "editor_font_size", "Editor font size", |
| 1173 | 1173 | NULL/*tooltip*/, |
| 1174 | 1174 | 100, |
| 1175 | 1175 | "100%", 100, "125%", 125, |
| 1176 | 1176 | "150%", 150, "175%", 175, |
| 1177 | 1177 | "200%", 200, NULL); |
| 1178 | - CX("<button class='wikiedit-save'>" | |
| 1178 | + CX("<div class='input-with-label'>" | |
| 1179 | + "<button class='wikiedit-save'>" | |
| 1179 | 1180 | "Save</button>" |
| 1180 | - /*will get moved around dynamically*/); | |
| 1181 | - CX("<button class='wikiedit-save-close'>" | |
| 1182 | - "Save & 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 & 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*/); | |
| 1183 | 1189 | 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 & Reload</button>" | |
| 1194 | + "<div class='help-buttonlet'>" | |
| 1195 | + "Reload the file from the server, discarding " | |
| 1186 | 1196 | "any local edits. To help avoid accidental loss of " |
| 1187 | 1197 | "edits, it requires confirmation (a second click) within " |
| 1188 | - "a few seconds or it will not reload.'" | |
| 1189 | - ">Discard & Reload</button>"); | |
| 1198 | + "a few seconds or it will not reload." | |
| 1199 | + "</div>" | |
| 1200 | + "</div>"); | |
| 1201 | + | |
| 1190 | 1202 | CX("</div>"); |
| 1191 | 1203 | CX("<div class='flex-container flex-column stretch'>"); |
| 1192 | 1204 | CX("<textarea name='content' id='wikiedit-content-editor' " |
| 1193 | 1205 | "class='wikiedit' rows='25'>"); |
| 1194 | 1206 | CX("</textarea>"); |
| @@ -1213,16 +1225,19 @@ | ||
| 1213 | 1225 | /* ^^^ fossil.page[methodName](content, callback) */ |
| 1214 | 1226 | "data-f-preview-to='#wikiedit-tab-preview-wrapper' " |
| 1215 | 1227 | /* ^^^ dest elem ID */ |
| 1216 | 1228 | ">Refresh</button>"); |
| 1217 | 1229 | /* 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>"); | |
| 1224 | 1239 | CX("<span class='save-button-slot'></span>"); |
| 1225 | 1240 | CX("</div>"/*.wikiedit-options*/); |
| 1226 | 1241 | CX("<div id='wikiedit-tab-preview-wrapper'></div>"); |
| 1227 | 1242 | CX("</div>"/*#wikiedit-tab-preview*/); |
| 1228 | 1243 | } |
| @@ -1270,13 +1285,16 @@ | ||
| 1270 | 1285 | CX("<h2>Wiki Name Rules</h2>"); |
| 1271 | 1286 | well_formed_wiki_name_rules(); |
| 1272 | 1287 | CX("</div>"/*#wikiedit-tab-save*/); |
| 1273 | 1288 | } |
| 1274 | 1289 | |
| 1290 | + if(!builtin_bundle_all_fossil_js_apis()){ | |
| 1291 | + builtin_emit_fossil_js_apis("fetch", "dom", "tabs", "confirmer", | |
| 1292 | + "storage", "popupwidget", 0); | |
| 1293 | + } | |
| 1275 | 1294 | 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"); | |
| 1278 | 1296 | builtin_fulfill_js_requests(); |
| 1279 | 1297 | /* Dynamically populate the editor... */ |
| 1280 | 1298 | style_emit_script_tag(0,0); |
| 1281 | 1299 | { |
| 1282 | 1300 | /* Render the current page list to save us an XHR request |
| 1283 | 1301 |
| --- 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 & 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 & 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 & 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 & 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 |