Fossil SCM

Clear the preview/diff views after a non-dry-run commit. Added toggle to include (or not) manifest in the commit response. Added branch name to finfo objects from the server so that we have the branch names for stashed non-leaf edits. Extended fossil.connectPagePreviewers() to be able to optionally specify methods, instead of element IDs, for the 'from' source and 'to' target, in order to support custom editor/preview widgets.

stephan 2020-05-16 17:05 fileedit-ajaxify
Commit b5944d4ede11319df4a18833831abcd47ac659367f6ee878d9e0ca02d698b326
+114 -50
--- src/fileedit.c
+++ src/fileedit.c
@@ -13,11 +13,11 @@
1313
** [email protected]
1414
** http://www.hwaci.com/drh/
1515
**
1616
*******************************************************************************
1717
**
18
-** This file contains code for the /fileedit page and related code.
18
+** This file contains code for the /fileedit page and related bits.
1919
*/
2020
#include "config.h"
2121
#include "fileedit.h"
2222
#include <assert.h>
2323
#include <stdarg.h>
@@ -29,12 +29,14 @@
2929
**
3030
** Use CheckinMiniInfo_init() to cleanly initialize one to a known
3131
** valid/empty default state.
3232
**
3333
** Memory for all non-const pointer members is owned by the
34
-** CheckinMiniInfo instance and is freed by CheckinMiniInfo_cleanup().
35
-** Similarly, each instance owns any memory for its Blob members.
34
+** CheckinMiniInfo instance, unless explicitly noted otherwise, and is
35
+** freed by CheckinMiniInfo_cleanup(). Similarly, each instance owns
36
+** any memory for its own Blob members, but NOT for its pointers to
37
+** blobs.
3638
*/
3739
struct CheckinMiniInfo {
3840
Manifest * pParent; /* parent checkin. Memory is owned by this
3941
object. */
4042
char *zParentUuid; /* Full UUID of pParent */
@@ -67,10 +69,13 @@
6769
6870
/*
6971
** CheckinMiniInfo::flags values.
7072
*/
7173
enum fossil_cimini_flags {
74
+/*
75
+** Must have a value of 0. All other flags have unspecified values.
76
+*/
7277
CIMINI_NONE = 0,
7378
/*
7479
** Tells checkin_mini() to use dry-run mode.
7580
*/
7681
CIMINI_DRY_RUN = 1,
@@ -118,27 +123,27 @@
118123
*/
119124
CIMINI_PREFER_DELTA = 1<<8,
120125
/*
121126
** A "stronger hint" to checkin_mini() to prefer creation of a delta
122127
** manifest if it at all can. It will decide not to only if creation
123
-** of a delta is not a realistic option. For this to work, it must be
124
-** set together with the CIMINI_PREFER_DELTA flag, but the two cannot
125
-** be combined in this enum.
128
+** of a delta is not a realistic option or if it's forbitted by the
129
+** forbid-delta-manifests repo config option. For this to work, it
130
+** must be set together with the CIMINI_PREFER_DELTA flag, but the two
131
+** cannot be combined in this enum.
126132
**
127133
** This option is ONLY INTENDED FOR TESTING, used in bypassing
128134
** heuristics which may otherwise disable generation of a delta on the
129135
** grounds of efficiency (e.g. not generating a delta if the parent
130136
** non-delta only has a few F-cards).
131
-**
132
-** The forbid-delta-manifests repo config option trumps this.
133137
*/
134138
CIMINI_STRONGLY_PREFER_DELTA = 1<<9,
135139
/*
136140
** Tells checkin_mini() to permit the addition of a new file. Normally
137
-** this is disabled because there are many cases where it could cause
138
-** the inadvertent addition of a new file when an update to an
139
-** existing was intended, as a side-effect of name-case differences.
141
+** this is disabled because there are hypothetically many cases where
142
+** it could cause the inadvertent addition of a new file when an
143
+** update to an existing was intended, as a side-effect of name-case
144
+** differences.
140145
*/
141146
CIMINI_ALLOW_NEW_FILE = 1<<10
142147
};
143148
144149
/*
@@ -213,10 +218,11 @@
213218
}else{
214219
/* File was removed from parent delta. */
215220
blob_appendf(pOut, "F %F\n", p->zName);
216221
}
217222
}
223
+
218224
/*
219225
** Handles the F-card parts for create_manifest_mini().
220226
**
221227
** If asDelta is true, F-cards will be handled as for a delta
222228
** manifest, and the caller MUST have added a B-card to pOut before
@@ -310,18 +316,18 @@
310316
}
311317
312318
/*
313319
** Creates a manifest file, written to pOut, from the state in the
314320
** fully-populated and semantically valid pCI argument. pCI is not
315
-** *semantically* modified but cannot be const because blob_str() may
316
-** need to NUL-terminate any given blob.
321
+** *semantically* modified by this routine but cannot be const because
322
+** blob_str() may need to NUL-terminate any given blob.
317323
**
318324
** Returns true on success. On error, returns 0 and, if pErr is not
319325
** NULL, writes an error message there.
320326
**
321327
** Intended only to be called via checkin_mini() or routines which
322
-** have already completely vetted pCI.
328
+** have already completely vetted pCI for semantic validity.
323329
*/
324330
static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI,
325331
Blob * pErr){
326332
Blob zCard = empty_blob; /* Z-card checksum */
327333
int asDelta = 0;
@@ -384,19 +390,18 @@
384390
return 1;
385391
#undef mf_err
386392
}
387393
388394
/*
389
-** EXPERIMENTAL! Subject to change or removal at any time.
390
-**
391395
** A so-called "single-file/mini/web checkin" is a slimmed-down form
392396
** of the checkin command which accepts only a single file and is
393397
** intended to accept edits to a file via the web interface or from
394398
** the CLI from outside of a checkout.
395399
**
396400
** Being fully non-interactive is a requirement for this function,
397
-** thus it cannot perform autosync or similar activities.
401
+** thus it cannot perform autosync or similar activities (which
402
+** includes checking for repo locks).
398403
**
399404
** This routine uses the state from the given fully-populated pCI
400405
** argument to add pCI->fileContent to the database, and create and
401406
** save a manifest for that change. Ownership of pCI and its contents
402407
** are unchanged.
@@ -417,19 +422,25 @@
417422
** style differs from its previous version, it is converted to the
418423
** same EOL style as the previous version. If this is done, the
419424
** pCI->fileHash is re-computed. Note that only pCI->fileContent,
420425
** not the original file, is affected by the conversion.
421426
**
427
+** - Else if one of the CIMINI_CONVERT_EOL_WINDOWS or
428
+** CIMINI_CONVERT_EOL_UNIX flags are set, pCI->fileContent is
429
+** converted, if needed, to the corresponding EOL style.
430
+**
431
+** - If EOL conversion takes place, pCI->fileHash is re-calculated.
432
+**
422433
** - If pCI->fileHash is empty, this routine populates it with the
423
-** repository's preferred hash algorithm.
434
+** repository's preferred hash algorithm (after any EOL conversion).
424435
**
425436
** - pCI->comment may be converted to Unix-style newlines.
426437
**
427438
** pCI's ownership is not modified.
428439
**
429
-** This function validates several of the inputs and fails if any
430
-** validation fails.
440
+** This function validates pCI's state and fails if any validation
441
+** fails.
431442
**
432443
** On error, returns false (0) and, if pErr is not NULL, writes a
433444
** diagnostic message there.
434445
**
435446
** Returns true on success. If pRid is not NULL, the RID of the
@@ -521,11 +532,12 @@
521532
}
522533
}
523534
/* Potential TODOs include:
524535
**
525536
** - Commit allows an empty checkin only with a flag, but we
526
- ** currently disallow it entirely. Conform with commit?
537
+ ** currently disallow an empty checkin entirely. Conform with
538
+ ** commit?
527539
**
528540
** Non-TODOs:
529541
**
530542
** - Check for a commit lock would require auto-sync, which this
531543
** code cannot do if it's going to be run via a web page.
@@ -631,11 +643,11 @@
631643
** that we have to delay this check until after the potentially
632644
** expensive EOL conversion. */
633645
assert(blob_size(&pCI->fileHash));
634646
if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash))
635647
&& manifest_file_mperm(zFilePrev)==pCI->filePerm){
636
- ci_err((pErr,"File is unchanged. Not saving."));
648
+ ci_err((pErr,"File is unchanged. Not committing."));
637649
}
638650
}
639651
#if 1
640652
/* Do we really want to normalize comment EOLs? Web-posting will
641653
** submit them in CRLF or LF format, depending on how exactly the
@@ -1185,10 +1197,17 @@
11851197
**
11861198
** User must have Write access to use this page.
11871199
**
11881200
** Responds with the raw content of the given page. On error it
11891201
** produces a JSON response as documented for fileedit_ajax_error().
1202
+**
1203
+** Extra response headers:
1204
+**
1205
+** x-fileedit-file-perm: empty or "x" or "l", representing PERM_REG,
1206
+** PERM_EXE, or PERM_LINK, respectively.
1207
+**
1208
+** x-fileedit-checkin-branch: branch name for the passed-in checkin.
11901209
*/
11911210
static void fileedit_ajax_content(void){
11921211
const char * zFilename = 0;
11931212
const char * zRev = 0;
11941213
int vid, frid;
@@ -1216,10 +1235,17 @@
12161235
char * zFuuid = fileedit_file_uuid(zFilename, vid, &fperm);
12171236
const char * zPerm = mfile_permint_mstring(fperm);
12181237
assert(zFuuid);
12191238
cgi_printf_header("x-fileedit-file-perm:%s\r\n", zPerm);
12201239
fossil_free(zFuuid);
1240
+ }
1241
+ { /* Send branch name via response header for UI usability reasons */
1242
+ char * zBranch = branch_of_rid(vid);
1243
+ if(zBranch!=0 && zBranch[0]!=0){
1244
+ cgi_printf_header("x-fileedit-checkin-branch: %s\r\n", zBranch);
1245
+ }
1246
+ fossil_free(zBranch);
12211247
}
12221248
cgi_set_content_type(zMime);
12231249
cgi_set_content(&content);
12241250
}
12251251
@@ -1243,10 +1269,18 @@
12431269
**
12441270
** User must have Write access to use this page.
12451271
**
12461272
** Responds with the HTML content of the preview. On error it produces
12471273
** a JSON response as documented for fileedit_ajax_error().
1274
+**
1275
+** Extra response headers:
1276
+**
1277
+** x-fileedit-render-mode: string representing the rendering mode
1278
+** which was really used (which will differ from the requested mode
1279
+** only if mode 0 (guess) was requested). The names are documented
1280
+** below in code and match those in the emitted JS object
1281
+** fossil.page.previewModes.
12481282
*/
12491283
static void fileedit_ajax_preview(void){
12501284
const char * zFilename = 0;
12511285
const char * zContent = P("content");
12521286
int renderMode = atoi(PD("render_mode","0"));
@@ -1567,12 +1601,12 @@
15671601
if(i++){
15681602
CX(",");
15691603
}
15701604
CX("{");
15711605
CX("\"checkin\":%!j,", db_column_text(&q, 1));
1572
- CX("\"timestamp\":%!j,", db_column_text(&q, 2));
1573
- CX("\"branch\":%!j", db_column_text(&q, 7));
1606
+ CX("\"branch\":%!j,", db_column_text(&q, 7));
1607
+ CX("\"timestamp\":%!j", db_column_text(&q, 2));
15741608
CX("}");
15751609
}
15761610
CX("]");
15771611
db_finalize(&q);
15781612
}else{
@@ -1586,28 +1620,33 @@
15861620
** Required query parameters:
15871621
**
15881622
** filename=FILENAME
15891623
** checkin=Parent checkin UUID
15901624
** content=text
1591
-** comment=text
1625
+** comment=non-empty text
15921626
**
15931627
** Optional query parameters:
15941628
**
1595
-** comment_mimetype=text
1629
+** comment_mimetype=text (NOT currently honored)
1630
+**
15961631
** dry_run=int (1 or 0)
1632
+**
1633
+** include_manifest=int (1 or 0), whether to include
1634
+** the generated manifest in the response.
15971635
**
15981636
**
1599
-** User must have Write access to use this page.
1637
+** User must have Write permissions to use this page.
16001638
**
16011639
** Responds with JSON (with some state repeated
16021640
** from the input in order to avoid certain race conditions
16031641
** client-side):
16041642
**
16051643
** {
16061644
** checkin: newUUID,
16071645
** filename: theFilename,
16081646
** mimetype: string,
1647
+** branch: name of the checkin's branch,
16091648
** isExe: bool,
16101649
** dryRun: bool,
16111650
** manifest: text of manifest,
16121651
** }
16131652
**
@@ -1620,10 +1659,11 @@
16201659
CheckinMiniInfo cimi; /* checkin state */
16211660
int rc; /* generic result code */
16221661
int newVid = 0; /* new version's RID */
16231662
char * zNewUuid = 0; /* newVid's UUID */
16241663
char const * zMimetype;
1664
+ char * zBranch = 0;
16251665
16261666
if(!fileedit_ajax_boostrap()){
16271667
return;
16281668
}
16291669
db_begin_transaction();
@@ -1635,11 +1675,13 @@
16351675
}
16361676
if(blob_size(&cimi.comment)==0){
16371677
fileedit_ajax_error(400,"Empty checkin comment is not permitted.");
16381678
goto end_cleanup;
16391679
}
1640
- cimi.pMfOut = &manifest;
1680
+ if(0!=atoi(PD("include_manifest","0"))){
1681
+ cimi.pMfOut = &manifest;
1682
+ }
16411683
checkin_mini(&cimi, &newVid, &err);
16421684
if(blob_size(&err)){
16431685
fileedit_ajax_error(500,"%b",&err);
16441686
goto end_cleanup;
16451687
}
@@ -1652,13 +1694,20 @@
16521694
CX("\"isExe\": %s,", cimi.filePerm==PERM_EXE ? "true" : "false");
16531695
zMimetype = mimetype_from_name(cimi.zFilename);
16541696
if(zMimetype!=0){
16551697
CX("\"mimetype\": %!j,", zMimetype);
16561698
}
1657
- CX("\"dryRun\": %s,",
1699
+ zBranch = branch_of_rid(newVid);
1700
+ if(zBranch!=0){
1701
+ CX("\"branch\": %!j,", zBranch);
1702
+ fossil_free(zBranch);
1703
+ }
1704
+ CX("\"dryRun\": %s",
16581705
(CIMINI_DRY_RUN & cimi.flags) ? "true" : "false");
1659
- CX("\"manifest\": %!j", blob_str(&manifest));
1706
+ if(blob_size(&manifest)>0){
1707
+ CX(",\"manifest\": %!j", blob_str(&manifest));
1708
+ }
16601709
CX("}");
16611710
db_end_transaction(0/*noting that dry-run mode will have already
16621711
** set this to rollback mode. */);
16631712
end_cleanup:
16641713
fossil_free(zNewUuid);
@@ -1668,22 +1717,27 @@
16681717
}
16691718
16701719
/*
16711720
** WEBPAGE: fileedit
16721721
**
1673
-** EXPERIMENTAL and subject to change and removal at any time. The goal
1674
-** is to allow online edits of files.
1675
-**
1676
-** Query parameters:
1677
-**
1678
-** filename=FILENAME Repo-relative path to the file.
1679
-** checkin=VERSION Checkin version, using any unambiguous
1680
-** supported symbolic version name.
1681
-**
1682
-** All other parameters are for internal use only, submitted via the
1683
-** form-submission process, and may change with any given revision of
1684
-** this code.
1722
+** Enables the online editing and committing of individual text files.
1723
+** Requires that the user have Write permissions.
1724
+**
1725
+** Optional query parameters:
1726
+**
1727
+** filename=FILENAME Repo-relative path to the file.
1728
+** checkin=VERSION Checkin version, using any unambiguous
1729
+** supported symbolic version name.
1730
+**
1731
+** Internal-use parameters:
1732
+**
1733
+** ajax=string The name of a page-specific AJAX operation.
1734
+**
1735
+** Which additional parameters are used by each distinct ajax value is
1736
+** an internal implementation detail and may change with any given
1737
+** build of this code. An unknown ajax value triggers an error, as
1738
+** documented for fileedit_ajax_error().
16851739
*/
16861740
void fileedit_page(void){
16871741
const char * zFilename = 0; /* filename. We'll accept 'name'
16881742
because that param is handled
16891743
specially by the core. */
@@ -1834,15 +1888,17 @@
18341888
"data-tab-parent='fileedit-tabs' "
18351889
"data-tab-label='Preview'"
18361890
">");
18371891
CX("<div class='fileedit-options flex-container flex-row'>");
18381892
CX("<button id='btn-preview-refresh' "
1839
- "data-f-preview-from='fileedit-content-editor' "
1840
- /* ^^^ text source elem ID*/
1893
+ "data-f-preview-from='fileContent' "
1894
+ /* ^^^ fossil.page[methodName]() OR text source elem ID,
1895
+ ** but we need a method in order to support clients swapping out
1896
+ ** the text editor with their own. */
18411897
"data-f-preview-via='_postPreview' "
18421898
/* ^^^ fossil.page[methodName](content, callback) */
1843
- "data-f-preview-to='fileedit-tab-preview-wrapper' "
1899
+ "data-f-preview-to='#fileedit-tab-preview-wrapper' "
18441900
/* ^^^ dest elem ID */
18451901
">Refresh</button>");
18461902
/* Toggle auto-update of preview when the Preview tab is selected. */
18471903
style_labeled_checkbox("cb-preview-autoupdate",
18481904
NULL,
@@ -1988,10 +2044,18 @@
19882044
? 2 : 0),
19892045
"Inherit", 0,
19902046
"Unix", 1,
19912047
"Windows", 2,
19922048
NULL);
2049
+ style_labeled_checkbox("cb-include-manifest",
2050
+ "include_manifest",
2051
+ "Response manifest?", "1",
2052
+ 0,
2053
+ "Include the manifest in the response? "
2054
+ "It's generally only useful for debug "
2055
+ "purposes.");
2056
+
19932057
CX("</div>"/*checkboxes*/);
19942058
}
19952059
19962060
{ /******* Commit comment, button, and result manifest *******/
19972061
CX("<fieldset class='fileedit-options commit-message'>"
@@ -2067,16 +2131,16 @@
20672131
"with many files.</li>");
20682132
CX("<li>The file selector allows, for usability's sake, only files "
20692133
"in leaf checkins to be selected, but files may be edited via "
20702134
"non-leaf checkins by passing them as the <code>filename</code> "
20712135
"and <code>checkin</code> URL arguments to this page.</li>");
2072
- CX("<li>The editor \"stashes\" some number of local edits in "
2073
- "one of <code>window.fileStorage</code> or "
2136
+ CX("<li>The editor stores some number of local edits in one of "
2137
+ "<code>window.fileStorage</code> or "
20742138
"<code>window.sessionStorage</code>, if able, but which storage "
2075
- "is unspecified and may differ across environments. When saving "
2076
- "or force-reloading a file, stashed edits to that version are "
2077
- "discarded.</li>");
2139
+ "is unspecified and may differ across environments. When "
2140
+ "committing or force-reloading a file, stashed edits to that "
2141
+ "version are discarded.</li>");
20782142
CX("</ul>");
20792143
}
20802144
CX("</div>"/*#fileedit-tab-help*/);
20812145
20822146
{
20832147
--- src/fileedit.c
+++ src/fileedit.c
@@ -13,11 +13,11 @@
13 ** [email protected]
14 ** http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file contains code for the /fileedit page and related code.
19 */
20 #include "config.h"
21 #include "fileedit.h"
22 #include <assert.h>
23 #include <stdarg.h>
@@ -29,12 +29,14 @@
29 **
30 ** Use CheckinMiniInfo_init() to cleanly initialize one to a known
31 ** valid/empty default state.
32 **
33 ** Memory for all non-const pointer members is owned by the
34 ** CheckinMiniInfo instance and is freed by CheckinMiniInfo_cleanup().
35 ** Similarly, each instance owns any memory for its Blob members.
 
 
36 */
37 struct CheckinMiniInfo {
38 Manifest * pParent; /* parent checkin. Memory is owned by this
39 object. */
40 char *zParentUuid; /* Full UUID of pParent */
@@ -67,10 +69,13 @@
67
68 /*
69 ** CheckinMiniInfo::flags values.
70 */
71 enum fossil_cimini_flags {
 
 
 
72 CIMINI_NONE = 0,
73 /*
74 ** Tells checkin_mini() to use dry-run mode.
75 */
76 CIMINI_DRY_RUN = 1,
@@ -118,27 +123,27 @@
118 */
119 CIMINI_PREFER_DELTA = 1<<8,
120 /*
121 ** A "stronger hint" to checkin_mini() to prefer creation of a delta
122 ** manifest if it at all can. It will decide not to only if creation
123 ** of a delta is not a realistic option. For this to work, it must be
124 ** set together with the CIMINI_PREFER_DELTA flag, but the two cannot
125 ** be combined in this enum.
 
126 **
127 ** This option is ONLY INTENDED FOR TESTING, used in bypassing
128 ** heuristics which may otherwise disable generation of a delta on the
129 ** grounds of efficiency (e.g. not generating a delta if the parent
130 ** non-delta only has a few F-cards).
131 **
132 ** The forbid-delta-manifests repo config option trumps this.
133 */
134 CIMINI_STRONGLY_PREFER_DELTA = 1<<9,
135 /*
136 ** Tells checkin_mini() to permit the addition of a new file. Normally
137 ** this is disabled because there are many cases where it could cause
138 ** the inadvertent addition of a new file when an update to an
139 ** existing was intended, as a side-effect of name-case differences.
 
140 */
141 CIMINI_ALLOW_NEW_FILE = 1<<10
142 };
143
144 /*
@@ -213,10 +218,11 @@
213 }else{
214 /* File was removed from parent delta. */
215 blob_appendf(pOut, "F %F\n", p->zName);
216 }
217 }
 
