Fossil SCM

Completely overhauled the /fileedit layout, using a homebrew tabbed interface.

stephan 2020-05-05 16:51 fileedit-ajaxify
Commit 33ffe5762bef215a33c802bedd28a0ef46aaec1b3b515cf8bb74e9a5841d0918
--- src/default_css.txt
+++ src/default_css.txt
@@ -866,14 +866,15 @@
866866
form.fileedit textarea {
867867
font-family: monospace;
868868
width: 100%;
869869
}
870870
form.fileedit fieldset {
871
- margin: 0.5em 0 0 0;
871
+ margin: 0.5em 0 0.5em 0;
872872
border-radius: 0.5em;
873873
border-color: inherit;
874874
border-width: 1px;
875
+ font-size: 85%;
875876
}
876877
form.fileedit fieldset > legend {
877878
margin: 0 0 0 1em;
878879
padding: 0 0.5em 0 0.5em;
879880
}
@@ -908,20 +909,17 @@
908909
}
909910
div.fileedit-preview {
910911
margin: 0;
911912
padding: 0;
912913
}
913
-.fileedit-preview > div:first-child {
914
- margin: 1em 0 0 0;
915
- border-bottom: 1px dashed;
916
-}
917
-div.fileedit-diff {
914
+div.fileedit-tab-diff-wrapper {
918915
margin: 0;
919916
padding: 0;
920917
}
921
-.fileedit-diff > div:first-child {
922
- border-bottom: 1px dashed;
918
+#fileedit-comment {
919
+ width: 100%;
920
+ font-family: monospace;
923921
}
924922
#fossil-status-bar {
925923
display: block;
926924
font-family: monospace;
927925
border-width: 1px;
@@ -928,10 +926,12 @@
928926
border-style: inset;
929927
border-color: inherit;
930928
min-height: 1.5em;
931929
font-size: 1.2em;
932930
padding: 0.2em;
931
+ margin: 0.25em 0;
932
+ flex: 0 0 auto;
933933
}
934934
#fossil-status-bar.error {
935935
color: darkred;
936936
background: yellow;
937937
}
@@ -967,12 +967,17 @@
967967
.input-with-label > span {
968968
margin: 0 0.25em 0 0.25em;
969969
vertical-align: middle;
970970
}
971971
.hidden {
972
- display: none;
972
+ position: absolute;
973
+ opacity: 0;
974
+ pointer-events: none;
973975
}
976
+//.hidden {
977
+// display: none;
978
+//}
974979
.font-size-100 {
975980
font-size: 100%;
976981
}
977982
.font-size-125 {
978983
font-size: 125%;
@@ -984,5 +989,46 @@
984989
font-size: 175%;
985990
}
986991
.font-size-200 {
987992
font-size: 200%;
988993
}
994
+.tab-container {
995
+ width: 100%;
996
+ display: flex;
997
+ flex-direction: column;
998
+ align-items: stretch;
999
+}
1000
+.tab-container > #fossil-status-bar {
1001
+ margin-top: 0;
1002
+}
1003
+.tab-container > .tabs {
1004
+ padding: 0.25em;
1005
+ margin: 0;
1006
+ display: flex;
1007
+ flex-direction: column;
1008
+ border-width: 1px;
1009
+ border-style: outset;
1010
+ border-color: inherit;
1011
+}
1012
+.tab-container > .tabs > .tab-panel {
1013
+ align-self: stretch;
1014
+ flex: 10 1 auto;
1015
+ display: block;
1016
+}
1017
+.tab-container > .tab-bar {
1018
+ display: flex;
1019
+ flex-direction: row;
1020
+ flex: 1 10 auto;
1021
+ align-self: stretch;
1022
+ flex-wrap: wrap;
1023
+}
1024
+.tab-container > .tab-bar > button {
1025
+ border-radius: 0.5em 0.5em 0 0;
1026
+ margin: 0.5em 0.5em 0 0.5em;
1027
+ align-self: baseline;
1028
+}
1029
+.tab-container > .tab-bar > button.selected {
1030
+ font-style: italic;
1031
+ font-weight: bold;
1032
+ margin: 0 0.5em;
1033
+ text-decoration: underline;
1034
+}
9891035
--- src/default_css.txt
+++ src/default_css.txt
@@ -866,14 +866,15 @@
866 form.fileedit textarea {
867 font-family: monospace;
868 width: 100%;
869 }
870 form.fileedit fieldset {
871 margin: 0.5em 0 0 0;
872 border-radius: 0.5em;
873 border-color: inherit;
874 border-width: 1px;
 
875 }
876 form.fileedit fieldset > legend {
877 margin: 0 0 0 1em;
878 padding: 0 0.5em 0 0.5em;
879 }
@@ -908,20 +909,17 @@
908 }
909 div.fileedit-preview {
910 margin: 0;
911 padding: 0;
912 }
913 .fileedit-preview > div:first-child {
914 margin: 1em 0 0 0;
915 border-bottom: 1px dashed;
916 }
917 div.fileedit-diff {
918 margin: 0;
919 padding: 0;
920 }
921 .fileedit-diff > div:first-child {
922 border-bottom: 1px dashed;
 
923 }
924 #fossil-status-bar {
925 display: block;
926 font-family: monospace;
927 border-width: 1px;
@@ -928,10 +926,12 @@
928 border-style: inset;
929 border-color: inherit;
930 min-height: 1.5em;
931 font-size: 1.2em;
932 padding: 0.2em;
 
 
933 }
934 #fossil-status-bar.error {
935 color: darkred;
936 background: yellow;
937 }
@@ -967,12 +967,17 @@
967 .input-with-label > span {
968 margin: 0 0.25em 0 0.25em;
969 vertical-align: middle;
970 }
971 .hidden {
972 display: none;
 
 
973 }
 
 
 
