Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Reworked how /fileedit loads its JS - it now fetches them rather than embedding them inline. Moved fossil.fetch() docs from style.c into the JS file. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | fileedit-ajaxify |
| Files: | files | file ages | folders |
| SHA3-256: |
b48212f6ea83876b7d9721081dfdbbc8 |
| User & Date: | stephan 2020-05-06 01:51:37.519 |
Context
|
2020-05-06
| ||
| 01:58 | Removed the FORM element - it was superfluous and particularly stubborn in how it responded to every button. ... (check-in: 7635d9345d user: stephan tags: fileedit-ajaxify) | |
| 01:51 | Reworked how /fileedit loads its JS - it now fetches them rather than embedding them inline. Moved fossil.fetch() docs from style.c into the JS file. ... (check-in: b48212f6ea user: stephan tags: fileedit-ajaxify) | |
|
2020-05-05
| ||
| 23:54 | Re-added the editor font size selector. Added a button to discard/reload edits, but it's too easy to activate by accident, so it's disabled until we have a common confirmation mechanism in place. Added timestamp to fossil.message() and fossil.error() output. ... (check-in: 25dfd243a1 user: stephan tags: fileedit-ajaxify) | |
Changes
Changes to src/default_css.txt.
| ︙ | ︙ | |||
859 860 861 862 863 864 865 866 867 868 869 870 871 872 |
// vertical-align: top;
// }
// #setup_skinedit_css_defaults > tbody > tr > td:nth-of-type(2) > div {
// max-width: 30em;
// overflow: auto;
// }
// .fileedit-XXX => /fileedit page
form.fileedit textarea {
font-family: monospace;
width: 100%;
}
form.fileedit fieldset {
margin: 0.5em 0 0.5em 0;
border-radius: 0.5em;
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 859 860 861 862 863 864 865 866 867 868 869 870 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 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 |
// vertical-align: top;
// }
// #setup_skinedit_css_defaults > tbody > tr > td:nth-of-type(2) > div {
// max-width: 30em;
// overflow: auto;
// }
// .fileedit-XXX => /fileedit page
.hidden {
position: absolute;
opacity: 0;
pointer-events: none;
}
//.hidden {
// display: none;
//}
#fossil-status-bar {
display: block;
font-family: monospace;
border-width: 1px;
border-style: inset;
border-color: inherit;
min-height: 1.5em;
font-size: 1.2em;
padding: 0.2em;
margin: 0.25em 0;
flex: 0 0 auto;
}
#fossil-status-bar.error {
color: darkred;
background: yellow;
}
//////////////////////////////////
// Styles for fossil.tabs.js:
.tab-container {
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
}
.tab-container > #fossil-status-bar {
margin-top: 0;
}
.tab-container > .tabs {
padding: 0.25em;
margin: 0;
display: flex;
flex-direction: column;
border-width: 1px;
border-style: outset;
border-color: inherit;
}
.tab-container > .tabs > .tab-panel {
align-self: stretch;
flex: 10 1 auto;
display: block;
}
.tab-container > .tab-bar {
display: flex;
flex-direction: row;
flex: 1 10 auto;
align-self: stretch;
flex-wrap: wrap;
}
.tab-container > .tab-bar > button {
border-radius: 0.5em 0.5em 0 0;
margin: 0.5em 0.5em 0 0.5em;
align-self: baseline;
}
.tab-container > .tab-bar > button.selected {
font-style: italic;
font-weight: bold;
margin: 0 0.5em;
text-decoration: underline;
}
//////////////////////////////////
// Styles for /fileedit:
form.fileedit textarea {
font-family: monospace;
width: 100%;
}
form.fileedit fieldset {
margin: 0.5em 0 0.5em 0;
border-radius: 0.5em;
|
| ︙ | ︙ | |||
915 916 917 918 919 920 921 |
margin: 0;
padding: 0;
}
#fileedit-comment {
width: 100%;
font-family: monospace;
}
| | | | | | | > > > | > > > > | | > > > > > > > > > > > | > > > | > > > > | > > | > > | > > > > > > > > > > > > | 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 |
margin: 0;
padding: 0;
}
#fileedit-comment {
width: 100%;
font-family: monospace;
}
.tab-container > .tabs > .tab-panel > .fileedit-options {
margin-top: 0;
border: none;
border-radius: 0;
border-bottom-width: 1px;
border-bottom-style: dotted;
}
.tab-container > .tabs > .tab-panel > .fileedit-options > button {
vertical-align: middle;
margin: 0.5em;
}
////////////////////////////////////////////////////////////////////
// Styles developed for /fileedit but which have wider
// applicability:
.flex-container {
display: flex;
}
.flex-container.row {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
.flex-container.row.stretch {
flex-direction: row;
flex-wrap: wrap;
align-items: stretch;
margin: 0;
}
.flex-container.column {
flex-direction: column;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
.flex-container.column.stretch {
align-items: stretch;
margin: 0;
}
.font-size-100 {
font-size: 100%;
}
.font-size-125 {
font-size: 125%;
}
.font-size-150 {
font-size: 150%;
}
.font-size-175 {
font-size: 175%;
}
.font-size-200 {
font-size: 200%;
}
//////////////////////////////////////////////////////////////////
// .input-with-label is intended to be a wrapper element which
// contains a SPAN label and an INPUT control.
.input-with-label {
border: 1px inset #808080;
border-radius: 0.5em;
padding: 0.25em 0.4em;
margin: 0 0.5em;
display: inline-block;
cursor: pointer;
|
| ︙ | ︙ | |||
964 965 966 967 968 969 970 |
.input-with-label > input[type=radio] {
vertical-align: sub;
}
.input-with-label > span {
margin: 0 0.25em 0 0.25em;
vertical-align: middle;
}
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 1074 1075 1076 1077 1078 1079 1080 |
.input-with-label > input[type=radio] {
vertical-align: sub;
}
.input-with-label > span {
margin: 0 0.25em 0 0.25em;
vertical-align: middle;
}
|
Changes to src/fileedit.c.
| ︙ | ︙ | |||
1450 1451 1452 1453 1454 1455 1456 | end_cleanup: fossil_free(zNewUuid); blob_reset(&err); blob_reset(&manifest); CheckinMiniInfo_cleanup(&cimi); } | < < < < < < < < < < | 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 | end_cleanup: fossil_free(zNewUuid); blob_reset(&err); blob_reset(&manifest); CheckinMiniInfo_cleanup(&cimi); } /* ** WEBPAGE: fileedit ** ** EXPERIMENTAL and subject to change and removal at any time. The goal ** is to allow online edits of files. ** ** Query parameters: |
| ︙ | ︙ | |||
1485 1486 1487 1488 1489 1490 1491 |
because that param is handled
specially by the core. */
const char * zRev; /* checkin version */
const char * zFileMime = 0; /* File mime type guess */
CheckinMiniInfo cimi; /* Checkin state */
int previewHtmlHeight = 0; /* iframe height (EMs) */
int previewRenderMode = FE_RENDER_GUESS; /* preview mode */
| < | 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 |
because that param is handled
specially by the core. */
const char * zRev; /* checkin version */
const char * zFileMime = 0; /* File mime type guess */
CheckinMiniInfo cimi; /* Checkin state */
int previewHtmlHeight = 0; /* iframe height (EMs) */
int previewRenderMode = FE_RENDER_GUESS; /* preview mode */
Blob err = empty_blob; /* Error report */
Blob endScript = empty_blob; /* Script code to run at the
end. This content will be
combined into a single JS
function call, thus each
entry must end with a
semicolon. */
|
| ︙ | ︙ | |||
1786 1787 1788 1789 1790 1791 1792 |
/* Dynamically populate the editor... */
blob_appendf(&endScript,
"fossil.page.loadFile('%j','%j');",
zFilename, cimi.zParentUuid);
end_footer:
| < > | > > | | | 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 |
/* Dynamically populate the editor... */
blob_appendf(&endScript,
"fossil.page.loadFile('%j','%j');",
zFilename, cimi.zParentUuid);
end_footer:
if(stmt.pStmt){
db_finalize(&stmt);
}
if(blob_size(&err)){
CX("<div class='fileedit-error-report'>%s</div>",
blob_str(&err));
}
blob_reset(&err);
CheckinMiniInfo_cleanup(&cimi);
style_emit_script_fossil_bootstrap(0);
style_emit_script_fetch(0);
style_emit_script_tabs(0);
style_emit_script_builtin("fossil.page.fileedit.js",0);
if(blob_size(&endScript)>0){
style_emit_script_tag(0,0);
CX("(function(){\n");
CX("try{\n%b\n}catch(e){console.error('Exception:',e)}\n",
&endScript);
CX("})();");
style_emit_script_tag(1,0);
}
db_end_transaction(0);
style_footer();
}
|
Changes to src/fossil.bootstrap.js.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
"use strict";
(function(global){
/* Bootstrapping bits for the global.fossil object. Must be
loaded after style.c:style_emit_script_tag() has initialized
that object.
*/
const timestring = function f(){
if(!f.rx1){
f.rx1 = /\.\d+Z$/;
}
const d = new Date();
return d.toISOString().replace(f.rx1,'').split('T').join(' ');
};
| > > > > < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
"use strict";
(function(global){
/* Bootstrapping bits for the global.fossil object. Must be
loaded after style.c:style_emit_script_tag() has initialized
that object.
*/
/**
Returns the current time in something approximating
ISO-8601 format.
*/
const timestring = function f(){
if(!f.rx1){
f.rx1 = /\.\d+Z$/;
}
const d = new Date();
return d.toISOString().replace(f.rx1,'').split('T').join(' ');
};
/*
** By default fossil.message() sends its arguments console.debug(). If
** fossil.message.targetElement is set, it is assumed to be a DOM
** element, its innerText gets assigned to the concatenation of all
** arguments (with a space between each), and the CSS 'error' class is
** removed from the object. Pass it a falsy value to clear the target
** element.
**
** Returns this object.
*/
global.fossil.message = function f(msg){
const args = Array.prototype.slice.call(arguments,0);
const tgt = f.targetElement;
args.unshift(timestring(),'UTC:');
if(tgt){
tgt.classList.remove('error');
tgt.innerText = args.join(' ');
}
else{
args.unshift('Fossil status:');
console.debug.apply(console,args);
|
| ︙ | ︙ | |||
50 51 52 53 54 55 56 |
** that element and sets its content as defined for message().
**
** Returns this object.
*/
global.fossil.error = function f(msg){
const args = Array.prototype.slice.call(arguments,0);
const tgt = global.fossil.message.targetElement;
| | | 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
** that element and sets its content as defined for message().
**
** Returns this object.
*/
global.fossil.error = function f(msg){
const args = Array.prototype.slice.call(arguments,0);
const tgt = global.fossil.message.targetElement;
args.unshift(timestring(),'UTC:');
if(tgt){
tgt.classList.add('error');
tgt.innerText = args.join(' ');
}
else{
args.unshift('Fossil error:');
console.error.apply(console,args);
|
| ︙ | ︙ |
Changes to src/fossil.fetch.js.
1 2 | "use strict"; /** | > | > > > | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
"use strict";
/**
Requires that window.fossil has already been set up.
window.fossil.fetch() is an HTTP request/response mini-framework
similar (but not identical) to the not-quite-ubiquitous
window.fetch().
JS usages:
fossil.fetch( URI [, onLoadCallback] );
fossil.fetch( URI [, optionsObject = {}] );
Noting that URI must be relative to the top of the repository and
should not start with a slash (if it does, it is stripped). It gets
the equivalent of "%R/" prepended to it.
The optionsObject may be an onload callback or an object with any
of these properties:
- onload: callback(responseData) (default = output response to
the console).
- onerror: callback(XHR onload event | exception)
(default = event or exception to the console).
- method: 'POST' | 'GET' (default = 'GET')
- payload: anything acceptable by XHR2.send(ARG) (DOMString,
Document, FormData, Blob, File, ArrayBuffer), or a plain object or
array, either of which gets JSON.stringify()'d. If payload is set
then the method is automatically set to 'POST'. If an object/array
is converted to JSON, the contentType option is automatically set
to 'application/json'. By default XHR2 will set the content type
based on the payload type.
- contentType: Optional request content type when POSTing. Ignored
if the method is not 'POST'.
- responseType: optional string. One of ("text", "arraybuffer",
"blob", or "document") (as specified by XHR2). Default = "text".
As an extension, it supports "json", which tells it that the
response is expected to be text and that it should be JSON.parse()d
before passing it on to the onload() callback.
- urlParams: string|object. If a string, it is assumed to be a
URI-encoded list of params in the form "key1=val1&key2=val2...",
with NO leading '?'. If it is an object, all of its properties get
converted to that form. Either way, the parameters get appended to
the URL before submitting the request.
When an options object does not provide onload() or onerror()
handlers of its own, this function falls back to
fossil.fetch.onload() and fossil.fetch.onerror() as defaults. The
default implementations route the data through the dev console and
(for onerror()) through fossil.error(). Individual pages may
overwrite those members to provide default implementations suitable
for the page's use.
Returns this object, noting that the XHR request is asynchronous,
and still in transit (or has yet to be sent) when that happens.
*/
window.fossil.fetch = function f(uri,opt){
if(!f.onerror){
f.onerror = function(e/*event or exception*/){
console.error("Ajax error:",e);
if(e instanceof Error){
fossil.error('Exception:',e);
}
else if(e.originalTarget && e.originalTarget.responseType==='text'){
const txt = e.originalTarget.responseText;
try{
/* The convention from the /filepage_xyz routes is to
return error responses in JSON form if possible:
{error: "..."}
*/
const j = JSON.parse(txt);
console.error("Error JSON:",j);
if(j.error){ fossil.error(j.error) };
}catch(e){/* Try harder */
fossil.error(txt)
}
|
| ︙ | ︙ |
Changes to src/style.c.
| ︙ | ︙ | |||
1423 1424 1425 1426 1427 1428 1429 | } va_end(vargs); } /* ** The first time this is called, it emits code to install and ** bootstrap the window.fossil object, using the built-in file | | < < > > > > > > > > | | > > > > | < > > > > > | | > > < > > > | > > | > > > | > | < | > > > | < < | | > > | | > | > > > | > > | < < < < < | < < < < < < < < < < < < < < | < < | | < < < < < < < < < < < < < < | < < < < < < < < < | < < < < < < | < < | | > > > > > > > > | | | | | > > > > | | | | 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 |
}
va_end(vargs);
}
/*
** The first time this is called, it emits code to install and
** bootstrap the window.fossil object, using the built-in file
** fossil.bootstrap.js (not to be confused with bootstrap.js).
**
** Subsequent calls are no-ops.
**
** If passed a true value, it emits the contents directly to the page
** output, else it emits a script tag with a src=builtin/... to load
** the script. It always outputs a small pre-bootstrap element in its
** own script tag to initialize parts which need C-runtime-level
** information, because loading the main fossil.bootstrap.js either
** inline or via a <script src=...>, as specified by the first
** argument.
*/
void style_emit_script_fossil_bootstrap(int asInline){
static int once = 0;
if(0==once++){
/* Set up the generic/app-agnostic parts of window.fossil
** which require C-level state... */
style_emit_script_tag(0,0);
CX("(function(){\n"
"if(!window.fossil) window.fossil={};\n"
"window.fossil.version = \"%j\";\n"
/* fossil.rootPath is the top-most CGI/server path,
including a trailing slash. */
"window.fossil.rootPath = \"%j\"+'/';\n",
get_version(), g.zTop);
/*
** fossil.page holds info about the current page. This is
** also where the current page "should" store any of its
** own page-specific state.
*/
CX("window.fossil.page = {"
"page:\"%T\""
"};\n", g.zPath);
CX("})();\n");
/* The remaining code is not dependent on C-runtime state... */
if(asInline){
CX("%s\n", builtin_text("fossil.bootstrap.js"));
}
style_emit_script_tag(1,0);
if(asInline==0){
style_emit_script_tag(0,"builtin/fossil.bootstrap.js");
}
}
}
/*
** If passed 0 as its first argument, it emits a script opener tag
** with this request's nonce. If passed non-0 it emits a script
** closing tag. Mnemonic for remembering the order in which to pass 0
** or 1 as the first argument to this function: 0 comes before 1.
**
** If passed 0 as its first argument and a non-NULL/non-empty zSrc,
** then it instead emits:
**
** <script src='%R/{{zSrc}}'></script>
**
** zSrc is always assumed to be a repository-relative path without
** a leading slash, and has %R/ prepended to it.
**
** Meaning that no follow-up call to pass a non-0 first argument
** to close the tag. zSrc is ignored if the first argument is not
** 0.
**
*/
void style_emit_script_tag(int isCloser, const char * zSrc){
if(0==isCloser){
if(zSrc!=0 && zSrc[0]!=0){
CX("<script src='%R/%T'></script>\n", zSrc);
}else{
CX("<script nonce='%s'>", style_nonce());
}
}else{
CX("</script>\n");
}
}
/*
** Emits a script tag which uses content from a builtin script file.
**
** If asInline is true, it is emitted directly as an opening tag, the
** content of the zName builtin file, and a closing tag. If it is false,
** a script tag loading it via src=builtin/{{zName}} is emitted.
*/
void style_emit_script_builtin(char const * zName, int asInline){
if(asInline){
style_emit_script_tag(0,0);
CX("%s", builtin_text(zName));
style_emit_script_tag(1,0);
}else{
char * zFull = mprintf("builtin/%s",zName);
style_emit_script_tag(0,zFull);
fossil_free(zFull);
}
}
/*
** The first time this is called, it emits the JS code from the
** built-in file fossil.fossil.js. Subsequent calls are no-ops.
**
** If passed a true value, it emits the contents directly
** to the page output, else it emits a script tag with a
** src=builtin/... to load the script.
**
** Note that this code relies on that loaded via
** style_emit_script_fossil_bootstrap() but it does not call that
** routine.
*/
void style_emit_script_fetch(int asInline){
static int once = 0;
if(0==once++){
style_emit_script_builtin("fossil.fetch.js", asInline);
}
}
/*
** The first time this is called, it emits the JS code from the
** built-in file fossil.dom.js. Subsequent calls are no-ops.
**
** If passed a true value, it emits the contents directly
** to the page output, else it emits a script tag with a
** src=builtin/... to load the script.
**
** Note that this code relies on that loaded via
** style_emit_script_fossil_bootstrap(), but it does not call that
** routine.
*/
void style_emit_script_dom(int asInline){
static int once = 0;
if(0==once++){
style_emit_script_builtin("fossil.dom.js", asInline);
}
}
/*
** The first time this is called, it calls style_emit_script_dom(),
** passing it the given asInline value, and emits the JS code from the
** built-in file fossil.tabs.js. Subsequent calls are no-ops.
**
** If passed a true value, it emits the contents directly
** to the page output, else it emits a script tag with a
** src=builtin/... to load the script.
*/
void style_emit_script_tabs(int asInline){
static int once = 0;
if(0==once++){
style_emit_script_dom(asInline);
style_emit_script_builtin("fossil.tabs.js",asInline);
}
}
|