218 /*
219 ** Handles the F-card parts for create_manifest_mini().
220 **
221 ** If asDelta is true, F-cards will be handled as for a delta
222 ** manifest, and the caller MUST have added a B-card to pOut before
@@ -310,18 +316,18 @@
310 }
311
312 /*
313 ** Creates a manifest file, written to pOut, from the state in the
314 ** fully-populated and semantically valid pCI argument. pCI is not
315 ** *semantically* modified but cannot be const because blob_str() may
316 ** need to NUL-terminate any given blob.
317 **
318 ** Returns true on success. On error, returns 0 and, if pErr is not
319 ** NULL, writes an error message there.
320 **
321 ** Intended only to be called via checkin_mini() or routines which
322 ** have already completely vetted pCI.
323 */
324 static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI,
325 Blob * pErr){
326 Blob zCard = empty_blob; /* Z-card checksum */
327 int asDelta = 0;
@@ -384,19 +390,18 @@
384 return 1;
385 #undef mf_err
386 }
387
388 /*
389 ** EXPERIMENTAL! Subject to change or removal at any time.
390 **
391 ** A so-called "single-file/mini/web checkin" is a slimmed-down form
392 ** of the checkin command which accepts only a single file and is
393 ** intended to accept edits to a file via the web interface or from
394 ** the CLI from outside of a checkout.
395 **
396 ** Being fully non-interactive is a requirement for this function,
397 ** thus it cannot perform autosync or similar activities.
 
398 **
399 ** This routine uses the state from the given fully-populated pCI
400 ** argument to add pCI->fileContent to the database, and create and
401 ** save a manifest for that change. Ownership of pCI and its contents
402 ** are unchanged.
@@ -417,19 +422,25 @@
417 ** style differs from its previous version, it is converted to the
418 ** same EOL style as the previous version. If this is done, the
419 ** pCI->fileHash is re-computed. Note that only pCI->fileContent,
420 ** not the original file, is affected by the conversion.
421 **
 
 
 
 
 
 
422 ** - If pCI->fileHash is empty, this routine populates it with the
423 ** repository's preferred hash algorithm.
424 **
425 ** - pCI->comment may be converted to Unix-style newlines.
426 **
427 ** pCI's ownership is not modified.
428 **
429 ** This function validates several of the inputs and fails if any
430 ** validation fails.
431 **
432 ** On error, returns false (0) and, if pErr is not NULL, writes a
433 ** diagnostic message there.
434 **
435 ** Returns true on success. If pRid is not NULL, the RID of the
@@ -521,11 +532,12 @@
521 }
522 }
523 /* Potential TODOs include:
524 **
525 ** - Commit allows an empty checkin only with a flag, but we
526 ** currently disallow it entirely. Conform with commit?
 
527 **
528 ** Non-TODOs:
529 **
530 ** - Check for a commit lock would require auto-sync, which this
531 ** code cannot do if it's going to be run via a web page.
@@ -631,11 +643,11 @@
631 ** that we have to delay this check until after the potentially
632 ** expensive EOL conversion. */
633 assert(blob_size(&pCI->fileHash));
634 if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash))
635 && manifest_file_mperm(zFilePrev)==pCI->filePerm){
636 ci_err((pErr,"File is unchanged. Not saving."));
637 }
638 }
639 #if 1
640 /* Do we really want to normalize comment EOLs? Web-posting will
641 ** submit them in CRLF or LF format, depending on how exactly the
@@ -1185,10 +1197,17 @@
1185 **
1186 ** User must have Write access to use this page.
1187 **
1188 ** Responds with the raw content of the given page. On error it
1189 ** produces a JSON response as documented for fileedit_ajax_error().
 
 
 
 
 
 
 
1190 */
1191 static void fileedit_ajax_content(void){
1192 const char * zFilename = 0;
1193 const char * zRev = 0;
1194 int vid, frid;
@@ -1216,10 +1235,17 @@
1216 char * zFuuid = fileedit_file_uuid(zFilename, vid, &fperm);
1217 const char * zPerm = mfile_permint_mstring(fperm);
1218 assert(zFuuid);
1219 cgi_printf_header("x-fileedit-file-perm:%s\r\n", zPerm);
1220 fossil_free(zFuuid);
 
 
 
 
 
 
 
1221 }
1222 cgi_set_content_type(zMime);
1223 cgi_set_content(&content);
1224 }
1225
@@ -1243,10 +1269,18 @@
1243 **
1244 ** User must have Write access to use this page.
1245 **
1246 ** Responds with the HTML content of the preview. On error it produces
1247 ** a JSON response as documented for fileedit_ajax_error().
 
 
 
 
 
 
 
 
1248 */
1249 static void fileedit_ajax_preview(void){
1250 const char * zFilename = 0;
1251 const char * zContent = P("content");
1252 int renderMode = atoi(PD("render_mode","0"));
@@ -1567,12 +1601,12 @@
1567 if(i++){
1568 CX(",");
1569 }
1570 CX("{");
1571 CX("\"checkin\":%!j,", db_column_text(&q, 1));
1572 CX("\"timestamp\":%!j,", db_column_text(&q, 2));
1573 CX("\"branch\":%!j", db_column_text(&q, 7));
1574 CX("}");
1575 }
1576 CX("]");
1577 db_finalize(&q);
1578 }else{
@@ -1586,28 +1620,33 @@
1586 ** Required query parameters:
1587 **
1588 ** filename=FILENAME
1589 ** checkin=Parent checkin UUID
1590 ** content=text
1591 ** comment=text
1592 **
1593 ** Optional query parameters:
1594 **
1595 ** comment_mimetype=text
 
1596 ** dry_run=int (1 or 0)
 
 
 
1597 **
1598 **
1599 ** User must have Write access to use this page.
1600 **
1601 ** Responds with JSON (with some state repeated
1602 ** from the input in order to avoid certain race conditions
1603 ** client-side):
1604 **
1605 ** {
1606 ** checkin: newUUID,
1607 ** filename: theFilename,
1608 ** mimetype: string,
 
1609 ** isExe: bool,
1610 ** dryRun: bool,
1611 ** manifest: text of manifest,
1612 ** }
1613 **
@@ -1620,10 +1659,11 @@
1620 CheckinMiniInfo cimi; /* checkin state */
1621 int rc; /* generic result code */
1622 int newVid = 0; /* new version's RID */
1623 char * zNewUuid = 0; /* newVid's UUID */
1624 char const * zMimetype;
 
1625
1626 if(!fileedit_ajax_boostrap()){
1627 return;
1628 }
1629 db_begin_transaction();
@@ -1635,11 +1675,13 @@
1635 }
1636 if(blob_size(&cimi.comment)==0){
1637 fileedit_ajax_error(400,"Empty checkin comment is not permitted.");
1638 goto end_cleanup;
1639 }
1640 cimi.pMfOut = &manifest;
 
 
1641 checkin_mini(&cimi, &newVid, &err);
1642 if(blob_size(&err)){
1643 fileedit_ajax_error(500,"%b",&err);
1644 goto end_cleanup;
1645 }
@@ -1652,13 +1694,20 @@
1652 CX("\"isExe\": %s,", cimi.filePerm==PERM_EXE ? "true" : "false");
1653 zMimetype = mimetype_from_name(cimi.zFilename);
1654 if(zMimetype!=0){
1655 CX("\"mimetype\": %!j,", zMimetype);
1656 }
1657 CX("\"dryRun\": %s,",
 
 
 
 
 
1658 (CIMINI_DRY_RUN & cimi.flags) ? "true" : "false");
1659 CX("\"manifest\": %!j", blob_str(&manifest));
 
 
1660 CX("}");
1661 db_end_transaction(0/*noting that dry-run mode will have already
1662 ** set this to rollback mode. */);
1663 end_cleanup:
1664 fossil_free(zNewUuid);
@@ -1668,22 +1717,27 @@
1668 }
1669
1670 /*
1671 ** WEBPAGE: fileedit
1672 **
1673 ** EXPERIMENTAL and subject to change and removal at any time. The goal
1674 ** is to allow online edits of files.
1675 **
1676 ** Query parameters:
1677 **
1678 ** filename=FILENAME Repo-relative path to the file.
1679 ** checkin=VERSION Checkin version, using any unambiguous
1680 ** supported symbolic version name.
1681 **
1682 ** All other parameters are for internal use only, submitted via the
1683 ** form-submission process, and may change with any given revision of
1684 ** this code.
 
 
 
 
 
1685 */
1686 void fileedit_page(void){
1687 const char * zFilename = 0; /* filename. We'll accept 'name'
1688 because that param is handled
1689 specially by the core. */
@@ -1834,15 +1888,17 @@
1834 "data-tab-parent='fileedit-tabs' "
1835 "data-tab-label='Preview'"
1836 ">");
1837 CX("<div class='fileedit-options flex-container flex-row'>");
1838 CX("<button id='btn-preview-refresh' "
1839 "data-f-preview-from='fileedit-content-editor' "
1840 /* ^^^ text source elem ID*/
 
 
1841 "data-f-preview-via='_postPreview' "
1842 /* ^^^ fossil.page[methodName](content, callback) */
1843 "data-f-preview-to='fileedit-tab-preview-wrapper' "
1844 /* ^^^ dest elem ID */
1845 ">Refresh</button>");
1846 /* Toggle auto-update of preview when the Preview tab is selected. */
1847 style_labeled_checkbox("cb-preview-autoupdate",
1848 NULL,
@@ -1988,10 +2044,18 @@
1988 ? 2 : 0),
1989 "Inherit", 0,
1990 "Unix", 1,
1991 "Windows", 2,
1992 NULL);
 
 
 
 
 
 
 
 
1993 CX("</div>"/*checkboxes*/);
1994 }
1995
1996 { /******* Commit comment, button, and result manifest *******/
1997 CX("<fieldset class='fileedit-options commit-message'>"
@@ -2067,16 +2131,16 @@
2067 "with many files.</li>");
2068 CX("<li>The file selector allows, for usability's sake, only files "
2069 "in leaf checkins to be selected, but files may be edited via "
2070 "non-leaf checkins by passing them as the <code>filename</code> "
2071 "and <code>checkin</code> URL arguments to this page.</li>");
2072 CX("<li>The editor \"stashes\" some number of local edits in "
2073 "one of <code>window.fileStorage</code> or "
2074 "<code>window.sessionStorage</code>, if able, but which storage "
2075 "is unspecified and may differ across environments. When saving "
2076 "or force-reloading a file, stashed edits to that version are "
2077 "discarded.</li>");
2078 CX("</ul>");
2079 }
2080 CX("</div>"/*#fileedit-tab-help*/);
2081
2082 {
2083
--- src/fileedit.c
+++ src/fileedit.c
@@ -13,11 +13,11 @@
13 ** [email protected]
14 ** http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file contains code for the /fileedit page and related bits.
19 */
20 #include "config.h"
21 #include "fileedit.h"
22 #include <assert.h>
23 #include <stdarg.h>
@@ -29,12 +29,14 @@
29 **
30 ** Use CheckinMiniInfo_init() to cleanly initialize one to a known
31 ** valid/empty default state.
32 **
33 ** Memory for all non-const pointer members is owned by the
34 ** CheckinMiniInfo instance, unless explicitly noted otherwise, and is
35 ** freed by CheckinMiniInfo_cleanup(). Similarly, each instance owns
36 ** any memory for its own Blob members, but NOT for its pointers to
37 ** blobs.
38 */
39 struct CheckinMiniInfo {
40 Manifest * pParent; /* parent checkin. Memory is owned by this
41 object. */
42 char *zParentUuid; /* Full UUID of pParent */
@@ -67,10 +69,13 @@
69
70 /*
71 ** CheckinMiniInfo::flags values.
72 */
73 enum fossil_cimini_flags {
74 /*
75 ** Must have a value of 0. All other flags have unspecified values.
76 */
77 CIMINI_NONE = 0,
78 /*
79 ** Tells checkin_mini() to use dry-run mode.
80 */
81 CIMINI_DRY_RUN = 1,
@@ -118,27 +123,27 @@
123 */
124 CIMINI_PREFER_DELTA = 1<<8,
125 /*
126 ** A "stronger hint" to checkin_mini() to prefer creation of a delta
127 ** manifest if it at all can. It will decide not to only if creation
128 ** of a delta is not a realistic option or if it's forbitted by the
129 ** forbid-delta-manifests repo config option. For this to work, it
130 ** must be set together with the CIMINI_PREFER_DELTA flag, but the two
131 ** cannot be combined in this enum.
132 **
133 ** This option is ONLY INTENDED FOR TESTING, used in bypassing
134 ** heuristics which may otherwise disable generation of a delta on the
135 ** grounds of efficiency (e.g. not generating a delta if the parent
136 ** non-delta only has a few F-cards).
 
 
137 */
138 CIMINI_STRONGLY_PREFER_DELTA = 1<<9,
139 /*
140 ** Tells checkin_mini() to permit the addition of a new file. Normally
141 ** this is disabled because there are hypothetically many cases where
142 ** it could cause the inadvertent addition of a new file when an
143 ** update to an existing was intended, as a side-effect of name-case
144 ** differences.
145 */
146 CIMINI_ALLOW_NEW_FILE = 1<<10
147 };
148
149 /*
@@ -213,10 +218,11 @@
218 }else{
219 /* File was removed from parent delta. */
220 blob_appendf(pOut, "F %F\n", p->zName);
221 }
222 }
223
224 /*
225 ** Handles the F-card parts for create_manifest_mini().
226 **
227 ** If asDelta is true, F-cards will be handled as for a delta
228 ** manifest, and the caller MUST have added a B-card to pOut before
@@ -310,18 +316,18 @@
316 }
317
318 /*
319 ** Creates a manifest file, written to pOut, from the state in the
320 ** fully-populated and semantically valid pCI argument. pCI is not
321 ** *semantically* modified by this routine but cannot be const because
322 ** blob_str() may need to NUL-terminate any given blob.
323 **
324 ** Returns true on success. On error, returns 0 and, if pErr is not
325 ** NULL, writes an error message there.
326 **
327 ** Intended only to be called via checkin_mini() or routines which
328 ** have already completely vetted pCI for semantic validity.
329 */
330 static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI,
331 Blob * pErr){
332 Blob zCard = empty_blob; /* Z-card checksum */
333 int asDelta = 0;
@@ -384,19 +390,18 @@
390 return 1;
391 #undef mf_err
392 }
393
394 /*
 
 
395 ** A so-called "single-file/mini/web checkin" is a slimmed-down form
396 ** of the checkin command which accepts only a single file and is
397 ** intended to accept edits to a file via the web interface or from
398 ** the CLI from outside of a checkout.
399 **
400 ** Being fully non-interactive is a requirement for this function,
401 ** thus it cannot perform autosync or similar activities (which
402 ** includes checking for repo locks).
403 **
404 ** This routine uses the state from the given fully-populated pCI
405 ** argument to add pCI->fileContent to the database, and create and
406 ** save a manifest for that change. Ownership of pCI and its contents
407 ** are unchanged.
@@ -417,19 +422,25 @@
422 ** style differs from its previous version, it is converted to the
423 ** same EOL style as the previous version. If this is done, the
424 ** pCI->fileHash is re-computed. Note that only pCI->fileContent,
425 ** not the original file, is affected by the conversion.
426 **
427 ** - Else if one of the CIMINI_CONVERT_EOL_WINDOWS or
428 ** CIMINI_CONVERT_EOL_UNIX flags are set, pCI->fileContent is
429 ** converted, if needed, to the corresponding EOL style.
430 **
431 ** - If EOL conversion takes place, pCI->fileHash is re-calculated.
432 **
433 ** - If pCI->fileHash is empty, this routine populates it with the
434 ** repository's preferred hash algorithm (after any EOL conversion).
435 **
436 ** - pCI->comment may be converted to Unix-style newlines.
437 **
438 ** pCI's ownership is not modified.
439 **
440 ** This function validates pCI's state and fails if any validation
441 ** fails.
442 **
443 ** On error, returns false (0) and, if pErr is not NULL, writes a
444 ** diagnostic message there.
445 **
446 ** Returns true on success. If pRid is not NULL, the RID of the
@@ -521,11 +532,12 @@
532 }
533 }
534 /* Potential TODOs include:
535 **
536 ** - Commit allows an empty checkin only with a flag, but we
537 ** currently disallow an empty checkin entirely. Conform with
538 ** commit?
539 **
540 ** Non-TODOs:
541 **
542 ** - Check for a commit lock would require auto-sync, which this
543 ** code cannot do if it's going to be run via a web page.
@@ -631,11 +643,11 @@
643 ** that we have to delay this check until after the potentially
644 ** expensive EOL conversion. */
645 assert(blob_size(&pCI->fileHash));
646 if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash))
647 && manifest_file_mperm(zFilePrev)==pCI->filePerm){
648 ci_err((pErr,"File is unchanged. Not committing."));
649 }
650 }
651 #if 1
652 /* Do we really want to normalize comment EOLs? Web-posting will
653 ** submit them in CRLF or LF format, depending on how exactly the
@@ -1185,10 +1197,17 @@
1197 **
1198 ** User must have Write access to use this page.
1199 **
1200 ** Responds with the raw content of the given page. On error it
1201 ** produces a JSON response as documented for fileedit_ajax_error().
1202 **
1203 ** Extra response headers:
1204 **
1205 ** x-fileedit-file-perm: empty or "x" or "l", representing PERM_REG,
1206 ** PERM_EXE, or PERM_LINK, respectively.
1207 **
1208 ** x-fileedit-checkin-branch: branch name for the passed-in checkin.
1209 */
1210 static void fileedit_ajax_content(void){
1211 const char * zFilename = 0;
1212 const char * zRev = 0;
1213 int vid, frid;
@@ -1216,10 +1235,17 @@
1235 char * zFuuid = fileedit_file_uuid(zFilename, vid, &fperm);
1236 const char * zPerm = mfile_permint_mstring(fperm);
1237 assert(zFuuid);
1238 cgi_printf_header("x-fileedit-file-perm:%s\r\n", zPerm);
1239 fossil_free(zFuuid);
1240 }
1241 { /* Send branch name via response header for UI usability reasons */
1242 char * zBranch = branch_of_rid(vid);
1243 if(zBranch!=0 && zBranch[0]!=0){
1244 cgi_printf_header("x-fileedit-checkin-branch: %s\r\n", zBranch);
1245 }
1246 fossil_free(zBranch);
1247 }
1248 cgi_set_content_type(zMime);
1249 cgi_set_content(&content);
1250 }
1251
@@ -1243,10 +1269,18 @@
1269 **
1270 ** User must have Write access to use this page.
1271 **
1272 ** Responds with the HTML content of the preview. On error it produces
1273 ** a JSON response as documented for fileedit_ajax_error().
1274 **
1275 ** Extra response headers:
1276 **
1277 ** x-fileedit-render-mode: string representing the rendering mode
1278 ** which was really used (which will differ from the requested mode
1279 ** only if mode 0 (guess) was requested). The names are documented
1280 ** below in code and match those in the emitted JS object
1281 ** fossil.page.previewModes.
1282 */
1283 static void fileedit_ajax_preview(void){
1284 const char * zFilename = 0;
1285 const char * zContent = P("content");
1286 int renderMode = atoi(PD("render_mode","0"));
@@ -1567,12 +1601,12 @@
1601 if(i++){
1602 CX(",");
1603 }
1604 CX("{");
1605 CX("\"checkin\":%!j,", db_column_text(&q, 1));
1606 CX("\"branch\":%!j,", db_column_text(&q, 7));
1607 CX("\"timestamp\":%!j", db_column_text(&q, 2));
1608 CX("}");
1609 }
1610 CX("]");
1611 db_finalize(&q);
1612 }else{
@@ -1586,28 +1620,33 @@
1620 ** Required query parameters:
1621 **
1622 ** filename=FILENAME
1623 ** checkin=Parent checkin UUID
1624 ** content=text
1625 ** comment=non-empty text
1626 **
1627 ** Optional query parameters:
1628 **
1629 ** comment_mimetype=text (NOT currently honored)
1630 **
1631 ** dry_run=int (1 or 0)
1632 **
1633 ** include_manifest=int (1 or 0), whether to include
1634 ** the generated manifest in the response.
1635 **
1636 **
1637 ** User must have Write permissions to use this page.
1638 **
1639 ** Responds with JSON (with some state repeated
1640 ** from the input in order to avoid certain race conditions
1641 ** client-side):
1642 **
1643 ** {
1644 ** checkin: newUUID,
1645 ** filename: theFilename,
1646 ** mimetype: string,
1647 ** branch: name of the checkin's branch,
1648 ** isExe: bool,
1649 ** dryRun: bool,
1650 ** manifest: text of manifest,
1651 ** }
1652 **
@@ -1620,10 +1659,11 @@
1659 CheckinMiniInfo cimi; /* checkin state */
1660 int rc; /* generic result code */
1661 int newVid = 0; /* new version's RID */
1662 char * zNewUuid = 0; /* newVid's UUID */
1663 char const * zMimetype;
1664 char * zBranch = 0;
1665
1666 if(!fileedit_ajax_boostrap()){
1667 return;
1668 }
1669 db_begin_transaction();
@@ -1635,11 +1675,13 @@
1675 }
1676 if(blob_size(&cimi.comment)==0){
1677 fileedit_ajax_error(400,"Empty checkin comment is not permitted.");
1678 goto end_cleanup;
1679 }
1680 if(0!=atoi(PD("include_manifest","0"))){
1681 cimi.pMfOut = &manifest;
1682 }
1683 checkin_mini(&cimi, &newVid, &err);
1684 if(blob_size(&err)){
1685 fileedit_ajax_error(500,"%b",&err);
1686 goto end_cleanup;
1687 }
@@ -1652,13 +1694,20 @@
1694 CX("\"isExe\": %s,", cimi.filePerm==PERM_EXE ? "true" : "false");
1695 zMimetype = mimetype_from_name(cimi.zFilename);
1696 if(zMimetype!=0){
1697 CX("\"mimetype\": %!j,", zMimetype);
1698 }
1699 zBranch = branch_of_rid(newVid);
1700 if(zBranch!=0){
1701 CX("\"branch\": %!j,", zBranch);
1702 fossil_free(zBranch);
1703 }
1704 CX("\"dryRun\": %s",
1705 (CIMINI_DRY_RUN & cimi.flags) ? "true" : "false");
1706 if(blob_size(&manifest)>0){
1707 CX(",\"manifest\": %!j", blob_str(&manifest));
1708 }
1709 CX("}");
1710 db_end_transaction(0/*noting that dry-run mode will have already
1711 ** set this to rollback mode. */);
1712 end_cleanup:
1713 fossil_free(zNewUuid);
@@ -1668,22 +1717,27 @@
1717 }
1718
1719 /*
1720 ** WEBPAGE: fileedit
1721 **
1722 ** Enables the online editing and committing of individual text files.
1723 ** Requires that the user have Write permissions.
1724 **
1725 ** Optional query parameters:
1726 **
1727 ** filename=FILENAME Repo-relative path to the file.
1728 ** checkin=VERSION Checkin version, using any unambiguous
1729 ** supported symbolic version name.
1730 **
1731 ** Internal-use parameters:
1732 **
1733 ** ajax=string The name of a page-specific AJAX operation.
1734 **
1735 ** Which additional parameters are used by each distinct ajax value is
1736 ** an internal implementation detail and may change with any given
1737 ** build of this code. An unknown ajax value triggers an error, as
1738 ** documented for fileedit_ajax_error().
1739 */
1740 void fileedit_page(void){
1741 const char * zFilename = 0; /* filename. We'll accept 'name'
1742 because that param is handled
1743 specially by the core. */
@@ -1834,15 +1888,17 @@
1888 "data-tab-parent='fileedit-tabs' "
1889 "data-tab-label='Preview'"
1890 ">");
1891 CX("<div class='fileedit-options flex-container flex-row'>");
1892 CX("<button id='btn-preview-refresh' "
1893 "data-f-preview-from='fileContent' "
1894 /* ^^^ fossil.page[methodName]() OR text source elem ID,
1895 ** but we need a method in order to support clients swapping out
1896 ** the text editor with their own. */
1897 "data-f-preview-via='_postPreview' "
1898 /* ^^^ fossil.page[methodName](content, callback) */
1899 "data-f-preview-to='#fileedit-tab-preview-wrapper' "
1900 /* ^^^ dest elem ID */
1901 ">Refresh</button>");
1902 /* Toggle auto-update of preview when the Preview tab is selected. */
1903 style_labeled_checkbox("cb-preview-autoupdate",
1904 NULL,
@@ -1988,10 +2044,18 @@
2044 ? 2 : 0),
2045 "Inherit", 0,
2046 "Unix", 1,
2047 "Windows", 2,
2048 NULL);
2049 style_labeled_checkbox("cb-include-manifest",
2050 "include_manifest",
2051 "Response manifest?", "1",
2052 0,
2053 "Include the manifest in the response? "
2054 "It's generally only useful for debug "
2055 "purposes.");
2056
2057 CX("</div>"/*checkboxes*/);
2058 }
2059
2060 { /******* Commit comment, button, and result manifest *******/
2061 CX("<fieldset class='fileedit-options commit-message'>"
@@ -2067,16 +2131,16 @@
2131 "with many files.</li>");
2132 CX("<li>The file selector allows, for usability's sake, only files "
2133 "in leaf checkins to be selected, but files may be edited via "
2134 "non-leaf checkins by passing them as the <code>filename</code> "
2135 "and <code>checkin</code> URL arguments to this page.</li>");
2136 CX("<li>The editor stores some number of local edits in one of "
2137 "<code>window.fileStorage</code> or "
2138 "<code>window.sessionStorage</code>, if able, but which storage "
2139 "is unspecified and may differ across environments. When "
2140 "committing or force-reloading a file, stashed edits to that "
2141 "version are discarded.</li>");
2142 CX("</ul>");
2143 }
2144 CX("</div>"/*#fileedit-tab-help*/);
2145
2146 {
2147
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -181,54 +181,59 @@
181181
- A CSS selector
182182
183183
Each element in the collection must have the following data
184184
attributes:
185185
186
- - data-f-preview-from: the DOM element id of the text source
187
- element. It must support .value to get the content.
186
+ - data-f-preview-from: is either a DOM element id, WITH a leading
187
+ '#' prefix, or the name of a method (see below). If it's an ID,
188
+ the DOM element must support .value to get the content.
188189
189190
- data-f-preview-to: the DOM element id of the target "previewer"
190
- element.
191
+ element, WITH a leading '#', or the name of a method (see below).
191192
192193
- data-f-preview-via: the name of a method (see below).
193194
194195
- OPTIONAL data-f-preview-as-text: a numeric value. Explained below.
195196
196197
Each element gets a click handler added to it which does the
197198
following:
198199
199
- 1) Reads the content from its data-f-preview-from element.
200
+ 1) Reads the content from its data-f-preview-from element or, if
201
+ that property refers to a method, calls the method without
202
+ arguments and uses its result as the content.
200203
201204
2) Passes the content to
202205
methodNamespace[f-data-post-via](content,callback). f-data-post-via
203206
is responsible for submitting the preview HTTP request, including
204207
any parameters the request might require. When the response
205208
arrives, it must pass the content of the response to its 2nd
206209
argument, an auto-generated callback installed by this mechanism
207210
which...
208211
209
- 3) Assigns the response text to the data-f-preview-to element. If
210
- data-f-preview-as-text is '0' (the default) then the content
211
- is assigned to the target element's innerHTML property, else
212
- it is assigned to the element's textContent property.
213
-
212
+ 3) Assigns the response text to the data-f-preview-to element or
213
+ passes it to the function methodNamespace[f-data-preview-to](content), as
214
+ appropriate. If data-f-preview-to is a DOM element and
215
+ data-f-preview-as-text is '0' (the default) then the content is
216
+ assigned to the target element's innerHTML property, else it is
217
+ assigned to the element's textContent property.
214218
215219
The methodNamespace (2nd argument) defaults to fossil.page, and
216
- data-f-preview-via must be a single method name, not a
217
- property-access-style string. e.g. "myPreview" is legal but
220
+ any method-name data properties, e.g. data-f-preview-via and
221
+ potentially data-f-preview-from/to, must be a single method name,
222
+ not a property-access-style string. e.g. "myPreview" is legal but
218223
"foo.myPreview" is not (unless, of course, the method is actually
219224
named "foo.myPreview" (which is legal but would be
220225
unconventional)).
221226
222227
An example...
223228
224229
First an input button:
225230
226231
<button id='test-preview-connector'
227
- data-f-preview-from='fileedit-content-editor' // elem ID
232
+ data-f-preview-from='#fileedit-content-editor' // elem ID or method name
228233
data-f-preview-via='myPreview' // method name
229
- data-f-preview-to='fileedit-tab-preview-wrapper' // elem ID
234
+ data-f-preview-to='#fileedit-tab-preview-wrapper' // elem ID or method name
230235
>Preview update</button>
231236
232237
And a sample data-f-preview-via method:
233238
234239
fossil.page.myPreview = function(content,callback){
@@ -264,17 +269,24 @@
264269
methodNamespace = F.page;
265270
}
266271
selector.forEach(function(e){
267272
e.addEventListener(
268273
'click', function(r){
269
- const eTo = document.querySelector('#'+e.dataset.fPreviewTo),
270
- eFrom = document.querySelector('#'+e.dataset.fPreviewFrom),
274
+ const eTo = '#'===e.dataset.fPreviewTo[0]
275
+ ? document.querySelector(e.dataset.fPreviewTo)
276
+ : methodNamespace[e.dataset.fPreviewTo],
277
+ eFrom = '#'===e.dataset.fPreviewFrom[0]
278
+ ? document.querySelector(e.dataset.fPreviewFrom)
279
+ : methodNamespace[e.dataset.fPreviewFrom],
271280
asText = +(e.dataset.fPreviewAsText || 0);
272281
eTo.textContent = "Fetching preview...";
273282
methodNamespace[e.dataset.fPreviewVia](
274
- eFrom.value,
275
- (r)=>eTo[asText ? 'textContent' : 'innerHTML'] = r||''
283
+ (eFrom instanceof Function ? eFrom() : eFrom.value),
284
+ (r)=>{
285
+ if(eTo instanceof Function) eTo(r||'');
286
+ else eTo[asText ? 'textContent' : 'innerHTML'] = r||'';
287
+ }
276288
);
277289
}, false
278290
);
279291
});
280292
return this;
281293
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -181,54 +181,59 @@
181 - A CSS selector
182
183 Each element in the collection must have the following data
184 attributes:
185
186 - data-f-preview-from: the DOM element id of the text source
187 element. It must support .value to get the content.
 
