Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | /fileedit now squelches errors related to 'missing' URL args, as they are now optional. It also now displays an informative warning at the top of the page if fileedit-glob is not set or is empty. Added a hint about hyperlink in the preview page. Added a Help tab with useful(?) tips. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | fileedit-ajaxify |
| Files: | files | file ages | folders |
| SHA3-256: |
c145140d3ee52d25d09a3f114d349657 |
| User & Date: | stephan 2020-05-12 10:24:59.810 |
Context
|
2020-05-12
| ||
| 13:18 | Added fossil.fetch() responseHeaders option to pass on one or more response headers to the onload() callback. ... (check-in: bbb738d07d user: stephan tags: fileedit-ajaxify) | |
| 10:24 | /fileedit now squelches errors related to 'missing' URL args, as they are now optional. It also now displays an informative warning at the top of the page if fileedit-glob is not set or is empty. Added a hint about hyperlink in the preview page. Added a Help tab with useful(?) tips. ... (check-in: c145140d3e user: stephan tags: fileedit-ajaxify) | |
| 08:54 | Double-clicking the status message bar now clears the message (useful when a long HTML-format error comes in via ajax). Removed the unsightly 'new and experimental' banner. ... (check-in: ff7ad7125f user: stephan tags: fileedit-ajaxify) | |
Changes
Changes to src/default_css.txt.
| ︙ | ︙ | |||
879 880 881 882 883 884 885 | border-color: inherit; min-height: 1.5em; font-size: 1.2em; padding: 0.2em; margin: 0.25em 0; flex: 0 0 auto; } | | > > > | 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 |
border-color: inherit;
min-height: 1.5em;
font-size: 1.2em;
padding: 0.2em;
margin: 0.25em 0;
flex: 0 0 auto;
}
.error {
color: darkred;
background: yellow;
}
body.fileedit .error {
padding: 0.25em;
}
//////////////////////////////////
// Styles for fossil.tabs.js:
.tab-container {
width: 100%;
display: flex;
flex-direction: column;
|
| ︙ | ︙ |
Changes to src/fileedit.c.
| ︙ | ︙ | |||
871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 |
fossil_warning("The checkout state is now out of sync "
"with regards to this commit. It needs to be "
"'update'd or 'close'd and re-'open'ed.");
}
CheckinMiniInfo_cleanup(&cimi);
}
/*
** Returns true if the given filename qualifies for online editing by
** the current user, else returns false.
**
** Editing requires that the user have the Write permission and that
** the filename match the glob defined by the fileedit-glob setting.
** A missing or empty value for that glob disables all editing.
*/
int fileedit_is_editable(const char *zFilename){
| > > > > > > > > > > > > > > > > > | < | < | | < < | < < < < | 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 |
fossil_warning("The checkout state is now out of sync "
"with regards to this commit. It needs to be "
"'update'd or 'close'd and re-'open'ed.");
}
CheckinMiniInfo_cleanup(&cimi);
}
/*
** If the fileedit-glob setting has a value, this returns its Glob
** object (in memory owned by this function), else it returns NULL.
*/
static Glob * fileedit_glob(void){
static Glob * pGlobs = 0;
static int once = 0;
if(0==pGlobs && once==0){
char * zGlobs = db_get("fileedit-glob",0);
once = 1;
if(0!=zGlobs && 0!=*zGlobs){
pGlobs = glob_create(zGlobs);
}
fossil_free(zGlobs);
}
return pGlobs;
}
/*
** Returns true if the given filename qualifies for online editing by
** the current user, else returns false.
**
** Editing requires that the user have the Write permission and that
** the filename match the glob defined by the fileedit-glob setting.
** A missing or empty value for that glob disables all editing.
*/
int fileedit_is_editable(const char *zFilename){
Glob * pGlobs = fileedit_glob();
if(pGlobs!=0 && zFilename!=0 && *zFilename!=0 && 0!=g.perm.Write){
return glob_match(pGlobs, zFilename);
}else{
return 0;
}
}
enum fileedit_render_preview_flags {
FE_PREVIEW_LINE_NUMBERS = 1
};
enum fileedit_render_modes {
/* GUESS must be 0. All others have unspecified values. */
FE_RENDER_GUESS = 0,
|
| ︙ | ︙ | |||
1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 | ** written to pErr. ** ** It always fails if it cannot completely resolve the 'file' and 'r' ** parameters, including verifying that the refer to a real ** file/version combination and editable by the current user. All ** others are optional (at this level, anyway, but upstream code might ** require them). ** ** Intended to be used only by /filepage and /filepage_commit. */ | > > > > > | > > > > > > > | 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 |
** written to pErr.
**
** It always fails if it cannot completely resolve the 'file' and 'r'
** parameters, including verifying that the refer to a real
** file/version combination and editable by the current user. All
** others are optional (at this level, anyway, but upstream code might
** require them).
**
** If the 3rd argument is not NULL and an error is related to a
** missing arg then *bIsMissingArg is set to true. This is
** intended to allow /fileedit to squelch certain initialization
** errors.
**
** Intended to be used only by /filepage and /filepage_commit.
*/
static int fileedit_setup_cimi_from_p(CheckinMiniInfo * p, Blob * pErr,
int * bIsMissingArg){
char * zFileUuid = 0; /* UUID of file content */
const char * zFlag; /* generic flag */
int rc = 0, vid = 0, frid = 0; /* result code, checkin/file rids */
#define fail(EXPR) blob_appendf EXPR; goto end_fail
zFlag = PD("filename",P("fn"));
if(zFlag==0 || !*zFlag){
rc = 400;
if(bIsMissingArg){
*bIsMissingArg = 1;
}
fail((pErr,"Missing required 'filename' parameter."));
}
p->zFilename = mprintf("%s",zFlag);
if(0==fileedit_is_editable(p->zFilename)){
rc = 403;
fail((pErr,"Filename [%h] is disallowed "
"by the [fileedit-glob] repository "
"setting.",
p->zFilename));
}
zFlag = PD("checkin",P("ci"));
if(!zFlag){
rc = 400;
if(bIsMissingArg){
*bIsMissingArg = 1;
}
fail((pErr,"Missing required 'checkin' parameter."));
}
vid = symbolic_name_to_rid(zFlag, "ci");
if(0==vid){
rc = 404;
fail((pErr,"Could not resolve checkin version."));
}else if(vid<0){
|
| ︙ | ︙ | |||
1545 1546 1547 1548 1549 1550 1551 |
char * zNewUuid = 0; /* newVid's UUID */
if(!fileedit_ajax_boostrap()){
return;
}
db_begin_transaction();
CheckinMiniInfo_init(&cimi);
| | | 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 |
char * zNewUuid = 0; /* newVid's UUID */
if(!fileedit_ajax_boostrap()){
return;
}
db_begin_transaction();
CheckinMiniInfo_init(&cimi);
rc = fileedit_setup_cimi_from_p(&cimi, &err, 0);
if(0!=rc){
fileedit_ajax_error(rc,"%b",&err);
goto end_cleanup;
}
if(blob_size(&cimi.comment)==0){
fileedit_ajax_error(400,"Empty checkin comment is not permitted.");
goto end_cleanup;
|
| ︙ | ︙ | |||
1641 1642 1643 1644 1645 1646 1647 |
db_begin_transaction();
CheckinMiniInfo_init(&cimi);
style_header("File Editor");
/* As of this point, don't use return or fossil_fatal(). Write any
** error in (&err) and goto end_footer instead so that we can be
** sure to do any cleanup and end the transaction cleanly.
*/
| > > | | | | | | > > > > > | 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 |
db_begin_transaction();
CheckinMiniInfo_init(&cimi);
style_header("File Editor");
/* As of this point, don't use return or fossil_fatal(). Write any
** error in (&err) and goto end_footer instead so that we can be
** sure to do any cleanup and end the transaction cleanly.
*/
{
int isMissingArg = 0;
if(fileedit_setup_cimi_from_p(&cimi, &err, &isMissingArg)==0){
zFilename = cimi.zFilename;
zRev = cimi.zParentUuid;
assert(zRev);
assert(zFilename);
zFileMime = mimetype_from_name(cimi.zFilename);
}else if(isMissingArg!=0){
/* Squelch these startup warnings - they're non-fatal now but
** used to be. */
blob_reset(&err);
}
}
/********************************************************************
** All errors which "could" have happened up to this point are of a
** degree which keep us from rendering the rest of the page, and
** thus have already caused us to skipped to the end of the page to
** render the errors. Any up-coming errors, barring malloc failure
|
| ︙ | ︙ | |||
1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 |
** whatever CSS we wish...
*/
style_emit_script_tag(0,0);
CX("document.body.classList.add('fileedit');\n");
style_emit_script_tag(1,0);
}
/* Status bar */
CX("<div id='fossil-status-bar' "
"title='Status message area. Double-click to clear them.'>"
"Status messages will go here.</div>\n"
/* will be moved into the tab container via JS */);
/* Main tab container... */
| > > > > > > > > | 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 |
** whatever CSS we wish...
*/
style_emit_script_tag(0,0);
CX("document.body.classList.add('fileedit');\n");
style_emit_script_tag(1,0);
}
if(fileedit_glob()==0){
CX("<div class='error'>To enable online editing, the "
"<code>fileedit-glob</code> repository setting must be set to a "
"comma- or newine-delimited list of glob values matching files "
"which may be edited online."
"</div>");
}
/* Status bar */
CX("<div id='fossil-status-bar' "
"title='Status message area. Double-click to clear them.'>"
"Status messages will go here.</div>\n"
/* will be moved into the tab container via JS */);
/* Main tab container... */
|
| ︙ | ︙ | |||
1749 1750 1751 1752 1753 1754 1755 |
/****** Preview tab ******/
{
CX("<div id='fileedit-tab-preview' "
"data-tab-parent='fileedit-tabs' "
"data-tab-label='Preview'"
">");
| | > | 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 |
/****** Preview tab ******/
{
CX("<div id='fileedit-tab-preview' "
"data-tab-parent='fileedit-tabs' "
"data-tab-label='Preview'"
">");
CX("<div class='fileedit-options flex-container flex-column'>");
CX("<div class='flex-container flex-row'>");
CX("<button id='btn-preview-refresh' "
"data-f-preview-from='fileedit-content-editor' "
/* ^^^ text source elem ID*/
"data-f-preview-via='_postPreview' "
/* ^^^ fossil.page[methodName](content, callback) */
"data-f-preview-to='fileedit-tab-preview-wrapper' "
/* ^^^ dest elem ID */
|
| ︙ | ︙ | |||
1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 |
/* Selection of line numbers for text preview */
style_labeled_checkbox("cb-line-numbers",
"preview_ln",
"Add line numbers to plain-text previews?",
"1", P("preview_ln")!=0,
"If on, plain-text files (only) will get "
"line numbers added to the preview.");
CX("</div>"/*.fileedit-options*/);
CX("<div id='fileedit-tab-preview-wrapper'></div>");
CX("</div>"/*#fileedit-tab-preview*/);
}
/****** Diff tab ******/
{
| > > > > > > | 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 |
/* Selection of line numbers for text preview */
style_labeled_checkbox("cb-line-numbers",
"preview_ln",
"Add line numbers to plain-text previews?",
"1", P("preview_ln")!=0,
"If on, plain-text files (only) will get "
"line numbers added to the preview.");
CX("</div>"/*.flex-container.flex-row (buttons/options)*/);
CX("<div class='fileedit-hint'>"
"Note that hyperlinks in previewed HTML are relative to "
"<em>this</em> page, and therefore not correct. Clicking "
"them will leave this page, losing any edits."
"</div>");
CX("</div>"/*.fileedit-options*/);
CX("<div id='fileedit-tab-preview-wrapper'></div>");
CX("</div>"/*#fileedit-tab-preview*/);
}
/****** Diff tab ******/
{
|
| ︙ | ︙ | |||
1840 1841 1842 1843 1844 1845 1846 |
"</div>");
CX("</div>"/*#fileedit-tab-diff*/);
}
/****** Commit ******/
CX("<div id='fileedit-tab-commit' "
"data-tab-parent='fileedit-tabs' "
| < < | 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 |
"</div>");
CX("</div>"/*#fileedit-tab-diff*/);
}
/****** Commit ******/
CX("<div id='fileedit-tab-commit' "
"data-tab-parent='fileedit-tabs' "
"data-tab-label='Commit'"
">");
{
/******* Commit flags/options *******/
CX("<div class='fileedit-options flex-container flex-row'>");
style_labeled_checkbox("cb-dry-run",
"dry_run", "Dry-run?", "1", 1,
"In dry-run mode, the Save button performs "
"all work needed for saving but then rolls "
|
| ︙ | ︙ | |||
1946 1947 1948 1949 1950 1951 1952 1953 |
CX("<div class='flex-container flex-column' "
"id='fileedit-commit-button-wrapper'>"
"<button id='fileedit-btn-commit'>Commit</button>"
"</div>\n");
CX("<div id='fileedit-manifest'></div>\n"
/* Manifest gets rendered here after a commit. */);
}
| > > > > > > > > > > > > > > > > > > > > > > > > > > | | > | > | | < < > | | < | 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 |
CX("<div class='flex-container flex-column' "
"id='fileedit-commit-button-wrapper'>"
"<button id='fileedit-btn-commit'>Commit</button>"
"</div>\n");
CX("<div id='fileedit-manifest'></div>\n"
/* Manifest gets rendered here after a commit. */);
}
CX("</div>"/*#fileedit-tab-commit*/);
/****** Help/Tips ******/
CX("<div id='fileedit-tab-help' "
"data-tab-parent='fileedit-tabs' "
"data-tab-label='Help'"
">");
{
CX("<h1>Help & Tips</h1>");
CX("<ul>");
CX("<li><strong>Only files matching the <code>fileedit-glob</code> "
"</strong> repository setting can be edited online. That setting "
"must be a comma- or newline-delimited list of glob patterns "
"for files which may be edited online.</li>");
CX("<li><strong>Clicking any links</strong> on this page will "
"leave the page, <strong>losing any edits</strong>.</li>");
CX("<li>Saving edits creates a new commit with a single modified "
"file.</li>");
CX("<li>\"Delta manifests\" (see the checkbox on the Commit tab) "
"make for smaller commit records, especially in repositories "
"with many files.</li>");
CX("<li>The file selector allows, for usability's sake, only files "
"in leaf checkins to be selected, but files may be edited via "
"non-leaf checkins by passing them as the <code>filename</code> "
"and <code>checkin</code> URL arguments to this page.</li>");
CX("</ul>");
}
CX("</div>"/*#fileedit-tab-help*/);
{
/* Dynamically populate the editor, display a any error
** in the err blob, and/or switch to tab #0, where the file
** selector lives... */
blob_appendf(&endScript,
"window.addEventListener('load',");
if(zRev && zFilename){
assert(0==blob_size(&err));
blob_appendf(&endScript,
"()=>fossil.page.loadFile(\"%j\",'%j')",
zFilename, cimi.zParentUuid);
}else{
blob_appendf(&endScript,"function(){");
if(blob_size(&err)>0){
blob_appendf(&endScript,
"fossil.error(\"%j\");\n",
blob_str(&err));
}
blob_appendf(&endScript,
"fossil.page.tabs.switchToTab(0);\n");
blob_appendf(&endScript,"}");
}
blob_appendf(&endScript,", false);\n");
}
if(stmt.pStmt){
db_finalize(&stmt);
|
| ︙ | ︙ |
Changes to src/fossil.dom.js.
| ︙ | ︙ | |||
55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
f.rx = /(\s+|\s*,\s*)/;
}
return str ? str.split(f.rx) : [str];
};
dom.div = dom.createElemFactory('div');
dom.p = dom.createElemFactory('p');
dom.header = dom.createElemFactory('header');
dom.footer = dom.createElemFactory('footer');
dom.section = dom.createElemFactory('section');
dom.span = dom.createElemFactory('span');
dom.strong = dom.createElemFactory('strong');
dom.em = dom.createElemFactory('em');
dom.img = function(src){
| > > | 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
f.rx = /(\s+|\s*,\s*)/;
}
return str ? str.split(f.rx) : [str];
};
dom.div = dom.createElemFactory('div');
dom.p = dom.createElemFactory('p');
dom.code = dom.createElemFactory('code');
dom.pre = dom.createElemFactory('pre');
dom.header = dom.createElemFactory('header');
dom.footer = dom.createElemFactory('footer');
dom.section = dom.createElemFactory('section');
dom.span = dom.createElemFactory('span');
dom.strong = dom.createElemFactory('strong');
dom.em = dom.createElemFactory('em');
dom.img = function(src){
|
| ︙ | ︙ |
Changes to src/fossil.page.fileedit.js.
| ︙ | ︙ | |||
61 62 63 64 65 66 67 |
this.finfo.checkin = ciUuid;
const selFiles = this.e.selectFiles;
if(!ciUuid){
D.clearElement(D.disable(selFiles, this.e.btnLoadFile));
return this;
}
const onload = (response)=>{
| | | < | > > > | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
this.finfo.checkin = ciUuid;
const selFiles = this.e.selectFiles;
if(!ciUuid){
D.clearElement(D.disable(selFiles, this.e.btnLoadFile));
return this;
}
const onload = (response)=>{
D.clearElement(selFiles, this.e.btnLoadFile);
D.append(
D.clearElement(this.e.fileListLabel),
"Editable files for ",
D.a(F.repoUrl('timeline',{
c: ciUuid
}), F.hashDigits(ciUuid))
);
this.cache.files[response.checkin] = response;
response.editableFiles.forEach(function(fn,n){
D.option(selFiles, fn);
});
if(selFiles.options.length){
D.enable(selFiles, this.e.btnLoadFile);
}
};
const got = this.cache.files[ciUuid];
if(got){
onload(got);
return this;
}
D.disable(selFiles,this.e.btnLoadFile);
|
| ︙ | ︙ |
Changes to src/style.c.
| ︙ | ︙ | |||
1523 1524 1525 1526 1527 1528 1529 |
"};\n", hash_digits(0), hash_digits(1));
/*
** fossil.page holds info about the current page. This is also
** where the current page "should" store any of its own
** page-specific state, and it is reserved for that purpose.
*/
CX("window.fossil.page = {"
| | | 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 |
"};\n", hash_digits(0), hash_digits(1));
/*
** fossil.page holds info about the current page. This is also
** where the current page "should" store any of its own
** page-specific state, and it is reserved for that purpose.
*/
CX("window.fossil.page = {"
"name:\"%T\""
"};\n", g.zPath);
CX("})();\n");
/* The remaining fossil object bootstrap code is not dependent on
** C-runtime state... */
if(asInline){
CX("%s\n", builtin_text("fossil.bootstrap.js"));
}
|
| ︙ | ︙ |