974 .font-size-100 {
975 font-size: 100%;
976 }
977 .font-size-125 {
978 font-size: 125%;
@@ -984,5 +989,46 @@
984 font-size: 175%;
985 }
986 .font-size-200 {
987 font-size: 200%;
988 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
989
--- src/default_css.txt
+++ src/default_css.txt
@@ -866,14 +866,15 @@
866 form.fileedit textarea {
867 font-family: monospace;
868 width: 100%;
869 }
870 form.fileedit fieldset {
871 margin: 0.5em 0 0.5em 0;
872 border-radius: 0.5em;
873 border-color: inherit;
874 border-width: 1px;
875 font-size: 85%;
876 }
877 form.fileedit fieldset > legend {
878 margin: 0 0 0 1em;
879 padding: 0 0.5em 0 0.5em;
880 }
@@ -908,20 +909,17 @@
909 }
910 div.fileedit-preview {
911 margin: 0;
912 padding: 0;
913 }
914 div.fileedit-tab-diff-wrapper {
 
 
 
 
915 margin: 0;
916 padding: 0;
917 }
918 #fileedit-comment {
919 width: 100%;
920 font-family: monospace;
921 }
922 #fossil-status-bar {
923 display: block;
924 font-family: monospace;
925 border-width: 1px;
@@ -928,10 +926,12 @@
926 border-style: inset;
927 border-color: inherit;
928 min-height: 1.5em;
929 font-size: 1.2em;
930 padding: 0.2em;
931 margin: 0.25em 0;
932 flex: 0 0 auto;
933 }
934 #fossil-status-bar.error {
935 color: darkred;
936 background: yellow;
937 }
@@ -967,12 +967,17 @@
967 .input-with-label > span {
968 margin: 0 0.25em 0 0.25em;
969 vertical-align: middle;
970 }
971 .hidden {
972 position: absolute;
973 opacity: 0;
974 pointer-events: none;
975 }
976 //.hidden {
977 // display: none;
978 //}
979 .font-size-100 {
980 font-size: 100%;
981 }
982 .font-size-125 {
983 font-size: 125%;
@@ -984,5 +989,46 @@
989 font-size: 175%;
990 }
991 .font-size-200 {
992 font-size: 200%;
993 }
994 .tab-container {
995 width: 100%;
996 display: flex;
997 flex-direction: column;
998 align-items: stretch;
999 }
1000 .tab-container > #fossil-status-bar {
1001 margin-top: 0;
1002 }
1003 .tab-container > .tabs {
1004 padding: 0.25em;
1005 margin: 0;
1006 display: flex;
1007 flex-direction: column;
1008 border-width: 1px;
1009 border-style: outset;
1010 border-color: inherit;
1011 }
1012 .tab-container > .tabs > .tab-panel {
1013 align-self: stretch;
1014 flex: 10 1 auto;
1015 display: block;
1016 }
1017 .tab-container > .tab-bar {
1018 display: flex;
1019 flex-direction: row;
1020 flex: 1 10 auto;
1021 align-self: stretch;
1022 flex-wrap: wrap;
1023 }
1024 .tab-container > .tab-bar > button {
1025 border-radius: 0.5em 0.5em 0 0;
1026 margin: 0.5em 0.5em 0 0.5em;
1027 align-self: baseline;
1028 }
1029 .tab-container > .tab-bar > button.selected {
1030 font-style: italic;
1031 font-weight: bold;
1032 margin: 0 0.5em;
1033 text-decoration: underline;
1034 }
1035
+234 -175
--- src/fileedit.c
+++ src/fileedit.c
@@ -1446,13 +1446,13 @@
14461446
14471447
/*
14481448
** Emits utility script code specific to the /fileedit page.
14491449
*/
14501450
static void fileedit_emit_page_script(){
1451
- style_emit_script_tag(0);
1452
- CX("%s\n", builtin_text("fossil.page.fileedit.js"));
1453
- style_emit_script_tag(1);
1451
+ style_emit_script_fetch();
1452
+ style_emit_script_tabs();
1453
+ style_emit_script_builtin("fossil.page.fileedit.js");
14541454
}
14551455
14561456
/*
14571457
** WEBPAGE: fileedit
14581458
**
@@ -1516,194 +1516,254 @@
15161516
** thus have already caused us to skipped to the end of the page to
15171517
** render the errors. Any up-coming errors, barring malloc failure
15181518
** or similar, are not "that" fatal. We can/should continue
15191519
** rendering the page, then output the error message at the end.
15201520
********************************************************************/
1521
- CX("<h1>Editing:</h1>");
1522
- CX("<p class='fileedit-hint'>");
1523
- CX("File: "
1524
- "[<a id='finfo-link' href='#'>info</a>] "
1525
- /* %R/finfo?name=%T&m=%!S */
1526
- "<code id='finfo-file-name'>(loading)</code><br>");
1527
- CX("Checkin Version: "
1528
- "[<a id='r-link' href='#'>info</a>] "
1529
- /* %R/info/%!S */
1530
- "<code id='r-label'>(loading...)</code><br>"
1531
- );
1532
- CX("Permalink: <code>"
1533
- "<a id='permalink' href='#'>(loading...)</a></code><br>"
1534
- "(Clicking the permalink will reload the page and discard "
1535
- "all edits!)",
1536
- zFilename, cimi.zParentUuid,
1537
- zFilename, cimi.zParentUuid);
1538
- CX("</p>");
15391521
CX("<p>This page is <em>NEW AND EXPERIMENTAL</em>. "
15401522
"USE AT YOUR OWN RISK, preferably on a test "
15411523
"repo.</p>\n");
1542
-
1543
- CX("<form action='#' method='POST' "
1544
- "class='fileedit' id='fileedit-form' "
1545
- "onsubmit='function(e){"
1546
- "e.preventDefault(); e.stopPropagation(); return false;"
1547
- "}'>\n");
1524
+
1525
+ /*
1526
+ ** We don't strictly need a FORM because we manually cherry-pick and
1527
+ ** submit the form data, but it being in a form allows us to easily
1528
+ ** use the FormData type for serialization.
1529
+ **
1530
+ ** TODO?: we can almost certainly replace this element with a plain
1531
+ ** DIV, which would eliminate the event-handling hassles of trying
1532
+ ** to suppress the submit... but it would also eliminate the option
1533
+ ** of using HTML form field validation.
1534
+ */
1535
+ CX("<form action='#' method='POST' class='fileedit' "
1536
+ "id='fileedit-form'>");
15481537
15491538
/******* Hidden fields *******/
15501539
CX("<input type='hidden' name='r' value='%s'>",
15511540
cimi.zParentUuid);
15521541
CX("<input type='hidden' name='file' value='%T'>",
15531542
zFilename);
15541543
1555
- /******* Content *******/
1556
- CX("<h3>File Content</h3>\n");
1557
- CX("<textarea name='content' id='fileedit-content' "
1558
- "rows='20' cols='80'>");
1559
- CX("Loading...");
1560
- CX("</textarea>\n");
1561
-
1544
+ /* Status bar */
15621545
CX("<div id='fossil-status-bar'>Async. status messages will go "
1563
- "here.</div>\n");
1564
-
1565
- /******* Flags/options *******/
1566
- CX("<fieldset class='fileedit-options' id='options'>"
1567
- "<legend>Options</legend><div>"
1568
- /* Chrome does not sanely lay out multiple
1569
- ** fieldset children after the <legend>, so
1570
- ** a containing div is necessary. */);
1571
- style_labeled_checkbox("cb-dry-run",
1572
- "dry_run", "Dry-run?", "1",
1573
- "In dry-run mode, the Save button performs "
1574
- "all work needed for saving but then rolls "
1575
- "back the transaction, and thus does not "
1576
- "really save.",
1577
- 1);
1578
- style_labeled_checkbox("cb-allow-fork",
1579
- "allow_fork", "Allow fork?", "1",
1580
- "Allow saving to create a fork?",
1581
- cimi.flags & CIMINI_ALLOW_FORK);
1582
- style_labeled_checkbox("cb-allow-older",
1583
- "allow_older", "Allow older?", "1",
1584
- "Allow saving against a parent version "
1585
- "which has a newer timestamp?",
1586
- cimi.flags & CIMINI_ALLOW_OLDER);
1587
- style_labeled_checkbox("cb-exec-bit",
1588
- "exec_bit", "Executable?", "1",
1589
- "Set the executable bit?",
1590
- PERM_EXE==cimi.filePerm);
1591
- style_labeled_checkbox("cb-allow-merge-conflict",
1592
- "allow_merge_conflict",
1593
- "Allow merge conflict markers?", "1",
1594
- "Allow saving even if the content contains "
1595
- "what appear to be fossil merge conflict "
1596
- "markers?",
1597
- cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
1598
- style_labeled_checkbox("cb-prefer-delta",
1599
- "prefer_delta",
1600
- "Prefer delta manifest?", "1",
1601
- "Will create a delta manifest, instead of "
1602
- "baseline, if conditions are favorable to do "
1603
- "so. This option is only a suggestion.",
1604
- cimi.flags & CIMINI_PREFER_DELTA);
1605
- style_select_list_int("select-eol-style",
1606
- "eol", "EOL Style",
1607
- "EOL conversion policy, noting that "
1608
- "form-processing may implicitly change the "
1609
- "line endings of the input.",
1610
- (cimi.flags & CIMINI_CONVERT_EOL_UNIX)
1611
- ? 1 : (cimi.flags & CIMINI_CONVERT_EOL_WINDOWS
1612
- ? 2 : 0),
1613
- "Inherit", 0,
1614
- "Unix", 1,
1615
- "Windows", 2,
1616
- NULL);
1617
- style_select_list_int("select-font-size",
1618
- "editor_font_size", "Editor Font Size",
1619
- NULL/*tooltip*/,
1620
- 100,
1621
- "100%", 100, "125%", 125,
1622
- "150%", 150, "175%", 175,
1623
- "200%", 200, NULL);
1624
-
1625
- CX("</div></fieldset>") /* end of checkboxes */;
1626
-
1627
- /******* Comment *******/
1628
- CX("<a id='comment'></a>");
1629
- CX("<fieldset><legend>Commit message</legend><div>");
1630
- CX("<textarea name='comment' rows='3' cols='80' "
1631
- "id='fileedit-comment'>");
1632
- /* ^^^ adding the 'required' attribute means we cannot even submit
1633
- ** for PREVIEW mode if it's empty :/. */
1634
- if(blob_size(&cimi.comment)){
1635
- CX("%h", blob_str(&cimi.comment));
1636
- }
1637
- CX("</textarea>\n");
1638
- CX("<div class='fileedit-hint'>Comments use the Fossil wiki markup "
1639
- "syntax.</div>\n"/*TODO: select for fossil/md/plain text*/);
1640
- CX("</div></fieldset>\n");
1641
-
1642
- /******* Buttons *******/
1643
- CX("<a id='buttons'></a>");
1644
- CX("<fieldset class='fileedit-options'>"
1645
- "<legend>Ask the server to...</legend><div>");
1646
- CX("<button id='fileedit-btn-commit'>Commit</button>");
1647
- CX("<button id='fileedit-btn-diffsbs'>Diff (SBS)</button>");
1648
- CX("<button id='fileedit-btn-diffu'>Diff (Unified)</button>");
1649
- CX("<button id='fileedit-btn-preview'>Preview</button>");
1650
- /* Default preview rendering mode selection... */
1651
- previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime);
1652
- style_select_list_int("select-preview-mode",
1653
- "preview_render_mode",
1654
- "Preview Mode",
1655
- "Preview mode format.",
1656
- previewRenderMode,
1657
- "Guess", FE_RENDER_GUESS,
1658
- "Wiki/Markdown", FE_RENDER_WIKI,
1659
- "HTML (iframe)", FE_RENDER_HTML,
1660
- "Plain Text", FE_RENDER_PLAIN_TEXT,
1661
- NULL);
1662
- /*
1663
- ** Set up a JS-side mapping of the FE_RENDER_xyz values. This is
1664
- ** used for dynamically toggling certain UI components on and off.
1665
- */
1666
- blob_appendf(&endScript, "fossil.page.previewModes={"
1667
- "guess: %d, %d: 'guess', wiki: %d, %d: 'wiki',"
1668
- "html: %d, %d: 'html', text: %d, %d: 'text'"
1669
- "};\n",
1670
- FE_RENDER_GUESS, FE_RENDER_GUESS,
1671
- FE_RENDER_WIKI, FE_RENDER_WIKI,
1672
- FE_RENDER_HTML, FE_RENDER_HTML,
1673
- FE_RENDER_PLAIN_TEXT, FE_RENDER_PLAIN_TEXT);
1674
-
1675
- /* Allow selection of HTML preview iframe height */
1676
- previewHtmlHeight = atoi(PD("preview_html_ems","0"));
1677
- if(!previewHtmlHeight){
1678
- previewHtmlHeight = 40;
1679
- }
1680
- style_select_list_int("select-preview-html-ems",
1681
- "preview_html_ems",
1682
- "HTML Preview IFrame Height (EMs)",
1683
- "Height (in EMs) of the iframe used for "
1684
- "HTML preview",
1685
- previewHtmlHeight,
1686
- "", 20, "", 40,
1687
- "", 60, "", 80,
1688
- "", 100, NULL);
1689
- /* Selection of line numbers for text preview */
1690
- style_labeled_checkbox("cb-line-numbers",
1691
- "preview_ln",
1692
- "Add line numbers to plain-text previews?",
1693
- "1",
1694
- "If on, plain-text files (only) will get "
1695
- "line numbers added to the preview.",
1696
- P("preview_ln")!=0);
1697
-
1698
- CX("</div></fieldset>");
1699
-
1546
+ "here.</div>\n"/* will be moved into the tab container via JS */);
1547
+
1548
+ /* Main tab container... */
1549
+ CX("<div id='fileedit-tabs' class='tab-container'></div>");
1550
+
1551
+ /***** File/version info tab *****/
1552
+ {
1553
+ CX("<div id='fileedit-tab-version' "
1554
+ "data-tab-parent='fileedit-tabs' "
1555
+ "data-tab-label='Version Info'"
1556
+ ">");
1557
+ CX("File: "
1558
+ "[<a id='finfo-link' href='#'>/finfo</a>] "
1559
+ /* %R/finfo?name=%T&m=%!S */
1560
+ "<code id='finfo-file-name'>(loading)</code><br>");
1561
+ CX("Checkin Version: "
1562
+ "[<a id='r-link' href='#'>/info</a>] "
1563
+ /* %R/info/%!S */
1564
+ "<code id='r-label'>(loading...)</code><br>"
1565
+ );
1566
+ CX("Permalink: <code>"
1567
+ "<a id='permalink' href='#'>(loading...)</a></code><br>"
1568
+ "(Clicking the permalink will reload the page and discard "
1569
+ "all edits!)",
1570
+ zFilename, cimi.zParentUuid,
1571
+ zFilename, cimi.zParentUuid);
1572
+ CX("</div>"/*#fileedit-tab-version*/);
1573
+ }
1574
+
1575
+ /******* Content tab *******/
1576
+ {
1577
+ CX("<div id='fileedit-tab-content' "
1578
+ "data-tab-parent='fileedit-tabs' "
1579
+ "data-tab-label='File Content'"
1580
+ ">");
1581
+ CX("<textarea name='content' id='fileedit-content-editor' "
1582
+ "rows='20' cols='80'>");
1583
+ CX("Loading...");
1584
+ CX("</textarea>\n");
1585
+ CX("</div>"/*#tab-file-content*/);
1586
+ }
1587
+
1588
+ /****** Preview tab ******/
1589
+ {
1590
+ CX("<div id='fileedit-tab-preview' "
1591
+ "data-tab-parent='fileedit-tabs' "
1592
+ "data-tab-label='Preview'"
1593
+ ">");
1594
+
1595
+ CX("<fieldset class='fileedit-options'>"
1596
+ "<legend>Preview...</legend><div>");
1597
+
1598
+ CX("<div class='preview-controls'>");
1599
+ CX("<button>Refresh</button>");
1600
+ /* Default preview rendering mode selection... */
1601
+ previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime);
1602
+ style_select_list_int("select-preview-mode",
1603
+ "preview_render_mode",
1604
+ "Preview Mode",
1605
+ "Preview mode format.",
1606
+ previewRenderMode,
1607
+ "Guess", FE_RENDER_GUESS,
1608
+ "Wiki/Markdown", FE_RENDER_WIKI,
1609
+ "HTML (iframe)", FE_RENDER_HTML,
1610
+ "Plain Text", FE_RENDER_PLAIN_TEXT,
1611
+ NULL);
1612
+ /*
1613
+ ** Set up a JS-side mapping of the FE_RENDER_xyz values. This is
1614
+ ** used for dynamically toggling certain UI components on and off.
1615
+ */
1616
+ blob_appendf(&endScript, "fossil.page.previewModes={"
1617
+ "guess: %d, %d: 'guess', wiki: %d, %d: 'wiki',"
1618
+ "html: %d, %d: 'html', text: %d, %d: 'text'"
1619
+ "};\n",
1620
+ FE_RENDER_GUESS, FE_RENDER_GUESS,
1621
+ FE_RENDER_WIKI, FE_RENDER_WIKI,
1622
+ FE_RENDER_HTML, FE_RENDER_HTML,
1623
+ FE_RENDER_PLAIN_TEXT, FE_RENDER_PLAIN_TEXT);
1624
+ /* Allow selection of HTML preview iframe height */
1625
+ previewHtmlHeight = atoi(PD("preview_html_ems","0"));
1626
+ if(!previewHtmlHeight){
1627
+ previewHtmlHeight = 40;
1628
+ }
1629
+ style_select_list_int("select-preview-html-ems",
1630
+ "preview_html_ems",
1631
+ "HTML Preview IFrame Height (EMs)",
1632
+ "Height (in EMs) of the iframe used for "
1633
+ "HTML preview",
1634
+ previewHtmlHeight,
1635
+ "", 20, "", 40,
1636
+ "", 60, "", 80,
1637
+ "", 100, NULL);
1638
+ /* Selection of line numbers for text preview */
1639
+ style_labeled_checkbox("cb-line-numbers",
1640
+ "preview_ln",
1641
+ "Add line numbers to plain-text previews?",
1642
+ "1",
1643
+ "If on, plain-text files (only) will get "
1644
+ "line numbers added to the preview.",
1645
+ P("preview_ln")!=0);
1646
+ CX("</div></fieldset>"/*.fileedit-options*/);
1647
+ CX("<div id='fileedit-tab-preview-wrapper'></div>");
1648
+ CX("</div>"/*#fileedit-tab-preview*/);
1649
+ }
1650
+
1651
+ /****** Diff tab ******/
1652
+ {
1653
+ CX("<div id='fileedit-tab-diff' "
1654
+ "data-tab-parent='fileedit-tabs' "
1655
+ "data-tab-label='Diff'"
1656
+ ">");
1657
+ CX("<div id='fileedit-tab-diff-buttons'>"
1658
+ "<button class='sbs'>Side-by-side</button>"
1659
+ "<button class='unified'>Unified</button>"
1660
+ "</div>");
1661
+ CX("<div id='fileedit-tab-diff-wrapper'>"
1662
+ "Diffs will be shown here."
1663
+ "</div>");
1664
+ CX("</div>");
1665
+ }
1666
+
1667
+
1668
+ /****** Commit ******/
1669
+ CX("<div id='fileedit-tab-commit' "
1670
+ "data-tab-parent='fileedit-tabs' "
1671
+ "data-tab-label='Commit'"
1672
+ ">");
1673
+
1674
+ {
1675
+ /******* Flags/options *******/
1676
+ CX("<fieldset class='fileedit-options'>"
1677
+ "<legend>Options</legend><div>"
1678
+ /* Chrome does not sanely lay out multiple
1679
+ ** fieldset children after the <legend>, so
1680
+ ** a containing div is necessary. */);
1681
+ style_labeled_checkbox("cb-dry-run",
1682
+ "dry_run", "Dry-run?", "1",
1683
+ "In dry-run mode, the Save button performs "
1684
+ "all work needed for saving but then rolls "
1685
+ "back the transaction, and thus does not "
1686
+ "really save.",
1687
+ 1);
1688
+ style_labeled_checkbox("cb-allow-fork",
1689
+ "allow_fork", "Allow fork?", "1",
1690
+ "Allow saving to create a fork?",
1691
+ cimi.flags & CIMINI_ALLOW_FORK);
1692
+ style_labeled_checkbox("cb-allow-older",
1693
+ "allow_older", "Allow older?", "1",
1694
+ "Allow saving against a parent version "
1695
+ "which has a newer timestamp?",
1696
+ cimi.flags & CIMINI_ALLOW_OLDER);
1697
+ style_labeled_checkbox("cb-exec-bit",
1698
+ "exec_bit", "Executable?", "1",
1699
+ "Set the executable bit?",
1700
+ PERM_EXE==cimi.filePerm);
1701
+ style_labeled_checkbox("cb-allow-merge-conflict",
1702
+ "allow_merge_conflict",
1703
+ "Allow merge conflict markers?", "1",
1704
+ "Allow saving even if the content contains "
1705
+ "what appear to be fossil merge conflict "
1706
+ "markers?",
1707
+ cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
1708
+ style_labeled_checkbox("cb-prefer-delta",
1709
+ "prefer_delta",
1710
+ "Prefer delta manifest?", "1",
1711
+ "Will create a delta manifest, instead of "
1712
+ "baseline, if conditions are favorable to "
1713
+ "do so. This option is only a suggestion.",
1714
+ cimi.flags & CIMINI_PREFER_DELTA);
1715
+ style_select_list_int("select-eol-style",
1716
+ "eol", "EOL Style",
1717
+ "EOL conversion policy, noting that "
1718
+ "form-processing may implicitly change the "
1719
+ "line endings of the input.",
1720
+ (cimi.flags & CIMINI_CONVERT_EOL_UNIX)
1721
+ ? 1 : (cimi.flags & CIMINI_CONVERT_EOL_WINDOWS
1722
+ ? 2 : 0),
1723
+ "Inherit", 0,
1724
+ "Unix", 1,
1725
+ "Windows", 2,
1726
+ NULL);
1727
+#if 0
1728
+ style_select_list_int("select-font-size",
1729
+ "editor_font_size", "Editor Font Size",
1730
+ NULL/*tooltip*/,
1731
+ 100,
1732
+ "100%", 100, "125%", 125,
1733
+ "150%", 150, "175%", 175,
1734
+ "200%", 200, NULL);
1735
+#endif
1736
+ CX("</div></fieldset>"/*checkboxes*/);
1737
+ }
1738
+
1739
+ { /******* Comment *******/
1740
+ CX("<fieldset class='fileedit-options'>"
1741
+ "<legend>Message (required)</legend><div>");
1742
+ CX("<input type='text' name='comment' "
1743
+ "id='fileedit-comment'>");
1744
+ /* ^^^ adding the 'required' attribute means we cannot even
1745
+ submit for PREVIEW mode if it's empty :/. */
1746
+ if(blob_size(&cimi.comment)){
1747
+ blob_appendf(&endScript,
1748
+ "document.querySelector('#fileedit-comment').value="
1749
+ "\"%h\";\n", blob_str(&cimi.comment));
1750
+ }
1751
+ CX("</input>\n");
1752
+ CX("<div class='fileedit-hint'>Comments use the Fossil wiki markup "
1753
+ "syntax.</div>\n"/*TODO: select for fossil/md/plain text*/);
1754
+ CX("</div></fieldset>\n"/*commit comment*/);
1755
+ CX("<div>"
1756
+ "<button id='fileedit-btn-commit'>Commit</button>"
1757
+ "</div>\n");
1758
+ CX("<div id='fileedit-manifest'></div>\n");
1759
+ }
1760
+
1761
+ CX("</div>"/*#fileedit-tab-commit*/);
1762
+
17001763
/******* End of form *******/
17011764
CX("</form>\n");
1702
-
1703
- CX("<div id='ajax-target'>%s</div>"
1704
- /* this is where preview/diff go */);
17051765
17061766
/* Dynamically populate the editor... */
17071767
blob_appendf(&endScript,
17081768
"fossil.page.loadFile('%j','%j');",
17091769
zFilename, cimi.zParentUuid);
@@ -1717,11 +1777,10 @@
17171777
CX("<div class='fileedit-error-report'>%s</div>",
17181778
blob_str(&err));
17191779
}
17201780
blob_reset(&err);
17211781
CheckinMiniInfo_cleanup(&cimi);
1722
- style_emit_script_fetch();
17231782
fileedit_emit_page_script();
17241783
if(blob_size(&endScript)>0){
17251784
style_emit_script_tag(0);
17261785
CX("(function(){\n");
17271786
CX("try{\n%b\n}catch(e){console.error('Exception:',e)}\n",
17281787
--- src/fileedit.c
+++ src/fileedit.c
@@ -1446,13 +1446,13 @@
1446
1447 /*
1448 ** Emits utility script code specific to the /fileedit page.
1449 */
1450 static void fileedit_emit_page_script(){
1451 style_emit_script_tag(0);
1452 CX("%s\n", builtin_text("fossil.page.fileedit.js"));
1453 style_emit_script_tag(1);
1454 }
1455
1456 /*
1457 ** WEBPAGE: fileedit
1458 **
@@ -1516,194 +1516,254 @@
1516 ** thus have already caused us to skipped to the end of the page to
1517 ** render the errors. Any up-coming errors, barring malloc failure
1518 ** or similar, are not "that" fatal. We can/should continue
1519 ** rendering the page, then output the error message at the end.
1520 ********************************************************************/
1521 CX("<h1>Editing:</h1>");
1522 CX("<p class='fileedit-hint'>");
1523 CX("File: "
1524 "[<a id='finfo-link' href='#'>info</a>] "
1525 /* %R/finfo?name=%T&m=%!S */
1526 "<code id='finfo-file-name'>(loading)</code><br>");
1527 CX("Checkin Version: "
1528 "[<a id='r-link' href='#'>info</a>] "
1529 /* %R/info/%!S */
1530 "<code id='r-label'>(loading...)</code><br>"
1531 );
1532 CX("Permalink: <code>"
1533 "<a id='permalink' href='#'>(loading...)</a></code><br>"
1534 "(Clicking the permalink will reload the page and discard "
1535 "all edits!)",
1536 zFilename, cimi.zParentUuid,
1537 zFilename, cimi.zParentUuid);
1538 CX("</p>");
1539 CX("<p>This page is <em>NEW AND EXPERIMENTAL</em>. "
1540 "USE AT YOUR OWN RISK, preferably on a test "
1541 "repo.</p>\n");
1542
1543 CX("<form action='#' method='POST' "
1544 "class='fileedit' id='fileedit-form' "
1545 "onsubmit='function(e){"
1546 "e.preventDefault(); e.stopPropagation(); return false;"
1547 "}'>\n");
 
 
 
 
 
 
 
1548
1549 /******* Hidden fields *******/
1550 CX("<input type='hidden' name='r' value='%s'>",
1551 cimi.zParentUuid);
1552 CX("<input type='hidden' name='file' value='%T'>",
1553 zFilename);
1554
1555 /******* Content *******/
1556 CX("<h3>File Content</h3>\n");
1557 CX("<textarea name='content' id='fileedit-content' "
1558 "rows='20' cols='80'>");
1559 CX("Loading...");
1560 CX("</textarea>\n");
1561
1562 CX("<div id='fossil-status-bar'>Async. status messages will go "
1563 "here.</div>\n");
1564
1565 /******* Flags/options *******/
1566 CX("<fieldset class='fileedit-options' id='options'>"
1567 "<legend>Options</legend><div>"
1568 /* Chrome does not sanely lay out multiple
1569 ** fieldset children after the <legend>, so
1570 ** a containing div is necessary. */);
1571 style_labeled_checkbox("cb-dry-run",
1572 "dry_run", "Dry-run?", "1",
1573 "In dry-run mode, the Save button performs "
1574 "all work needed for saving but then rolls "
1575 "back the transaction, and thus does not "
1576 "really save.",
1577 1);
1578 style_labeled_checkbox("cb-allow-fork",
1579 "allow_fork", "Allow fork?", "1",
1580 "Allow saving to create a fork?",
1581 cimi.flags & CIMINI_ALLOW_FORK);
1582 style_labeled_checkbox("cb-allow-older",
1583 "allow_older", "Allow older?", "1",
1584 "Allow saving against a parent version "
1585 "which has a newer timestamp?",
1586 cimi.flags & CIMINI_ALLOW_OLDER);
1587 style_labeled_checkbox("cb-exec-bit",
1588 "exec_bit", "Executable?", "1",
1589 "Set the executable bit?",
1590 PERM_EXE==cimi.filePerm);
1591 style_labeled_checkbox("cb-allow-merge-conflict",
1592 "allow_merge_conflict",
1593 "Allow merge conflict markers?", "1",
1594 "Allow saving even if the content contains "
1595 "what appear to be fossil merge conflict "
1596 "markers?",
1597 cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
1598 style_labeled_checkbox("cb-prefer-delta",
1599 "prefer_delta",
1600 "Prefer delta manifest?", "1",
1601 "Will create a delta manifest, instead of "
1602 "baseline, if conditions are favorable to do "
1603 "so. This option is only a suggestion.",
1604 cimi.flags & CIMINI_PREFER_DELTA);
1605 style_select_list_int("select-eol-style",
1606 "eol", "EOL Style",
1607 "EOL conversion policy, noting that "
1608 "form-processing may implicitly change the "
1609 "line endings of the input.",
1610 (cimi.flags & CIMINI_CONVERT_EOL_UNIX)
1611 ? 1 : (cimi.flags & CIMINI_CONVERT_EOL_WINDOWS
1612 ? 2 : 0),
1613 "Inherit", 0,
1614 "Unix", 1,
1615 "Windows", 2,
1616 NULL);
1617 style_select_list_int("select-font-size",
1618 "editor_font_size", "Editor Font Size",
1619 NULL/*tooltip*/,
1620 100,
1621 "100%", 100, "125%", 125,
1622 "150%", 150, "175%", 175,
1623 "200%", 200, NULL);
1624
1625 CX("</div></fieldset>") /* end of checkboxes */;
1626
1627 /******* Comment *******/
1628 CX("<a id='comment'></a>");
1629 CX("<fieldset><legend>Commit message</legend><div>");
1630 CX("<textarea name='comment' rows='3' cols='80' "
1631 "id='fileedit-comment'>");
1632 /* ^^^ adding the 'required' attribute means we cannot even submit
1633 ** for PREVIEW mode if it's empty :/. */
1634 if(blob_size(&cimi.comment)){
1635 CX("%h", blob_str(&cimi.comment));
1636 }
1637 CX("</textarea>\n");
1638 CX("<div class='fileedit-hint'>Comments use the Fossil wiki markup "
1639 "syntax.</div>\n"/*TODO: select for fossil/md/plain text*/);
1640 CX("</div></fieldset>\n");
1641
1642 /******* Buttons *******/
1643 CX("<a id='buttons'></a>");
1644 CX("<fieldset class='fileedit-options'>"
1645 "<legend>Ask the server to...</legend><div>");
1646 CX("<button id='fileedit-btn-commit'>Commit</button>");
1647 CX("<button id='fileedit-btn-diffsbs'>Diff (SBS)</button>");
1648 CX("<button id='fileedit-btn-diffu'>Diff (Unified)</button>");
1649 CX("<button id='fileedit-btn-preview'>Preview</button>");
1650 /* Default preview rendering mode selection... */
1651 previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime);
1652 style_select_list_int("select-preview-mode",
1653 "preview_render_mode",
1654 "Preview Mode",
1655 "Preview mode format.",
1656 previewRenderMode,
1657 "Guess", FE_RENDER_GUESS,
1658 "Wiki/Markdown", FE_RENDER_WIKI,
1659 "HTML (iframe)", FE_RENDER_HTML,
1660 "Plain Text", FE_RENDER_PLAIN_TEXT,
1661 NULL);
1662 /*
1663 ** Set up a JS-side mapping of the FE_RENDER_xyz values. This is
1664 ** used for dynamically toggling certain UI components on and off.
1665 */
1666 blob_appendf(&endScript, "fossil.page.previewModes={"
1667 "guess: %d, %d: 'guess', wiki: %d, %d: 'wiki',"
1668 "html: %d, %d: 'html', text: %d, %d: 'text'"
1669 "};\n",
1670 FE_RENDER_GUESS, FE_RENDER_GUESS,
1671 FE_RENDER_WIKI, FE_RENDER_WIKI,
1672 FE_RENDER_HTML, FE_RENDER_HTML,
1673 FE_RENDER_PLAIN_TEXT, FE_RENDER_PLAIN_TEXT);
1674
1675 /* Allow selection of HTML preview iframe height */
1676 previewHtmlHeight = atoi(PD("preview_html_ems","0"));
1677 if(!previewHtmlHeight){
1678 previewHtmlHeight = 40;
1679 }
1680 style_select_list_int("select-preview-html-ems",
1681 "preview_html_ems",
1682 "HTML Preview IFrame Height (EMs)",
1683 "Height (in EMs) of the iframe used for "
1684 "HTML preview",
1685 previewHtmlHeight,
1686 "", 20, "", 40,
1687 "", 60, "", 80,
1688 "", 100, NULL);
1689 /* Selection of line numbers for text preview */
1690 style_labeled_checkbox("cb-line-numbers",
1691 "preview_ln",
1692 "Add line numbers to plain-text previews?",
1693 "1",
1694 "If on, plain-text files (only) will get "
1695 "line numbers added to the preview.",
1696 P("preview_ln")!=0);
1697
1698 CX("</div></fieldset>");
1699
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1700 /******* End of form *******/
1701 CX("</form>\n");
1702
1703 CX("<div id='ajax-target'>%s</div>"
1704 /* this is where preview/diff go */);
1705
1706 /* Dynamically populate the editor... */
1707 blob_appendf(&endScript,
1708 "fossil.page.loadFile('%j','%j');",
1709 zFilename, cimi.zParentUuid);
@@ -1717,11 +1777,10 @@
1717 CX("<div class='fileedit-error-report'>%s</div>",
1718 blob_str(&err));
1719 }
1720 blob_reset(&err);
1721 CheckinMiniInfo_cleanup(&cimi);
1722 style_emit_script_fetch();
1723 fileedit_emit_page_script();
1724 if(blob_size(&endScript)>0){
1725 style_emit_script_tag(0);
1726 CX("(function(){\n");
1727 CX("try{\n%b\n}catch(e){console.error('Exception:',e)}\n",
1728
--- src/fileedit.c
+++ src/fileedit.c
@@ -1446,13 +1446,13 @@
1446
1447 /*
1448 ** Emits utility script code specific to the /fileedit page.
1449 */
1450 static void fileedit_emit_page_script(){
1451 style_emit_script_fetch();
1452 style_emit_script_tabs();
1453 style_emit_script_builtin("fossil.page.fileedit.js");
1454 }
1455
1456 /*
1457 ** WEBPAGE: fileedit
1458 **
@@ -1516,194 +1516,254 @@
1516 ** thus have already caused us to skipped to the end of the page to
1517 ** render the errors. Any up-coming errors, barring malloc failure
1518 ** or similar, are not "that" fatal. We can/should continue
1519 ** rendering the page, then output the error message at the end.
1520 ********************************************************************/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1521 CX("<p>This page is <em>NEW AND EXPERIMENTAL</em>. "
1522 "USE AT YOUR OWN RISK, preferably on a test "
1523 "repo.</p>\n");
1524
1525 /*
1526 ** We don't strictly need a FORM because we manually cherry-pick and
1527 ** submit the form data, but it being in a form allows us to easily
1528 ** use the FormData type for serialization.
1529 **
1530 ** TODO?: we can almost certainly replace this element with a plain
1531 ** DIV, which would eliminate the event-handling hassles of trying
1532 ** to suppress the submit... but it would also eliminate the option
1533 ** of using HTML form field validation.
1534 */
1535 CX("<form action='#' method='POST' class='fileedit' "
1536 "id='fileedit-form'>");
1537
1538 /******* Hidden fields *******/
1539 CX("<input type='hidden' name='r' value='%s'>",
1540 cimi.zParentUuid);
1541 CX("<input type='hidden' name='file' value='%T'>",
1542 zFilename);
1543
1544 /* Status bar */
 
 
 
 
 
 
1545 CX("<div id='fossil-status-bar'>Async. status messages will go "
1546 "here.</div>\n"/* will be moved into the tab container via JS */);
1547
1548 /* Main tab container... */
1549 CX("<div id='fileedit-tabs' class='tab-container'></div>");
1550
1551 /***** File/version info tab *****/
1552 {
1553 CX("<div id='fileedit-tab-version' "
1554 "data-tab-parent='fileedit-tabs' "
1555 "data-tab-label='Version Info'"
1556 ">");
1557 CX("File: "
1558 "[<a id='finfo-link' href='#'>/finfo</a>] "
1559 /* %R/finfo?name=%T&m=%!S */
1560 "<code id='finfo-file-name'>(loading)</code><br>");
1561 CX("Checkin Version: "
1562 "[<a id='r-link' href='#'>/info</a>] "
1563 /* %R/info/%!S */
1564 "<code id='r-label'>(loading...)</code><br>"
1565 );
1566 CX("Permalink: <code>"
1567 "<a id='permalink' href='#'>(loading...)</a></code><br>"
1568 "(Clicking the permalink will reload the page and discard "
1569 "all edits!)",
1570 zFilename, cimi.zParentUuid,
1571 zFilename, cimi.zParentUuid);
1572 CX("</div>"/*#fileedit-tab-version*/);
1573 }
1574
1575 /******* Content tab *******/
1576 {
1577 CX("<div id='fileedit-tab-content' "
1578 "data-tab-parent='fileedit-tabs' "
1579 "data-tab-label='File Content'"
1580 ">");
1581 CX("<textarea name='content' id='fileedit-content-editor' "
1582 "rows='20' cols='80'>");
1583 CX("Loading...");
1584 CX("</textarea>\n");
1585 CX("</div>"/*#tab-file-content*/);
1586 }
1587
1588 /****** Preview tab ******/
1589 {
1590 CX("<div id='fileedit-tab-preview' "
1591 "data-tab-parent='fileedit-tabs' "
1592 "data-tab-label='Preview'"
1593 ">");
1594
1595 CX("<fieldset class='fileedit-options'>"
1596 "<legend>Preview...</legend><div>");
1597
1598 CX("<div class='preview-controls'>");
1599 CX("<button>Refresh</button>");
1600 /* Default preview rendering mode selection... */
1601 previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime);
1602 style_select_list_int("select-preview-mode",
1603 "preview_render_mode",
1604 "Preview Mode",
1605 "Preview mode format.",
1606 previewRenderMode,
1607 "Guess", FE_RENDER_GUESS,
1608 "Wiki/Markdown", FE_RENDER_WIKI,
1609 "HTML (iframe)", FE_RENDER_HTML,
1610 "Plain Text", FE_RENDER_PLAIN_TEXT,
1611 NULL);
1612 /*
1613 ** Set up a JS-side mapping of the FE_RENDER_xyz values. This is
1614 ** used for dynamically toggling certain UI components on and off.
1615 */
1616 blob_appendf(&endScript, "fossil.page.previewModes={"
1617 "guess: %d, %d: 'guess', wiki: %d, %d: 'wiki',"
1618 "html: %d, %d: 'html', text: %d, %d: 'text'"
1619 "};\n",
1620 FE_RENDER_GUESS, FE_RENDER_GUESS,
1621 FE_RENDER_WIKI, FE_RENDER_WIKI,
1622 FE_RENDER_HTML, FE_RENDER_HTML,
1623 FE_RENDER_PLAIN_TEXT, FE_RENDER_PLAIN_TEXT);
1624 /* Allow selection of HTML preview iframe height */
1625 previewHtmlHeight = atoi(PD("preview_html_ems","0"));
1626 if(!previewHtmlHeight){
1627 previewHtmlHeight = 40;
1628 }
1629 style_select_list_int("select-preview-html-ems",
1630 "preview_html_ems",
1631 "HTML Preview IFrame Height (EMs)",
1632 "Height (in EMs) of the iframe used for "
1633 "HTML preview",
1634 previewHtmlHeight,
1635 "", 20, "", 40,
1636 "", 60, "", 80,
1637 "", 100, NULL);
1638 /* Selection of line numbers for text preview */
1639 style_labeled_checkbox("cb-line-numbers",
1640 "preview_ln",
1641 "Add line numbers to plain-text previews?",
1642 "1",
1643 "If on, plain-text files (only) will get "
1644 "line numbers added to the preview.",
1645 P("preview_ln")!=0);
1646 CX("</div></fieldset>"/*.fileedit-options*/);
1647 CX("<div id='fileedit-tab-preview-wrapper'></div>");
1648 CX("</div>"/*#fileedit-tab-preview*/);
1649 }
1650
1651 /****** Diff tab ******/
1652 {
1653 CX("<div id='fileedit-tab-diff' "
1654 "data-tab-parent='fileedit-tabs' "
1655 "data-tab-label='Diff'"
1656 ">");
1657 CX("<div id='fileedit-tab-diff-buttons'>"
1658 "<button class='sbs'>Side-by-side</button>"
1659 "<button class='unified'>Unified</button>"
1660 "</div>");
1661 CX("<div id='fileedit-tab-diff-wrapper'>"
1662 "Diffs will be shown here."
1663 "</div>");
1664 CX("</div>");
1665 }
1666
1667
1668 /****** Commit ******/
1669 CX("<div id='fileedit-tab-commit' "
1670 "data-tab-parent='fileedit-tabs' "
1671 "data-tab-label='Commit'"
1672 ">");
1673
1674 {
1675 /******* Flags/options *******/
1676 CX("<fieldset class='fileedit-options'>"
1677 "<legend>Options</legend><div>"
1678 /* Chrome does not sanely lay out multiple
1679 ** fieldset children after the <legend>, so
1680 ** a containing div is necessary. */);
1681 style_labeled_checkbox("cb-dry-run",
1682 "dry_run", "Dry-run?", "1",
1683 "In dry-run mode, the Save button performs "
1684 "all work needed for saving but then rolls "
1685 "back the transaction, and thus does not "
1686 "really save.",
1687 1);
1688 style_labeled_checkbox("cb-allow-fork",
1689 "allow_fork", "Allow fork?", "1",
1690 "Allow saving to create a fork?",
1691 cimi.flags & CIMINI_ALLOW_FORK);
1692 style_labeled_checkbox("cb-allow-older",
1693 "allow_older", "Allow older?", "1",
1694 "Allow saving against a parent version "
1695 "which has a newer timestamp?",
1696 cimi.flags & CIMINI_ALLOW_OLDER);
1697 style_labeled_checkbox("cb-exec-bit",
1698 "exec_bit", "Executable?", "1",
1699 "Set the executable bit?",
1700 PERM_EXE==cimi.filePerm);
1701 style_labeled_checkbox("cb-allow-merge-conflict",
1702 "allow_merge_conflict",
1703 "Allow merge conflict markers?", "1",
1704 "Allow saving even if the content contains "
1705 "what appear to be fossil merge conflict "
1706 "markers?",
1707 cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
1708 style_labeled_checkbox("cb-prefer-delta",
1709 "prefer_delta",
1710 "Prefer delta manifest?", "1",
1711 "Will create a delta manifest, instead of "
1712 "baseline, if conditions are favorable to "
1713 "do so. This option is only a suggestion.",
1714 cimi.flags & CIMINI_PREFER_DELTA);
1715 style_select_list_int("select-eol-style",
1716 "eol", "EOL Style",
1717 "EOL conversion policy, noting that "
1718 "form-processing may implicitly change the "
1719 "line endings of the input.",
1720 (cimi.flags & CIMINI_CONVERT_EOL_UNIX)
1721 ? 1 : (cimi.flags & CIMINI_CONVERT_EOL_WINDOWS
1722 ? 2 : 0),
1723 "Inherit", 0,
1724 "Unix", 1,
1725 "Windows", 2,
1726 NULL);
1727 #if 0
1728 style_select_list_int("select-font-size",
1729 "editor_font_size", "Editor Font Size",
1730 NULL/*tooltip*/,
1731 100,
1732 "100%", 100, "125%", 125,
1733 "150%", 150, "175%", 175,
1734 "200%", 200, NULL);
1735 #endif
1736 CX("</div></fieldset>"/*checkboxes*/);
1737 }
1738
1739 { /******* Comment *******/
1740 CX("<fieldset class='fileedit-options'>"
1741 "<legend>Message (required)</legend><div>");
1742 CX("<input type='text' name='comment' "
1743 "id='fileedit-comment'>");
1744 /* ^^^ adding the 'required' attribute means we cannot even
1745 submit for PREVIEW mode if it's empty :/. */
1746 if(blob_size(&cimi.comment)){
1747 blob_appendf(&endScript,
1748 "document.querySelector('#fileedit-comment').value="
1749 "\"%h\";\n", blob_str(&cimi.comment));
1750 }
1751 CX("</input>\n");
1752 CX("<div class='fileedit-hint'>Comments use the Fossil wiki markup "
1753 "syntax.</div>\n"/*TODO: select for fossil/md/plain text*/);
1754 CX("</div></fieldset>\n"/*commit comment*/);
1755 CX("<div>"
1756 "<button id='fileedit-btn-commit'>Commit</button>"
1757 "</div>\n");
1758 CX("<div id='fileedit-manifest'></div>\n");
1759 }
1760
1761 CX("</div>"/*#fileedit-tab-commit*/);
1762
1763 /******* End of form *******/
1764 CX("</form>\n");
 
 
 
1765
1766 /* Dynamically populate the editor... */
1767 blob_appendf(&endScript,
1768 "fossil.page.loadFile('%j','%j');",
1769 zFilename, cimi.zParentUuid);
@@ -1717,11 +1777,10 @@
1777 CX("<div class='fileedit-error-report'>%s</div>",
1778 blob_str(&err));
1779 }
1780 blob_reset(&err);
1781 CheckinMiniInfo_cleanup(&cimi);
 
1782 fileedit_emit_page_script();
1783 if(blob_size(&endScript)>0){
1784 style_emit_script_tag(0);
1785 CX("(function(){\n");
1786 CX("try{\n%b\n}catch(e){console.error('Exception:',e)}\n",
1787
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -52,10 +52,32 @@
5252
console.error.apply(console,args);
5353
}
5454
return this;
5555
};
5656
57
+/**
58
+ For each property in the given object, its key/value are encoded
59
+ for use as URL parameters and the combined string is
60
+ returned. e.g. {a:1,b:2} encodes to "a=1&b=2".
61
+
62
+ If the 2nd argument is an array, each encoded element is appended
63
+ to that array and tgtArray is returned. The above object would be
64
+ appended as ['a','=','1','&','b','=','2']. This form is used for
65
+ building up parameter lists before join('')ing the array to create
66
+ the result string.
67
+*/
68
+window.fossil.encodeUrlArgs = function(obj,tgtArray){
69
+ if(!obj) return '';
70
+ const a = (tgtArray instanceof Array) ? tgtArray : [];
71
+ let k, i = 0;
72
+ for( k in obj ){
73
+ if(i++) a.push('&');
74
+ a.push(encodeURIComponent(k),
75
+ '=',encodeURIComponent(obj[k]));
76
+ }
77
+ return a===tgtArray ? a : a.join('');
78
+};
5779
/**
5880
repoUrl( repoRelativePath [,urlParams] )
5981
6082
Creates a URL by prepending this.rootPath to the given path
6183
(which must be relative from the top of the site, without a
@@ -68,13 +90,9 @@
6890
if(!urlParams) return this.rootPath+path;
6991
const url=[this.rootPath,path];
7092
url.push('?');
7193
if('string'===typeof urlParams) url.push(urlParams);
7294
else if('object'===typeof urlParams){
73
- let k, i = 0;
74
- for( k in urlParams ){
75
- if(i++) url.push('&');
76
- url.push(k,'=',encodeURIComponent(urlParams[k]));
77
- }
95
+ this.encodeUrlArgs(urlParams, url);
7896
}
7997
return url.join('');
8098
};
8199
82100
ADDED src/fossil.dom.js
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -52,10 +52,32 @@
52 console.error.apply(console,args);
53 }
54 return this;
55 };
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57 /**
58 repoUrl( repoRelativePath [,urlParams] )
59
60 Creates a URL by prepending this.rootPath to the given path
61 (which must be relative from the top of the site, without a
@@ -68,13 +90,9 @@
68 if(!urlParams) return this.rootPath+path;
69 const url=[this.rootPath,path];
70 url.push('?');
71 if('string'===typeof urlParams) url.push(urlParams);
72 else if('object'===typeof urlParams){
73 let k, i = 0;
74 for( k in urlParams ){
75 if(i++) url.push('&');
76 url.push(k,'=',encodeURIComponent(urlParams[k]));
77 }
78 }
79 return url.join('');
80 };
81
82 DDED src/fossil.dom.js
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -52,10 +52,32 @@
52 console.error.apply(console,args);
53 }
54 return this;
55 };
56
57 /**
58 For each property in the given object, its key/value are encoded
59 for use as URL parameters and the combined string is
60 returned. e.g. {a:1,b:2} encodes to "a=1&b=2".
61
62 If the 2nd argument is an array, each encoded element is appended
63 to that array and tgtArray is returned. The above object would be
64 appended as ['a','=','1','&','b','=','2']. This form is used for
65 building up parameter lists before join('')ing the array to create
66 the result string.
67 */
68 window.fossil.encodeUrlArgs = function(obj,tgtArray){
69 if(!obj) return '';
70 const a = (tgtArray instanceof Array) ? tgtArray : [];
71 let k, i = 0;
72 for( k in obj ){
73 if(i++) a.push('&');
74 a.push(encodeURIComponent(k),
75 '=',encodeURIComponent(obj[k]));
76 }
77 return a===tgtArray ? a : a.join('');
78 };
79 /**
80 repoUrl( repoRelativePath [,urlParams] )
81
82 Creates a URL by prepending this.rootPath to the given path
83 (which must be relative from the top of the site, without a
@@ -68,13 +90,9 @@
90 if(!urlParams) return this.rootPath+path;
91 const url=[this.rootPath,path];
92 url.push('?');
93 if('string'===typeof urlParams) url.push(urlParams);
94 else if('object'===typeof urlParams){
95 this.encodeUrlArgs(urlParams, url);
 
 
 
 
96 }
97 return url.join('');
98 };
99
100 DDED src/fossil.dom.js
--- a/src/fossil.dom.js
+++ b/src/fossil.dom.js
@@ -0,0 +1,7 @@
1
+e || 'number| 'numberThis will
2
+
3
+ in t));
4
+}
5
+return e}
6
+|| 'number| 'numberThis
7
+ using the DOM API
--- a/src/fossil.dom.js
+++ b/src/fossil.dom.js
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
--- a/src/fossil.dom.js
+++ b/src/fossil.dom.js
@@ -0,0 +1,7 @@
1 e || 'number| 'numberThis will
2
3 in t));
4 }
5 return e}
6 || 'number| 'numberThis
7 using the DOM API
--- src/fossil.fetch.js
+++ src/fossil.fetch.js
@@ -38,15 +38,15 @@
3838
opt.method = 'POST';
3939
if(!(payload instanceof FormData)
4040
&& !(payload instanceof Document)
4141
&& !(payload instanceof Blob)
4242
&& !(payload instanceof File)
43
- && !(payload instanceof ArrayBuffer)){
44
- if('object'===typeof payload || payload instanceof Array){
45
- payload = JSON.stringify(payload);
46
- opt.contentType = 'application/json';
47
- }
43
+ && !(payload instanceof ArrayBuffer)
44
+ && ('object'===typeof payload
45
+ || payload instanceof Array)){
46
+ payload = JSON.stringify(payload);
47
+ opt.contentType = 'application/json';
4848
}
4949
}
5050
const url=[window.fossil.repoUrl(uri,opt.urlParams)],
5151
x=new XMLHttpRequest();
5252
if('POST'===opt.method && 'string'===typeof opt.contentType){
5353
--- src/fossil.fetch.js
+++ src/fossil.fetch.js
@@ -38,15 +38,15 @@
38 opt.method = 'POST';
39 if(!(payload instanceof FormData)
40 && !(payload instanceof Document)
41 && !(payload instanceof Blob)
42 && !(payload instanceof File)
43 && !(payload instanceof ArrayBuffer)){
44 if('object'===typeof payload || payload instanceof Array){
45 payload = JSON.stringify(payload);
46 opt.contentType = 'application/json';
47 }
48 }
49 }
50 const url=[window.fossil.repoUrl(uri,opt.urlParams)],
51 x=new XMLHttpRequest();
52 if('POST'===opt.method && 'string'===typeof opt.contentType){
53
--- src/fossil.fetch.js
+++ src/fossil.fetch.js
@@ -38,15 +38,15 @@
38 opt.method = 'POST';
39 if(!(payload instanceof FormData)
40 && !(payload instanceof Document)
41 && !(payload instanceof Blob)
42 && !(payload instanceof File)
43 && !(payload instanceof ArrayBuffer)
44 && ('object'===typeof payload
45 || payload instanceof Array)){
46 payload = JSON.stringify(payload);
47 opt.contentType = 'application/json';
48 }
49 }
50 const url=[window.fossil.repoUrl(uri,opt.urlParams)],
51 x=new XMLHttpRequest();
52 if('POST'===opt.method && 'string'===typeof opt.contentType){
53
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -1,60 +1,81 @@
1
-(function(){
1
+(function(F/*the fossil object*/){
22
"use strict";
33
/**
44
Code for the /filepage app. Requires that the fossil JS
55
bootstrapping is complete and fossil.fetch() has been installed.
66
*/
7
- const E = (s)=>document.querySelector(s);
7
+ const E = (s)=>document.querySelector(s),
8
+ D = F.dom;
89
window.addEventListener("load", function() {
9
- const P = fossil.page;
10
+ const P = F.page;
11
+ P.tabs = new fossil.TabManager('#fileedit-tabs');
1012
P.e = {
11
- taEditor: E('#fileedit-content'),
13
+ taEditor: E('#fileedit-content-editor'),
1214
taComment: E('#fileedit-comment'),
1315
ajaxContentTarget: E('#ajax-target'),
1416
form: E('#fileedit-form'),
15
- btnPreview: E("#fileedit-btn-preview"),
16
- btnDiffSbs: E("#fileedit-btn-diffsbs"),
17
- btnDiffU: E("#fileedit-btn-diffu"),
17
+ //btnPreview: E("#fileedit-btn-preview"),
18
+ //btnDiffSbs: E("#fileedit-btn-diffsbs"),
19
+ //btnDiffU: E("#fileedit-btn-diffu"),
1820
btnCommit: E("#fileedit-btn-commit"),
1921
selectPreviewModeWrap: E('#select-preview-mode'),
2022
selectHtmlEmsWrap: E('#select-preview-html-ems'),
2123
selectEolWrap: E('#select-preview-html-ems'),
22
- cbLineNumbersWrap: E('#cb-line-numbers')
24
+ cbLineNumbersWrap: E('#cb-line-numbers'),
25
+ tabs:{
26
+ content: E('#fileedit-tab-content'),
27
+ preview: E('#fileedit-tab-preview'),
28
+ diff: E('#fileedit-tab-diff'),
29
+ commit: E('#fileedit-tab-commit')
30
+ }
2331
};
2432
const stopEvent = function(e){
25
- e.preventDefault();
26
- e.stopPropagation();
33
+ //e.preventDefault();
34
+ //e.stopPropagation();
2735
return P;
2836
};
2937
3038
P.e.form.addEventListener("submit", function(e) {
3139
e.target.checkValidity();
32
- stopEvent(e);
40
+ e.preventDefault();
41
+ e.stopPropagation();
42
+ return false;
3343
}, false);
34
- P.e.btnPreview.addEventListener(
35
- "click",(e)=>stopEvent(e).preview(),false
36
- );
37
- P.e.btnDiffSbs.addEventListener(
38
- "click",(e)=>stopEvent(e).diff(true),false
39
- );
40
- P.e.btnDiffU.addEventListener(
41
- "click",(e)=>stopEvent(e).diff(false), false
44
+ //P.tabs.getButtonForTab(P.e.tabs.preview)
45
+ P.e.tabs.preview.querySelector(
46
+ 'button'
47
+ ).addEventListener(
48
+ "click",(e)=>P.preview(), false
49
+ );
50
+
51
+ document.querySelector('#fileedit-form').addEventListener(
52
+ "click",function(e){
53
+ stopEvent(e);
54
+ return false;
55
+ }
56
+ );
57
+
58
+ const diffButtons = E('#fileedit-tab-diff-buttons');
59
+ diffButtons.querySelector('button.sbs').addEventListener(
60
+ "click",(e)=>P.diff(true), false
61
+ );
62
+ diffButtons.querySelector('button.unified').addEventListener(
63
+ "click",(e)=>P.diff(false), false
4264
);
4365
P.e.btnCommit.addEventListener(
4466
"click",(e)=>stopEvent(e).commit(), false
4567
);
46
-
4768
/**
4869
Cosmetic: jump through some hoops to enable/disable
4970
certain preview options depending on the current
5071
preview mode...
5172
*/
5273
const selectPreviewMode =
5374
P.e.selectPreviewModeWrap.querySelector('select');
5475
selectPreviewMode.addEventListener(
55
- "change",function(e){
76
+ "change", function(e){
5677
const mode = e.target.value,
5778
name = P.previewModes[mode],
5879
hide = [], unhide = [];
5980
if('guess'===name){
6081
unhide.push(P.e.cbLineNumbersWrap,
@@ -83,39 +104,42 @@
83104
);
84105
selectFontSize.dispatchEvent(
85106
// Force UI update
86107
new Event('change',{target:selectFontSize})
87108
);
88
- }, false);
89109
90
-
110
+ P.tabs.e.container.insertBefore(
111
+ E('#fossil-status-bar'), P.tabs.e.tabs
112
+ );
113
+ }, false);
91114
92115
/**
93116
updateVersion() updates filename and version in relevant UI
94117
elements...
95118
96119
Returns this object.
97120
*/
98
- fossil.page.updateVersion = function(file,rev){
121
+ F.page.updateVersion = function(file,rev){
99122
this.finfo = {file,r:rev};
100123
const E = (s)=>document.querySelector(s),
101
- euc = encodeURIComponent;
124
+ euc = encodeURIComponent,
125
+ rShort = rev.substr(0,16);
102126
E('#r-label').innerText=rev;
103127
E('#finfo-link').setAttribute(
104128
'href',
105
- fossil.rootPath+'finfo?name='+euc(file)+'&m='+rev
129
+ F.repoUrl('finfo',{name:file, m:rShort})
106130
);
107131
E('#finfo-file-name').innerText=file;
108132
E('#r-link').setAttribute(
109133
'href',
110
- fossil.rootPath+'/info/'+rev
134
+ F.repoUrl('info/'+rev)
111135
);
112136
E('#r-label').innerText = rev;
113
- const purl = fossil.rootPath+'fileedit?file='+euc(file)+
114
- '&r='+rev;
115
- var e = E('#permalink');
116
- e.innerText=purl;
137
+ const purlArgs = F.encodeUrlArgs({file, r:rShort});
138
+ const purl = F.repoUrl('fileedit',purlArgs);
139
+ const e = E('#permalink');
140
+ e.innerText='fileedit?'+purlArgs;
117141
e.setAttribute('href',purl);
118142
return this;
119143
};
120144
121145
/**
@@ -122,18 +146,21 @@
122146
loadFile() loads (file,version) and updates the relevant UI elements
123147
to reflect the loaded state.
124148
125149
Returns this object, noting that the load is async.
126150
*/
127
- fossil.page.loadFile = function(file,rev){
151
+ F.page.loadFile = function(file,rev){
128152
delete this.finfo;
129
- fossil.fetch('fileedit_content',{
153
+ const self = this;
154
+ F.fetch('fileedit_content',{
130155
urlParams:{file:file,r:rev},
131156
onload:(r)=>{
132
- document.getElementById('fileedit-content').value=r;
133
- fossil.message('Loaded content.');
134
- fossil.page.updateVersion(file,rev);
157
+ F.message('Loaded content.');
158
+ self.e.taEditor.value = r;
159
+ self.updateVersion(file,rev);
160
+ self.preview();
161
+ self.tabs.switchToTab(self.e.tabs.content);
135162
}
136163
});
137164
return this;
138165
};
139166
@@ -141,25 +168,25 @@
141168
Fetches the page preview based on the contents and settings of this
142169
page's form, and updates this.e.ajaxContentTarget with the preview.
143170
144171
Returns this object, noting that the operation is async.
145172
*/
146
- fossil.page.preview = function(){
173
+ F.page.preview = function(switchToTab){
147174
if(!this.finfo){
148
- fossil.error("No content is loaded.");
175
+ F.error("No content is loaded.");
149176
return this;
150177
}
151178
const content = this.e.taEditor.value,
152
- target = this.e.ajaxContentTarget;
179
+ target = this.e.tabs.preview.querySelector(
180
+ '#fileedit-tab-preview-wrapper'
181
+ ),
182
+ self = this;
153183
const updateView = function(c){
154
- target.innerHTML = [
155
- "<div class='fileedit-preview'>",
156
- "<div>Preview</div>",
157
- c||'',
158
- "</div><!--.fileedit-diff-->"
159
- ].join('');
160
- fossil.message('Updated preview.');
184
+ if(c) target.innerHTML = c;
185
+ else D.clearElement(target);
186
+ F.message('Updated preview.');
187
+ if(switchToTab) self.tabs.switchToTab(self.e.tabs.preview);
161188
};
162189
if(!content){
163190
updateView('');
164191
return this;
165192
}
@@ -167,11 +194,12 @@
167194
fd.append('render_mode',E('select[name=preview_render_mode]').value);
168195
fd.append('file',this.finfo.file);
169196
fd.append('ln',E('[name=preview_ln]').checked ? 1 : 0);
170197
fd.append('iframe_height', E('[name=preview_html_ems]').value);
171198
fd.append('content',content);
172
- fossil.message(
199
+ target.innerText = "Fetching preview...";
200
+ F.message(
173201
"Fetching preview..."
174202
).fetch('fileedit_preview',{
175203
payload: fd,
176204
onload: updateView
177205
});
@@ -182,43 +210,39 @@
182210
Fetches the page preview based on the contents and settings of this
183211
page's form, and updates this.e.ajaxContentTarget with the preview.
184212
185213
Returns this object, noting that the operation is async.
186214
*/
187
- fossil.page.diff = function(sbs){
188
- if(!this.finfo){
189
- fossil.error("No content is loaded.");
190
- return this;
191
- }
192
- const self = this;
193
- const content = this.e.taEditor.value,
194
- target = this.e.ajaxContentTarget;
195
- const updateView = function(c){
196
- target.innerHTML = [
197
- "<div class='fileedit-diff'>",
198
- "<div>Diff <code>[",
199
- self.finfo.r,
200
- "]</code> &rarr; Local Edits</div>",
201
- c||'',
202
- "</div><!--.fileedit-diff-->"
203
- ].join('');
204
- fossil.message('Updated diff.');
205
- };
206
- if(!content){
207
- updateView('');
208
- return this;
209
- }
215
+ F.page.diff = function(sbs){
216
+ if(!this.finfo){
217
+ F.error("No content is loaded.");
218
+ return this;
219
+ }
220
+ const content = this.e.taEditor.value,
221
+ target = this.e.tabs.diff.querySelector(
222
+ '#fileedit-tab-diff-wrapper'
223
+ ),
224
+ self = this;
210225
const fd = new FormData();
211226
fd.append('file',this.finfo.file);
212227
fd.append('r', this.finfo.r);
213228
fd.append('sbs', sbs ? 1 : 0);
214229
fd.append('content',content);
215
- fossil.message(
230
+ F.message(
216231
"Fetching diff..."
217232
).fetch('fileedit_diff',{
218233
payload: fd,
219
- onload: updateView
234
+ onload: function(c){
235
+ target.innerHTML = [
236
+ "<div>Diff <code>[",
237
+ self.finfo.r,
238
+ "]</code> &rarr; Local Edits</div>",
239
+ c||'No changes.'
240
+ ].join('');
241
+ F.message('Updated diff.');
242
+ self.tabs.switchToTab(self.e.tabs.diff);
243
+ }
220244
});
221245
return this;
222246
};
223247
224248
/**
@@ -225,18 +249,18 @@
225249
Performs an async commit based on the form contents and updates
226250
the UI.
227251
228252
Returns this object.
229253
*/
230
- fossil.page.commit = function f(){
254
+ F.page.commit = function f(){
231255
if(!this.finfo){
232
- fossil.error("No content is loaded.");
256
+ F.error("No content is loaded.");
233257
return this;
234258
}
235259
const self = this;
236260
const content = this.e.taEditor.value,
237
- target = this.e.ajaxContentTarget,
261
+ target = document.querySelector('#fileedit-manifest'),
238262
cbDryRun = E('[name=dry_run]'),
239263
isDryRun = cbDryRun.checked,
240264
filename = this.finfo.file;
241265
if(!f.updateView){
242266
f.updateView = function(c){
@@ -255,13 +279,14 @@
255279
];
256280
if(!c.dryRun){
257281
msg.push('Re-activating dry-run mode.');
258282
self.e.taComment.value = '';
259283
cbDryRun.checked = true;
260
- fossil.page.updateVersion(filename, c.uuid);
284
+ F.page.updateVersion(filename, c.uuid);
261285
}
262
- fossil.message.apply(fossil, msg);
286
+ F.message.apply(fossil, msg);
287
+ self.tabs.switchToTab(self.e.tabs.commit);
263288
};
264289
}
265290
if(!content){
266291
f.updateView('');
267292
return this;
@@ -290,17 +315,16 @@
290315
if(e.checked) fd.append(name, 1);
291316
}else{
292317
console.error("Missing checkbox? name =",name);
293318
}
294319
});
295
- fossil.message(
320
+ F.message(
296321
"Checking in..."
297322
).fetch('fileedit_commit',{
298323
payload: fd,
299324
responseType: 'json',
300325
onload: f.updateView
301326
});
302327
return this;
303328
};
304329
305
-
306
-})();
330
+})(window.fossil);
307331
308332
ADDED src/fossil.tabs.js
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -1,60 +1,81 @@
1 (function(){
2 "use strict";
3 /**
4 Code for the /filepage app. Requires that the fossil JS
5 bootstrapping is complete and fossil.fetch() has been installed.
6 */
7 const E = (s)=>document.querySelector(s);
 
8 window.addEventListener("load", function() {
9 const P = fossil.page;
 
10 P.e = {
11 taEditor: E('#fileedit-content'),
12 taComment: E('#fileedit-comment'),
13 ajaxContentTarget: E('#ajax-target'),
14 form: E('#fileedit-form'),
15 btnPreview: E("#fileedit-btn-preview"),
16 btnDiffSbs: E("#fileedit-btn-diffsbs"),
17 btnDiffU: E("#fileedit-btn-diffu"),
18 btnCommit: E("#fileedit-btn-commit"),
19 selectPreviewModeWrap: E('#select-preview-mode'),
20 selectHtmlEmsWrap: E('#select-preview-html-ems'),
21 selectEolWrap: E('#select-preview-html-ems'),
22 cbLineNumbersWrap: E('#cb-line-numbers')
 
 
 
 
 
 
23 };
24 const stopEvent = function(e){
25 e.preventDefault();
26 e.stopPropagation();
27 return P;
28 };
29
30 P.e.form.addEventListener("submit", function(e) {
31 e.target.checkValidity();
32 stopEvent(e);
 
 
33 }, false);
34 P.e.btnPreview.addEventListener(
35 "click",(e)=>stopEvent(e).preview(),false
36 );
37 P.e.btnDiffSbs.addEventListener(
38 "click",(e)=>stopEvent(e).diff(true),false
39 );
40 P.e.btnDiffU.addEventListener(
41 "click",(e)=>stopEvent(e).diff(false), false
 
 
 
 
 
 
 
 
 
 
 
 
42 );
43 P.e.btnCommit.addEventListener(
44 "click",(e)=>stopEvent(e).commit(), false
45 );
46
47 /**
48 Cosmetic: jump through some hoops to enable/disable
49 certain preview options depending on the current
50 preview mode...
51 */
52 const selectPreviewMode =
53 P.e.selectPreviewModeWrap.querySelector('select');
54 selectPreviewMode.addEventListener(
55 "change",function(e){
56 const mode = e.target.value,
57 name = P.previewModes[mode],
58 hide = [], unhide = [];
59 if('guess'===name){
60 unhide.push(P.e.cbLineNumbersWrap,
@@ -83,39 +104,42 @@
83 );
84 selectFontSize.dispatchEvent(
85 // Force UI update
86 new Event('change',{target:selectFontSize})
87 );
88 }, false);
89
90
 
 
 
91
92 /**
93 updateVersion() updates filename and version in relevant UI
94 elements...
95
96 Returns this object.
97 */
98 fossil.page.updateVersion = function(file,rev){
99 this.finfo = {file,r:rev};
100 const E = (s)=>document.querySelector(s),
101 euc = encodeURIComponent;
 
102 E('#r-label').innerText=rev;
103 E('#finfo-link').setAttribute(
104 'href',
105 fossil.rootPath+'finfo?name='+euc(file)+'&m='+rev
106 );
107 E('#finfo-file-name').innerText=file;
108 E('#r-link').setAttribute(
109 'href',
110 fossil.rootPath+'/info/'+rev
111 );
112 E('#r-label').innerText = rev;
113 const purl = fossil.rootPath+'fileedit?file='+euc(file)+
114 '&r='+rev;
115 var e = E('#permalink');
116 e.innerText=purl;
117 e.setAttribute('href',purl);
118 return this;
119 };
120
121 /**
@@ -122,18 +146,21 @@
122 loadFile() loads (file,version) and updates the relevant UI elements
123 to reflect the loaded state.
124
125 Returns this object, noting that the load is async.
126 */
127 fossil.page.loadFile = function(file,rev){
128 delete this.finfo;
129 fossil.fetch('fileedit_content',{
 
130 urlParams:{file:file,r:rev},
131 onload:(r)=>{
132 document.getElementById('fileedit-content').value=r;
133 fossil.message('Loaded content.');
134 fossil.page.updateVersion(file,rev);
 
 
135 }
136 });
137 return this;
138 };
139
@@ -141,25 +168,25 @@
141 Fetches the page preview based on the contents and settings of this
142 page's form, and updates this.e.ajaxContentTarget with the preview.
143
144 Returns this object, noting that the operation is async.
145 */
146 fossil.page.preview = function(){
147 if(!this.finfo){
148 fossil.error("No content is loaded.");
149 return this;
150 }
151 const content = this.e.taEditor.value,
152 target = this.e.ajaxContentTarget;
 
 
 
153 const updateView = function(c){
154 target.innerHTML = [
155 "<div class='fileedit-preview'>",
156 "<div>Preview</div>",
157 c||'',
158 "</div><!--.fileedit-diff-->"
159 ].join('');
160 fossil.message('Updated preview.');
161 };
162 if(!content){
163 updateView('');
164 return this;
165 }
@@ -167,11 +194,12 @@
167 fd.append('render_mode',E('select[name=preview_render_mode]').value);
168 fd.append('file',this.finfo.file);
169 fd.append('ln',E('[name=preview_ln]').checked ? 1 : 0);
170 fd.append('iframe_height', E('[name=preview_html_ems]').value);
171 fd.append('content',content);
172 fossil.message(
 
173 "Fetching preview..."
174 ).fetch('fileedit_preview',{
175 payload: fd,
176 onload: updateView
177 });
@@ -182,43 +210,39 @@
182 Fetches the page preview based on the contents and settings of this
183 page's form, and updates this.e.ajaxContentTarget with the preview.
184
185 Returns this object, noting that the operation is async.
186 */
187 fossil.page.diff = function(sbs){
188 if(!this.finfo){
189 fossil.error("No content is loaded.");
190 return this;
191 }
192 const self = this;
193 const content = this.e.taEditor.value,
194 target = this.e.ajaxContentTarget;
195 const updateView = function(c){
196 target.innerHTML = [
197 "<div class='fileedit-diff'>",
198 "<div>Diff <code>[",
199 self.finfo.r,
200 "]</code> &rarr; Local Edits</div>",
201 c||'',
202 "</div><!--.fileedit-diff-->"
203 ].join('');
204 fossil.message('Updated diff.');
205 };
206 if(!content){
207 updateView('');
208 return this;
209 }
210 const fd = new FormData();
211 fd.append('file',this.finfo.file);
212 fd.append('r', this.finfo.r);
213 fd.append('sbs', sbs ? 1 : 0);
214 fd.append('content',content);
215 fossil.message(
216 "Fetching diff..."
217 ).fetch('fileedit_diff',{
218 payload: fd,
219 onload: updateView
 
 
 
 
 
 
 
 
 
220 });
221 return this;
222 };
223
224 /**
@@ -225,18 +249,18 @@
225 Performs an async commit based on the form contents and updates
226 the UI.
227
228 Returns this object.
229 */
230 fossil.page.commit = function f(){
231 if(!this.finfo){
232 fossil.error("No content is loaded.");
233 return this;
234 }
235 const self = this;
236 const content = this.e.taEditor.value,
237 target = this.e.ajaxContentTarget,
238 cbDryRun = E('[name=dry_run]'),
239 isDryRun = cbDryRun.checked,
240 filename = this.finfo.file;
241 if(!f.updateView){
242 f.updateView = function(c){
@@ -255,13 +279,14 @@
255 ];
256 if(!c.dryRun){
257 msg.push('Re-activating dry-run mode.');
258 self.e.taComment.value = '';
259 cbDryRun.checked = true;
260 fossil.page.updateVersion(filename, c.uuid);
261 }
262 fossil.message.apply(fossil, msg);
 
263 };
264 }
265 if(!content){
266 f.updateView('');
267 return this;
@@ -290,17 +315,16 @@
290 if(e.checked) fd.append(name, 1);
291 }else{
292 console.error("Missing checkbox? name =",name);
293 }
294 });
295 fossil.message(
296 "Checking in..."
297 ).fetch('fileedit_commit',{
298 payload: fd,
299 responseType: 'json',
300 onload: f.updateView
301 });
302 return this;
303 };
304
305
306 })();
307
308 DDED src/fossil.tabs.js
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -1,60 +1,81 @@
1 (function(F/*the fossil object*/){
2 "use strict";
3 /**
4 Code for the /filepage app. Requires that the fossil JS
5 bootstrapping is complete and fossil.fetch() has been installed.
6 */
7 const E = (s)=>document.querySelector(s),
8 D = F.dom;
9 window.addEventListener("load", function() {
10 const P = F.page;
11 P.tabs = new fossil.TabManager('#fileedit-tabs');
12 P.e = {
13 taEditor: E('#fileedit-content-editor'),
14 taComment: E('#fileedit-comment'),
15 ajaxContentTarget: E('#ajax-target'),
16 form: E('#fileedit-form'),
17 //btnPreview: E("#fileedit-btn-preview"),
18 //btnDiffSbs: E("#fileedit-btn-diffsbs"),
19 //btnDiffU: E("#fileedit-btn-diffu"),
20 btnCommit: E("#fileedit-btn-commit"),
21 selectPreviewModeWrap: E('#select-preview-mode'),
22 selectHtmlEmsWrap: E('#select-preview-html-ems'),
23 selectEolWrap: E('#select-preview-html-ems'),
24 cbLineNumbersWrap: E('#cb-line-numbers'),
25 tabs:{
26 content: E('#fileedit-tab-content'),
27 preview: E('#fileedit-tab-preview'),
28 diff: E('#fileedit-tab-diff'),
29 commit: E('#fileedit-tab-commit')
30 }
31 };
32 const stopEvent = function(e){
33 //e.preventDefault();
34 //e.stopPropagation();
35 return P;
36 };
37
38 P.e.form.addEventListener("submit", function(e) {
39 e.target.checkValidity();
40 e.preventDefault();
41 e.stopPropagation();
42 return false;
43 }, false);
44 //P.tabs.getButtonForTab(P.e.tabs.preview)
45 P.e.tabs.preview.querySelector(
46 'button'
47 ).addEventListener(
48 "click",(e)=>P.preview(), false
49 );
50
51 document.querySelector('#fileedit-form').addEventListener(
52 "click",function(e){
53 stopEvent(e);
54 return false;
55 }
56 );
57
58 const diffButtons = E('#fileedit-tab-diff-buttons');
59 diffButtons.querySelector('button.sbs').addEventListener(
60 "click",(e)=>P.diff(true), false
61 );
62 diffButtons.querySelector('button.unified').addEventListener(
63 "click",(e)=>P.diff(false), false
64 );
65 P.e.btnCommit.addEventListener(
66 "click",(e)=>stopEvent(e).commit(), false
67 );
 
68 /**
69 Cosmetic: jump through some hoops to enable/disable
70 certain preview options depending on the current
71 preview mode...
72 */
73 const selectPreviewMode =
74 P.e.selectPreviewModeWrap.querySelector('select');
75 selectPreviewMode.addEventListener(
76 "change", function(e){
77 const mode = e.target.value,
78 name = P.previewModes[mode],
79 hide = [], unhide = [];
80 if('guess'===name){
81 unhide.push(P.e.cbLineNumbersWrap,
@@ -83,39 +104,42 @@
104 );
105 selectFontSize.dispatchEvent(
106 // Force UI update
107 new Event('change',{target:selectFontSize})
108 );
 
109
110 P.tabs.e.container.insertBefore(
111 E('#fossil-status-bar'), P.tabs.e.tabs
112 );
113 }, false);
114
115 /**
116 updateVersion() updates filename and version in relevant UI
117 elements...
118
119 Returns this object.
120 */
121 F.page.updateVersion = function(file,rev){
122 this.finfo = {file,r:rev};
123 const E = (s)=>document.querySelector(s),
124 euc = encodeURIComponent,
125 rShort = rev.substr(0,16);
126 E('#r-label').innerText=rev;
127 E('#finfo-link').setAttribute(
128 'href',
129 F.repoUrl('finfo',{name:file, m:rShort})
130 );
131 E('#finfo-file-name').innerText=file;
132 E('#r-link').setAttribute(
133 'href',
134 F.repoUrl('info/'+rev)
135 );
136 E('#r-label').innerText = rev;
137 const purlArgs = F.encodeUrlArgs({file, r:rShort});
138 const purl = F.repoUrl('fileedit',purlArgs);
139 const e = E('#permalink');
140 e.innerText='fileedit?'+purlArgs;
141 e.setAttribute('href',purl);
142 return this;
143 };
144
145 /**
@@ -122,18 +146,21 @@
146 loadFile() loads (file,version) and updates the relevant UI elements
147 to reflect the loaded state.
148
149 Returns this object, noting that the load is async.
150 */
151 F.page.loadFile = function(file,rev){
152 delete this.finfo;
153 const self = this;
154 F.fetch('fileedit_content',{
155 urlParams:{file:file,r:rev},
156 onload:(r)=>{
157 F.message('Loaded content.');
158 self.e.taEditor.value = r;
159 self.updateVersion(file,rev);
160 self.preview();
161 self.tabs.switchToTab(self.e.tabs.content);
162 }
163 });
164 return this;
165 };
166
@@ -141,25 +168,25 @@
168 Fetches the page preview based on the contents and settings of this
169 page's form, and updates this.e.ajaxContentTarget with the preview.
170
171 Returns this object, noting that the operation is async.
172 */
173 F.page.preview = function(switchToTab){
174 if(!this.finfo){
175 F.error("No content is loaded.");
176 return this;
177 }
178 const content = this.e.taEditor.value,
179 target = this.e.tabs.preview.querySelector(
180 '#fileedit-tab-preview-wrapper'
181 ),
182 self = this;
183 const updateView = function(c){
184 if(c) target.innerHTML = c;
185 else D.clearElement(target);
186 F.message('Updated preview.');
187 if(switchToTab) self.tabs.switchToTab(self.e.tabs.preview);
 
 
 
188 };
189 if(!content){
190 updateView('');
191 return this;
192 }
@@ -167,11 +194,12 @@
194 fd.append('render_mode',E('select[name=preview_render_mode]').value);
195 fd.append('file',this.finfo.file);
196 fd.append('ln',E('[name=preview_ln]').checked ? 1 : 0);
197 fd.append('iframe_height', E('[name=preview_html_ems]').value);
198 fd.append('content',content);
199 target.innerText = "Fetching preview...";
200 F.message(
201 "Fetching preview..."
202 ).fetch('fileedit_preview',{
203 payload: fd,
204 onload: updateView
205 });
@@ -182,43 +210,39 @@
210 Fetches the page preview based on the contents and settings of this
211 page's form, and updates this.e.ajaxContentTarget with the preview.
212
213 Returns this object, noting that the operation is async.
214 */
215 F.page.diff = function(sbs){
216 if(!this.finfo){
217 F.error("No content is loaded.");
218 return this;
219 }
220 const content = this.e.taEditor.value,
221 target = this.e.tabs.diff.querySelector(
222 '#fileedit-tab-diff-wrapper'
223 ),
224 self = this;
 
 
 
 
 
 
 
 
 
 
 
 
 
225 const fd = new FormData();
226 fd.append('file',this.finfo.file);
227 fd.append('r', this.finfo.r);
228 fd.append('sbs', sbs ? 1 : 0);
229 fd.append('content',content);
230 F.message(
231 "Fetching diff..."
232 ).fetch('fileedit_diff',{
233 payload: fd,
234 onload: function(c){
235 target.innerHTML = [
236 "<div>Diff <code>[",
237 self.finfo.r,
238 "]</code> &rarr; Local Edits</div>",
239 c||'No changes.'
240 ].join('');
241 F.message('Updated diff.');
242 self.tabs.switchToTab(self.e.tabs.diff);
243 }
244 });
245 return this;
246 };
247
248 /**
@@ -225,18 +249,18 @@
249 Performs an async commit based on the form contents and updates
250 the UI.
251
252 Returns this object.
253 */
254 F.page.commit = function f(){
255 if(!this.finfo){
256 F.error("No content is loaded.");
257 return this;
258 }
259 const self = this;
260 const content = this.e.taEditor.value,
261 target = document.querySelector('#fileedit-manifest'),
262 cbDryRun = E('[name=dry_run]'),
263 isDryRun = cbDryRun.checked,
264 filename = this.finfo.file;
265 if(!f.updateView){
266 f.updateView = function(c){
@@ -255,13 +279,14 @@
279 ];
280 if(!c.dryRun){
281 msg.push('Re-activating dry-run mode.');
282 self.e.taComment.value = '';
283 cbDryRun.checked = true;
284 F.page.updateVersion(filename, c.uuid);
285 }
286 F.message.apply(fossil, msg);
287 self.tabs.switchToTab(self.e.tabs.commit);
288 };
289 }
290 if(!content){
291 f.updateView('');
292 return this;
@@ -290,17 +315,16 @@
315 if(e.checked) fd.append(name, 1);
316 }else{
317 console.error("Missing checkbox? name =",name);
318 }
319 });
320 F.message(
321 "Checking in..."
322 ).fetch('fileedit_commit',{
323 payload: fd,
324 responseType: 'json',
325 onload: f.updateView
326 });
327 return this;
328 };
329
330 })(window.fossil);
 
331
332 DDED src/fossil.tabs.js
--- a/src/fossil.tabs.js
+++ b/src/fossil.tabs.js
@@ -0,0 +1,9 @@
1
+"
2
+ to areturn arg;
3
+ rn arg;
4
+ };
5
+D[yes ? 'removeClass' before."
6
+ to areturn arg;
7
+ };
8
+D[yay}else{button(lbconst stopEvente.preventDefault();
9
+
--- a/src/fossil.tabs.js
+++ b/src/fossil.tabs.js
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
--- a/src/fossil.tabs.js
+++ b/src/fossil.tabs.js
@@ -0,0 +1,9 @@
1 "
2 to areturn arg;
3 rn arg;
4 };
5 D[yes ? 'removeClass' before."
6 to areturn arg;
7 };
8 D[yay}else{button(lbconst stopEvente.preventDefault();
9
--- src/main.mk
+++ src/main.mk
@@ -219,12 +219,14 @@
219219
$(SRCDIR)/ci_edit.js \
220220
$(SRCDIR)/copybtn.js \
221221
$(SRCDIR)/diff.tcl \
222222
$(SRCDIR)/forum.js \
223223
$(SRCDIR)/fossil.bootstrap.js \
224
+ $(SRCDIR)/fossil.dom.js \
224225
$(SRCDIR)/fossil.fetch.js \
225226
$(SRCDIR)/fossil.page.fileedit.js \
227
+ $(SRCDIR)/fossil.tabs.js \
226228
$(SRCDIR)/graph.js \
227229
$(SRCDIR)/href.js \
228230
$(SRCDIR)/login.js \
229231
$(SRCDIR)/markdown.md \
230232
$(SRCDIR)/menu.js \
231233
--- src/main.mk
+++ src/main.mk
@@ -219,12 +219,14 @@
219 $(SRCDIR)/ci_edit.js \
220 $(SRCDIR)/copybtn.js \
221 $(SRCDIR)/diff.tcl \
222 $(SRCDIR)/forum.js \
223 $(SRCDIR)/fossil.bootstrap.js \
 
224 $(SRCDIR)/fossil.fetch.js \
225 $(SRCDIR)/fossil.page.fileedit.js \
 
226 $(SRCDIR)/graph.js \
227 $(SRCDIR)/href.js \
228 $(SRCDIR)/login.js \
229 $(SRCDIR)/markdown.md \
230 $(SRCDIR)/menu.js \
231
--- src/main.mk
+++ src/main.mk
@@ -219,12 +219,14 @@
219 $(SRCDIR)/ci_edit.js \
220 $(SRCDIR)/copybtn.js \
221 $(SRCDIR)/diff.tcl \
222 $(SRCDIR)/forum.js \
223 $(SRCDIR)/fossil.bootstrap.js \
224 $(SRCDIR)/fossil.dom.js \
225 $(SRCDIR)/fossil.fetch.js \
226 $(SRCDIR)/fossil.page.fileedit.js \
227 $(SRCDIR)/fossil.tabs.js \
228 $(SRCDIR)/graph.js \
229 $(SRCDIR)/href.js \
230 $(SRCDIR)/login.js \
231 $(SRCDIR)/markdown.md \
232 $(SRCDIR)/menu.js \
233
+91 -42
--- src/style.c
+++ src/style.c
@@ -1422,55 +1422,84 @@
14221422
CX("</span>\n");
14231423
}
14241424
va_end(vargs);
14251425
}
14261426
1427
+/*
1428
+** The first time this is called, it emits code to install and
1429
+** bootstrap the window.fossil object, using the built-in file
1430
+** fossil.bootstrap.js (not to be confused with bootstrap.js). It does
1431
+** NOT wrap that in a script tag because it's called from
1432
+** style_emit_script_tag().
1433
+**
1434
+** Subsequent calls are no-ops.
1435
+*/
1436
+static void style_emit_script_fossil_bootstrap(){
1437
+ static int once = 0;
1438
+ if(0==once++){
1439
+ /* Set up the generic/app-agnostic parts of window.fossil */
1440
+ CX("(function(){\n"
1441
+ "if(!window.fossil) window.fossil={};\n"
1442
+ "window.fossil.version = \"%j\";\n"
1443
+ /* fossil.rootPath is the top-most CGI/server path,
1444
+ including a trailing slash. */
1445
+ "window.fossil.rootPath = \"%j\"+'/';\n",
1446
+ get_version(), g.zTop);
1447
+ /*
1448
+ ** fossil.page holds info about the current page. This is
1449
+ ** also where the current page "should" store any of its
1450
+ ** own page-specific state.
1451
+ */
1452
+ CX("window.fossil.page = {"
1453
+ "page:\"%T\""
1454
+ "};\n", g.zPath);
1455
+ /* The remaining code is not dependent on C-runtime state... */
1456
+ CX("%s\n", builtin_text("fossil.bootstrap.js"));
1457
+ CX("})();\n");
1458
+ }
1459
+}
14271460
14281461
/*
14291462
** If passed 0, it emits a script opener tag with this request's
1430
-** nonce. If passed non-0 it emits a script closing tag. The very
1431
-** first time it is called, it emits some bootstrapping JS code
1432
-** immediately after the script opener. Specifically, it defines
1433
-** window.fossil if it's not already defined, and may set some
1434
-** properties on it.
1463
+** nonce. If passed non-0 it emits a script closing tag.
1464
+**
1465
+** The very first time it is called, it emits some bootstrapping JS
1466
+** code immediately after the script opener. Specifically, it defines
1467
+** window.fossil if it's not already defined, and sets up its most
1468
+** basic functionality.
14351469
*/
14361470
void style_emit_script_tag(int phase){
14371471
static int once = 0;
14381472
if(0==phase){
14391473
CX("<script nonce='%s'>", style_nonce());
1440
- if(0==once){
1441
- once = 1;
1442
- /* Set up the generic/app-agnostic parts of window.fossil */
1443
- CX("(function(){\n");
1444
- CX("\nif(!window.fossil) window.fossil={};\n");
1445
- CX("window.fossil.version = '%j';\n", get_version());
1446
- /* fossil.rootPath is the top-most CGI/server path,
1447
- including a trailing slash. */
1448
- CX("window.fossil.rootPath = '%j'+'/';\n", g.zTop);
1449
- /*
1450
- ** fossil.page holds info about the current page. This is
1451
- ** also where the current page "should" store any of its
1452
- ** own page-specific state.
1453
- */
1454
- CX("window.fossil.page = {"
1455
- "page:'%T'"
1456
- "};\n", g.zPath);
1457
- CX("%s\n", builtin_text("fossil.bootstrap.js"));
1458
- CX("})();\n");
1474
+ if(0==once++){
1475
+ style_emit_script_fossil_bootstrap();
14591476
}
14601477
}else{
14611478
CX("</script>\n");
14621479
}
14631480
}
14641481
1482
+/*
1483
+** Emits the text of builtin_text(zName), which is assumed to be
1484
+** JavaScript code, and wrapps that in a pair of calls to
1485
+** style_emit_script_tag().
1486
+*/
1487
+void style_emit_script_builtin(char const * zName){
1488
+ style_emit_script_tag(0);
1489
+ CX("%s", builtin_text(zName));
1490
+ style_emit_script_tag(1);
1491
+}
14651492
14661493
/*
1467
-** The *FIRST* time this is called, it emits a JS script block,
1468
-** including tags, which defines window.fossil.fetch(), which works
1469
-** similarly (not identically) to the not-quite-ubiquitous global
1470
-** fetch(). It calls style_emit_script_tag(), which may inject
1471
-** other JS bootstrapping bits.
1494
+** The first time this is called, it emits a JS script block,
1495
+** including tags, using the contents of the built-in file
1496
+** fossil.fetch.js, which defines window.fossil.fetch(), an HTTP
1497
+** request/response mini-framework similar (but not identical) to the
1498
+** not-quite-ubiquitous window.fetch(). It calls
1499
+** style_emit_script_tag(), which may inject other JS bootstrapping
1500
+** bits. Subsequent calls are no-ops.
14721501
**
14731502
** JS usages:
14741503
**
14751504
** fossil.fetch( URI [, onLoadCallback] );
14761505
**
@@ -1491,31 +1520,30 @@
14911520
**
14921521
** - method: 'POST' | 'GET' (default = 'GET')
14931522
**
14941523
** - payload: anything acceptable by XHR2.send(ARG) (DOMString,
14951524
** Document, FormData, Blob, File, ArrayBuffer), or a plain object
1496
-** or array, either of which gets JSON.stringify()'d. If set then
1497
-** the method is automatically set to 'POST'. If an object/array is
1498
-** converted to JSON, the content-type is set to 'application/json'.
1499
-** By default XHR2 will set the content type based on the payload
1500
-** type.
1525
+** or array, either of which gets JSON.stringify()'d. If payload is
1526
+** set then the method is automatically set to 'POST'. If an
1527
+** object/array is converted to JSON, the contentType option is
1528
+** automatically set to 'application/json'. By default XHR2 will set
1529
+** the content type based on the payload type.
15011530
**
15021531
** - contentType: Optional request content type when POSTing. Ignored
15031532
** if the method is not 'POST'.
15041533
**
15051534
** - responseType: optional string. One of ("text", "arraybuffer",
15061535
** "blob", or "document") (as specified by XHR2). Default = "text".
15071536
** As an extension, it supports "json", which tells it that the
15081537
** response is expected to be text and that it should be
1509
-** JSON.parse()d before passing it on to the onload() callback. In
1510
-** this case, if the payload property is an object/array.
1538
+** JSON.parse()d before passing it on to the onload() callback.
15111539
**
15121540
** - urlParams: string|object. If a string, it is assumed to be a
15131541
** URI-encoded list of params in the form "key1=val1&key2=val2...",
15141542
** with NO leading '?'. If it is an object, all of its properties
15151543
** get converted to that form. Either way, the parameters get
1516
-** appended to the URL.
1544
+** appended to the URL before submitting the request.
15171545
**
15181546
** When an options object does not provide onload() or onerror()
15191547
** handlers of its own, this function falls back to
15201548
** fossil.fetch.onload() and fossil.fetch.onerror() as defaults. The
15211549
** default implementations route the data through the dev console and
@@ -1526,12 +1554,33 @@
15261554
** Returns this object, noting that the XHR request is asynchronous,
15271555
** and still in transit (or has yet to be sent) when that happens.
15281556
*/
15291557
void style_emit_script_fetch(){
15301558
static int once = 0;
1531
- if(0==once){
1532
- once = 1;
1533
- style_emit_script_tag(0);
1534
- CX("%s", builtin_text("fossil.fetch.js"));
1535
- style_emit_script_tag(1);
1559
+ if(0==once++){
1560
+ style_emit_script_builtin("fossil.fetch.js");
1561
+ }
1562
+}
1563
+
1564
+/*
1565
+** The first time this is called, it emits the JS code from the
1566
+** built-in file fossil.dom.js. Subsequent calls are no-ops.
1567
+*/
1568
+void style_emit_script_dom(){
1569
+ static int once = 0;
1570
+ if(0==once++){
1571
+ style_emit_script_builtin("fossil.dom.js");
1572
+ }
1573
+}
1574
+
1575
+/*
1576
+** The first time this is called, it calls style_emit_script_dom() and
1577
+** emits the JS code from the built-in file fossil.tabs.js.
1578
+** Subsequent calls are no-ops.
1579
+*/
1580
+void style_emit_script_tabs(){
1581
+ static int once = 0;
1582
+ if(0==once++){
1583
+ style_emit_script_dom();
1584
+ style_emit_script_builtin("fossil.tabs.js");
15361585
}
15371586
}
15381587
--- src/style.c
+++ src/style.c
@@ -1422,55 +1422,84 @@
1422 CX("</span>\n");
1423 }
1424 va_end(vargs);
1425 }
1426
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1427
1428 /*
1429 ** If passed 0, it emits a script opener tag with this request's
1430 ** nonce. If passed non-0 it emits a script closing tag. The very
1431 ** first time it is called, it emits some bootstrapping JS code
1432 ** immediately after the script opener. Specifically, it defines
1433 ** window.fossil if it's not already defined, and may set some
1434 ** properties on it.
 
1435 */
1436 void style_emit_script_tag(int phase){
1437 static int once = 0;
1438 if(0==phase){
1439 CX("<script nonce='%s'>", style_nonce());
1440 if(0==once){
1441 once = 1;
1442 /* Set up the generic/app-agnostic parts of window.fossil */
1443 CX("(function(){\n");
1444 CX("\nif(!window.fossil) window.fossil={};\n");
1445 CX("window.fossil.version = '%j';\n", get_version());
1446 /* fossil.rootPath is the top-most CGI/server path,
1447 including a trailing slash. */
1448 CX("window.fossil.rootPath = '%j'+'/';\n", g.zTop);
1449 /*
1450 ** fossil.page holds info about the current page. This is
1451 ** also where the current page "should" store any of its
1452 ** own page-specific state.
1453 */
1454 CX("window.fossil.page = {"
1455 "page:'%T'"
1456 "};\n", g.zPath);
1457 CX("%s\n", builtin_text("fossil.bootstrap.js"));
1458 CX("})();\n");
1459 }
1460 }else{
1461 CX("</script>\n");
1462 }
1463 }
1464
 
 
 
 
 
 
 
 
 
 
1465
1466 /*
1467 ** The *FIRST* time this is called, it emits a JS script block,
1468 ** including tags, which defines window.fossil.fetch(), which works
1469 ** similarly (not identically) to the not-quite-ubiquitous global
1470 ** fetch(). It calls style_emit_script_tag(), which may inject
1471 ** other JS bootstrapping bits.
 
 
1472 **
1473 ** JS usages:
1474 **
1475 ** fossil.fetch( URI [, onLoadCallback] );
1476 **
@@ -1491,31 +1520,30 @@
1491 **
1492 ** - method: 'POST' | 'GET' (default = 'GET')
1493 **
1494 ** - payload: anything acceptable by XHR2.send(ARG) (DOMString,
1495 ** Document, FormData, Blob, File, ArrayBuffer), or a plain object
1496 ** or array, either of which gets JSON.stringify()'d. If set then
1497 ** the method is automatically set to 'POST'. If an object/array is
1498 ** converted to JSON, the content-type is set to 'application/json'.
1499 ** By default XHR2 will set the content type based on the payload
1500 ** type.
1501 **
1502 ** - contentType: Optional request content type when POSTing. Ignored
1503 ** if the method is not 'POST'.
1504 **
1505 ** - responseType: optional string. One of ("text", "arraybuffer",
1506 ** "blob", or "document") (as specified by XHR2). Default = "text".
1507 ** As an extension, it supports "json", which tells it that the
1508 ** response is expected to be text and that it should be
1509 ** JSON.parse()d before passing it on to the onload() callback. In
1510 ** this case, if the payload property is an object/array.
1511 **
1512 ** - urlParams: string|object. If a string, it is assumed to be a
1513 ** URI-encoded list of params in the form "key1=val1&key2=val2...",
1514 ** with NO leading '?'. If it is an object, all of its properties
1515 ** get converted to that form. Either way, the parameters get
1516 ** appended to the URL.
1517 **
1518 ** When an options object does not provide onload() or onerror()
1519 ** handlers of its own, this function falls back to
1520 ** fossil.fetch.onload() and fossil.fetch.onerror() as defaults. The
1521 ** default implementations route the data through the dev console and
@@ -1526,12 +1554,33 @@
1526 ** Returns this object, noting that the XHR request is asynchronous,
1527 ** and still in transit (or has yet to be sent) when that happens.
1528 */
1529 void style_emit_script_fetch(){
1530 static int once = 0;
1531 if(0==once){
1532 once = 1;
1533 style_emit_script_tag(0);
1534 CX("%s", builtin_text("fossil.fetch.js"));
1535 style_emit_script_tag(1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1536 }
1537 }
1538
--- src/style.c
+++ src/style.c
@@ -1422,55 +1422,84 @@
1422 CX("</span>\n");
1423 }
1424 va_end(vargs);
1425 }
1426
1427 /*
1428 ** The first time this is called, it emits code to install and
1429 ** bootstrap the window.fossil object, using the built-in file
1430 ** fossil.bootstrap.js (not to be confused with bootstrap.js). It does
1431 ** NOT wrap that in a script tag because it's called from
1432 ** style_emit_script_tag().
1433 **
1434 ** Subsequent calls are no-ops.
1435 */
1436 static void style_emit_script_fossil_bootstrap(){
1437 static int once = 0;
1438 if(0==once++){
1439 /* Set up the generic/app-agnostic parts of window.fossil */
1440 CX("(function(){\n"
1441 "if(!window.fossil) window.fossil={};\n"
1442 "window.fossil.version = \"%j\";\n"
1443 /* fossil.rootPath is the top-most CGI/server path,
1444 including a trailing slash. */
1445 "window.fossil.rootPath = \"%j\"+'/';\n",
1446 get_version(), g.zTop);
1447 /*
1448 ** fossil.page holds info about the current page. This is
1449 ** also where the current page "should" store any of its
1450 ** own page-specific state.
1451 */
1452 CX("window.fossil.page = {"
1453 "page:\"%T\""
1454 "};\n", g.zPath);
1455 /* The remaining code is not dependent on C-runtime state... */
1456 CX("%s\n", builtin_text("fossil.bootstrap.js"));
1457 CX("})();\n");
1458 }
1459 }
1460
1461 /*
1462 ** If passed 0, it emits a script opener tag with this request's
1463 ** nonce. If passed non-0 it emits a script closing tag.
1464 **
1465 ** The very first time it is called, it emits some bootstrapping JS
1466 ** code immediately after the script opener. Specifically, it defines
1467 ** window.fossil if it's not already defined, and sets up its most
1468 ** basic functionality.
1469 */
1470 void style_emit_script_tag(int phase){
1471 static int once = 0;
1472 if(0==phase){
1473 CX("<script nonce='%s'>", style_nonce());
1474 if(0==once++){
1475 style_emit_script_fossil_bootstrap();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1476 }
1477 }else{
1478 CX("</script>\n");
1479 }
1480 }
1481
1482 /*
1483 ** Emits the text of builtin_text(zName), which is assumed to be
1484 ** JavaScript code, and wrapps that in a pair of calls to
1485 ** style_emit_script_tag().
1486 */
1487 void style_emit_script_builtin(char const * zName){
1488 style_emit_script_tag(0);
1489 CX("%s", builtin_text(zName));
1490 style_emit_script_tag(1);
1491 }
1492
1493 /*
1494 ** The first time this is called, it emits a JS script block,
1495 ** including tags, using the contents of the built-in file
1496 ** fossil.fetch.js, which defines window.fossil.fetch(), an HTTP
1497 ** request/response mini-framework similar (but not identical) to the
1498 ** not-quite-ubiquitous window.fetch(). It calls
1499 ** style_emit_script_tag(), which may inject other JS bootstrapping
1500 ** bits. Subsequent calls are no-ops.
1501 **
1502 ** JS usages:
1503 **
1504 ** fossil.fetch( URI [, onLoadCallback] );
1505 **
@@ -1491,31 +1520,30 @@
1520 **
1521 ** - method: 'POST' | 'GET' (default = 'GET')
1522 **
1523 ** - payload: anything acceptable by XHR2.send(ARG) (DOMString,
1524 ** Document, FormData, Blob, File, ArrayBuffer), or a plain object
1525 ** or array, either of which gets JSON.stringify()'d. If payload is
1526 ** set then the method is automatically set to 'POST'. If an
1527 ** object/array is converted to JSON, the contentType option is
1528 ** automatically set to 'application/json'. By default XHR2 will set
1529 ** the content type based on the payload type.
1530 **
1531 ** - contentType: Optional request content type when POSTing. Ignored
1532 ** if the method is not 'POST'.
1533 **
1534 ** - responseType: optional string. One of ("text", "arraybuffer",
1535 ** "blob", or "document") (as specified by XHR2). Default = "text".
1536 ** As an extension, it supports "json", which tells it that the
1537 ** response is expected to be text and that it should be
1538 ** JSON.parse()d before passing it on to the onload() callback.
 
1539 **
1540 ** - urlParams: string|object. If a string, it is assumed to be a
1541 ** URI-encoded list of params in the form "key1=val1&key2=val2...",
1542 ** with NO leading '?'. If it is an object, all of its properties
1543 ** get converted to that form. Either way, the parameters get
1544 ** appended to the URL before submitting the request.
1545 **
1546 ** When an options object does not provide onload() or onerror()
1547 ** handlers of its own, this function falls back to
1548 ** fossil.fetch.onload() and fossil.fetch.onerror() as defaults. The
1549 ** default implementations route the data through the dev console and
@@ -1526,12 +1554,33 @@
1554 ** Returns this object, noting that the XHR request is asynchronous,
1555 ** and still in transit (or has yet to be sent) when that happens.
1556 */
1557 void style_emit_script_fetch(){
1558 static int once = 0;
1559 if(0==once++){
1560 style_emit_script_builtin("fossil.fetch.js");
1561 }
1562 }
1563
1564 /*
1565 ** The first time this is called, it emits the JS code from the
1566 ** built-in file fossil.dom.js. Subsequent calls are no-ops.
1567 */
1568 void style_emit_script_dom(){
1569 static int once = 0;
1570 if(0==once++){
1571 style_emit_script_builtin("fossil.dom.js");
1572 }
1573 }
1574
1575 /*
1576 ** The first time this is called, it calls style_emit_script_dom() and
1577 ** emits the JS code from the built-in file fossil.tabs.js.
1578 ** Subsequent calls are no-ops.
1579 */
1580 void style_emit_script_tabs(){
1581 static int once = 0;
1582 if(0==once++){
1583 style_emit_script_dom();
1584 style_emit_script_builtin("fossil.tabs.js");
1585 }
1586 }
1587
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -641,12 +641,14 @@
641641
$(SRCDIR)/ci_edit.js \
642642
$(SRCDIR)/copybtn.js \
643643
$(SRCDIR)/diff.tcl \
644644
$(SRCDIR)/forum.js \
645645
$(SRCDIR)/fossil.bootstrap.js \
646
+ $(SRCDIR)/fossil.dom.js \
646647
$(SRCDIR)/fossil.fetch.js \
647648
$(SRCDIR)/fossil.page.fileedit.js \
649
+ $(SRCDIR)/fossil.tabs.js \
648650
$(SRCDIR)/graph.js \
649651
$(SRCDIR)/href.js \
650652
$(SRCDIR)/login.js \
651653
$(SRCDIR)/markdown.md \
652654
$(SRCDIR)/menu.js \
653655
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -641,12 +641,14 @@
641 $(SRCDIR)/ci_edit.js \
642 $(SRCDIR)/copybtn.js \
643 $(SRCDIR)/diff.tcl \
644 $(SRCDIR)/forum.js \
645 $(SRCDIR)/fossil.bootstrap.js \
 
646 $(SRCDIR)/fossil.fetch.js \
647 $(SRCDIR)/fossil.page.fileedit.js \
 
648 $(SRCDIR)/graph.js \
649 $(SRCDIR)/href.js \
650 $(SRCDIR)/login.js \
651 $(SRCDIR)/markdown.md \
652 $(SRCDIR)/menu.js \
653
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -641,12 +641,14 @@
641 $(SRCDIR)/ci_edit.js \
642 $(SRCDIR)/copybtn.js \
643 $(SRCDIR)/diff.tcl \
644 $(SRCDIR)/forum.js \
645 $(SRCDIR)/fossil.bootstrap.js \
646 $(SRCDIR)/fossil.dom.js \
647 $(SRCDIR)/fossil.fetch.js \
648 $(SRCDIR)/fossil.page.fileedit.js \
649 $(SRCDIR)/fossil.tabs.js \
650 $(SRCDIR)/graph.js \
651 $(SRCDIR)/href.js \
652 $(SRCDIR)/login.js \
653 $(SRCDIR)/markdown.md \
654 $(SRCDIR)/menu.js \
655
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -548,12 +548,14 @@
548548
$(SRCDIR)\ci_edit.js \
549549
$(SRCDIR)\copybtn.js \
550550
$(SRCDIR)\diff.tcl \
551551
$(SRCDIR)\forum.js \
552552
$(SRCDIR)\fossil.bootstrap.js \
553
+ $(SRCDIR)\fossil.dom.js \
553554
$(SRCDIR)\fossil.fetch.js \
554555
$(SRCDIR)\fossil.page.fileedit.js \
556
+ $(SRCDIR)\fossil.tabs.js \
555557
$(SRCDIR)\graph.js \
556558
$(SRCDIR)\href.js \
557559
$(SRCDIR)\login.js \
558560
$(SRCDIR)\markdown.md \
559561
$(SRCDIR)\menu.js \
560562
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -548,12 +548,14 @@
548 $(SRCDIR)\ci_edit.js \
549 $(SRCDIR)\copybtn.js \
550 $(SRCDIR)\diff.tcl \
551 $(SRCDIR)\forum.js \
552 $(SRCDIR)\fossil.bootstrap.js \
 
553 $(SRCDIR)\fossil.fetch.js \
554 $(SRCDIR)\fossil.page.fileedit.js \
 
555 $(SRCDIR)\graph.js \
556 $(SRCDIR)\href.js \
557 $(SRCDIR)\login.js \
558 $(SRCDIR)\markdown.md \
559 $(SRCDIR)\menu.js \
560
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -548,12 +548,14 @@
548 $(SRCDIR)\ci_edit.js \
549 $(SRCDIR)\copybtn.js \
550 $(SRCDIR)\diff.tcl \
551 $(SRCDIR)\forum.js \
552 $(SRCDIR)\fossil.bootstrap.js \
553 $(SRCDIR)\fossil.dom.js \
554 $(SRCDIR)\fossil.fetch.js \
555 $(SRCDIR)\fossil.page.fileedit.js \
556 $(SRCDIR)\fossil.tabs.js \
557 $(SRCDIR)\graph.js \
558 $(SRCDIR)\href.js \
559 $(SRCDIR)\login.js \
560 $(SRCDIR)\markdown.md \
561 $(SRCDIR)\menu.js \
562

Keyboard Shortcuts

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