188
189 - data-f-preview-to: the DOM element id of the target "previewer"
190 element.
191
192 - data-f-preview-via: the name of a method (see below).
193
194 - OPTIONAL data-f-preview-as-text: a numeric value. Explained below.
195
196 Each element gets a click handler added to it which does the
197 following:
198
199 1) Reads the content from its data-f-preview-from element.
 
 
200
201 2) Passes the content to
202 methodNamespace[f-data-post-via](content,callback). f-data-post-via
203 is responsible for submitting the preview HTTP request, including
204 any parameters the request might require. When the response
205 arrives, it must pass the content of the response to its 2nd
206 argument, an auto-generated callback installed by this mechanism
207 which...
208
209 3) Assigns the response text to the data-f-preview-to element. If
210 data-f-preview-as-text is '0' (the default) then the content
211 is assigned to the target element's innerHTML property, else
212 it is assigned to the element's textContent property.
213
 
214
215 The methodNamespace (2nd argument) defaults to fossil.page, and
216 data-f-preview-via must be a single method name, not a
217 property-access-style string. e.g. "myPreview" is legal but
 
218 "foo.myPreview" is not (unless, of course, the method is actually
219 named "foo.myPreview" (which is legal but would be
220 unconventional)).
221
222 An example...
223
224 First an input button:
225
226 <button id='test-preview-connector'
227 data-f-preview-from='fileedit-content-editor' // elem ID
228 data-f-preview-via='myPreview' // method name
229 data-f-preview-to='fileedit-tab-preview-wrapper' // elem ID
230 >Preview update</button>
231
232 And a sample data-f-preview-via method:
233
234 fossil.page.myPreview = function(content,callback){
@@ -264,17 +269,24 @@
264 methodNamespace = F.page;
265 }
266 selector.forEach(function(e){
267 e.addEventListener(
268 'click', function(r){
269 const eTo = document.querySelector('#'+e.dataset.fPreviewTo),
270 eFrom = document.querySelector('#'+e.dataset.fPreviewFrom),
 
 
 
 
271 asText = +(e.dataset.fPreviewAsText || 0);
272 eTo.textContent = "Fetching preview...";
273 methodNamespace[e.dataset.fPreviewVia](
274 eFrom.value,
275 (r)=>eTo[asText ? 'textContent' : 'innerHTML'] = r||''
 
 
 
276 );
277 }, false
278 );
279 });
280 return this;
281
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -181,54 +181,59 @@
181 - A CSS selector
182
183 Each element in the collection must have the following data
184 attributes:
185
186 - data-f-preview-from: is either a DOM element id, WITH a leading
187 '#' prefix, or the name of a method (see below). If it's an ID,
188 the DOM element must support .value to get the content.
189
190 - data-f-preview-to: the DOM element id of the target "previewer"
191 element, WITH a leading '#', or the name of a method (see below).
192
193 - data-f-preview-via: the name of a method (see below).
194
195 - OPTIONAL data-f-preview-as-text: a numeric value. Explained below.
196
197 Each element gets a click handler added to it which does the
198 following:
199
200 1) Reads the content from its data-f-preview-from element or, if
201 that property refers to a method, calls the method without
202 arguments and uses its result as the content.
203
204 2) Passes the content to
205 methodNamespace[f-data-post-via](content,callback). f-data-post-via
206 is responsible for submitting the preview HTTP request, including
207 any parameters the request might require. When the response
208 arrives, it must pass the content of the response to its 2nd
209 argument, an auto-generated callback installed by this mechanism
210 which...
211
212 3) Assigns the response text to the data-f-preview-to element or
213 passes it to the function methodNamespace[f-data-preview-to](content), as
214 appropriate. If data-f-preview-to is a DOM element and
215 data-f-preview-as-text is '0' (the default) then the content is
216 assigned to the target element's innerHTML property, else it is
217 assigned to the element's textContent property.
218
219 The methodNamespace (2nd argument) defaults to fossil.page, and
220 any method-name data properties, e.g. data-f-preview-via and
221 potentially data-f-preview-from/to, must be a single method name,
222 not a property-access-style string. e.g. "myPreview" is legal but
223 "foo.myPreview" is not (unless, of course, the method is actually
224 named "foo.myPreview" (which is legal but would be
225 unconventional)).
226
227 An example...
228
229 First an input button:
230
231 <button id='test-preview-connector'
232 data-f-preview-from='#fileedit-content-editor' // elem ID or method name
233 data-f-preview-via='myPreview' // method name
234 data-f-preview-to='#fileedit-tab-preview-wrapper' // elem ID or method name
235 >Preview update</button>
236
237 And a sample data-f-preview-via method:
238
239 fossil.page.myPreview = function(content,callback){
@@ -264,17 +269,24 @@
269 methodNamespace = F.page;
270 }
271 selector.forEach(function(e){
272 e.addEventListener(
273 'click', function(r){
274 const eTo = '#'===e.dataset.fPreviewTo[0]
275 ? document.querySelector(e.dataset.fPreviewTo)
276 : methodNamespace[e.dataset.fPreviewTo],
277 eFrom = '#'===e.dataset.fPreviewFrom[0]
278 ? document.querySelector(e.dataset.fPreviewFrom)
279 : methodNamespace[e.dataset.fPreviewFrom],
280 asText = +(e.dataset.fPreviewAsText || 0);
281 eTo.textContent = "Fetching preview...";
282 methodNamespace[e.dataset.fPreviewVia](
283 (eFrom instanceof Function ? eFrom() : eFrom.value),
284 (r)=>{
285 if(eTo instanceof Function) eTo(r||'');
286 else eTo[asText ? 'textContent' : 'innerHTML'] = r||'';
287 }
288 );
289 }, false
290 );
291 });
292 return this;
293
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -1,27 +1,36 @@
11
(function(F/*the fossil object*/){
22
"use strict";
33
/**
4
- Code for the /filepage app. Requires that the fossil JS
5
- bootstrapping is complete and that several fossil JS APIs have
6
- been installed: fossil.fetch, fossil.dom, fossil.tabs,
7
- fossil.storage, fossil.confirmer.
4
+ Client-side implementation of the /filepage app. Requires that
5
+ the fossil JS bootstrapping is complete and that several fossil
6
+ JS APIs have been installed: fossil.fetch, fossil.dom,
7
+ fossil.tabs, fossil.storage, fossil.confirmer.
88
9
- Custom events, handled via fossil.page.addEventListener():
9
+ Custom events which can be listened for via
10
+ fossil.page.addEventListener():
1011
1112
- Event 'fileedit-file-loaded': passes on information when it
12
- loads a file, in the form of an object:
13
+ loads a file (whether from the network or its internal local-edit
14
+ cache), in the form of an "finfo" object:
1315
1416
{
15
- filename: string,
16
- checkin: UUID string,
17
- isExe: bool,
18
- mimetype: mimetype string, as determined by the fossil server.
17
+ filename: string,
18
+ checkin: UUID string,
19
+ branch: branch name of UUID,
20
+ isExe: bool, true only for executable files
21
+ mimetype: mimetype string, as determined by the fossil server.
1922
}
23
+
24
+ The internal docs and code frequently use the term "finfo", and such
25
+ references refer to an object with that form.
2026
2127
The fossil.page.fileContent() method gets or sets the current file
2228
content for the page.
29
+
30
+ - Event 'fileedit-committed': is fired when a commit completes,
31
+ passing on the same info as fileedit-file-loaded.
2332
2433
- Event 'fileedit-content-replaced': when the editor's content is
2534
replaced, as opposed to it being edited via user
2635
interaction. This normally happens via selecting a file to
2736
load. The event detail is the fossil.page object, not the current
@@ -445,14 +454,17 @@
445454
);
446455
const sel = this.e.select = D.select();
447456
const btnClear = this.e.btnClear
448457
= D.addClass(D.button("Clear"),'hidden');
449458
D.append(flow, wrapper);
450
- D.append(wrapper, "Local edits ("+(F.storage.storageImplName())+"):",
459
+ D.append(wrapper, "Local edits (",
460
+ D.append(D.code(),
461
+ F.storage.storageImplName()),
462
+ "):",
451463
sel, btnClear);
452464
D.attr(wrapper, "title", [
453
- 'Locally "stashed" edits. Timestamps are the last local edit time.',
465
+ 'Locally-edited files. Timestamps are the last local edit time.',
454466
'Only the',P.config.defaultMaxStashSize,'most recent checkin/file',
455467
'combinations are retained.',
456468
'Committing or reloading a file removes it from this stash.'
457469
].join(' '));
458470
D.option(D.disable(sel), "(empty)");
@@ -552,14 +564,15 @@
552564
553565
F.onPageLoad(function() {
554566
P.base = {tag: E('base')};
555567
P.base.originalHref = P.base.tag.href;
556568
P.tabs = new fossil.TabManager('#fileedit-tabs');
557
- P.e = {
569
+ P.e = { /* various DOM elements we work with... */
558570
taEditor: E('#fileedit-content-editor'),
559571
taCommentSmall: E('#fileedit-comment'),
560572
taCommentBig: E('#fileedit-comment-big'),
573
+ taComment: undefined/*gets set to one of taComment{Big,Small}*/,
561574
ajaxContentTarget: E('#ajax-target'),
562575
btnCommit: E("#fileedit-btn-commit"),
563576
btnReload: E("#fileedit-tab-content > .fileedit-options > "
564577
+"button.fileedit-content-reload"),
565578
selectPreviewMode: E('#select-preview-mode select'),
@@ -569,12 +582,14 @@
569582
selectFontSizeWrap: E('#select-font-size'),
570583
selectDiffWS: E('select[name=diff_ws]'),
571584
cbLineNumbersWrap: E('#cb-line-numbers'),
572585
cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'),
573586
previewTarget: E('#fileedit-tab-preview-wrapper'),
587
+ manifestTarget: E('#fileedit-manifest'),
574588
diffTarget: E('#fileedit-tab-diff-wrapper'),
575589
cbIsExe: E('input[type=checkbox][name=exec_bit]'),
590
+ cbManifest: E('input[type=checkbox][name=include_manifest]'),
576591
fsFileVersionDetails: E('#file-version-details'),
577592
tabs:{
578593
content: E('#fileedit-tab-content'),
579594
preview: E('#fileedit-tab-preview'),
580595
diff: E('#fileedit-tab-diff'),
@@ -691,21 +706,23 @@
691706
// Force UI update
692707
new Event('change',{target:selectFontSize})
693708
);
694709
}
695710
696
- if(0){ // only for testing
697
- P.addEventListener(
698
- 'fileedit-file-loaded',
699
- (e)=>console.debug('fileedit-file-loaded ==>',e)
700
- );
701
- }
702
-
703
- P.addEventListener(
704
- // Clear diff/preview when new content is loaded/set
705
- 'fileedit-content-replaced',
706
- ()=>D.clearElement(P.e.diffTarget, P.e.previewTarget)
711
+ P.addEventListener(
712
+ // Clear certain views when new content is loaded/set
713
+ 'fileedit-content-replaced',
714
+ ()=>D.clearElement(P.e.diffTarget, P.e.previewTarget, P.e.manifestTarget)
715
+ );
716
+ P.addEventListener(
717
+ // Clear certain views after a non-dry-run commit
718
+ 'fileedit-committed',
719
+ (e)=>{
720
+ if(!e.detail.dryRun){
721
+ D.clearElement(P.e.diffTarget, P.e.previewTarget);
722
+ }
723
+ }
707724
);
708725
709726
P.fileSelectWidget.init();
710727
P.stashWidget.init(P.e.tabs.content.lastElementChild);
711728
}/*F.onPageLoad()*/);
@@ -909,10 +926,22 @@
909926
it (emitting an error message if no file is loaded).
910927
911928
Returns this object, noting that the load is async. After loading
912929
it triggers a 'fileedit-file-loaded' event, passing it
913930
this.finfo.
931
+
932
+ If a locally-edited copy of the given file/rev is found, that
933
+ copy is used instead of one fetched from the server, but it is
934
+ still treated as a load event.
935
+
936
+ Alternate call forms:
937
+
938
+ - no arguments: re-loads from this.finfo.
939
+
940
+ - 1 argument: assumed to be an finfo-style object. Must have at
941
+ least {filename, checkin} properties, but need not have other
942
+ finfo state.
914943
*/
915944
P.loadFile = function(file,rev){
916945
if(0===arguments.length){
917946
/* Reload from this.finfo */
918947
if(!affirmHasFile()) return this;
@@ -928,10 +957,11 @@
928957
const onload = (r,headers)=>{
929958
delete self.finfo;
930959
self.updateVersion({
931960
filename: file,
932961
checkin: rev,
962
+ branch: headers['x-fileedit-checkin-branch'],
933963
isExe: ('x'===headers['x-fileedit-file-perm']),
934964
mimetype: headers['content-type'].split(';').shift()
935965
});
936966
self.tabs.switchToTab(self.e.tabs.content);
937967
self.e.cbIsExe.checked = self.finfo.isExe;
@@ -943,11 +973,12 @@
943973
if(stashFinfo){ // fake a response from the stash...
944974
this.finfo = stashFinfo;
945975
this.e.cbIsExe.checked = !!stashFinfo.isExe;
946976
onload(this.contentFromStash()||'',{
947977
'x-fileedit-file-perm': stashFinfo.isExe ? 'x' : undefined,
948
- 'content-type': stashFinfo.mimetype
978
+ 'content-type': stashFinfo.mimetype,
979
+ 'x-fileedit-checkin-branch': stashFinfo.branch
949980
});
950981
F.message("Fetched from the local-edit stash:",
951982
F.hashDigits(stashFinfo.checkin),
952983
stashFinfo.filename);
953984
return this;
@@ -958,11 +989,14 @@
958989
urlParams: {
959990
ajax: 'content',
960991
filename:file,
961992
checkin:rev
962993
},
963
- responseHeaders: ['x-fileedit-file-perm', 'content-type'],
994
+ responseHeaders: [
995
+ 'x-fileedit-file-perm',
996
+ 'x-fileedit-checkin-branch',
997
+ 'content-type'],
964998
onload:(r,headers)=>{
965999
onload(r,headers);
9661000
F.message('Loaded content for',
9671001
F.hashDigits(self.finfo.checkin),
9681002
self.finfo.filename);
@@ -1076,42 +1110,41 @@
10761110
*/
10771111
P.commit = function f(){
10781112
if(!affirmHasFile()) return this;
10791113
const self = this;
10801114
const content = this.fileContent(),
1081
- target = document.querySelector('#fileedit-manifest'),
1115
+ target = D.clearElement(P.e.manifestTarget),
10821116
cbDryRun = E('[name=dry_run]'),
10831117
isDryRun = cbDryRun.checked,
10841118
filename = this.finfo.filename;
10851119
if(!f.onload){
10861120
f.onload = function(c){
10871121
const oldFinfo = JSON.parse(JSON.stringify(self.finfo))
1088
- target.innerHTML = [
1089
- "<h3>Manifest",
1090
- (c.dryRun?" (dry run)":""),
1091
- ": ", F.hashDigits(c.checkin),"</h3>",
1092
- "<code class='fileedit-manifest'>",
1093
- c.manifest,
1094
- "</code></pre>"
1095
- ].join('');
1122
+ if(c.manifest){
1123
+ target.innerHTML = [
1124
+ "<h3>Manifest",
1125
+ (c.dryRun?" (dry run)":""),
1126
+ ": ", F.hashDigits(c.checkin),"</h3>",
1127
+ "<code class='fileedit-manifest'>",
1128
+ c.manifest,
1129
+ "</code></pre>"
1130
+ ].join('');
1131
+ delete c.manifest/*so we don't stash this with finfo*/;
1132
+ }
10961133
const msg = [
10971134
'Committed',
10981135
c.dryRun ? '(dry run)' : '',
10991136
'[', F.hashDigits(c.checkin) ,'].'
11001137
];
11011138
if(!c.dryRun){
1102
- if(0){
1103
- msg.push('Re-activating dry-run mode.');
1104
- cbDryRun.checked = true;
1105
- }
11061139
self.unstashContent(oldFinfo);
1107
- delete c.manifest;
11081140
self.finfo = c;
11091141
self.e.taComment.value = '';
11101142
self.updateVersion();
11111143
self.fileSelectWidget.loadLeaves();
11121144
}
1145
+ self.dispatchEvent('fileedit-committed', c);
11131146
F.message.apply(F, msg);
11141147
self.tabs.switchToTab(self.e.tabs.commit);
11151148
};
11161149
}
11171150
const fd = new FormData();
@@ -1133,10 +1166,11 @@
11331166
/* Checkboxes: */
11341167
['allow_fork',
11351168
'allow_older',
11361169
'exec_bit',
11371170
'allow_merge_conflict',
1171
+ 'include_manifest',
11381172
'prefer_delta'
11391173
].forEach(function(name){
11401174
var e = E('[name='+name+']');
11411175
if(e){
11421176
fd.append(name, e.checked ? 1 : 0);
11431177
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -1,27 +1,36 @@
1 (function(F/*the fossil object*/){
2 "use strict";
3 /**
4 Code for the /filepage app. Requires that the fossil JS
5 bootstrapping is complete and that several fossil JS APIs have
6 been installed: fossil.fetch, fossil.dom, fossil.tabs,
7 fossil.storage, fossil.confirmer.
8
9 Custom events, handled via fossil.page.addEventListener():
 
10
11 - Event 'fileedit-file-loaded': passes on information when it
12 loads a file, in the form of an object:
 
13
14 {
15 filename: string,
16 checkin: UUID string,
17 isExe: bool,
18 mimetype: mimetype string, as determined by the fossil server.
 
19 }
 
 
 
20
21 The fossil.page.fileContent() method gets or sets the current file
22 content for the page.
 
 
 
23
24 - Event 'fileedit-content-replaced': when the editor's content is
25 replaced, as opposed to it being edited via user
26 interaction. This normally happens via selecting a file to
27 load. The event detail is the fossil.page object, not the current
@@ -445,14 +454,17 @@
445 );
446 const sel = this.e.select = D.select();
447 const btnClear = this.e.btnClear
448 = D.addClass(D.button("Clear"),'hidden');
449 D.append(flow, wrapper);
450 D.append(wrapper, "Local edits ("+(F.storage.storageImplName())+"):",
 
 
 
451 sel, btnClear);
452 D.attr(wrapper, "title", [
453 'Locally "stashed" edits. Timestamps are the last local edit time.',
454 'Only the',P.config.defaultMaxStashSize,'most recent checkin/file',
455 'combinations are retained.',
456 'Committing or reloading a file removes it from this stash.'
457 ].join(' '));
458 D.option(D.disable(sel), "(empty)");
@@ -552,14 +564,15 @@
552
553 F.onPageLoad(function() {
554 P.base = {tag: E('base')};
555 P.base.originalHref = P.base.tag.href;
556 P.tabs = new fossil.TabManager('#fileedit-tabs');
557 P.e = {
558 taEditor: E('#fileedit-content-editor'),
559 taCommentSmall: E('#fileedit-comment'),
560 taCommentBig: E('#fileedit-comment-big'),
 
561 ajaxContentTarget: E('#ajax-target'),
562 btnCommit: E("#fileedit-btn-commit"),
563 btnReload: E("#fileedit-tab-content > .fileedit-options > "
564 +"button.fileedit-content-reload"),
565 selectPreviewMode: E('#select-preview-mode select'),
@@ -569,12 +582,14 @@
569 selectFontSizeWrap: E('#select-font-size'),
570 selectDiffWS: E('select[name=diff_ws]'),
571 cbLineNumbersWrap: E('#cb-line-numbers'),
572 cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'),
573 previewTarget: E('#fileedit-tab-preview-wrapper'),
 
574 diffTarget: E('#fileedit-tab-diff-wrapper'),
575 cbIsExe: E('input[type=checkbox][name=exec_bit]'),
 
576 fsFileVersionDetails: E('#file-version-details'),
577 tabs:{
578 content: E('#fileedit-tab-content'),
579 preview: E('#fileedit-tab-preview'),
580 diff: E('#fileedit-tab-diff'),
@@ -691,21 +706,23 @@
691 // Force UI update
692 new Event('change',{target:selectFontSize})
693 );
694 }
695
696 if(0){ // only for testing
697 P.addEventListener(
698 'fileedit-file-loaded',
699 (e)=>console.debug('fileedit-file-loaded ==>',e)
700 );
701 }
702
703 P.addEventListener(
704 // Clear diff/preview when new content is loaded/set
705 'fileedit-content-replaced',
706 ()=>D.clearElement(P.e.diffTarget, P.e.previewTarget)
 
 
707 );
708
709 P.fileSelectWidget.init();
710 P.stashWidget.init(P.e.tabs.content.lastElementChild);
711 }/*F.onPageLoad()*/);
@@ -909,10 +926,22 @@
909 it (emitting an error message if no file is loaded).
910
911 Returns this object, noting that the load is async. After loading
912 it triggers a 'fileedit-file-loaded' event, passing it
913 this.finfo.
 
 
 
 
 
 
 
 
 
 
 
 
914 */
915 P.loadFile = function(file,rev){
916 if(0===arguments.length){
917 /* Reload from this.finfo */
918 if(!affirmHasFile()) return this;
@@ -928,10 +957,11 @@
928 const onload = (r,headers)=>{
929 delete self.finfo;
930 self.updateVersion({
931 filename: file,
932 checkin: rev,
 
933 isExe: ('x'===headers['x-fileedit-file-perm']),
934 mimetype: headers['content-type'].split(';').shift()
935 });
936 self.tabs.switchToTab(self.e.tabs.content);
937 self.e.cbIsExe.checked = self.finfo.isExe;
@@ -943,11 +973,12 @@
943 if(stashFinfo){ // fake a response from the stash...
944 this.finfo = stashFinfo;
945 this.e.cbIsExe.checked = !!stashFinfo.isExe;
946 onload(this.contentFromStash()||'',{
947 'x-fileedit-file-perm': stashFinfo.isExe ? 'x' : undefined,
948 'content-type': stashFinfo.mimetype
 
949 });
950 F.message("Fetched from the local-edit stash:",
951 F.hashDigits(stashFinfo.checkin),
952 stashFinfo.filename);
953 return this;
@@ -958,11 +989,14 @@
958 urlParams: {
959 ajax: 'content',
960 filename:file,
961 checkin:rev
962 },
963 responseHeaders: ['x-fileedit-file-perm', 'content-type'],
 
 
 
964 onload:(r,headers)=>{
965 onload(r,headers);
966 F.message('Loaded content for',
967 F.hashDigits(self.finfo.checkin),
968 self.finfo.filename);
@@ -1076,42 +1110,41 @@
1076 */
1077 P.commit = function f(){
1078 if(!affirmHasFile()) return this;
1079 const self = this;
1080 const content = this.fileContent(),
1081 target = document.querySelector('#fileedit-manifest'),
1082 cbDryRun = E('[name=dry_run]'),
1083 isDryRun = cbDryRun.checked,
1084 filename = this.finfo.filename;
1085 if(!f.onload){
1086 f.onload = function(c){
1087 const oldFinfo = JSON.parse(JSON.stringify(self.finfo))
1088 target.innerHTML = [
1089 "<h3>Manifest",
1090 (c.dryRun?" (dry run)":""),
1091 ": ", F.hashDigits(c.checkin),"</h3>",
1092 "<code class='fileedit-manifest'>",
1093 c.manifest,
1094 "</code></pre>"
1095 ].join('');
 
 
 
1096 const msg = [
1097 'Committed',
1098 c.dryRun ? '(dry run)' : '',
1099 '[', F.hashDigits(c.checkin) ,'].'
1100 ];
1101 if(!c.dryRun){
1102 if(0){
1103 msg.push('Re-activating dry-run mode.');
1104 cbDryRun.checked = true;
1105 }
1106 self.unstashContent(oldFinfo);
1107 delete c.manifest;
1108 self.finfo = c;
1109 self.e.taComment.value = '';
1110 self.updateVersion();
1111 self.fileSelectWidget.loadLeaves();
1112 }
 
1113 F.message.apply(F, msg);
1114 self.tabs.switchToTab(self.e.tabs.commit);
1115 };
1116 }
1117 const fd = new FormData();
@@ -1133,10 +1166,11 @@
1133 /* Checkboxes: */
1134 ['allow_fork',
1135 'allow_older',
1136 'exec_bit',
1137 'allow_merge_conflict',
 
1138 'prefer_delta'
1139 ].forEach(function(name){
1140 var e = E('[name='+name+']');
1141 if(e){
1142 fd.append(name, e.checked ? 1 : 0);
1143
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -1,27 +1,36 @@
1 (function(F/*the fossil object*/){
2 "use strict";
3 /**
4 Client-side implementation of the /filepage app. Requires that
5 the fossil JS bootstrapping is complete and that several fossil
6 JS APIs have been installed: fossil.fetch, fossil.dom,
7 fossil.tabs, fossil.storage, fossil.confirmer.
8
9 Custom events which can be listened for via
10 fossil.page.addEventListener():
11
12 - Event 'fileedit-file-loaded': passes on information when it
13 loads a file (whether from the network or its internal local-edit
14 cache), in the form of an "finfo" object:
15
16 {
17 filename: string,
18 checkin: UUID string,
19 branch: branch name of UUID,
20 isExe: bool, true only for executable files
21 mimetype: mimetype string, as determined by the fossil server.
22 }
23
24 The internal docs and code frequently use the term "finfo", and such
25 references refer to an object with that form.
26
27 The fossil.page.fileContent() method gets or sets the current file
28 content for the page.
29
30 - Event 'fileedit-committed': is fired when a commit completes,
31 passing on the same info as fileedit-file-loaded.
32
33 - Event 'fileedit-content-replaced': when the editor's content is
34 replaced, as opposed to it being edited via user
35 interaction. This normally happens via selecting a file to
36 load. The event detail is the fossil.page object, not the current
@@ -445,14 +454,17 @@
454 );
455 const sel = this.e.select = D.select();
456 const btnClear = this.e.btnClear
457 = D.addClass(D.button("Clear"),'hidden');
458 D.append(flow, wrapper);
459 D.append(wrapper, "Local edits (",
460 D.append(D.code(),
461 F.storage.storageImplName()),
462 "):",
463 sel, btnClear);
464 D.attr(wrapper, "title", [
465 'Locally-edited files. Timestamps are the last local edit time.',
466 'Only the',P.config.defaultMaxStashSize,'most recent checkin/file',
467 'combinations are retained.',
468 'Committing or reloading a file removes it from this stash.'
469 ].join(' '));
470 D.option(D.disable(sel), "(empty)");
@@ -552,14 +564,15 @@
564
565 F.onPageLoad(function() {
566 P.base = {tag: E('base')};
567 P.base.originalHref = P.base.tag.href;
568 P.tabs = new fossil.TabManager('#fileedit-tabs');
569 P.e = { /* various DOM elements we work with... */
570 taEditor: E('#fileedit-content-editor'),
571 taCommentSmall: E('#fileedit-comment'),
572 taCommentBig: E('#fileedit-comment-big'),
573 taComment: undefined/*gets set to one of taComment{Big,Small}*/,
574 ajaxContentTarget: E('#ajax-target'),
575 btnCommit: E("#fileedit-btn-commit"),
576 btnReload: E("#fileedit-tab-content > .fileedit-options > "
577 +"button.fileedit-content-reload"),
578 selectPreviewMode: E('#select-preview-mode select'),
@@ -569,12 +582,14 @@
582 selectFontSizeWrap: E('#select-font-size'),
583 selectDiffWS: E('select[name=diff_ws]'),
584 cbLineNumbersWrap: E('#cb-line-numbers'),
585 cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'),
586 previewTarget: E('#fileedit-tab-preview-wrapper'),
587 manifestTarget: E('#fileedit-manifest'),
588 diffTarget: E('#fileedit-tab-diff-wrapper'),
589 cbIsExe: E('input[type=checkbox][name=exec_bit]'),
590 cbManifest: E('input[type=checkbox][name=include_manifest]'),
591 fsFileVersionDetails: E('#file-version-details'),
592 tabs:{
593 content: E('#fileedit-tab-content'),
594 preview: E('#fileedit-tab-preview'),
595 diff: E('#fileedit-tab-diff'),
@@ -691,21 +706,23 @@
706 // Force UI update
707 new Event('change',{target:selectFontSize})
708 );
709 }
710
711 P.addEventListener(
712 // Clear certain views when new content is loaded/set
713 'fileedit-content-replaced',
714 ()=>D.clearElement(P.e.diffTarget, P.e.previewTarget, P.e.manifestTarget)
715 );
716 P.addEventListener(
717 // Clear certain views after a non-dry-run commit
718 'fileedit-committed',
719 (e)=>{
720 if(!e.detail.dryRun){
721 D.clearElement(P.e.diffTarget, P.e.previewTarget);
722 }
723 }
724 );
725
726 P.fileSelectWidget.init();
727 P.stashWidget.init(P.e.tabs.content.lastElementChild);
728 }/*F.onPageLoad()*/);
@@ -909,10 +926,22 @@
926 it (emitting an error message if no file is loaded).
927
928 Returns this object, noting that the load is async. After loading
929 it triggers a 'fileedit-file-loaded' event, passing it
930 this.finfo.
931
932 If a locally-edited copy of the given file/rev is found, that
933 copy is used instead of one fetched from the server, but it is
934 still treated as a load event.
935
936 Alternate call forms:
937
938 - no arguments: re-loads from this.finfo.
939
940 - 1 argument: assumed to be an finfo-style object. Must have at
941 least {filename, checkin} properties, but need not have other
942 finfo state.
943 */
944 P.loadFile = function(file,rev){
945 if(0===arguments.length){
946 /* Reload from this.finfo */
947 if(!affirmHasFile()) return this;
@@ -928,10 +957,11 @@
957 const onload = (r,headers)=>{
958 delete self.finfo;
959 self.updateVersion({
960 filename: file,
961 checkin: rev,
962 branch: headers['x-fileedit-checkin-branch'],
963 isExe: ('x'===headers['x-fileedit-file-perm']),
964 mimetype: headers['content-type'].split(';').shift()
965 });
966 self.tabs.switchToTab(self.e.tabs.content);
967 self.e.cbIsExe.checked = self.finfo.isExe;
@@ -943,11 +973,12 @@
973 if(stashFinfo){ // fake a response from the stash...
974 this.finfo = stashFinfo;
975 this.e.cbIsExe.checked = !!stashFinfo.isExe;
976 onload(this.contentFromStash()||'',{
977 'x-fileedit-file-perm': stashFinfo.isExe ? 'x' : undefined,
978 'content-type': stashFinfo.mimetype,
979 'x-fileedit-checkin-branch': stashFinfo.branch
980 });
981 F.message("Fetched from the local-edit stash:",
982 F.hashDigits(stashFinfo.checkin),
983 stashFinfo.filename);
984 return this;
@@ -958,11 +989,14 @@
989 urlParams: {
990 ajax: 'content',
991 filename:file,
992 checkin:rev
993 },
994 responseHeaders: [
995 'x-fileedit-file-perm',
996 'x-fileedit-checkin-branch',
997 'content-type'],
998 onload:(r,headers)=>{
999 onload(r,headers);
1000 F.message('Loaded content for',
1001 F.hashDigits(self.finfo.checkin),
1002 self.finfo.filename);
@@ -1076,42 +1110,41 @@
1110 */
1111 P.commit = function f(){
1112 if(!affirmHasFile()) return this;
1113 const self = this;
1114 const content = this.fileContent(),
1115 target = D.clearElement(P.e.manifestTarget),
1116 cbDryRun = E('[name=dry_run]'),
1117 isDryRun = cbDryRun.checked,
1118 filename = this.finfo.filename;
1119 if(!f.onload){
1120 f.onload = function(c){
1121 const oldFinfo = JSON.parse(JSON.stringify(self.finfo))
1122 if(c.manifest){
1123 target.innerHTML = [
1124 "<h3>Manifest",
1125 (c.dryRun?" (dry run)":""),
1126 ": ", F.hashDigits(c.checkin),"</h3>",
1127 "<code class='fileedit-manifest'>",
1128 c.manifest,
1129 "</code></pre>"
1130 ].join('');
1131 delete c.manifest/*so we don't stash this with finfo*/;
1132 }
1133 const msg = [
1134 'Committed',
1135 c.dryRun ? '(dry run)' : '',
1136 '[', F.hashDigits(c.checkin) ,'].'
1137 ];
1138 if(!c.dryRun){
 
 
 
 
1139 self.unstashContent(oldFinfo);
 
1140 self.finfo = c;
1141 self.e.taComment.value = '';
1142 self.updateVersion();
1143 self.fileSelectWidget.loadLeaves();
1144 }
1145 self.dispatchEvent('fileedit-committed', c);
1146 F.message.apply(F, msg);
1147 self.tabs.switchToTab(self.e.tabs.commit);
1148 };
1149 }
1150 const fd = new FormData();
@@ -1133,10 +1166,11 @@
1166 /* Checkboxes: */
1167 ['allow_fork',
1168 'allow_older',
1169 'exec_bit',
1170 'allow_merge_conflict',
1171 'include_manifest',
1172 'prefer_delta'
1173 ].forEach(function(name){
1174 var e = E('[name='+name+']');
1175 if(e){
1176 fd.append(name, e.checked ? 1 : 0);
1177

Keyboard Shortcuts

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