Fossil SCM
Several small style tweaks. Changed style_labeled_checkbox() parameter order for better readability. TabManager class now supports events alerting before/after a tab is switched to. Added auto-refresh of preview when the preview tab is activated, with a checkbox to disable it for slow connections and/or large documents (the refresh button still works as before).
Commit
ee175636aa9a8862b2ae2e7d42c3365efe798a5f4e9bfb8bf4e194e0ce17fb41
Parent
875b60bccdd9395…
5 files changed
+16
-2
+28
-19
+33
-29
+52
-2
+9
-6
+16
-2
| --- src/default_css.txt | ||
| +++ src/default_css.txt | ||
| @@ -996,19 +996,27 @@ | ||
| 996 | 996 | #fileedit-comment { |
| 997 | 997 | width: 100%; |
| 998 | 998 | font-family: monospace; |
| 999 | 999 | } |
| 1000 | 1000 | .tab-container > .tabs > .tab-panel > .fileedit-options { |
| 1001 | - margin-top: 0.25em 0; | |
| 1001 | + margin-top: 0; | |
| 1002 | 1002 | border: none; |
| 1003 | 1003 | border-radius: 0; |
| 1004 | 1004 | border-bottom-width: 1px; |
| 1005 | 1005 | border-bottom-style: dotted; |
| 1006 | 1006 | } |
| 1007 | 1007 | .tab-container > .tabs > .tab-panel > .fileedit-options > button { |
| 1008 | 1008 | vertical-align: middle; |
| 1009 | 1009 | margin: 0.5em; |
| 1010 | +} | |
| 1011 | +.tab-container > .tabs > .tab-panel > .fileedit-options > input { | |
| 1012 | + vertical-align: middle; | |
| 1013 | + margin: 0.5em; | |
| 1014 | +} | |
| 1015 | +.tab-container > .tabs > .tab-panel > .fileedit-options > .input-with-label { | |
| 1016 | + vertical-align: middle; | |
| 1017 | + margin: 0.5em; | |
| 1010 | 1018 | } |
| 1011 | 1019 | //////////////////////////////////////////////////////////////////// |
| 1012 | 1020 | // Styles developed for /fileedit but which have wider |
| 1013 | 1021 | // applicability: |
| 1014 | 1022 | .flex-container { |
| @@ -1020,10 +1028,13 @@ | ||
| 1020 | 1028 | justify-content: center; |
| 1021 | 1029 | align-items: center; |
| 1022 | 1030 | } |
| 1023 | 1031 | .fileedit-options.flex-container.row { |
| 1024 | 1032 | align-items: first baseline; |
| 1033 | +} | |
| 1034 | +.fileedit-options > div > * { | |
| 1035 | + margin: 0.25em; | |
| 1025 | 1036 | } |
| 1026 | 1037 | .flex-container.row.stretch { |
| 1027 | 1038 | flex-direction: row; |
| 1028 | 1039 | flex-wrap: wrap; |
| 1029 | 1040 | align-items: stretch; |
| @@ -1036,10 +1047,13 @@ | ||
| 1036 | 1047 | align-items: center; |
| 1037 | 1048 | } |
| 1038 | 1049 | .flex-container.column.stretch { |
| 1039 | 1050 | align-items: stretch; |
| 1040 | 1051 | margin: 0; |
| 1052 | +} | |
| 1053 | +.flex-container.child-gap-small > * { | |
| 1054 | + margin: 0.25em; | |
| 1041 | 1055 | } |
| 1042 | 1056 | .font-size-100 { |
| 1043 | 1057 | font-size: 100%; |
| 1044 | 1058 | } |
| 1045 | 1059 | .font-size-125 { |
| @@ -1061,11 +1075,11 @@ | ||
| 1061 | 1075 | border: 1px inset #808080; |
| 1062 | 1076 | border-radius: 0.5em; |
| 1063 | 1077 | padding: 0.25em 0.4em; |
| 1064 | 1078 | margin: 0 0.5em; |
| 1065 | 1079 | display: inline-block; |
| 1066 | - cursor: pointer; | |
| 1080 | + cursor: default; | |
| 1067 | 1081 | } |
| 1068 | 1082 | .input-with-label > * { |
| 1069 | 1083 | vertical-align: middle; |
| 1070 | 1084 | } |
| 1071 | 1085 | .input-with-label > input { |
| 1072 | 1086 |
| --- src/default_css.txt | |
| +++ src/default_css.txt | |
| @@ -996,19 +996,27 @@ | |
| 996 | #fileedit-comment { |
| 997 | width: 100%; |
| 998 | font-family: monospace; |
| 999 | } |
| 1000 | .tab-container > .tabs > .tab-panel > .fileedit-options { |
| 1001 | margin-top: 0.25em 0; |
| 1002 | border: none; |
| 1003 | border-radius: 0; |
| 1004 | border-bottom-width: 1px; |
| 1005 | border-bottom-style: dotted; |
| 1006 | } |
| 1007 | .tab-container > .tabs > .tab-panel > .fileedit-options > button { |
| 1008 | vertical-align: middle; |
| 1009 | margin: 0.5em; |
| 1010 | } |
| 1011 | //////////////////////////////////////////////////////////////////// |
| 1012 | // Styles developed for /fileedit but which have wider |
| 1013 | // applicability: |
| 1014 | .flex-container { |
| @@ -1020,10 +1028,13 @@ | |
| 1020 | justify-content: center; |
| 1021 | align-items: center; |
| 1022 | } |
| 1023 | .fileedit-options.flex-container.row { |
| 1024 | align-items: first baseline; |
| 1025 | } |
| 1026 | .flex-container.row.stretch { |
| 1027 | flex-direction: row; |
| 1028 | flex-wrap: wrap; |
| 1029 | align-items: stretch; |
| @@ -1036,10 +1047,13 @@ | |
| 1036 | align-items: center; |
| 1037 | } |
| 1038 | .flex-container.column.stretch { |
| 1039 | align-items: stretch; |
| 1040 | margin: 0; |
| 1041 | } |
| 1042 | .font-size-100 { |
| 1043 | font-size: 100%; |
| 1044 | } |
| 1045 | .font-size-125 { |
| @@ -1061,11 +1075,11 @@ | |
| 1061 | border: 1px inset #808080; |
| 1062 | border-radius: 0.5em; |
| 1063 | padding: 0.25em 0.4em; |
| 1064 | margin: 0 0.5em; |
| 1065 | display: inline-block; |
| 1066 | cursor: pointer; |
| 1067 | } |
| 1068 | .input-with-label > * { |
| 1069 | vertical-align: middle; |
| 1070 | } |
| 1071 | .input-with-label > input { |
| 1072 |
| --- src/default_css.txt | |
| +++ src/default_css.txt | |
| @@ -996,19 +996,27 @@ | |
| 996 | #fileedit-comment { |
| 997 | width: 100%; |
| 998 | font-family: monospace; |
| 999 | } |
| 1000 | .tab-container > .tabs > .tab-panel > .fileedit-options { |
| 1001 | margin-top: 0; |
| 1002 | border: none; |
| 1003 | border-radius: 0; |
| 1004 | border-bottom-width: 1px; |
| 1005 | border-bottom-style: dotted; |
| 1006 | } |
| 1007 | .tab-container > .tabs > .tab-panel > .fileedit-options > button { |
| 1008 | vertical-align: middle; |
| 1009 | margin: 0.5em; |
| 1010 | } |
| 1011 | .tab-container > .tabs > .tab-panel > .fileedit-options > input { |
| 1012 | vertical-align: middle; |
| 1013 | margin: 0.5em; |
| 1014 | } |
| 1015 | .tab-container > .tabs > .tab-panel > .fileedit-options > .input-with-label { |
| 1016 | vertical-align: middle; |
| 1017 | margin: 0.5em; |
| 1018 | } |
| 1019 | //////////////////////////////////////////////////////////////////// |
| 1020 | // Styles developed for /fileedit but which have wider |
| 1021 | // applicability: |
| 1022 | .flex-container { |
| @@ -1020,10 +1028,13 @@ | |
| 1028 | justify-content: center; |
| 1029 | align-items: center; |
| 1030 | } |
| 1031 | .fileedit-options.flex-container.row { |
| 1032 | align-items: first baseline; |
| 1033 | } |
| 1034 | .fileedit-options > div > * { |
| 1035 | margin: 0.25em; |
| 1036 | } |
| 1037 | .flex-container.row.stretch { |
| 1038 | flex-direction: row; |
| 1039 | flex-wrap: wrap; |
| 1040 | align-items: stretch; |
| @@ -1036,10 +1047,13 @@ | |
| 1047 | align-items: center; |
| 1048 | } |
| 1049 | .flex-container.column.stretch { |
| 1050 | align-items: stretch; |
| 1051 | margin: 0; |
| 1052 | } |
| 1053 | .flex-container.child-gap-small > * { |
| 1054 | margin: 0.25em; |
| 1055 | } |
| 1056 | .font-size-100 { |
| 1057 | font-size: 100%; |
| 1058 | } |
| 1059 | .font-size-125 { |
| @@ -1061,11 +1075,11 @@ | |
| 1075 | border: 1px inset #808080; |
| 1076 | border-radius: 0.5em; |
| 1077 | padding: 0.25em 0.4em; |
| 1078 | margin: 0 0.5em; |
| 1079 | display: inline-block; |
| 1080 | cursor: default; |
| 1081 | } |
| 1082 | .input-with-label > * { |
| 1083 | vertical-align: middle; |
| 1084 | } |
| 1085 | .input-with-label > input { |
| 1086 |
+28
-19
| --- src/fileedit.c | ||
| +++ src/fileedit.c | ||
| @@ -1606,11 +1606,12 @@ | ||
| 1606 | 1606 | { |
| 1607 | 1607 | CX("<div id='fileedit-tab-content' " |
| 1608 | 1608 | "data-tab-parent='fileedit-tabs' " |
| 1609 | 1609 | "data-tab-label='File Content'" |
| 1610 | 1610 | ">"); |
| 1611 | - CX("<div class='fileedit-options flex-container row'>"); | |
| 1611 | + CX("<div class='fileedit-options " | |
| 1612 | + "flex-container row child-gap-small'>"); | |
| 1612 | 1613 | if(1){ |
| 1613 | 1614 | /* Discard/reload button. Leave this out until we have a |
| 1614 | 1615 | ** nice way of offering confirmation, e.g. like the old |
| 1615 | 1616 | ** jQuery.confirmer plugin which required a 2nd click of the |
| 1616 | 1617 | ** button within X seconds to confirm. Right now it's simply |
| @@ -1654,10 +1655,20 @@ | ||
| 1654 | 1655 | "data-f-preview-via='_postPreview' " |
| 1655 | 1656 | /* ^^^ fossil.page[methodName](content, callback) */ |
| 1656 | 1657 | "data-f-preview-to='fileedit-tab-preview-wrapper' " |
| 1657 | 1658 | /* ^^^ dest elem ID */ |
| 1658 | 1659 | ">Refresh</button>"); |
| 1660 | + /* Toggle auto-update of preview when the Preview tab is selected. */ | |
| 1661 | + style_labeled_checkbox("cb-preview-autoupdate", | |
| 1662 | + NULL, | |
| 1663 | + "Auto-refresh?", | |
| 1664 | + "1", 1, | |
| 1665 | + "If on, the preview will automatically " | |
| 1666 | + "refresh when this tab is selected. Not " | |
| 1667 | + "recommended for large files or slow " | |
| 1668 | + "connections."); | |
| 1669 | + | |
| 1659 | 1670 | /* Default preview rendering mode selection... */ |
| 1660 | 1671 | previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime); |
| 1661 | 1672 | style_select_list_int("select-preview-mode", |
| 1662 | 1673 | "preview_render_mode", |
| 1663 | 1674 | "Preview Mode", |
| @@ -1700,14 +1711,13 @@ | ||
| 1700 | 1711 | "", 100, NULL); |
| 1701 | 1712 | /* Selection of line numbers for text preview */ |
| 1702 | 1713 | style_labeled_checkbox("cb-line-numbers", |
| 1703 | 1714 | "preview_ln", |
| 1704 | 1715 | "Add line numbers to plain-text previews?", |
| 1705 | - "1", | |
| 1716 | + "1", P("preview_ln")!=0, | |
| 1706 | 1717 | "If on, plain-text files (only) will get " |
| 1707 | - "line numbers added to the preview.", | |
| 1708 | - P("preview_ln")!=0); | |
| 1718 | + "line numbers added to the preview."); | |
| 1709 | 1719 | CX("</div>"/*.fileedit-options*/); |
| 1710 | 1720 | CX("<div id='fileedit-tab-preview-wrapper'></div>"); |
| 1711 | 1721 | CX("</div>"/*#fileedit-tab-preview*/); |
| 1712 | 1722 | } |
| 1713 | 1723 | |
| @@ -1738,43 +1748,42 @@ | ||
| 1738 | 1748 | |
| 1739 | 1749 | { |
| 1740 | 1750 | /******* Commit flags/options *******/ |
| 1741 | 1751 | CX("<div class='fileedit-options flex-container row'>"); |
| 1742 | 1752 | style_labeled_checkbox("cb-dry-run", |
| 1743 | - "dry_run", "Dry-run?", "1", | |
| 1753 | + "dry_run", "Dry-run?", "1", 1, | |
| 1744 | 1754 | "In dry-run mode, the Save button performs " |
| 1745 | 1755 | "all work needed for saving but then rolls " |
| 1746 | 1756 | "back the transaction, and thus does not " |
| 1747 | - "really save.", | |
| 1748 | - 1); | |
| 1757 | + "really save."); | |
| 1749 | 1758 | style_labeled_checkbox("cb-allow-fork", |
| 1750 | 1759 | "allow_fork", "Allow fork?", "1", |
| 1751 | - "Allow saving to create a fork?", | |
| 1752 | - cimi.flags & CIMINI_ALLOW_FORK); | |
| 1760 | + cimi.flags & CIMINI_ALLOW_FORK, | |
| 1761 | + "Allow saving to create a fork?"); | |
| 1753 | 1762 | style_labeled_checkbox("cb-allow-older", |
| 1754 | 1763 | "allow_older", "Allow older?", "1", |
| 1764 | + cimi.flags & CIMINI_ALLOW_OLDER, | |
| 1755 | 1765 | "Allow saving against a parent version " |
| 1756 | - "which has a newer timestamp?", | |
| 1757 | - cimi.flags & CIMINI_ALLOW_OLDER); | |
| 1766 | + "which has a newer timestamp?"); | |
| 1758 | 1767 | style_labeled_checkbox("cb-exec-bit", |
| 1759 | 1768 | "exec_bit", "Executable?", "1", |
| 1760 | - "Set the executable bit?", | |
| 1761 | - PERM_EXE==cimi.filePerm); | |
| 1769 | + PERM_EXE==cimi.filePerm, | |
| 1770 | + "Set the executable bit?"); | |
| 1762 | 1771 | style_labeled_checkbox("cb-allow-merge-conflict", |
| 1763 | 1772 | "allow_merge_conflict", |
| 1764 | 1773 | "Allow merge conflict markers?", "1", |
| 1774 | + cimi.flags & CIMINI_ALLOW_MERGE_MARKER, | |
| 1765 | 1775 | "Allow saving even if the content contains " |
| 1766 | 1776 | "what appear to be fossil merge conflict " |
| 1767 | - "markers?", | |
| 1768 | - cimi.flags & CIMINI_ALLOW_MERGE_MARKER); | |
| 1777 | + "markers?"); | |
| 1769 | 1778 | style_labeled_checkbox("cb-prefer-delta", |
| 1770 | 1779 | "prefer_delta", |
| 1771 | 1780 | "Prefer delta manifest?", "1", |
| 1781 | + cimi.flags & CIMINI_PREFER_DELTA, | |
| 1772 | 1782 | "Will create a delta manifest, instead of " |
| 1773 | 1783 | "baseline, if conditions are favorable to " |
| 1774 | - "do so. This option is only a suggestion.", | |
| 1775 | - cimi.flags & CIMINI_PREFER_DELTA); | |
| 1784 | + "do so. This option is only a suggestion."); | |
| 1776 | 1785 | style_select_list_int("select-eol-style", |
| 1777 | 1786 | "eol", "EOL Style", |
| 1778 | 1787 | "EOL conversion policy, noting that " |
| 1779 | 1788 | "form-processing may implicitly change the " |
| 1780 | 1789 | "line endings of the input.", |
| @@ -1797,16 +1806,16 @@ | ||
| 1797 | 1806 | CX("<input type='text' name='comment' " |
| 1798 | 1807 | "id='fileedit-comment'></input>"); |
| 1799 | 1808 | CX("<textarea name='commentBig' class='hidden' " |
| 1800 | 1809 | "rows='5' id='fileedit-comment-big'></textarea>\n"); |
| 1801 | 1810 | { /* comment options... */ |
| 1802 | - CX("<div class='fileedit-options flex-container column'>"); | |
| 1811 | + CX("<div class='flex-container column child-gap-small'>"); | |
| 1803 | 1812 | CX("<button id='comment-toggle' " |
| 1804 | 1813 | "title='Toggle between single- and multi-line comment mode, " |
| 1805 | 1814 | "noting that switching from multi- to single-line will cause " |
| 1806 | 1815 | "newlines to get stripped.'" |
| 1807 | - ">toggle single-/multi-line</button> "); | |
| 1816 | + ">Toggle single-/multi-line</button> "); | |
| 1808 | 1817 | if(0){ |
| 1809 | 1818 | /* Manifests support an N-card (comment mime type) but it has |
| 1810 | 1819 | ** yet to be honored where comments are rendered, so we don't |
| 1811 | 1820 | ** currently offer it as an option here: |
| 1812 | 1821 | ** https://fossil-scm.org/forum/forumpost/662da045a1 |
| 1813 | 1822 |
| --- src/fileedit.c | |
| +++ src/fileedit.c | |
| @@ -1606,11 +1606,12 @@ | |
| 1606 | { |
| 1607 | CX("<div id='fileedit-tab-content' " |
| 1608 | "data-tab-parent='fileedit-tabs' " |
| 1609 | "data-tab-label='File Content'" |
| 1610 | ">"); |
| 1611 | CX("<div class='fileedit-options flex-container row'>"); |
| 1612 | if(1){ |
| 1613 | /* Discard/reload button. Leave this out until we have a |
| 1614 | ** nice way of offering confirmation, e.g. like the old |
| 1615 | ** jQuery.confirmer plugin which required a 2nd click of the |
| 1616 | ** button within X seconds to confirm. Right now it's simply |
| @@ -1654,10 +1655,20 @@ | |
| 1654 | "data-f-preview-via='_postPreview' " |
| 1655 | /* ^^^ fossil.page[methodName](content, callback) */ |
| 1656 | "data-f-preview-to='fileedit-tab-preview-wrapper' " |
| 1657 | /* ^^^ dest elem ID */ |
| 1658 | ">Refresh</button>"); |
| 1659 | /* Default preview rendering mode selection... */ |
| 1660 | previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime); |
| 1661 | style_select_list_int("select-preview-mode", |
| 1662 | "preview_render_mode", |
| 1663 | "Preview Mode", |
| @@ -1700,14 +1711,13 @@ | |
| 1700 | "", 100, NULL); |
| 1701 | /* Selection of line numbers for text preview */ |
| 1702 | style_labeled_checkbox("cb-line-numbers", |
| 1703 | "preview_ln", |
| 1704 | "Add line numbers to plain-text previews?", |
| 1705 | "1", |
| 1706 | "If on, plain-text files (only) will get " |
| 1707 | "line numbers added to the preview.", |
| 1708 | P("preview_ln")!=0); |
| 1709 | CX("</div>"/*.fileedit-options*/); |
| 1710 | CX("<div id='fileedit-tab-preview-wrapper'></div>"); |
| 1711 | CX("</div>"/*#fileedit-tab-preview*/); |
| 1712 | } |
| 1713 | |
| @@ -1738,43 +1748,42 @@ | |
| 1738 | |
| 1739 | { |
| 1740 | /******* Commit flags/options *******/ |
| 1741 | CX("<div class='fileedit-options flex-container row'>"); |
| 1742 | style_labeled_checkbox("cb-dry-run", |
| 1743 | "dry_run", "Dry-run?", "1", |
| 1744 | "In dry-run mode, the Save button performs " |
| 1745 | "all work needed for saving but then rolls " |
| 1746 | "back the transaction, and thus does not " |
| 1747 | "really save.", |
| 1748 | 1); |
| 1749 | style_labeled_checkbox("cb-allow-fork", |
| 1750 | "allow_fork", "Allow fork?", "1", |
| 1751 | "Allow saving to create a fork?", |
| 1752 | cimi.flags & CIMINI_ALLOW_FORK); |
| 1753 | style_labeled_checkbox("cb-allow-older", |
| 1754 | "allow_older", "Allow older?", "1", |
| 1755 | "Allow saving against a parent version " |
| 1756 | "which has a newer timestamp?", |
| 1757 | cimi.flags & CIMINI_ALLOW_OLDER); |
| 1758 | style_labeled_checkbox("cb-exec-bit", |
| 1759 | "exec_bit", "Executable?", "1", |
| 1760 | "Set the executable bit?", |
| 1761 | PERM_EXE==cimi.filePerm); |
| 1762 | style_labeled_checkbox("cb-allow-merge-conflict", |
| 1763 | "allow_merge_conflict", |
| 1764 | "Allow merge conflict markers?", "1", |
| 1765 | "Allow saving even if the content contains " |
| 1766 | "what appear to be fossil merge conflict " |
| 1767 | "markers?", |
| 1768 | cimi.flags & CIMINI_ALLOW_MERGE_MARKER); |
| 1769 | style_labeled_checkbox("cb-prefer-delta", |
| 1770 | "prefer_delta", |
| 1771 | "Prefer delta manifest?", "1", |
| 1772 | "Will create a delta manifest, instead of " |
| 1773 | "baseline, if conditions are favorable to " |
| 1774 | "do so. This option is only a suggestion.", |
| 1775 | cimi.flags & CIMINI_PREFER_DELTA); |
| 1776 | style_select_list_int("select-eol-style", |
| 1777 | "eol", "EOL Style", |
| 1778 | "EOL conversion policy, noting that " |
| 1779 | "form-processing may implicitly change the " |
| 1780 | "line endings of the input.", |
| @@ -1797,16 +1806,16 @@ | |
| 1797 | CX("<input type='text' name='comment' " |
| 1798 | "id='fileedit-comment'></input>"); |
| 1799 | CX("<textarea name='commentBig' class='hidden' " |
| 1800 | "rows='5' id='fileedit-comment-big'></textarea>\n"); |
| 1801 | { /* comment options... */ |
| 1802 | CX("<div class='fileedit-options flex-container column'>"); |
| 1803 | CX("<button id='comment-toggle' " |
| 1804 | "title='Toggle between single- and multi-line comment mode, " |
| 1805 | "noting that switching from multi- to single-line will cause " |
| 1806 | "newlines to get stripped.'" |
| 1807 | ">toggle single-/multi-line</button> "); |
| 1808 | if(0){ |
| 1809 | /* Manifests support an N-card (comment mime type) but it has |
| 1810 | ** yet to be honored where comments are rendered, so we don't |
| 1811 | ** currently offer it as an option here: |
| 1812 | ** https://fossil-scm.org/forum/forumpost/662da045a1 |
| 1813 |
| --- src/fileedit.c | |
| +++ src/fileedit.c | |
| @@ -1606,11 +1606,12 @@ | |
| 1606 | { |
| 1607 | CX("<div id='fileedit-tab-content' " |
| 1608 | "data-tab-parent='fileedit-tabs' " |
| 1609 | "data-tab-label='File Content'" |
| 1610 | ">"); |
| 1611 | CX("<div class='fileedit-options " |
| 1612 | "flex-container row child-gap-small'>"); |
| 1613 | if(1){ |
| 1614 | /* Discard/reload button. Leave this out until we have a |
| 1615 | ** nice way of offering confirmation, e.g. like the old |
| 1616 | ** jQuery.confirmer plugin which required a 2nd click of the |
| 1617 | ** button within X seconds to confirm. Right now it's simply |
| @@ -1654,10 +1655,20 @@ | |
| 1655 | "data-f-preview-via='_postPreview' " |
| 1656 | /* ^^^ fossil.page[methodName](content, callback) */ |
| 1657 | "data-f-preview-to='fileedit-tab-preview-wrapper' " |
| 1658 | /* ^^^ dest elem ID */ |
| 1659 | ">Refresh</button>"); |
| 1660 | /* Toggle auto-update of preview when the Preview tab is selected. */ |
| 1661 | style_labeled_checkbox("cb-preview-autoupdate", |
| 1662 | NULL, |
| 1663 | "Auto-refresh?", |
| 1664 | "1", 1, |
| 1665 | "If on, the preview will automatically " |
| 1666 | "refresh when this tab is selected. Not " |
| 1667 | "recommended for large files or slow " |
| 1668 | "connections."); |
| 1669 | |
| 1670 | /* Default preview rendering mode selection... */ |
| 1671 | previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime); |
| 1672 | style_select_list_int("select-preview-mode", |
| 1673 | "preview_render_mode", |
| 1674 | "Preview Mode", |
| @@ -1700,14 +1711,13 @@ | |
| 1711 | "", 100, NULL); |
| 1712 | /* Selection of line numbers for text preview */ |
| 1713 | style_labeled_checkbox("cb-line-numbers", |
| 1714 | "preview_ln", |
| 1715 | "Add line numbers to plain-text previews?", |
| 1716 | "1", P("preview_ln")!=0, |
| 1717 | "If on, plain-text files (only) will get " |
| 1718 | "line numbers added to the preview."); |
| 1719 | CX("</div>"/*.fileedit-options*/); |
| 1720 | CX("<div id='fileedit-tab-preview-wrapper'></div>"); |
| 1721 | CX("</div>"/*#fileedit-tab-preview*/); |
| 1722 | } |
| 1723 | |
| @@ -1738,43 +1748,42 @@ | |
| 1748 | |
| 1749 | { |
| 1750 | /******* Commit flags/options *******/ |
| 1751 | CX("<div class='fileedit-options flex-container row'>"); |
| 1752 | style_labeled_checkbox("cb-dry-run", |
| 1753 | "dry_run", "Dry-run?", "1", 1, |
| 1754 | "In dry-run mode, the Save button performs " |
| 1755 | "all work needed for saving but then rolls " |
| 1756 | "back the transaction, and thus does not " |
| 1757 | "really save."); |
| 1758 | style_labeled_checkbox("cb-allow-fork", |
| 1759 | "allow_fork", "Allow fork?", "1", |
| 1760 | cimi.flags & CIMINI_ALLOW_FORK, |
| 1761 | "Allow saving to create a fork?"); |
| 1762 | style_labeled_checkbox("cb-allow-older", |
| 1763 | "allow_older", "Allow older?", "1", |
| 1764 | cimi.flags & CIMINI_ALLOW_OLDER, |
| 1765 | "Allow saving against a parent version " |
| 1766 | "which has a newer timestamp?"); |
| 1767 | style_labeled_checkbox("cb-exec-bit", |
| 1768 | "exec_bit", "Executable?", "1", |
| 1769 | PERM_EXE==cimi.filePerm, |
| 1770 | "Set the executable bit?"); |
| 1771 | style_labeled_checkbox("cb-allow-merge-conflict", |
| 1772 | "allow_merge_conflict", |
| 1773 | "Allow merge conflict markers?", "1", |
| 1774 | cimi.flags & CIMINI_ALLOW_MERGE_MARKER, |
| 1775 | "Allow saving even if the content contains " |
| 1776 | "what appear to be fossil merge conflict " |
| 1777 | "markers?"); |
| 1778 | style_labeled_checkbox("cb-prefer-delta", |
| 1779 | "prefer_delta", |
| 1780 | "Prefer delta manifest?", "1", |
| 1781 | cimi.flags & CIMINI_PREFER_DELTA, |
| 1782 | "Will create a delta manifest, instead of " |
| 1783 | "baseline, if conditions are favorable to " |
| 1784 | "do so. This option is only a suggestion."); |
| 1785 | style_select_list_int("select-eol-style", |
| 1786 | "eol", "EOL Style", |
| 1787 | "EOL conversion policy, noting that " |
| 1788 | "form-processing may implicitly change the " |
| 1789 | "line endings of the input.", |
| @@ -1797,16 +1806,16 @@ | |
| 1806 | CX("<input type='text' name='comment' " |
| 1807 | "id='fileedit-comment'></input>"); |
| 1808 | CX("<textarea name='commentBig' class='hidden' " |
| 1809 | "rows='5' id='fileedit-comment-big'></textarea>\n"); |
| 1810 | { /* comment options... */ |
| 1811 | CX("<div class='flex-container column child-gap-small'>"); |
| 1812 | CX("<button id='comment-toggle' " |
| 1813 | "title='Toggle between single- and multi-line comment mode, " |
| 1814 | "noting that switching from multi- to single-line will cause " |
| 1815 | "newlines to get stripped.'" |
| 1816 | ">Toggle single-/multi-line</button> "); |
| 1817 | if(0){ |
| 1818 | /* Manifests support an N-card (comment mime type) but it has |
| 1819 | ** yet to be honored where comments are rendered, so we don't |
| 1820 | ** currently offer it as an option here: |
| 1821 | ** https://fossil-scm.org/forum/forumpost/662da045a1 |
| 1822 |
+33
-29
| --- src/fossil.page.fileedit.js | ||
| +++ src/fossil.page.fileedit.js | ||
| @@ -19,10 +19,11 @@ | ||
| 19 | 19 | +"button.fileedit-content-reload"), |
| 20 | 20 | selectPreviewModeWrap: E('#select-preview-mode'), |
| 21 | 21 | selectHtmlEmsWrap: E('#select-preview-html-ems'), |
| 22 | 22 | selectEolWrap: E('#select-preview-html-ems'), |
| 23 | 23 | cbLineNumbersWrap: E('#cb-line-numbers'), |
| 24 | + cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'), | |
| 24 | 25 | tabs:{ |
| 25 | 26 | content: E('#fileedit-tab-content'), |
| 26 | 27 | preview: E('#fileedit-tab-preview'), |
| 27 | 28 | diff: E('#fileedit-tab-diff'), |
| 28 | 29 | commit: E('#fileedit-tab-commit') |
| @@ -47,20 +48,23 @@ | ||
| 47 | 48 | tab panels. Seems to be the best fit in terms of |
| 48 | 49 | functionality and visibility. */ |
| 49 | 50 | E('#fossil-status-bar'), P.tabs.e.tabs |
| 50 | 51 | ); |
| 51 | 52 | |
| 52 | - const stopEvent = function(e){ | |
| 53 | - //e.preventDefault(); | |
| 54 | - //e.stopPropagation(); | |
| 55 | - return P; | |
| 56 | - }; | |
| 57 | - | |
| 58 | - //P.tabs.getButtonForTab(P.e.tabs.preview) | |
| 53 | + P.tabs.addEventListener( | |
| 54 | + /* Set up auto-refresh of the preview tab... */ | |
| 55 | + 'before-switch-to', function(ev){ | |
| 56 | + if(ev.detail===P.e.tabs.preview | |
| 57 | + && P.e.cbAutoPreview.checked){ | |
| 58 | + P.preview(); | |
| 59 | + } | |
| 60 | + } | |
| 61 | + ); | |
| 62 | + | |
| 59 | 63 | F.connectPagePreviewers( |
| 60 | 64 | P.e.tabs.preview.querySelector( |
| 61 | - 'button' | |
| 65 | + '#btn-preview-refresh' | |
| 62 | 66 | ) |
| 63 | 67 | ); |
| 64 | 68 | |
| 65 | 69 | const diffButtons = E('#fileedit-tab-diff-buttons'); |
| 66 | 70 | diffButtons.querySelector('button.sbs').addEventListener( |
| @@ -215,11 +219,10 @@ | ||
| 215 | 219 | urlParams: {filename:file,checkin:rev}, |
| 216 | 220 | onload:(r)=>{ |
| 217 | 221 | F.message('Loaded content.'); |
| 218 | 222 | self.e.taEditor.value = r; |
| 219 | 223 | self.updateVersion(file,rev); |
| 220 | - self.preview(); | |
| 221 | 224 | self.tabs.switchToTab(self.e.tabs.content); |
| 222 | 225 | } |
| 223 | 226 | }); |
| 224 | 227 | return this; |
| 225 | 228 | }; |
| @@ -229,48 +232,46 @@ | ||
| 229 | 232 | this page's input fields, and updates the UI with with the |
| 230 | 233 | preview. |
| 231 | 234 | |
| 232 | 235 | Returns this object, noting that the operation is async. |
| 233 | 236 | */ |
| 234 | - P.preview = function(switchToTab){ | |
| 237 | + P.preview = function f(switchToTab){ | |
| 235 | 238 | if(!this.finfo){ |
| 236 | 239 | F.error("No content is loaded."); |
| 237 | 240 | return this; |
| 238 | 241 | } |
| 239 | - const target = this.e.tabs.preview.querySelector( | |
| 240 | - '#fileedit-tab-preview-wrapper' | |
| 241 | - ); | |
| 242 | + if(!f.target){ | |
| 243 | + f.target = this.e.tabs.preview.querySelector( | |
| 244 | + '#fileedit-tab-preview-wrapper' | |
| 245 | + ); | |
| 246 | + } | |
| 242 | 247 | const self = this; |
| 243 | 248 | const updateView = function(c){ |
| 244 | - D.clearElement(target); | |
| 245 | - if('string'===typeof c) target.innerHTML = c; | |
| 249 | + D.clearElement(f.target); | |
| 250 | + if('string'===typeof c) f.target.innerHTML = c; | |
| 246 | 251 | if(switchToTab) self.tabs.switchToTab(self.e.tabs.preview); |
| 247 | 252 | }; |
| 248 | - const content = this.e.taEditor.value; | |
| 249 | - if(!content){ | |
| 250 | - updateView(''); | |
| 251 | - return this; | |
| 252 | - } | |
| 253 | - this._postPreview(content, updateView); | |
| 254 | - return this; | |
| 253 | + return this._postPreview(this.e.taEditor.value, updateView); | |
| 255 | 254 | }; |
| 256 | 255 | |
| 257 | 256 | /** |
| 258 | 257 | Callback for use with F.connectPagePreviewers() |
| 259 | 258 | */ |
| 260 | 259 | P._postPreview = function(content,callback){ |
| 261 | 260 | if(!content){ |
| 262 | 261 | callback(content); |
| 263 | - return; | |
| 262 | + return this; | |
| 264 | 263 | } |
| 265 | 264 | const fd = new FormData(); |
| 266 | 265 | fd.append('render_mode',E('select[name=preview_render_mode]').value); |
| 267 | 266 | fd.append('filename',this.finfo.filename); |
| 268 | 267 | fd.append('ln',E('[name=preview_ln]').checked ? 1 : 0); |
| 269 | 268 | fd.append('iframe_height', E('[name=preview_html_ems]').value); |
| 270 | 269 | fd.append('content',content || ''); |
| 271 | - fossil.fetch('fileedit_preview',{ | |
| 270 | + F.message( | |
| 271 | + "Fetching preview..." | |
| 272 | + ).fetch('fileedit_preview',{ | |
| 272 | 273 | payload: fd, |
| 273 | 274 | onload: (r)=>{ |
| 274 | 275 | callback(r); |
| 275 | 276 | F.message('Updated preview.'); |
| 276 | 277 | }, |
| @@ -277,29 +278,32 @@ | ||
| 277 | 278 | onerror: (e)=>{ |
| 278 | 279 | fossil.fetch.onerror(e); |
| 279 | 280 | callback("Error fetching preview: "+e); |
| 280 | 281 | } |
| 281 | 282 | }); |
| 283 | + return this; | |
| 282 | 284 | }; |
| 283 | 285 | |
| 284 | 286 | |
| 285 | 287 | /** |
| 286 | 288 | Fetches the content diff based on the contents and settings of this |
| 287 | 289 | page's input fields, and updates the UI with the diff view. |
| 288 | 290 | |
| 289 | 291 | Returns this object, noting that the operation is async. |
| 290 | 292 | */ |
| 291 | - P.diff = function(sbs){ | |
| 293 | + P.diff = function f(sbs){ | |
| 292 | 294 | if(!this.finfo){ |
| 293 | 295 | F.error("No content is loaded."); |
| 294 | 296 | return this; |
| 295 | 297 | } |
| 296 | 298 | const content = this.e.taEditor.value, |
| 297 | - target = this.e.tabs.diff.querySelector( | |
| 298 | - '#fileedit-tab-diff-wrapper' | |
| 299 | - ), | |
| 300 | 299 | self = this; |
| 300 | + if(!f.target){ | |
| 301 | + f.target = this.e.tabs.diff.querySelector( | |
| 302 | + '#fileedit-tab-diff-wrapper' | |
| 303 | + ); | |
| 304 | + } | |
| 301 | 305 | const fd = new FormData(); |
| 302 | 306 | fd.append('filename',this.finfo.filename); |
| 303 | 307 | fd.append('checkin', this.finfo.checkin); |
| 304 | 308 | fd.append('sbs', sbs ? 1 : 0); |
| 305 | 309 | fd.append('content',content); |
| @@ -306,11 +310,11 @@ | ||
| 306 | 310 | F.message( |
| 307 | 311 | "Fetching diff..." |
| 308 | 312 | ).fetch('fileedit_diff',{ |
| 309 | 313 | payload: fd, |
| 310 | 314 | onload: function(c){ |
| 311 | - target.innerHTML = [ | |
| 315 | + f.target.innerHTML = [ | |
| 312 | 316 | "<div>Diff <code>[", |
| 313 | 317 | self.finfo.checkin, |
| 314 | 318 | "]</code> → Local Edits</div>", |
| 315 | 319 | c||'No changes.' |
| 316 | 320 | ].join(''); |
| 317 | 321 |
| --- src/fossil.page.fileedit.js | |
| +++ src/fossil.page.fileedit.js | |
| @@ -19,10 +19,11 @@ | |
| 19 | +"button.fileedit-content-reload"), |
| 20 | selectPreviewModeWrap: E('#select-preview-mode'), |
| 21 | selectHtmlEmsWrap: E('#select-preview-html-ems'), |
| 22 | selectEolWrap: E('#select-preview-html-ems'), |
| 23 | cbLineNumbersWrap: E('#cb-line-numbers'), |
| 24 | tabs:{ |
| 25 | content: E('#fileedit-tab-content'), |
| 26 | preview: E('#fileedit-tab-preview'), |
| 27 | diff: E('#fileedit-tab-diff'), |
| 28 | commit: E('#fileedit-tab-commit') |
| @@ -47,20 +48,23 @@ | |
| 47 | tab panels. Seems to be the best fit in terms of |
| 48 | functionality and visibility. */ |
| 49 | E('#fossil-status-bar'), P.tabs.e.tabs |
| 50 | ); |
| 51 | |
| 52 | const stopEvent = function(e){ |
| 53 | //e.preventDefault(); |
| 54 | //e.stopPropagation(); |
| 55 | return P; |
| 56 | }; |
| 57 | |
| 58 | //P.tabs.getButtonForTab(P.e.tabs.preview) |
| 59 | F.connectPagePreviewers( |
| 60 | P.e.tabs.preview.querySelector( |
| 61 | 'button' |
| 62 | ) |
| 63 | ); |
| 64 | |
| 65 | const diffButtons = E('#fileedit-tab-diff-buttons'); |
| 66 | diffButtons.querySelector('button.sbs').addEventListener( |
| @@ -215,11 +219,10 @@ | |
| 215 | urlParams: {filename:file,checkin:rev}, |
| 216 | onload:(r)=>{ |
| 217 | F.message('Loaded content.'); |
| 218 | self.e.taEditor.value = r; |
| 219 | self.updateVersion(file,rev); |
| 220 | self.preview(); |
| 221 | self.tabs.switchToTab(self.e.tabs.content); |
| 222 | } |
| 223 | }); |
| 224 | return this; |
| 225 | }; |
| @@ -229,48 +232,46 @@ | |
| 229 | this page's input fields, and updates the UI with with the |
| 230 | preview. |
| 231 | |
| 232 | Returns this object, noting that the operation is async. |
| 233 | */ |
| 234 | P.preview = function(switchToTab){ |
| 235 | if(!this.finfo){ |
| 236 | F.error("No content is loaded."); |
| 237 | return this; |
| 238 | } |
| 239 | const target = this.e.tabs.preview.querySelector( |
| 240 | '#fileedit-tab-preview-wrapper' |
| 241 | ); |
| 242 | const self = this; |
| 243 | const updateView = function(c){ |
| 244 | D.clearElement(target); |
| 245 | if('string'===typeof c) target.innerHTML = c; |
| 246 | if(switchToTab) self.tabs.switchToTab(self.e.tabs.preview); |
| 247 | }; |
| 248 | const content = this.e.taEditor.value; |
| 249 | if(!content){ |
| 250 | updateView(''); |
| 251 | return this; |
| 252 | } |
| 253 | this._postPreview(content, updateView); |
| 254 | return this; |
| 255 | }; |
| 256 | |
| 257 | /** |
| 258 | Callback for use with F.connectPagePreviewers() |
| 259 | */ |
| 260 | P._postPreview = function(content,callback){ |
| 261 | if(!content){ |
| 262 | callback(content); |
| 263 | return; |
| 264 | } |
| 265 | const fd = new FormData(); |
| 266 | fd.append('render_mode',E('select[name=preview_render_mode]').value); |
| 267 | fd.append('filename',this.finfo.filename); |
| 268 | fd.append('ln',E('[name=preview_ln]').checked ? 1 : 0); |
| 269 | fd.append('iframe_height', E('[name=preview_html_ems]').value); |
| 270 | fd.append('content',content || ''); |
| 271 | fossil.fetch('fileedit_preview',{ |
| 272 | payload: fd, |
| 273 | onload: (r)=>{ |
| 274 | callback(r); |
| 275 | F.message('Updated preview.'); |
| 276 | }, |
| @@ -277,29 +278,32 @@ | |
| 277 | onerror: (e)=>{ |
| 278 | fossil.fetch.onerror(e); |
| 279 | callback("Error fetching preview: "+e); |
| 280 | } |
| 281 | }); |
| 282 | }; |
| 283 | |
| 284 | |
| 285 | /** |
| 286 | Fetches the content diff based on the contents and settings of this |
| 287 | page's input fields, and updates the UI with the diff view. |
| 288 | |
| 289 | Returns this object, noting that the operation is async. |
| 290 | */ |
| 291 | P.diff = function(sbs){ |
| 292 | if(!this.finfo){ |
| 293 | F.error("No content is loaded."); |
| 294 | return this; |
| 295 | } |
| 296 | const content = this.e.taEditor.value, |
| 297 | target = this.e.tabs.diff.querySelector( |
| 298 | '#fileedit-tab-diff-wrapper' |
| 299 | ), |
| 300 | self = this; |
| 301 | const fd = new FormData(); |
| 302 | fd.append('filename',this.finfo.filename); |
| 303 | fd.append('checkin', this.finfo.checkin); |
| 304 | fd.append('sbs', sbs ? 1 : 0); |
| 305 | fd.append('content',content); |
| @@ -306,11 +310,11 @@ | |
| 306 | F.message( |
| 307 | "Fetching diff..." |
| 308 | ).fetch('fileedit_diff',{ |
| 309 | payload: fd, |
| 310 | onload: function(c){ |
| 311 | target.innerHTML = [ |
| 312 | "<div>Diff <code>[", |
| 313 | self.finfo.checkin, |
| 314 | "]</code> → Local Edits</div>", |
| 315 | c||'No changes.' |
| 316 | ].join(''); |
| 317 |
| --- src/fossil.page.fileedit.js | |
| +++ src/fossil.page.fileedit.js | |
| @@ -19,10 +19,11 @@ | |
| 19 | +"button.fileedit-content-reload"), |
| 20 | selectPreviewModeWrap: E('#select-preview-mode'), |
| 21 | selectHtmlEmsWrap: E('#select-preview-html-ems'), |
| 22 | selectEolWrap: E('#select-preview-html-ems'), |
| 23 | cbLineNumbersWrap: E('#cb-line-numbers'), |
| 24 | cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'), |
| 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') |
| @@ -47,20 +48,23 @@ | |
| 48 | tab panels. Seems to be the best fit in terms of |
| 49 | functionality and visibility. */ |
| 50 | E('#fossil-status-bar'), P.tabs.e.tabs |
| 51 | ); |
| 52 | |
| 53 | P.tabs.addEventListener( |
| 54 | /* Set up auto-refresh of the preview tab... */ |
| 55 | 'before-switch-to', function(ev){ |
| 56 | if(ev.detail===P.e.tabs.preview |
| 57 | && P.e.cbAutoPreview.checked){ |
| 58 | P.preview(); |
| 59 | } |
| 60 | } |
| 61 | ); |
| 62 | |
| 63 | F.connectPagePreviewers( |
| 64 | P.e.tabs.preview.querySelector( |
| 65 | '#btn-preview-refresh' |
| 66 | ) |
| 67 | ); |
| 68 | |
| 69 | const diffButtons = E('#fileedit-tab-diff-buttons'); |
| 70 | diffButtons.querySelector('button.sbs').addEventListener( |
| @@ -215,11 +219,10 @@ | |
| 219 | urlParams: {filename:file,checkin:rev}, |
| 220 | onload:(r)=>{ |
| 221 | F.message('Loaded content.'); |
| 222 | self.e.taEditor.value = r; |
| 223 | self.updateVersion(file,rev); |
| 224 | self.tabs.switchToTab(self.e.tabs.content); |
| 225 | } |
| 226 | }); |
| 227 | return this; |
| 228 | }; |
| @@ -229,48 +232,46 @@ | |
| 232 | this page's input fields, and updates the UI with with the |
| 233 | preview. |
| 234 | |
| 235 | Returns this object, noting that the operation is async. |
| 236 | */ |
| 237 | P.preview = function f(switchToTab){ |
| 238 | if(!this.finfo){ |
| 239 | F.error("No content is loaded."); |
| 240 | return this; |
| 241 | } |
| 242 | if(!f.target){ |
| 243 | f.target = this.e.tabs.preview.querySelector( |
| 244 | '#fileedit-tab-preview-wrapper' |
| 245 | ); |
| 246 | } |
| 247 | const self = this; |
| 248 | const updateView = function(c){ |
| 249 | D.clearElement(f.target); |
| 250 | if('string'===typeof c) f.target.innerHTML = c; |
| 251 | if(switchToTab) self.tabs.switchToTab(self.e.tabs.preview); |
| 252 | }; |
| 253 | return this._postPreview(this.e.taEditor.value, updateView); |
| 254 | }; |
| 255 | |
| 256 | /** |
| 257 | Callback for use with F.connectPagePreviewers() |
| 258 | */ |
| 259 | P._postPreview = function(content,callback){ |
| 260 | if(!content){ |
| 261 | callback(content); |
| 262 | return this; |
| 263 | } |
| 264 | const fd = new FormData(); |
| 265 | fd.append('render_mode',E('select[name=preview_render_mode]').value); |
| 266 | fd.append('filename',this.finfo.filename); |
| 267 | fd.append('ln',E('[name=preview_ln]').checked ? 1 : 0); |
| 268 | fd.append('iframe_height', E('[name=preview_html_ems]').value); |
| 269 | fd.append('content',content || ''); |
| 270 | F.message( |
| 271 | "Fetching preview..." |
| 272 | ).fetch('fileedit_preview',{ |
| 273 | payload: fd, |
| 274 | onload: (r)=>{ |
| 275 | callback(r); |
| 276 | F.message('Updated preview.'); |
| 277 | }, |
| @@ -277,29 +278,32 @@ | |
| 278 | onerror: (e)=>{ |
| 279 | fossil.fetch.onerror(e); |
| 280 | callback("Error fetching preview: "+e); |
| 281 | } |
| 282 | }); |
| 283 | return this; |
| 284 | }; |
| 285 | |
| 286 | |
| 287 | /** |
| 288 | Fetches the content diff based on the contents and settings of this |
| 289 | page's input fields, and updates the UI with the diff view. |
| 290 | |
| 291 | Returns this object, noting that the operation is async. |
| 292 | */ |
| 293 | P.diff = function f(sbs){ |
| 294 | if(!this.finfo){ |
| 295 | F.error("No content is loaded."); |
| 296 | return this; |
| 297 | } |
| 298 | const content = this.e.taEditor.value, |
| 299 | self = this; |
| 300 | if(!f.target){ |
| 301 | f.target = this.e.tabs.diff.querySelector( |
| 302 | '#fileedit-tab-diff-wrapper' |
| 303 | ); |
| 304 | } |
| 305 | const fd = new FormData(); |
| 306 | fd.append('filename',this.finfo.filename); |
| 307 | fd.append('checkin', this.finfo.checkin); |
| 308 | fd.append('sbs', sbs ? 1 : 0); |
| 309 | fd.append('content',content); |
| @@ -306,11 +310,11 @@ | |
| 310 | F.message( |
| 311 | "Fetching diff..." |
| 312 | ).fetch('fileedit_diff',{ |
| 313 | payload: fd, |
| 314 | onload: function(c){ |
| 315 | f.target.innerHTML = [ |
| 316 | "<div>Diff <code>[", |
| 317 | self.finfo.checkin, |
| 318 | "]</code> → Local Edits</div>", |
| 319 | c||'No changes.' |
| 320 | ].join(''); |
| 321 |
+52
-2
| --- src/fossil.tabs.js | ||
| +++ src/fossil.tabs.js | ||
| @@ -30,11 +30,14 @@ | ||
| 30 | 30 | }; |
| 31 | 31 | |
| 32 | 32 | TabManager.prototype = { |
| 33 | 33 | /** |
| 34 | 34 | Initializes the tabs associated with the given tab container |
| 35 | - (DOM element or selector for a single element). | |
| 35 | + (DOM element or selector for a single element). This must be | |
| 36 | + called once before using any other member functions of a given | |
| 37 | + instance, noting that the constructor will call this if it is | |
| 38 | + passed an argument. | |
| 36 | 39 | |
| 37 | 40 | The tab container must have an 'id' attribute. This function |
| 38 | 41 | looks through the DOM for all elements which have |
| 39 | 42 | data-tab-parent=thatId. For each one it creates a button to |
| 40 | 43 | switch to that tab and moves the element into this.e.tabs. |
| @@ -45,11 +48,11 @@ | ||
| 45 | 48 | When it's done, it auto-selects the first tab unless a tab has |
| 46 | 49 | a truthy numeric value in its data-tab-select attribute, in |
| 47 | 50 | which case the last tab to have such a property is selected. |
| 48 | 51 | |
| 49 | 52 | This method must only be called once per instance. TabManagers |
| 50 | - may be nested but may not share any tabs instances. | |
| 53 | + may be nested but must not share any tabs instances. | |
| 51 | 54 | |
| 52 | 55 | Returns this object. |
| 53 | 56 | |
| 54 | 57 | DOM elements of potential interest to users: |
| 55 | 58 | |
| @@ -86,10 +89,11 @@ | ||
| 86 | 89 | if(+c.dataset.tabSelect) selectIndex=n; |
| 87 | 90 | this.addTab(c); |
| 88 | 91 | }); |
| 89 | 92 | return this.switchToTab(selectIndex); |
| 90 | 93 | }, |
| 94 | + | |
| 91 | 95 | /** |
| 92 | 96 | For the given tab element, unique selector string, or integer |
| 93 | 97 | (0-based tab number), returns the button associated with that |
| 94 | 98 | tab, or undefined if the argument does not match any current |
| 95 | 99 | tab. |
| @@ -122,10 +126,48 @@ | ||
| 122 | 126 | btn.$manager = this; |
| 123 | 127 | btn.$tab = tab; |
| 124 | 128 | btn.addEventListener('click', f.click, false); |
| 125 | 129 | return this; |
| 126 | 130 | }, |
| 131 | + | |
| 132 | + /** | |
| 133 | + Internal. Fires a new CustomEvent to all listeners which have | |
| 134 | + registered via this.addEventListener(). | |
| 135 | + */ | |
| 136 | + _dispatchEvent: function(name, detail){ | |
| 137 | + try{ | |
| 138 | + this.e.container.dispatchEvent( | |
| 139 | + new CustomEvent(name, {detail: detail}) | |
| 140 | + ); | |
| 141 | + }catch(e){ | |
| 142 | + /* ignore */ | |
| 143 | + } | |
| 144 | + return this; | |
| 145 | + }, | |
| 146 | + | |
| 147 | + /** | |
| 148 | + Registers an event listener for this object's custom events. | |
| 149 | + The callback gets a CustomEvent object with a 'detail' | |
| 150 | + propertly holding any tab-related state for the event. The events | |
| 151 | + are: | |
| 152 | + | |
| 153 | + - 'before-switch-to' is emitted immediately before a new tab is | |
| 154 | + switched to. detail = the tab element. | |
| 155 | + | |
| 156 | + - 'after-switch-to' is emitted immediately after a new tab is | |
| 157 | + switched to. detail = the tab element. | |
| 158 | + | |
| 159 | + Any exceptions thrown by listeners are caught and ignored, to | |
| 160 | + avoid that they knock the tab state out of sync. | |
| 161 | + | |
| 162 | + Returns this object. | |
| 163 | + */ | |
| 164 | + addEventListener: function(eventName, callback){ | |
| 165 | + this.e.container.addEventListener(eventName, callback, false); | |
| 166 | + return this; | |
| 167 | + }, | |
| 168 | + | |
| 127 | 169 | /** |
| 128 | 170 | If the given DOM element, unique selector, or integer (0-based |
| 129 | 171 | tab number) is one of this object's tabs, the UI makes that tab |
| 130 | 172 | the currently-visible one. Returns this object. |
| 131 | 173 | */ |
| @@ -133,13 +175,21 @@ | ||
| 133 | 175 | tab = tabArg(tab,this); |
| 134 | 176 | const self = this; |
| 135 | 177 | this.e.tabs.childNodes.forEach((e,ndx)=>{ |
| 136 | 178 | const btn = this.e.tabBar.childNodes[ndx]; |
| 137 | 179 | if(e===tab){ |
| 180 | + if(D.hasClass(e,'selected')){ | |
| 181 | + return; | |
| 182 | + } | |
| 183 | + self._dispatchEvent('before-switch-to',tab); | |
| 138 | 184 | setVisible(e, true); |
| 139 | 185 | D.addClass(btn,'selected'); |
| 186 | + self._dispatchEvent('after-switch-to',tab); | |
| 140 | 187 | }else{ |
| 188 | + if(D.hasClass(e,'selected')){ | |
| 189 | + return; | |
| 190 | + } | |
| 141 | 191 | setVisible(e, false); |
| 142 | 192 | D.removeClass(btn,'selected'); |
| 143 | 193 | } |
| 144 | 194 | }); |
| 145 | 195 | return this; |
| 146 | 196 |
| --- src/fossil.tabs.js | |
| +++ src/fossil.tabs.js | |
| @@ -30,11 +30,14 @@ | |
| 30 | }; |
| 31 | |
| 32 | TabManager.prototype = { |
| 33 | /** |
| 34 | Initializes the tabs associated with the given tab container |
| 35 | (DOM element or selector for a single element). |
| 36 | |
| 37 | The tab container must have an 'id' attribute. This function |
| 38 | looks through the DOM for all elements which have |
| 39 | data-tab-parent=thatId. For each one it creates a button to |
| 40 | switch to that tab and moves the element into this.e.tabs. |
| @@ -45,11 +48,11 @@ | |
| 45 | When it's done, it auto-selects the first tab unless a tab has |
| 46 | a truthy numeric value in its data-tab-select attribute, in |
| 47 | which case the last tab to have such a property is selected. |
| 48 | |
| 49 | This method must only be called once per instance. TabManagers |
| 50 | may be nested but may not share any tabs instances. |
| 51 | |
| 52 | Returns this object. |
| 53 | |
| 54 | DOM elements of potential interest to users: |
| 55 | |
| @@ -86,10 +89,11 @@ | |
| 86 | if(+c.dataset.tabSelect) selectIndex=n; |
| 87 | this.addTab(c); |
| 88 | }); |
| 89 | return this.switchToTab(selectIndex); |
| 90 | }, |
| 91 | /** |
| 92 | For the given tab element, unique selector string, or integer |
| 93 | (0-based tab number), returns the button associated with that |
| 94 | tab, or undefined if the argument does not match any current |
| 95 | tab. |
| @@ -122,10 +126,48 @@ | |
| 122 | btn.$manager = this; |
| 123 | btn.$tab = tab; |
| 124 | btn.addEventListener('click', f.click, false); |
| 125 | return this; |
| 126 | }, |
| 127 | /** |
| 128 | If the given DOM element, unique selector, or integer (0-based |
| 129 | tab number) is one of this object's tabs, the UI makes that tab |
| 130 | the currently-visible one. Returns this object. |
| 131 | */ |
| @@ -133,13 +175,21 @@ | |
| 133 | tab = tabArg(tab,this); |
| 134 | const self = this; |
| 135 | this.e.tabs.childNodes.forEach((e,ndx)=>{ |
| 136 | const btn = this.e.tabBar.childNodes[ndx]; |
| 137 | if(e===tab){ |
| 138 | setVisible(e, true); |
| 139 | D.addClass(btn,'selected'); |
| 140 | }else{ |
| 141 | setVisible(e, false); |
| 142 | D.removeClass(btn,'selected'); |
| 143 | } |
| 144 | }); |
| 145 | return this; |
| 146 |
| --- src/fossil.tabs.js | |
| +++ src/fossil.tabs.js | |
| @@ -30,11 +30,14 @@ | |
| 30 | }; |
| 31 | |
| 32 | TabManager.prototype = { |
| 33 | /** |
| 34 | Initializes the tabs associated with the given tab container |
| 35 | (DOM element or selector for a single element). This must be |
| 36 | called once before using any other member functions of a given |
| 37 | instance, noting that the constructor will call this if it is |
| 38 | passed an argument. |
| 39 | |
| 40 | The tab container must have an 'id' attribute. This function |
| 41 | looks through the DOM for all elements which have |
| 42 | data-tab-parent=thatId. For each one it creates a button to |
| 43 | switch to that tab and moves the element into this.e.tabs. |
| @@ -45,11 +48,11 @@ | |
| 48 | When it's done, it auto-selects the first tab unless a tab has |
| 49 | a truthy numeric value in its data-tab-select attribute, in |
| 50 | which case the last tab to have such a property is selected. |
| 51 | |
| 52 | This method must only be called once per instance. TabManagers |
| 53 | may be nested but must not share any tabs instances. |
| 54 | |
| 55 | Returns this object. |
| 56 | |
| 57 | DOM elements of potential interest to users: |
| 58 | |
| @@ -86,10 +89,11 @@ | |
| 89 | if(+c.dataset.tabSelect) selectIndex=n; |
| 90 | this.addTab(c); |
| 91 | }); |
| 92 | return this.switchToTab(selectIndex); |
| 93 | }, |
| 94 | |
| 95 | /** |
| 96 | For the given tab element, unique selector string, or integer |
| 97 | (0-based tab number), returns the button associated with that |
| 98 | tab, or undefined if the argument does not match any current |
| 99 | tab. |
| @@ -122,10 +126,48 @@ | |
| 126 | btn.$manager = this; |
| 127 | btn.$tab = tab; |
| 128 | btn.addEventListener('click', f.click, false); |
| 129 | return this; |
| 130 | }, |
| 131 | |
| 132 | /** |
| 133 | Internal. Fires a new CustomEvent to all listeners which have |
| 134 | registered via this.addEventListener(). |
| 135 | */ |
| 136 | _dispatchEvent: function(name, detail){ |
| 137 | try{ |
| 138 | this.e.container.dispatchEvent( |
| 139 | new CustomEvent(name, {detail: detail}) |
| 140 | ); |
| 141 | }catch(e){ |
| 142 | /* ignore */ |
| 143 | } |
| 144 | return this; |
| 145 | }, |
| 146 | |
| 147 | /** |
| 148 | Registers an event listener for this object's custom events. |
| 149 | The callback gets a CustomEvent object with a 'detail' |
| 150 | propertly holding any tab-related state for the event. The events |
| 151 | are: |
| 152 | |
| 153 | - 'before-switch-to' is emitted immediately before a new tab is |
| 154 | switched to. detail = the tab element. |
| 155 | |
| 156 | - 'after-switch-to' is emitted immediately after a new tab is |
| 157 | switched to. detail = the tab element. |
| 158 | |
| 159 | Any exceptions thrown by listeners are caught and ignored, to |
| 160 | avoid that they knock the tab state out of sync. |
| 161 | |
| 162 | Returns this object. |
| 163 | */ |
| 164 | addEventListener: function(eventName, callback){ |
| 165 | this.e.container.addEventListener(eventName, callback, false); |
| 166 | return this; |
| 167 | }, |
| 168 | |
| 169 | /** |
| 170 | If the given DOM element, unique selector, or integer (0-based |
| 171 | tab number) is one of this object's tabs, the UI makes that tab |
| 172 | the currently-visible one. Returns this object. |
| 173 | */ |
| @@ -133,13 +175,21 @@ | |
| 175 | tab = tabArg(tab,this); |
| 176 | const self = this; |
| 177 | this.e.tabs.childNodes.forEach((e,ndx)=>{ |
| 178 | const btn = this.e.tabBar.childNodes[ndx]; |
| 179 | if(e===tab){ |
| 180 | if(D.hasClass(e,'selected')){ |
| 181 | return; |
| 182 | } |
| 183 | self._dispatchEvent('before-switch-to',tab); |
| 184 | setVisible(e, true); |
| 185 | D.addClass(btn,'selected'); |
| 186 | self._dispatchEvent('after-switch-to',tab); |
| 187 | }else{ |
| 188 | if(D.hasClass(e,'selected')){ |
| 189 | return; |
| 190 | } |
| 191 | setVisible(e, false); |
| 192 | D.removeClass(btn,'selected'); |
| 193 | } |
| 194 | }); |
| 195 | return this; |
| 196 |
+9
-6
| --- src/style.c | ||
| +++ src/style.c | ||
| @@ -1313,30 +1313,33 @@ | ||
| 1313 | 1313 | ** <input type='checkbox' name={{zFieldName}} value={{zValue}} |
| 1314 | 1314 | ** {{isChecked ? " checked : ""}}/> |
| 1315 | 1315 | ** <span>{{zLabel}}</span> |
| 1316 | 1316 | ** </span> |
| 1317 | 1317 | ** |
| 1318 | -** zFieldName, zLabel, and zValue are required. zWrapperId and zTip | |
| 1319 | -** are optional. | |
| 1318 | +** zLabel, and zValue are required. zFieldName, zWrapperId, and zTip | |
| 1319 | +** are may be NULL or empty. | |
| 1320 | 1320 | ** |
| 1321 | 1321 | ** Be sure that the input-with-label CSS class is defined sensibly, in |
| 1322 | 1322 | ** particular, having its display:inline-block is useful for alignment |
| 1323 | 1323 | ** purposes. |
| 1324 | 1324 | */ |
| 1325 | 1325 | void style_labeled_checkbox(const char * zWrapperId, |
| 1326 | 1326 | const char *zFieldName, const char * zLabel, |
| 1327 | - const char * zValue, const char * zTip, | |
| 1328 | - int isChecked){ | |
| 1327 | + const char * zValue, int isChecked, | |
| 1328 | + const char * zTip){ | |
| 1329 | 1329 | CX("<span class='input-with-label'"); |
| 1330 | 1330 | if(zTip && *zTip){ |
| 1331 | 1331 | CX(" title='%h'", zTip); |
| 1332 | 1332 | } |
| 1333 | 1333 | if(zWrapperId && *zWrapperId){ |
| 1334 | 1334 | CX(" id='%s'",zWrapperId); |
| 1335 | 1335 | } |
| 1336 | - CX("><input type='checkbox' name='%s' value='%T'%s/>", | |
| 1337 | - zFieldName, | |
| 1336 | + CX("><input type='checkbox' "); | |
| 1337 | + if(zFieldName && *zFieldName){ | |
| 1338 | + CX("name='%s' ",zFieldName); | |
| 1339 | + } | |
| 1340 | + CX("value='%T'%s/>", | |
| 1338 | 1341 | zValue ? zValue : "", isChecked ? " checked" : ""); |
| 1339 | 1342 | CX("<span>%h</span></span>", zLabel); |
| 1340 | 1343 | } |
| 1341 | 1344 | |
| 1342 | 1345 | /* |
| 1343 | 1346 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -1313,30 +1313,33 @@ | |
| 1313 | ** <input type='checkbox' name={{zFieldName}} value={{zValue}} |
| 1314 | ** {{isChecked ? " checked : ""}}/> |
| 1315 | ** <span>{{zLabel}}</span> |
| 1316 | ** </span> |
| 1317 | ** |
| 1318 | ** zFieldName, zLabel, and zValue are required. zWrapperId and zTip |
| 1319 | ** are optional. |
| 1320 | ** |
| 1321 | ** Be sure that the input-with-label CSS class is defined sensibly, in |
| 1322 | ** particular, having its display:inline-block is useful for alignment |
| 1323 | ** purposes. |
| 1324 | */ |
| 1325 | void style_labeled_checkbox(const char * zWrapperId, |
| 1326 | const char *zFieldName, const char * zLabel, |
| 1327 | const char * zValue, const char * zTip, |
| 1328 | int isChecked){ |
| 1329 | CX("<span class='input-with-label'"); |
| 1330 | if(zTip && *zTip){ |
| 1331 | CX(" title='%h'", zTip); |
| 1332 | } |
| 1333 | if(zWrapperId && *zWrapperId){ |
| 1334 | CX(" id='%s'",zWrapperId); |
| 1335 | } |
| 1336 | CX("><input type='checkbox' name='%s' value='%T'%s/>", |
| 1337 | zFieldName, |
| 1338 | zValue ? zValue : "", isChecked ? " checked" : ""); |
| 1339 | CX("<span>%h</span></span>", zLabel); |
| 1340 | } |
| 1341 | |
| 1342 | /* |
| 1343 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -1313,30 +1313,33 @@ | |
| 1313 | ** <input type='checkbox' name={{zFieldName}} value={{zValue}} |
| 1314 | ** {{isChecked ? " checked : ""}}/> |
| 1315 | ** <span>{{zLabel}}</span> |
| 1316 | ** </span> |
| 1317 | ** |
| 1318 | ** zLabel, and zValue are required. zFieldName, zWrapperId, and zTip |
| 1319 | ** are may be NULL or empty. |
| 1320 | ** |
| 1321 | ** Be sure that the input-with-label CSS class is defined sensibly, in |
| 1322 | ** particular, having its display:inline-block is useful for alignment |
| 1323 | ** purposes. |
| 1324 | */ |
| 1325 | void style_labeled_checkbox(const char * zWrapperId, |
| 1326 | const char *zFieldName, const char * zLabel, |
| 1327 | const char * zValue, int isChecked, |
| 1328 | const char * zTip){ |
| 1329 | CX("<span class='input-with-label'"); |
| 1330 | if(zTip && *zTip){ |
| 1331 | CX(" title='%h'", zTip); |
| 1332 | } |
| 1333 | if(zWrapperId && *zWrapperId){ |
| 1334 | CX(" id='%s'",zWrapperId); |
| 1335 | } |
| 1336 | CX("><input type='checkbox' "); |
| 1337 | if(zFieldName && *zFieldName){ |
| 1338 | CX("name='%s' ",zFieldName); |
| 1339 | } |
| 1340 | CX("value='%T'%s/>", |
| 1341 | zValue ? zValue : "", isChecked ? " checked" : ""); |
| 1342 | CX("<span>%h</span></span>", zLabel); |
| 1343 | } |
| 1344 | |
| 1345 | /* |
| 1346 |