Fossil SCM

Ajaxified commit. All that's left is cleanup and prettification.

stephan 2020-05-05 06:48 fileedit-ajaxify
Commit 1a6c5090ce31697a4f5d92806dff0bab9b53ae20d4ef9f8530fa184b9af60a73
--- src/default_css.txt
+++ src/default_css.txt
@@ -902,10 +902,11 @@
902902
}
903903
code.fileedit-manifest {
904904
display: block;
905905
height: 16em;
906906
overflow: auto;
907
+ white-space: pre;
907908
}
908909
div.fileedit-preview {
909910
margin: 0;
910911
padding: 0;
911912
}
@@ -965,5 +966,8 @@
965966
}
966967
.input-with-label > span {
967968
margin: 0 0.25em 0 0.25em;
968969
vertical-align: middle;
969970
}
971
+.hidden {
972
+ display: none;
973
+}
970974
--- src/default_css.txt
+++ src/default_css.txt
@@ -902,10 +902,11 @@
902 }
903 code.fileedit-manifest {
904 display: block;
905 height: 16em;
906 overflow: auto;
 
907 }
908 div.fileedit-preview {
909 margin: 0;
910 padding: 0;
911 }
@@ -965,5 +966,8 @@
965 }
966 .input-with-label > span {
967 margin: 0 0.25em 0 0.25em;
968 vertical-align: middle;
969 }
 
 
 
970
--- src/default_css.txt
+++ src/default_css.txt
@@ -902,10 +902,11 @@
902 }
903 code.fileedit-manifest {
904 display: block;
905 height: 16em;
906 overflow: auto;
907 white-space: pre;
908 }
909 div.fileedit-preview {
910 margin: 0;
911 padding: 0;
912 }
@@ -965,5 +966,8 @@
966 }
967 .input-with-label > span {
968 margin: 0 0.25em 0 0.25em;
969 vertical-align: middle;
970 }
971 .hidden {
972 display: none;
973 }
974
+278 -192
--- src/fileedit.c
+++ src/fileedit.c
@@ -1220,10 +1220,206 @@
12201220
fileedit_render_diff(&content, frid, zRevUuid, isSbs);
12211221
fossil_free(zRevUuid);
12221222
blob_reset(&content);
12231223
}
12241224
1225
+/*
1226
+** Sets up and validates must, but not all, of p's checkin-related
1227
+** state from the CGI environment. Returns 0 on success or a suggested
1228
+** HTTP result code on error, in which case a message will have been
1229
+** written to pErr.
1230
+**
1231
+** It always fails if it cannot completely resolve the 'file' and 'r'
1232
+** parameters, including verifying that the refer to a real
1233
+** file/version combination. Most others are optional.
1234
+**
1235
+** Intended to be used only by /filepage and /filepage_commit.
1236
+*/
1237
+static int fileedit_setup_cimi_from_p(CheckinMiniInfo * p, Blob * pErr){
1238
+ char * zFileUuid = 0;
1239
+ const char * zFlag;
1240
+ int rc = 0, vid = 0, frid = 0;
1241
+
1242
+#define fail(EXPR) blob_appendf EXPR; goto end_fail
1243
+ zFlag = PD("file",P("name"));
1244
+ if(zFlag==0 || !*zFlag){
1245
+ rc = 400;
1246
+ fail((pErr,"Missing required 'file' parameter."));
1247
+ }
1248
+ p->zFilename = mprintf("%s",zFlag);
1249
+
1250
+ if(0==fileedit_is_editable(p->zFilename)){
1251
+ rc = 403;
1252
+ fail((pErr,"Filename [%h] is disallowed "
1253
+ "by the [fileedit-glob] repository "
1254
+ "setting.",
1255
+ p->zFilename));
1256
+ }
1257
+
1258
+ zFlag = P("r");
1259
+ if(!zFlag){
1260
+ rc = 400;
1261
+ fail((pErr,"Missing required 'r' parameter."));
1262
+ }
1263
+ vid = symbolic_name_to_rid(zFlag, "ci");
1264
+ if(0==vid){
1265
+ rc = 404;
1266
+ fail((pErr,"Could not resolve checkin version."));
1267
+ }
1268
+ p->zParentUuid = rid_to_uuid(vid)/*fully expand it*/;
1269
+
1270
+ zFileUuid = fileedit_file_uuid(p->zFilename, vid, &p->filePerm);
1271
+ if(!zFileUuid){
1272
+ rc = 404;
1273
+ fail((pErr,"Checkin [%S] does not contain file: "
1274
+ "[%h]", p->zParentUuid, p->zFilename));
1275
+ }else if(PERM_LNK==p->filePerm){
1276
+ rc = 400;
1277
+ fail((pErr,"Editing symlinks is not permitted."));
1278
+ }
1279
+
1280
+ /* Find the repo-side file entry or fail... */
1281
+ frid = fast_uuid_to_rid(zFileUuid);
1282
+ assert(frid);
1283
+
1284
+ /* Read file content from submit request or repo... */
1285
+ zFlag = P("content");
1286
+ if(zFlag==0){
1287
+ content_get(frid, &p->fileContent);
1288
+ }else{
1289
+ blob_init(&p->fileContent,zFlag,-1);
1290
+ }
1291
+ if(looks_like_binary(&p->fileContent)){
1292
+ rc = 400;
1293
+ fail((pErr,"File appears to be binary. Cannot edit: "
1294
+ "[%h]",p->zFilename));
1295
+ }
1296
+
1297
+ zFlag = PT("comment");
1298
+ if(zFlag!=0 && *zFlag!=0){
1299
+ blob_append(&p->comment, zFlag, -1);
1300
+ }
1301
+ zFlag = P("comment_mimetype");
1302
+ if(zFlag){
1303
+ p->zCommentMimetype = mprintf("%s",zFlag);
1304
+ zFlag = 0;
1305
+ }
1306
+ p->zUser = mprintf("%s",g.zLogin);
1307
+#define p_int(K) atoi(PD(K,"0"))
1308
+ if(p_int("dry_run")!=0){
1309
+ p->flags |= CIMINI_DRY_RUN;
1310
+ }
1311
+ if(p_int("allow_fork")!=0){
1312
+ p->flags |= CIMINI_ALLOW_FORK;
1313
+ }
1314
+ if(p_int("allow_older")!=0){
1315
+ p->flags |= CIMINI_ALLOW_OLDER;
1316
+ }
1317
+ if(p_int("exec_bit")!=0){
1318
+ p->filePerm = PERM_EXE;
1319
+ }
1320
+ if(p_int("allow_merge_conflict")!=0){
1321
+ p->flags |= CIMINI_ALLOW_MERGE_MARKER;
1322
+ }
1323
+ if(p_int("prefer_delta")!=0){
1324
+ p->flags |= CIMINI_PREFER_DELTA;
1325
+ }
1326
+
1327
+ /* EOL conversion policy... */
1328
+ {
1329
+ const int eolMode = p_int("eol");
1330
+ switch(eolMode){
1331
+ case 1: p->flags |= CIMINI_CONVERT_EOL_UNIX; break;
1332
+ case 2: p->flags |= CIMINI_CONVERT_EOL_WINDOWS; break;
1333
+ default: p->flags |= CIMINI_CONVERT_EOL_INHERIT; break;
1334
+ }
1335
+ }
1336
+#undef p_int
1337
+ /*
1338
+ ** TODO?: date-override date selection field. Maybe use
1339
+ ** an input[type=datetime-local].
1340
+ */
1341
+ return 0;
1342
+end_fail:
1343
+#undef fail
1344
+ fossil_free(zFileUuid);
1345
+ return rc ? rc : 500;
1346
+}
1347
+
1348
+/*
1349
+** WEBPAGE: fileedit_commit
1350
+**
1351
+** Required query parameters:
1352
+**
1353
+** file=FILENAME
1354
+** r=Parent checkin UUID
1355
+** content=text
1356
+** comment=text
1357
+**
1358
+** Optional query parameters:
1359
+**
1360
+** comment_mimetype=text
1361
+** dry_run=int (1 or 0)
1362
+**
1363
+**
1364
+** User must have Write access to use this page.
1365
+**
1366
+** Responds with JSON:
1367
+**
1368
+** {uuid: newUUID,
1369
+** manifest: text of manifest,
1370
+** dryRun: bool
1371
+** }
1372
+**
1373
+** On error it produces a JSON response as documented for
1374
+** fileedit_ajax_error().
1375
+*/
1376
+void fileedit_ajax_commit(){
1377
+ int newVid = 0;
1378
+ Blob err = empty_blob;
1379
+ Blob manifest = empty_blob;
1380
+ CheckinMiniInfo cimi;
1381
+ int rc;
1382
+ char * zNewUuid = 0;
1383
+
1384
+ if(!fileedit_ajax_boostrap()){
1385
+ goto end_cleanup;
1386
+ }
1387
+ db_begin_transaction();
1388
+ CheckinMiniInfo_init(&cimi);
1389
+ rc = fileedit_setup_cimi_from_p(&cimi,&err);
1390
+ if(0!=rc){
1391
+ fileedit_ajax_error(rc,"%b",&err);
1392
+ goto end_cleanup;
1393
+ }
1394
+ if(blob_size(&cimi.comment)==0){
1395
+ fileedit_ajax_error(400,"Empty checkin comment is not permitted.");
1396
+ goto end_cleanup;
1397
+ }
1398
+ cimi.pMfOut = &manifest;
1399
+ checkin_mini(&cimi, &newVid, &err);
1400
+ if(blob_size(&err)){
1401
+ fileedit_ajax_error(500,"%b",&err);
1402
+ goto end_cleanup;
1403
+ }
1404
+ assert(newVid>0);
1405
+ zNewUuid = rid_to_uuid(newVid);
1406
+ cgi_set_content_type("application/json");
1407
+ CX("{");
1408
+ CX("\"uuid\":\"%j\",", zNewUuid);
1409
+ CX("\"dryRun\": %s,",
1410
+ (CIMINI_DRY_RUN & cimi.flags) ? "true" : "false");
1411
+ CX("\"manifest\": \"%j\"", blob_str(&manifest));
1412
+ CX("}");
1413
+ db_end_transaction(0);
1414
+end_cleanup:
1415
+ fossil_free(zNewUuid);
1416
+ blob_reset(&err);
1417
+ blob_reset(&manifest);
1418
+ CheckinMiniInfo_cleanup(&cimi);
1419
+}
1420
+
12251421
12261422
/*
12271423
** Emits utility script code specific to the /fileedit page.
12281424
*/
12291425
static void fileedit_emit_page_script(){
@@ -1247,34 +1443,21 @@
12471443
** All other parameters are for internal use only, submitted via the
12481444
** form-submission process, and may change with any given revision of
12491445
** this code.
12501446
*/
12511447
void fileedit_page(){
1252
- enum submit_modes {
1253
- SUBMIT_NONE = 0, SUBMIT_SAVE, SUBMIT_PREVIEW,
1254
- SUBMIT_DIFF_SBS, SUBMIT_DIFF_UNIFIED,
1255
- SUBMIT_end /* sentinel for range validation */
1256
- };
1257
- const char * zFilename = PD("file",P("name"));
1258
- /* filename. We'll accept 'name'
1448
+ const char * zFilename; /* filename. We'll accept 'name'
12591449
because that param is handled
12601450
specially by the core. */
1261
- const char * zRev = P("r"); /* checkin version */
1262
- const char * zContent = P("content"); /* file content */
1263
- const char * zComment = P("comment"); /* checkin comment */
1451
+ const char * zRev; /* checkin version */
12641452
const char * zFileMime = 0; /* File mime type guess */
12651453
CheckinMiniInfo cimi; /* Checkin state */
1266
- int submitMode = SUBMIT_NONE; /* See mapping below */
1267
- int vid, newVid = 0; /* checkin rid */
1268
- int frid = 0; /* File content rid */
1269
- int previewLn = P("preview_ln")!=0; /* Line number mode */
12701454
int previewHtmlHeight = 0; /* iframe height (EMs) */
12711455
int previewRenderMode = FE_RENDER_GUESS; /* preview mode */
12721456
char * zFileUuid = 0; /* File content UUID */
12731457
Blob err = empty_blob; /* Error report */
12741458
Blob submitResult = empty_blob; /* Error report */
1275
- const char * zFlagCheck = 0; /* Temp url flag holder */
12761459
Blob endScript = empty_blob; /* Script code to run at the
12771460
end. This content will be
12781461
combined into a single JS
12791462
function call, thus each
12801463
entry must end with a
@@ -1287,103 +1470,26 @@
12871470
login_needed(g.anon.Write);
12881471
return;
12891472
}
12901473
db_begin_transaction();
12911474
CheckinMiniInfo_init(&cimi);
1292
- submitMode = atoi(PD("submit","0"));
1293
- if(submitMode < SUBMIT_NONE || submitMode >= SUBMIT_end){
1294
- submitMode = 0;
1295
- }
1296
- zFlagCheck = P("comment_mimetype");
1297
- if(zFlagCheck){
1298
- cimi.zCommentMimetype = mprintf("%s",zFlagCheck);
1299
- zFlagCheck = 0;
1300
- }
1301
- cimi.zUser = mprintf("%s",g.zLogin);
13021475
13031476
style_header("File Editor");
13041477
/* As of this point, don't use return or fossil_fatal(), use
13051478
** fail((&err,...)) instead so that we can be sure to do any
13061479
** cleanup and end the transaction cleanly.
13071480
*/
1308
- if(!zRev || !*zRev || !zFilename || !*zFilename){
1309
- fail((&err,"Missing required URL parameters: "
1310
- "file=FILE and r=CHECKIN"));
1311
- }
1312
- if(0==fileedit_is_editable(zFilename)){
1313
- fail((&err,"Filename <code>%h</code> is disallowed "
1314
- "by the <code>fileedit-glob</code> repository "
1315
- "setting.",
1316
- zFilename));
1317
- }
1318
- vid = symbolic_name_to_rid(zRev, "ci");
1319
- if(0==vid){
1320
- fail((&err,"Could not resolve checkin version."));
1321
- }
1322
- cimi.zFilename = mprintf("%s",zFilename);
1323
- zFileMime = mimetype_from_name(zFilename);
1324
-
1325
- /* Find the repo-side file entry or fail... */
1326
- cimi.zParentUuid = rid_to_uuid(vid);
1327
- zFileUuid = fileedit_file_uuid(zFilename, vid, &cimi.filePerm);
1328
- if(!zFileUuid){
1329
- fail((&err,"Checkin [%S] does not contain file: "
1330
- "<code>%h</code>",
1331
- cimi.zParentUuid, zFilename));
1332
- }
1333
- else if(PERM_LNK==cimi.filePerm){
1334
- fail((&err,"Editing symlinks is not permitted."));
1335
- }
1336
- frid = fast_uuid_to_rid(zFileUuid);
1337
- assert(frid);
1338
-
1339
- /* Read file content from submit request or repo... */
1340
- if(zContent==0){
1341
- content_get(frid, &cimi.fileContent);
1342
- zContent = blob_size(&cimi.fileContent)
1343
- ? blob_str(&cimi.fileContent) : NULL;
1344
- }else{
1345
- blob_init(&cimi.fileContent,zContent,-1);
1346
- }
1347
- if(looks_like_binary(&cimi.fileContent)){
1348
- fail((&err,"File appears to be binary. Cannot edit: "
1349
- "<code>%h</code>",zFilename));
1350
- }
1351
-
1352
- /*
1353
- ** TODO?: date-override date selection field. Maybe use
1354
- ** an input[type=datetime-local].
1355
- */
1356
- if(SUBMIT_NONE==submitMode || P("dry_run")!=0){
1357
- cimi.flags |= CIMINI_DRY_RUN;
1358
- }
1359
- if(P("allow_fork")!=0){
1360
- cimi.flags |= CIMINI_ALLOW_FORK;
1361
- }
1362
- if(P("allow_older")!=0){
1363
- cimi.flags |= CIMINI_ALLOW_OLDER;
1364
- }
1365
- if(P("exec_bit")!=0){
1366
- cimi.filePerm = PERM_EXE;
1367
- }
1368
- if(P("allow_merge_conflict")!=0){
1369
- cimi.flags |= CIMINI_ALLOW_MERGE_MARKER;
1370
- }
1371
- if(P("prefer_delta")!=0){
1372
- cimi.flags |= CIMINI_PREFER_DELTA;
1373
- }
1374
- /* EOL conversion policy... */
1375
- {
1376
- const int eolMode = submitMode==SUBMIT_NONE
1377
- ? 0 : atoi(PD("eol","0"));
1378
- switch(eolMode){
1379
- case 1: cimi.flags |= CIMINI_CONVERT_EOL_UNIX; break;
1380
- case 2: cimi.flags |= CIMINI_CONVERT_EOL_WINDOWS; break;
1381
- default: cimi.flags |= CIMINI_CONVERT_EOL_INHERIT; break;
1382
- }
1383
- }
1384
-
1481
+ if(fileedit_setup_cimi_from_p(&cimi, &err)!=0){
1482
+ goto end_footer;
1483
+ }
1484
+ zFilename = cimi.zFilename;
1485
+ zRev = cimi.zParentUuid;
1486
+
1487
+ assert(zRev);
1488
+ assert(zFilename);
1489
+ zFileMime = mimetype_from_name(cimi.zFilename);
1490
+
13851491
/********************************************************************
13861492
** All errors which "could" have happened up to this point are of a
13871493
** degree which keep us from rendering the rest of the page, and
13881494
** thus fail() has already skipped to the end of the page to render
13891495
** the errors. Any up-coming errors, barring malloc failure or
@@ -1396,54 +1502,10 @@
13961502
** SAVE, we can capture all of the output, and thus can perform that
13971503
** work before rendering, which is important so that we have the
13981504
** proper version information when rendering the rest of the page.
13991505
********************************************************************/
14001506
#undef fail
1401
- while(SUBMIT_SAVE==submitMode){
1402
- Blob manifest = empty_blob;
1403
- /*cimi.flags |= CIMINI_STRONGLY_PREFER_DELTA;*/
1404
- if(zComment && *zComment){
1405
- blob_append(&cimi.comment, zComment, -1);
1406
- }else{
1407
- blob_append(&err,"Empty checkin comment is not permitted.",-1);
1408
- break;
1409
- }
1410
- cimi.pMfOut = &manifest;
1411
- checkin_mini(&cimi, &newVid, &err);
1412
- if(newVid!=0){
1413
- char * zNewUuid = rid_to_uuid(newVid);
1414
- blob_appendf(&submitResult,
1415
- "<h3>Manifest%s: %S</h3><pre>"
1416
- "<code class='fileedit-manifest'>%h</code>"
1417
- "</pre>",
1418
- (cimi.flags & CIMINI_DRY_RUN) ? " (dry run)" : "",
1419
- zNewUuid, blob_str(&manifest));
1420
- if(CIMINI_DRY_RUN & cimi.flags){
1421
- fossil_free(zNewUuid);
1422
- }else{
1423
- /* Update cimi version info... */
1424
- assert(cimi.pParent);
1425
- assert(cimi.zParentUuid);
1426
- fossil_free(zFileUuid);
1427
- zFileUuid = fileedit_file_uuid(cimi.zFilename, newVid, 0);
1428
- manifest_destroy(cimi.pParent);
1429
- cimi.pParent = 0;
1430
- fossil_free(cimi.zParentUuid);
1431
- cimi.zParentUuid = zNewUuid;
1432
- zComment = 0;
1433
- cimi.flags |= CIMINI_DRY_RUN /* for sanity's sake */;
1434
- }
1435
- }
1436
- /* On error, the error message is in the err blob and will
1437
- ** be emitted at the end. */
1438
- cimi.pMfOut = 0;
1439
- blob_reset(&manifest);
1440
- break;
1441
- }
1442
-
1443
- CX("<div id='fossil-status-bar'>Async. status messages will go "
1444
- "here.</div>\n");
14451507
CX("<h1>Editing:</h1>");
14461508
CX("<p class='fileedit-hint'>");
14471509
CX("File: "
14481510
"[<a id='finfo-link' href='#'>info</a>] "
14491511
/* %R/finfo?name=%T&m=%!S */
@@ -1480,45 +1542,56 @@
14801542
CX("<h3>File Content</h3>\n");
14811543
CX("<textarea name='content' id='fileedit-content' "
14821544
"rows='20' cols='80'>");
14831545
CX("Loading...");
14841546
CX("</textarea>\n");
1547
+
1548
+ CX("<div id='fossil-status-bar'>Async. status messages will go "
1549
+ "here.</div>\n");
1550
+
14851551
/******* Flags/options *******/
14861552
CX("<fieldset class='fileedit-options' id='options'>"
14871553
"<legend>Options</legend><div>"
14881554
/* Chrome does not sanely lay out multiple
14891555
** fieldset children after the <legend>, so
14901556
** a containing div is necessary. */);
1491
- style_labeled_checkbox("dry_run", "Dry-run?", "1",
1557
+ style_labeled_checkbox("cb-dry-run",
1558
+ "dry_run", "Dry-run?", "1",
14921559
"In dry-run mode, the Save button performs "
14931560
"all work needed for saving but then rolls "
14941561
"back the transaction, and thus does not "
14951562
"really save.",
1496
- cimi.flags & CIMINI_DRY_RUN);
1497
- style_labeled_checkbox("allow_fork", "Allow fork?", "1",
1563
+ 1);
1564
+ style_labeled_checkbox("cb-allow-fork",
1565
+ "allow_fork", "Allow fork?", "1",
14981566
"Allow saving to create a fork?",
14991567
cimi.flags & CIMINI_ALLOW_FORK);
1500
- style_labeled_checkbox("allow_older", "Allow older?", "1",
1568
+ style_labeled_checkbox("cb-allow-older",
1569
+ "allow_older", "Allow older?", "1",
15011570
"Allow saving against a parent version "
15021571
"which has a newer timestamp?",
15031572
cimi.flags & CIMINI_ALLOW_OLDER);
1504
- style_labeled_checkbox("exec_bit", "Executable?", "1",
1573
+ style_labeled_checkbox("cb-exec-bit",
1574
+ "exec_bit", "Executable?", "1",
15051575
"Set the executable bit?",
15061576
PERM_EXE==cimi.filePerm);
1507
- style_labeled_checkbox("allow_merge_conflict",
1577
+ style_labeled_checkbox("cb-allow-merge-conflict",
1578
+ "allow_merge_conflict",
15081579
"Allow merge conflict markers?", "1",
15091580
"Allow saving even if the content contains "
15101581
"what appear to be fossil merge conflict "
15111582
"markers?",
15121583
cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
1513
- style_labeled_checkbox("prefer_delta",
1584
+ style_labeled_checkbox("cb-prefer-delta",
1585
+ "prefer_delta",
15141586
"Prefer delta manifest?", "1",
15151587
"Will create a delta manifest, instead of "
15161588
"baseline, if conditions are favorable to do "
15171589
"so. This option is only a suggestion.",
15181590
cimi.flags & CIMINI_PREFER_DELTA);
1519
- style_select_list_int("eol", "EOL Style",
1591
+ style_select_list_int("select-eol-style",
1592
+ "eol", "EOL Style",
15201593
"EOL conversion policy, noting that "
15211594
"form-processing may implicitly change the "
15221595
"line endings of the input.",
15231596
(cimi.flags & CIMINI_CONVERT_EOL_UNIX)
15241597
? 1 : (cimi.flags & CIMINI_CONVERT_EOL_WINDOWS
@@ -1534,12 +1607,12 @@
15341607
CX("<a id='comment'></a>");
15351608
CX("<fieldset><legend>Commit message</legend><div>");
15361609
CX("<textarea name='comment' rows='3' cols='80'>");
15371610
/* ^^^ adding the 'required' attribute means we cannot even submit
15381611
** for PREVIEW mode if it's empty :/. */
1539
- if(zComment && *zComment){
1540
- CX("%h", zComment);
1612
+ if(blob_size(&cimi.comment)){
1613
+ CX("%h", blob_str(&cimi.comment));
15411614
}
15421615
CX("</textarea>\n");
15431616
CX("<div class='fileedit-hint'>Comments use the Fossil wiki markup "
15441617
"syntax.</div>\n"/*TODO: select for fossil/md/plain text*/);
15451618
CX("</div></fieldset>\n");
@@ -1547,61 +1620,74 @@
15471620
/******* Buttons *******/
15481621
CX("<a id='buttons'></a>");
15491622
CX("<fieldset class='fileedit-options'>"
15501623
"<legend>Ask the server to...</legend><div>");
15511624
CX("<button id='fileedit-btn-commit'>Commit</button>");
1552
- CX("<button id='fileedit-btn-preview'>Preview</button>");
1553
- {
1554
- /* Preview rendering mode selection... */
1555
- previewRenderMode = atoi(PD("preview_render_mode","0"));
1556
- previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime);
1557
- style_select_list_int("preview_render_mode",
1558
- "Preview Mode",
1559
- "Preview mode format.",
1560
- previewRenderMode,
1561
- "Guess", FE_RENDER_GUESS,
1562
- "Wiki/Markdown", FE_RENDER_WIKI,
1563
- "HTML (iframe)", FE_RENDER_HTML,
1564
- "Plain Text", FE_RENDER_PLAIN_TEXT,
1565
- NULL);
1566
- previewHtmlHeight = atoi(PD("preview_html_ems","0"));
1567
- if(!previewHtmlHeight){
1568
- previewHtmlHeight = 40;
1569
- }
1570
- /* Allow selection of HTML preview iframe height */
1571
- style_select_list_int("preview_html_ems",
1572
- "HTML Preview IFrame Height (EMs)",
1573
- "Height (in EMs) of the iframe used for "
1574
- "HTML preview",
1575
- previewHtmlHeight,
1576
- "", 20, "", 40,
1577
- "", 60, "", 80,
1578
- "", 100, NULL);
1579
- style_labeled_checkbox("preview_ln",
1580
- "Add line numbers to plain-text previews?",
1581
- "1",
1582
- "If on, plain-text files (only) will get "
1583
- "line numbers added to the preview.",
1584
- previewLn);
1585
- }
15861625
CX("<button id='fileedit-btn-diffsbs'>Diff (SBS)</button>");
15871626
CX("<button id='fileedit-btn-diffu'>Diff (Unified)</button>");
1627
+ CX("<button id='fileedit-btn-preview'>Preview</button>");
1628
+ /* Default preview rendering mode selection... */
1629
+ previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime);
1630
+ style_select_list_int("select-preview-mode",
1631
+ "preview_render_mode",
1632
+ "Preview Mode",
1633
+ "Preview mode format.",
1634
+ previewRenderMode,
1635
+ "Guess", FE_RENDER_GUESS,
1636
+ "Wiki/Markdown", FE_RENDER_WIKI,
1637
+ "HTML (iframe)", FE_RENDER_HTML,
1638
+ "Plain Text", FE_RENDER_PLAIN_TEXT,
1639
+ NULL);
1640
+ /*
1641
+ ** Set up a JS-side mapping of the FE_RENDER_xyz values
1642
+ */
1643
+ blob_appendf(&endScript, "fossil.page.previewModes={"
1644
+ "guess: %d, %d: 'guess', wiki: %d, %d: 'wiki',"
1645
+ "html: %d, %d: 'html', text: %d, %d: 'text'"
1646
+ "};\n",
1647
+ FE_RENDER_GUESS, FE_RENDER_GUESS,
1648
+ FE_RENDER_WIKI, FE_RENDER_WIKI,
1649
+ FE_RENDER_HTML, FE_RENDER_HTML,
1650
+ FE_RENDER_PLAIN_TEXT, FE_RENDER_PLAIN_TEXT);
1651
+
1652
+ /* Allow selection of HTML preview iframe height */
1653
+ previewHtmlHeight = atoi(PD("preview_html_ems","0"));
1654
+ if(!previewHtmlHeight){
1655
+ previewHtmlHeight = 40;
1656
+ }
1657
+ style_select_list_int("select-preview-html-ems",
1658
+ "preview_html_ems",
1659
+ "HTML Preview IFrame Height (EMs)",
1660
+ "Height (in EMs) of the iframe used for "
1661
+ "HTML preview",
1662
+ previewHtmlHeight,
1663
+ "", 20, "", 40,
1664
+ "", 60, "", 80,
1665
+ "", 100, NULL);
1666
+ /* Selection of line numbers for text preview */
1667
+ style_labeled_checkbox("cb-line-numbers",
1668
+ "preview_ln",
1669
+ "Add line numbers to plain-text previews?",
1670
+ "1",
1671
+ "If on, plain-text files (only) will get "
1672
+ "line numbers added to the preview.",
1673
+ P("preview_ln")!=0);
1674
+
15881675
CX("</div></fieldset>");
15891676
15901677
/******* End of form *******/
15911678
CX("</form>\n");
15921679
1593
- CX("<div id='ajax-target'></div>"
1680
+ CX("<div id='ajax-target'>%s</div>"
15941681
/* this is where preview/diff go */);
15951682
15961683
/* Dynamically populate the editor... */
15971684
blob_appendf(&endScript,
15981685
"fossil.page.loadFile('%j','%j');",
15991686
zFilename, cimi.zParentUuid);
16001687
16011688
end_footer:
1602
- zContent = 0;
16031689
fossil_free(zFileUuid);
16041690
if(stmt.pStmt){
16051691
db_finalize(&stmt);
16061692
}
16071693
if(blob_size(&err)){
16081694
--- src/fileedit.c
+++ src/fileedit.c
@@ -1220,10 +1220,206 @@
1220 fileedit_render_diff(&content, frid, zRevUuid, isSbs);
1221 fossil_free(zRevUuid);
1222 blob_reset(&content);
1223 }
1224
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1225
1226 /*
1227 ** Emits utility script code specific to the /fileedit page.
1228 */
1229 static void fileedit_emit_page_script(){
@@ -1247,34 +1443,21 @@
1247 ** All other parameters are for internal use only, submitted via the
1248 ** form-submission process, and may change with any given revision of
1249 ** this code.
1250 */
1251 void fileedit_page(){
1252 enum submit_modes {
1253 SUBMIT_NONE = 0, SUBMIT_SAVE, SUBMIT_PREVIEW,
1254 SUBMIT_DIFF_SBS, SUBMIT_DIFF_UNIFIED,
1255 SUBMIT_end /* sentinel for range validation */
1256 };
1257 const char * zFilename = PD("file",P("name"));
1258 /* filename. We'll accept 'name'
1259 because that param is handled
1260 specially by the core. */
1261 const char * zRev = P("r"); /* checkin version */
1262 const char * zContent = P("content"); /* file content */
1263 const char * zComment = P("comment"); /* checkin comment */
1264 const char * zFileMime = 0; /* File mime type guess */
1265 CheckinMiniInfo cimi; /* Checkin state */
1266 int submitMode = SUBMIT_NONE; /* See mapping below */
1267 int vid, newVid = 0; /* checkin rid */
1268 int frid = 0; /* File content rid */
1269 int previewLn = P("preview_ln")!=0; /* Line number mode */
1270 int previewHtmlHeight = 0; /* iframe height (EMs) */
1271 int previewRenderMode = FE_RENDER_GUESS; /* preview mode */
1272 char * zFileUuid = 0; /* File content UUID */
1273 Blob err = empty_blob; /* Error report */
1274 Blob submitResult = empty_blob; /* Error report */
1275 const char * zFlagCheck = 0; /* Temp url flag holder */
1276 Blob endScript = empty_blob; /* Script code to run at the
1277 end. This content will be
1278 combined into a single JS
1279 function call, thus each
1280 entry must end with a
@@ -1287,103 +1470,26 @@
1287 login_needed(g.anon.Write);
1288 return;
1289 }
1290 db_begin_transaction();
1291 CheckinMiniInfo_init(&cimi);
1292 submitMode = atoi(PD("submit","0"));
1293 if(submitMode < SUBMIT_NONE || submitMode >= SUBMIT_end){
1294 submitMode = 0;
1295 }
1296 zFlagCheck = P("comment_mimetype");
1297 if(zFlagCheck){
1298 cimi.zCommentMimetype = mprintf("%s",zFlagCheck);
1299 zFlagCheck = 0;
1300 }
1301 cimi.zUser = mprintf("%s",g.zLogin);
1302
1303 style_header("File Editor");
1304 /* As of this point, don't use return or fossil_fatal(), use
1305 ** fail((&err,...)) instead so that we can be sure to do any
1306 ** cleanup and end the transaction cleanly.
1307 */
1308 if(!zRev || !*zRev || !zFilename || !*zFilename){
1309 fail((&err,"Missing required URL parameters: "
1310 "file=FILE and r=CHECKIN"));
1311 }
1312 if(0==fileedit_is_editable(zFilename)){
1313 fail((&err,"Filename <code>%h</code> is disallowed "
1314 "by the <code>fileedit-glob</code> repository "
1315 "setting.",
1316 zFilename));
1317 }
1318 vid = symbolic_name_to_rid(zRev, "ci");
1319 if(0==vid){
1320 fail((&err,"Could not resolve checkin version."));
1321 }
1322 cimi.zFilename = mprintf("%s",zFilename);
1323 zFileMime = mimetype_from_name(zFilename);
1324
1325 /* Find the repo-side file entry or fail... */
1326 cimi.zParentUuid = rid_to_uuid(vid);
1327 zFileUuid = fileedit_file_uuid(zFilename, vid, &cimi.filePerm);
1328 if(!zFileUuid){
1329 fail((&err,"Checkin [%S] does not contain file: "
1330 "<code>%h</code>",
1331 cimi.zParentUuid, zFilename));
1332 }
1333 else if(PERM_LNK==cimi.filePerm){
1334 fail((&err,"Editing symlinks is not permitted."));
1335 }
1336 frid = fast_uuid_to_rid(zFileUuid);
1337 assert(frid);
1338
1339 /* Read file content from submit request or repo... */
1340 if(zContent==0){
1341 content_get(frid, &cimi.fileContent);
1342 zContent = blob_size(&cimi.fileContent)
1343 ? blob_str(&cimi.fileContent) : NULL;
1344 }else{
1345 blob_init(&cimi.fileContent,zContent,-1);
1346 }
1347 if(looks_like_binary(&cimi.fileContent)){
1348 fail((&err,"File appears to be binary. Cannot edit: "
1349 "<code>%h</code>",zFilename));
1350 }
1351
1352 /*
1353 ** TODO?: date-override date selection field. Maybe use
1354 ** an input[type=datetime-local].
1355 */
1356 if(SUBMIT_NONE==submitMode || P("dry_run")!=0){
1357 cimi.flags |= CIMINI_DRY_RUN;
1358 }
1359 if(P("allow_fork")!=0){
1360 cimi.flags |= CIMINI_ALLOW_FORK;
1361 }
1362 if(P("allow_older")!=0){
1363 cimi.flags |= CIMINI_ALLOW_OLDER;
1364 }
1365 if(P("exec_bit")!=0){
1366 cimi.filePerm = PERM_EXE;
1367 }
1368 if(P("allow_merge_conflict")!=0){
1369 cimi.flags |= CIMINI_ALLOW_MERGE_MARKER;
1370 }
1371 if(P("prefer_delta")!=0){
1372 cimi.flags |= CIMINI_PREFER_DELTA;
1373 }
1374 /* EOL conversion policy... */
1375 {
1376 const int eolMode = submitMode==SUBMIT_NONE
1377 ? 0 : atoi(PD("eol","0"));
1378 switch(eolMode){
1379 case 1: cimi.flags |= CIMINI_CONVERT_EOL_UNIX; break;
1380 case 2: cimi.flags |= CIMINI_CONVERT_EOL_WINDOWS; break;
1381 default: cimi.flags |= CIMINI_CONVERT_EOL_INHERIT; break;
1382 }
1383 }
1384
1385 /********************************************************************
1386 ** All errors which "could" have happened up to this point are of a
1387 ** degree which keep us from rendering the rest of the page, and
1388 ** thus fail() has already skipped to the end of the page to render
1389 ** the errors. Any up-coming errors, barring malloc failure or
@@ -1396,54 +1502,10 @@
1396 ** SAVE, we can capture all of the output, and thus can perform that
1397 ** work before rendering, which is important so that we have the
1398 ** proper version information when rendering the rest of the page.
1399 ********************************************************************/
1400 #undef fail
1401 while(SUBMIT_SAVE==submitMode){
1402 Blob manifest = empty_blob;
1403 /*cimi.flags |= CIMINI_STRONGLY_PREFER_DELTA;*/
1404 if(zComment && *zComment){
1405 blob_append(&cimi.comment, zComment, -1);
1406 }else{
1407 blob_append(&err,"Empty checkin comment is not permitted.",-1);
1408 break;
1409 }
1410 cimi.pMfOut = &manifest;
1411 checkin_mini(&cimi, &newVid, &err);
1412 if(newVid!=0){
1413 char * zNewUuid = rid_to_uuid(newVid);
1414 blob_appendf(&submitResult,
1415 "<h3>Manifest%s: %S</h3><pre>"
1416 "<code class='fileedit-manifest'>%h</code>"
1417 "</pre>",
1418 (cimi.flags & CIMINI_DRY_RUN) ? " (dry run)" : "",
1419 zNewUuid, blob_str(&manifest));
1420 if(CIMINI_DRY_RUN & cimi.flags){
1421 fossil_free(zNewUuid);
1422 }else{
1423 /* Update cimi version info... */
1424 assert(cimi.pParent);
1425 assert(cimi.zParentUuid);
1426 fossil_free(zFileUuid);
1427 zFileUuid = fileedit_file_uuid(cimi.zFilename, newVid, 0);
1428 manifest_destroy(cimi.pParent);
1429 cimi.pParent = 0;
1430 fossil_free(cimi.zParentUuid);
1431 cimi.zParentUuid = zNewUuid;
1432 zComment = 0;
1433 cimi.flags |= CIMINI_DRY_RUN /* for sanity's sake */;
1434 }
1435 }
1436 /* On error, the error message is in the err blob and will
1437 ** be emitted at the end. */
1438 cimi.pMfOut = 0;
1439 blob_reset(&manifest);
1440 break;
1441 }
1442
1443 CX("<div id='fossil-status-bar'>Async. status messages will go "
1444 "here.</div>\n");
1445 CX("<h1>Editing:</h1>");
1446 CX("<p class='fileedit-hint'>");
1447 CX("File: "
1448 "[<a id='finfo-link' href='#'>info</a>] "
1449 /* %R/finfo?name=%T&m=%!S */
@@ -1480,45 +1542,56 @@
1480 CX("<h3>File Content</h3>\n");
1481 CX("<textarea name='content' id='fileedit-content' "
1482 "rows='20' cols='80'>");
1483 CX("Loading...");
1484 CX("</textarea>\n");
 
 
 
 
1485 /******* Flags/options *******/
1486 CX("<fieldset class='fileedit-options' id='options'>"
1487 "<legend>Options</legend><div>"
1488 /* Chrome does not sanely lay out multiple
1489 ** fieldset children after the <legend>, so
1490 ** a containing div is necessary. */);
1491 style_labeled_checkbox("dry_run", "Dry-run?", "1",
 
1492 "In dry-run mode, the Save button performs "
1493 "all work needed for saving but then rolls "
1494 "back the transaction, and thus does not "
1495 "really save.",
1496 cimi.flags & CIMINI_DRY_RUN);
1497 style_labeled_checkbox("allow_fork", "Allow fork?", "1",
 
1498 "Allow saving to create a fork?",
1499 cimi.flags & CIMINI_ALLOW_FORK);
1500 style_labeled_checkbox("allow_older", "Allow older?", "1",
 
1501 "Allow saving against a parent version "
1502 "which has a newer timestamp?",
1503 cimi.flags & CIMINI_ALLOW_OLDER);
1504 style_labeled_checkbox("exec_bit", "Executable?", "1",
 
1505 "Set the executable bit?",
1506 PERM_EXE==cimi.filePerm);
1507 style_labeled_checkbox("allow_merge_conflict",
 
1508 "Allow merge conflict markers?", "1",
1509 "Allow saving even if the content contains "
1510 "what appear to be fossil merge conflict "
1511 "markers?",
1512 cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
1513 style_labeled_checkbox("prefer_delta",
 
1514 "Prefer delta manifest?", "1",
1515 "Will create a delta manifest, instead of "
1516 "baseline, if conditions are favorable to do "
1517 "so. This option is only a suggestion.",
1518 cimi.flags & CIMINI_PREFER_DELTA);
1519 style_select_list_int("eol", "EOL Style",
 
1520 "EOL conversion policy, noting that "
1521 "form-processing may implicitly change the "
1522 "line endings of the input.",
1523 (cimi.flags & CIMINI_CONVERT_EOL_UNIX)
1524 ? 1 : (cimi.flags & CIMINI_CONVERT_EOL_WINDOWS
@@ -1534,12 +1607,12 @@
1534 CX("<a id='comment'></a>");
1535 CX("<fieldset><legend>Commit message</legend><div>");
1536 CX("<textarea name='comment' rows='3' cols='80'>");
1537 /* ^^^ adding the 'required' attribute means we cannot even submit
1538 ** for PREVIEW mode if it's empty :/. */
1539 if(zComment && *zComment){
1540 CX("%h", zComment);
1541 }
1542 CX("</textarea>\n");
1543 CX("<div class='fileedit-hint'>Comments use the Fossil wiki markup "
1544 "syntax.</div>\n"/*TODO: select for fossil/md/plain text*/);
1545 CX("</div></fieldset>\n");
@@ -1547,61 +1620,74 @@
1547 /******* Buttons *******/
1548 CX("<a id='buttons'></a>");
1549 CX("<fieldset class='fileedit-options'>"
1550 "<legend>Ask the server to...</legend><div>");
1551 CX("<button id='fileedit-btn-commit'>Commit</button>");
1552 CX("<button id='fileedit-btn-preview'>Preview</button>");
1553 {
1554 /* Preview rendering mode selection... */
1555 previewRenderMode = atoi(PD("preview_render_mode","0"));
1556 previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime);
1557 style_select_list_int("preview_render_mode",
1558 "Preview Mode",
1559 "Preview mode format.",
1560 previewRenderMode,
1561 "Guess", FE_RENDER_GUESS,
1562 "Wiki/Markdown", FE_RENDER_WIKI,
1563 "HTML (iframe)", FE_RENDER_HTML,
1564 "Plain Text", FE_RENDER_PLAIN_TEXT,
1565 NULL);
1566 previewHtmlHeight = atoi(PD("preview_html_ems","0"));
1567 if(!previewHtmlHeight){
1568 previewHtmlHeight = 40;
1569 }
1570 /* Allow selection of HTML preview iframe height */
1571 style_select_list_int("preview_html_ems",
1572 "HTML Preview IFrame Height (EMs)",
1573 "Height (in EMs) of the iframe used for "
1574 "HTML preview",
1575 previewHtmlHeight,
1576 "", 20, "", 40,
1577 "", 60, "", 80,
1578 "", 100, NULL);
1579 style_labeled_checkbox("preview_ln",
1580 "Add line numbers to plain-text previews?",
1581 "1",
1582 "If on, plain-text files (only) will get "
1583 "line numbers added to the preview.",
1584 previewLn);
1585 }
1586 CX("<button id='fileedit-btn-diffsbs'>Diff (SBS)</button>");
1587 CX("<button id='fileedit-btn-diffu'>Diff (Unified)</button>");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1588 CX("</div></fieldset>");
1589
1590 /******* End of form *******/
1591 CX("</form>\n");
1592
1593 CX("<div id='ajax-target'></div>"
1594 /* this is where preview/diff go */);
1595
1596 /* Dynamically populate the editor... */
1597 blob_appendf(&endScript,
1598 "fossil.page.loadFile('%j','%j');",
1599 zFilename, cimi.zParentUuid);
1600
1601 end_footer:
1602 zContent = 0;
1603 fossil_free(zFileUuid);
1604 if(stmt.pStmt){
1605 db_finalize(&stmt);
1606 }
1607 if(blob_size(&err)){
1608
--- src/fileedit.c
+++ src/fileedit.c
@@ -1220,10 +1220,206 @@
1220 fileedit_render_diff(&content, frid, zRevUuid, isSbs);
1221 fossil_free(zRevUuid);
1222 blob_reset(&content);
1223 }
1224
1225 /*
1226 ** Sets up and validates must, but not all, of p's checkin-related
1227 ** state from the CGI environment. Returns 0 on success or a suggested
1228 ** HTTP result code on error, in which case a message will have been
1229 ** written to pErr.
1230 **
1231 ** It always fails if it cannot completely resolve the 'file' and 'r'
1232 ** parameters, including verifying that the refer to a real
1233 ** file/version combination. Most others are optional.
1234 **
1235 ** Intended to be used only by /filepage and /filepage_commit.
1236 */
1237 static int fileedit_setup_cimi_from_p(CheckinMiniInfo * p, Blob * pErr){
1238 char * zFileUuid = 0;
1239 const char * zFlag;
1240 int rc = 0, vid = 0, frid = 0;
1241
1242 #define fail(EXPR) blob_appendf EXPR; goto end_fail
1243 zFlag = PD("file",P("name"));
1244 if(zFlag==0 || !*zFlag){
1245 rc = 400;
1246 fail((pErr,"Missing required 'file' parameter."));
1247 }
1248 p->zFilename = mprintf("%s",zFlag);
1249
1250 if(0==fileedit_is_editable(p->zFilename)){
1251 rc = 403;
1252 fail((pErr,"Filename [%h] is disallowed "
1253 "by the [fileedit-glob] repository "
1254 "setting.",
1255 p->zFilename));
1256 }
1257
1258 zFlag = P("r");
1259 if(!zFlag){
1260 rc = 400;
1261 fail((pErr,"Missing required 'r' parameter."));
1262 }
1263 vid = symbolic_name_to_rid(zFlag, "ci");
1264 if(0==vid){
1265 rc = 404;
1266 fail((pErr,"Could not resolve checkin version."));
1267 }
1268 p->zParentUuid = rid_to_uuid(vid)/*fully expand it*/;
1269
1270 zFileUuid = fileedit_file_uuid(p->zFilename, vid, &p->filePerm);
1271 if(!zFileUuid){
1272 rc = 404;
1273 fail((pErr,"Checkin [%S] does not contain file: "
1274 "[%h]", p->zParentUuid, p->zFilename));
1275 }else if(PERM_LNK==p->filePerm){
1276 rc = 400;
1277 fail((pErr,"Editing symlinks is not permitted."));
1278 }
1279
1280 /* Find the repo-side file entry or fail... */
1281 frid = fast_uuid_to_rid(zFileUuid);
1282 assert(frid);
1283
1284 /* Read file content from submit request or repo... */
1285 zFlag = P("content");
1286 if(zFlag==0){
1287 content_get(frid, &p->fileContent);
1288 }else{
1289 blob_init(&p->fileContent,zFlag,-1);
1290 }
1291 if(looks_like_binary(&p->fileContent)){
1292 rc = 400;
1293 fail((pErr,"File appears to be binary. Cannot edit: "
1294 "[%h]",p->zFilename));
1295 }
1296
1297 zFlag = PT("comment");
1298 if(zFlag!=0 && *zFlag!=0){
1299 blob_append(&p->comment, zFlag, -1);
1300 }
1301 zFlag = P("comment_mimetype");
1302 if(zFlag){
1303 p->zCommentMimetype = mprintf("%s",zFlag);
1304 zFlag = 0;
1305 }
1306 p->zUser = mprintf("%s",g.zLogin);
1307 #define p_int(K) atoi(PD(K,"0"))
1308 if(p_int("dry_run")!=0){
1309 p->flags |= CIMINI_DRY_RUN;
1310 }
1311 if(p_int("allow_fork")!=0){
1312 p->flags |= CIMINI_ALLOW_FORK;
1313 }
1314 if(p_int("allow_older")!=0){
1315 p->flags |= CIMINI_ALLOW_OLDER;
1316 }
1317 if(p_int("exec_bit")!=0){
1318 p->filePerm = PERM_EXE;
1319 }
1320 if(p_int("allow_merge_conflict")!=0){
1321 p->flags |= CIMINI_ALLOW_MERGE_MARKER;
1322 }
1323 if(p_int("prefer_delta")!=0){
1324 p->flags |= CIMINI_PREFER_DELTA;
1325 }
1326
1327 /* EOL conversion policy... */
1328 {
1329 const int eolMode = p_int("eol");
1330 switch(eolMode){
1331 case 1: p->flags |= CIMINI_CONVERT_EOL_UNIX; break;
1332 case 2: p->flags |= CIMINI_CONVERT_EOL_WINDOWS; break;
1333 default: p->flags |= CIMINI_CONVERT_EOL_INHERIT; break;
1334 }
1335 }
1336 #undef p_int
1337 /*
1338 ** TODO?: date-override date selection field. Maybe use
1339 ** an input[type=datetime-local].
1340 */
1341 return 0;
1342 end_fail:
1343 #undef fail
1344 fossil_free(zFileUuid);
1345 return rc ? rc : 500;
1346 }
1347
1348 /*
1349 ** WEBPAGE: fileedit_commit
1350 **
1351 ** Required query parameters:
1352 **
1353 ** file=FILENAME
1354 ** r=Parent checkin UUID
1355 ** content=text
1356 ** comment=text
1357 **
1358 ** Optional query parameters:
1359 **
1360 ** comment_mimetype=text
1361 ** dry_run=int (1 or 0)
1362 **
1363 **
1364 ** User must have Write access to use this page.
1365 **
1366 ** Responds with JSON:
1367 **
1368 ** {uuid: newUUID,
1369 ** manifest: text of manifest,
1370 ** dryRun: bool
1371 ** }
1372 **
1373 ** On error it produces a JSON response as documented for
1374 ** fileedit_ajax_error().
1375 */
1376 void fileedit_ajax_commit(){
1377 int newVid = 0;
1378 Blob err = empty_blob;
1379 Blob manifest = empty_blob;
1380 CheckinMiniInfo cimi;
1381 int rc;
1382 char * zNewUuid = 0;
1383
1384 if(!fileedit_ajax_boostrap()){
1385 goto end_cleanup;
1386 }
1387 db_begin_transaction();
1388 CheckinMiniInfo_init(&cimi);
1389 rc = fileedit_setup_cimi_from_p(&cimi,&err);
1390 if(0!=rc){
1391 fileedit_ajax_error(rc,"%b",&err);
1392 goto end_cleanup;
1393 }
1394 if(blob_size(&cimi.comment)==0){
1395 fileedit_ajax_error(400,"Empty checkin comment is not permitted.");
1396 goto end_cleanup;
1397 }
1398 cimi.pMfOut = &manifest;
1399 checkin_mini(&cimi, &newVid, &err);
1400 if(blob_size(&err)){
1401 fileedit_ajax_error(500,"%b",&err);
1402 goto end_cleanup;
1403 }
1404 assert(newVid>0);
1405 zNewUuid = rid_to_uuid(newVid);
1406 cgi_set_content_type("application/json");
1407 CX("{");
1408 CX("\"uuid\":\"%j\",", zNewUuid);
1409 CX("\"dryRun\": %s,",
1410 (CIMINI_DRY_RUN & cimi.flags) ? "true" : "false");
1411 CX("\"manifest\": \"%j\"", blob_str(&manifest));
1412 CX("}");
1413 db_end_transaction(0);
1414 end_cleanup:
1415 fossil_free(zNewUuid);
1416 blob_reset(&err);
1417 blob_reset(&manifest);
1418 CheckinMiniInfo_cleanup(&cimi);
1419 }
1420
1421
1422 /*
1423 ** Emits utility script code specific to the /fileedit page.
1424 */
1425 static void fileedit_emit_page_script(){
@@ -1247,34 +1443,21 @@
1443 ** All other parameters are for internal use only, submitted via the
1444 ** form-submission process, and may change with any given revision of
1445 ** this code.
1446 */
1447 void fileedit_page(){
1448 const char * zFilename; /* filename. We'll accept 'name'
 
 
 
 
 
 
1449 because that param is handled
1450 specially by the core. */
1451 const char * zRev; /* checkin version */
 
 
1452 const char * zFileMime = 0; /* File mime type guess */
1453 CheckinMiniInfo cimi; /* Checkin state */
 
 
 
 
1454 int previewHtmlHeight = 0; /* iframe height (EMs) */
1455 int previewRenderMode = FE_RENDER_GUESS; /* preview mode */
1456 char * zFileUuid = 0; /* File content UUID */
1457 Blob err = empty_blob; /* Error report */
1458 Blob submitResult = empty_blob; /* Error report */
 
1459 Blob endScript = empty_blob; /* Script code to run at the
1460 end. This content will be
1461 combined into a single JS
1462 function call, thus each
1463 entry must end with a
@@ -1287,103 +1470,26 @@
1470 login_needed(g.anon.Write);
1471 return;
1472 }
1473 db_begin_transaction();
1474 CheckinMiniInfo_init(&cimi);
 
 
 
 
 
 
 
 
 
 
1475
1476 style_header("File Editor");
1477 /* As of this point, don't use return or fossil_fatal(), use
1478 ** fail((&err,...)) instead so that we can be sure to do any
1479 ** cleanup and end the transaction cleanly.
1480 */
1481 if(fileedit_setup_cimi_from_p(&cimi, &err)!=0){
1482 goto end_footer;
1483 }
1484 zFilename = cimi.zFilename;
1485 zRev = cimi.zParentUuid;
1486
1487 assert(zRev);
1488 assert(zFilename);
1489 zFileMime = mimetype_from_name(cimi.zFilename);
1490
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1491 /********************************************************************
1492 ** All errors which "could" have happened up to this point are of a
1493 ** degree which keep us from rendering the rest of the page, and
1494 ** thus fail() has already skipped to the end of the page to render
1495 ** the errors. Any up-coming errors, barring malloc failure or
@@ -1396,54 +1502,10 @@
1502 ** SAVE, we can capture all of the output, and thus can perform that
1503 ** work before rendering, which is important so that we have the
1504 ** proper version information when rendering the rest of the page.
1505 ********************************************************************/
1506 #undef fail
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1507 CX("<h1>Editing:</h1>");
1508 CX("<p class='fileedit-hint'>");
1509 CX("File: "
1510 "[<a id='finfo-link' href='#'>info</a>] "
1511 /* %R/finfo?name=%T&m=%!S */
@@ -1480,45 +1542,56 @@
1542 CX("<h3>File Content</h3>\n");
1543 CX("<textarea name='content' id='fileedit-content' "
1544 "rows='20' cols='80'>");
1545 CX("Loading...");
1546 CX("</textarea>\n");
1547
1548 CX("<div id='fossil-status-bar'>Async. status messages will go "
1549 "here.</div>\n");
1550
1551 /******* Flags/options *******/
1552 CX("<fieldset class='fileedit-options' id='options'>"
1553 "<legend>Options</legend><div>"
1554 /* Chrome does not sanely lay out multiple
1555 ** fieldset children after the <legend>, so
1556 ** a containing div is necessary. */);
1557 style_labeled_checkbox("cb-dry-run",
1558 "dry_run", "Dry-run?", "1",
1559 "In dry-run mode, the Save button performs "
1560 "all work needed for saving but then rolls "
1561 "back the transaction, and thus does not "
1562 "really save.",
1563 1);
1564 style_labeled_checkbox("cb-allow-fork",
1565 "allow_fork", "Allow fork?", "1",
1566 "Allow saving to create a fork?",
1567 cimi.flags & CIMINI_ALLOW_FORK);
1568 style_labeled_checkbox("cb-allow-older",
1569 "allow_older", "Allow older?", "1",
1570 "Allow saving against a parent version "
1571 "which has a newer timestamp?",
1572 cimi.flags & CIMINI_ALLOW_OLDER);
1573 style_labeled_checkbox("cb-exec-bit",
1574 "exec_bit", "Executable?", "1",
1575 "Set the executable bit?",
1576 PERM_EXE==cimi.filePerm);
1577 style_labeled_checkbox("cb-allow-merge-conflict",
1578 "allow_merge_conflict",
1579 "Allow merge conflict markers?", "1",
1580 "Allow saving even if the content contains "
1581 "what appear to be fossil merge conflict "
1582 "markers?",
1583 cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
1584 style_labeled_checkbox("cb-prefer-delta",
1585 "prefer_delta",
1586 "Prefer delta manifest?", "1",
1587 "Will create a delta manifest, instead of "
1588 "baseline, if conditions are favorable to do "
1589 "so. This option is only a suggestion.",
1590 cimi.flags & CIMINI_PREFER_DELTA);
1591 style_select_list_int("select-eol-style",
1592 "eol", "EOL Style",
1593 "EOL conversion policy, noting that "
1594 "form-processing may implicitly change the "
1595 "line endings of the input.",
1596 (cimi.flags & CIMINI_CONVERT_EOL_UNIX)
1597 ? 1 : (cimi.flags & CIMINI_CONVERT_EOL_WINDOWS
@@ -1534,12 +1607,12 @@
1607 CX("<a id='comment'></a>");
1608 CX("<fieldset><legend>Commit message</legend><div>");
1609 CX("<textarea name='comment' rows='3' cols='80'>");
1610 /* ^^^ adding the 'required' attribute means we cannot even submit
1611 ** for PREVIEW mode if it's empty :/. */
1612 if(blob_size(&cimi.comment)){
1613 CX("%h", blob_str(&cimi.comment));
1614 }
1615 CX("</textarea>\n");
1616 CX("<div class='fileedit-hint'>Comments use the Fossil wiki markup "
1617 "syntax.</div>\n"/*TODO: select for fossil/md/plain text*/);
1618 CX("</div></fieldset>\n");
@@ -1547,61 +1620,74 @@
1620 /******* Buttons *******/
1621 CX("<a id='buttons'></a>");
1622 CX("<fieldset class='fileedit-options'>"
1623 "<legend>Ask the server to...</legend><div>");
1624 CX("<button id='fileedit-btn-commit'>Commit</button>");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1625 CX("<button id='fileedit-btn-diffsbs'>Diff (SBS)</button>");
1626 CX("<button id='fileedit-btn-diffu'>Diff (Unified)</button>");
1627 CX("<button id='fileedit-btn-preview'>Preview</button>");
1628 /* Default preview rendering mode selection... */
1629 previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime);
1630 style_select_list_int("select-preview-mode",
1631 "preview_render_mode",
1632 "Preview Mode",
1633 "Preview mode format.",
1634 previewRenderMode,
1635 "Guess", FE_RENDER_GUESS,
1636 "Wiki/Markdown", FE_RENDER_WIKI,
1637 "HTML (iframe)", FE_RENDER_HTML,
1638 "Plain Text", FE_RENDER_PLAIN_TEXT,
1639 NULL);
1640 /*
1641 ** Set up a JS-side mapping of the FE_RENDER_xyz values
1642 */
1643 blob_appendf(&endScript, "fossil.page.previewModes={"
1644 "guess: %d, %d: 'guess', wiki: %d, %d: 'wiki',"
1645 "html: %d, %d: 'html', text: %d, %d: 'text'"
1646 "};\n",
1647 FE_RENDER_GUESS, FE_RENDER_GUESS,
1648 FE_RENDER_WIKI, FE_RENDER_WIKI,
1649 FE_RENDER_HTML, FE_RENDER_HTML,
1650 FE_RENDER_PLAIN_TEXT, FE_RENDER_PLAIN_TEXT);
1651
1652 /* Allow selection of HTML preview iframe height */
1653 previewHtmlHeight = atoi(PD("preview_html_ems","0"));
1654 if(!previewHtmlHeight){
1655 previewHtmlHeight = 40;
1656 }
1657 style_select_list_int("select-preview-html-ems",
1658 "preview_html_ems",
1659 "HTML Preview IFrame Height (EMs)",
1660 "Height (in EMs) of the iframe used for "
1661 "HTML preview",
1662 previewHtmlHeight,
1663 "", 20, "", 40,
1664 "", 60, "", 80,
1665 "", 100, NULL);
1666 /* Selection of line numbers for text preview */
1667 style_labeled_checkbox("cb-line-numbers",
1668 "preview_ln",
1669 "Add line numbers to plain-text previews?",
1670 "1",
1671 "If on, plain-text files (only) will get "
1672 "line numbers added to the preview.",
1673 P("preview_ln")!=0);
1674
1675 CX("</div></fieldset>");
1676
1677 /******* End of form *******/
1678 CX("</form>\n");
1679
1680 CX("<div id='ajax-target'>%s</div>"
1681 /* this is where preview/diff go */);
1682
1683 /* Dynamically populate the editor... */
1684 blob_appendf(&endScript,
1685 "fossil.page.loadFile('%j','%j');",
1686 zFilename, cimi.zParentUuid);
1687
1688 end_footer:
 
1689 fossil_free(zFileUuid);
1690 if(stmt.pStmt){
1691 db_finalize(&stmt);
1692 }
1693 if(blob_size(&err)){
1694
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -17,11 +17,11 @@
1717
window.fossil.message = function f(msg){
1818
const args = Array.prototype.slice.call(arguments,0);
1919
const tgt = f.targetElement;
2020
if(tgt){
2121
tgt.classList.remove('error');
22
- tgt.innerText = msg || args.join(' ');
22
+ tgt.innerText = args.join(' ');
2323
}
2424
else{
2525
args.unshift('Fossil status:');
2626
console.debug.apply(console,args);
2727
}
@@ -43,13 +43,13 @@
4343
window.fossil.error = function f(msg){
4444
const args = Array.prototype.slice.call(arguments,0);
4545
const tgt = window.fossil.message.targetElement;
4646
if(tgt){
4747
tgt.classList.add('error');
48
- tgt.innerText = msg || args.join(' ');
48
+ tgt.innerText = args.join(' ');
4949
}
5050
else{
5151
args.unshift('Fossil error:');
5252
console.error.apply(console,args);
5353
}
5454
return this;
5555
};
5656
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -17,11 +17,11 @@
17 window.fossil.message = function f(msg){
18 const args = Array.prototype.slice.call(arguments,0);
19 const tgt = f.targetElement;
20 if(tgt){
21 tgt.classList.remove('error');
22 tgt.innerText = msg || args.join(' ');
23 }
24 else{
25 args.unshift('Fossil status:');
26 console.debug.apply(console,args);
27 }
@@ -43,13 +43,13 @@
43 window.fossil.error = function f(msg){
44 const args = Array.prototype.slice.call(arguments,0);
45 const tgt = window.fossil.message.targetElement;
46 if(tgt){
47 tgt.classList.add('error');
48 tgt.innerText = msg || args.join(' ');
49 }
50 else{
51 args.unshift('Fossil error:');
52 console.error.apply(console,args);
53 }
54 return this;
55 };
56
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -17,11 +17,11 @@
17 window.fossil.message = function f(msg){
18 const args = Array.prototype.slice.call(arguments,0);
19 const tgt = f.targetElement;
20 if(tgt){
21 tgt.classList.remove('error');
22 tgt.innerText = args.join(' ');
23 }
24 else{
25 args.unshift('Fossil status:');
26 console.debug.apply(console,args);
27 }
@@ -43,13 +43,13 @@
43 window.fossil.error = function f(msg){
44 const args = Array.prototype.slice.call(arguments,0);
45 const tgt = window.fossil.message.targetElement;
46 if(tgt){
47 tgt.classList.add('error');
48 tgt.innerText = args.join(' ');
49 }
50 else{
51 args.unshift('Fossil error:');
52 console.error.apply(console,args);
53 }
54 return this;
55 };
56
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -1,25 +1,26 @@
11
(function(){
22
"use strict";
33
/**
4
- Code for the /filepage app. Requires that the fossil JS bootstrappin
5
- is complete and fossil.fetch() has been installed.
4
+ Code for the /filepage app. Requires that the fossil JS
5
+ bootstrapping is complete and fossil.fetch() has been installed.
66
*/
7
-
87
const E = (s)=>document.querySelector(s);
9
-
10
-
118
window.addEventListener("load", function() {
129
const P = fossil.page;
1310
P.e = {
1411
editor: E('#fileedit-content'),
1512
ajaxContentTarget: E('#ajax-target'),
1613
form: E('#fileedit-form'),
1714
btnPreview: E("#fileedit-btn-preview"),
1815
btnDiffSbs: E("#fileedit-btn-diffsbs"),
1916
btnDiffU: E("#fileedit-btn-diffu"),
20
- btnCommit: E("#fileedit-btn-commit")
17
+ btnCommit: E("#fileedit-btn-commit"),
18
+ selectPreviewModeWrap: E('#select-preview-mode'),
19
+ selectHtmlEmsWrap: E('#select-preview-html-ems'),
20
+ selectEolWrap: E('#select-preview-html-ems'),
21
+ cbLineNumbersWrap: E('#cb-line-numbers')
2122
};
2223
const stopEvent = function(e){
2324
e.preventDefault();
2425
e.stopPropagation();
2526
return P;
@@ -36,10 +37,44 @@
3637
"click",(e)=>stopEvent(e).diff(true),false
3738
);
3839
P.e.btnDiffU.addEventListener(
3940
"click",(e)=>stopEvent(e).diff(false), false
4041
);
42
+ P.e.btnCommit.addEventListener(
43
+ "click",(e)=>stopEvent(e).commit(), false
44
+ );
45
+
46
+ /**
47
+ Cosmetic: jump through some hoops to enable/disable
48
+ certain preview options depending on the current
49
+ preview mode...
50
+ */
51
+ const selectPreviewMode =
52
+ P.e.selectPreviewModeWrap.querySelector('select');
53
+ console.debug('selectPreviewMode =',selectPreviewMode);
54
+ selectPreviewMode.addEventListener(
55
+ "change",function(e){
56
+ const mode = e.target.value,
57
+ name = P.previewModes[mode],
58
+ hide = [], unhide = [];
59
+ if('guess'===name){
60
+ unhide.push(P.e.cbLineNumbersWrap,
61
+ P.e.selectHtmlEmsWrap);
62
+ }else{
63
+ if('text'!==name) hide.push(P.e.cbLineNumbersWrap);
64
+ else unhide.push(P.e.cbLineNumbersWrap);
65
+ if('html'!==name) hide.push(P.e.selectHtmlEmsWrap);
66
+ else unhide.push(P.e.selectHtmlEmsWrap);
67
+ }
68
+ hide.forEach((e)=>e.classList.add('hidden'));
69
+ unhide.forEach((e)=>e.classList.remove('hidden'));
70
+ }, false
71
+ );
72
+ selectPreviewMode.dispatchEvent(
73
+ // Force UI update
74
+ new Event('change',{target:selectPreviewMode})
75
+ );
4176
}, false);
4277
4378
4479
4580
/**
@@ -172,6 +207,84 @@
172207
onload: updateView
173208
});
174209
return this;
175210
};
176211
212
+ /**
213
+ Performs an async commit based on the form contents and updates
214
+ the UI.
215
+
216
+ Returns this object.
217
+ */
218
+ fossil.page.commit = function f(){
219
+ if(!this.finfo){
220
+ fossil.error("No content is loaded.");
221
+ return this;
222
+ }
223
+ const self = this;
224
+ const content = this.e.editor.value,
225
+ target = this.e.ajaxContentTarget,
226
+ cbDryRun = E('[name=dry_run]'),
227
+ isDryRun = cbDryRun.checked,
228
+ filename = this.finfo.file;
229
+ if(!f.updateView){
230
+ f.updateView = function(c){
231
+ target.innerHTML = [
232
+ "<h3>Manifest",
233
+ (c.dryRun?" (dry run)":""),
234
+ ": ", c.uuid.substring(0,16),"</h3>",
235
+ "<code class='fileedit-manifest'>",
236
+ c.manifest,
237
+ "</code></pre>"
238
+ ].join('');
239
+ fossil.message(
240
+ c.dryRun ? 'Committed (dry run):' : 'Committed:',
241
+ c.uuid
242
+ );
243
+ if(!c.dryRun){
244
+ cbDryRun.checked = true;
245
+ fossil.page.updateVersion(filename, c.uuid);
246
+ }
247
+ };
248
+ }
249
+ if(!content){
250
+ f.updateView('');
251
+ return this;
252
+ }
253
+ const fd = new FormData();
254
+ fd.append('file',filename);
255
+ fd.append('r', this.finfo.r);
256
+ fd.append('content',content);
257
+ fd.append('dry_run',isDryRun ? 1 : 0);
258
+ /* Text fields or select lists... */
259
+ ['comment_mimetype',
260
+ 'comment'
261
+ ].forEach(function(name){
262
+ var e = E('[name='+name+']');
263
+ if(e) fd.append(name,e.value);
264
+ });
265
+ /* Checkboxes: */
266
+ ['allow_fork',
267
+ 'allow_older',
268
+ 'exec_bit',
269
+ 'allow_merge_conflict',
270
+ 'prefer_delta'
271
+ ].forEach(function(name){
272
+ var e = E('[name='+name+']');
273
+ if(e){
274
+ if(e.checked) fd.append(name, 1);
275
+ }else{
276
+ console.error("Missing checkbox? name =",name);
277
+ }
278
+ });
279
+ fossil.message(
280
+ "Checking in..."
281
+ ).fetch('fileedit_commit',{
282
+ payload: fd,
283
+ responseType: 'json',
284
+ onload: f.updateView
285
+ });
286
+ return this;
287
+ };
288
+
289
+
177290
})();
178291
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -1,25 +1,26 @@
1 (function(){
2 "use strict";
3 /**
4 Code for the /filepage app. Requires that the fossil JS bootstrappin
5 is complete and fossil.fetch() has been installed.
6 */
7
8 const E = (s)=>document.querySelector(s);
9
10
11 window.addEventListener("load", function() {
12 const P = fossil.page;
13 P.e = {
14 editor: E('#fileedit-content'),
15 ajaxContentTarget: E('#ajax-target'),
16 form: E('#fileedit-form'),
17 btnPreview: E("#fileedit-btn-preview"),
18 btnDiffSbs: E("#fileedit-btn-diffsbs"),
19 btnDiffU: E("#fileedit-btn-diffu"),
20 btnCommit: E("#fileedit-btn-commit")
 
 
 
 
21 };
22 const stopEvent = function(e){
23 e.preventDefault();
24 e.stopPropagation();
25 return P;
@@ -36,10 +37,44 @@
36 "click",(e)=>stopEvent(e).diff(true),false
37 );
38 P.e.btnDiffU.addEventListener(
39 "click",(e)=>stopEvent(e).diff(false), false
40 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41 }, false);
42
43
44
45 /**
@@ -172,6 +207,84 @@
172 onload: updateView
173 });
174 return this;
175 };
176
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177 })();
178
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -1,25 +1,26 @@
1 (function(){
2 "use strict";
3 /**
4 Code for the /filepage app. Requires that the fossil JS
5 bootstrapping is complete and fossil.fetch() has been installed.
6 */
 
7 const E = (s)=>document.querySelector(s);
 
 
8 window.addEventListener("load", function() {
9 const P = fossil.page;
10 P.e = {
11 editor: E('#fileedit-content'),
12 ajaxContentTarget: E('#ajax-target'),
13 form: E('#fileedit-form'),
14 btnPreview: E("#fileedit-btn-preview"),
15 btnDiffSbs: E("#fileedit-btn-diffsbs"),
16 btnDiffU: E("#fileedit-btn-diffu"),
17 btnCommit: E("#fileedit-btn-commit"),
18 selectPreviewModeWrap: E('#select-preview-mode'),
19 selectHtmlEmsWrap: E('#select-preview-html-ems'),
20 selectEolWrap: E('#select-preview-html-ems'),
21 cbLineNumbersWrap: E('#cb-line-numbers')
22 };
23 const stopEvent = function(e){
24 e.preventDefault();
25 e.stopPropagation();
26 return P;
@@ -36,10 +37,44 @@
37 "click",(e)=>stopEvent(e).diff(true),false
38 );
39 P.e.btnDiffU.addEventListener(
40 "click",(e)=>stopEvent(e).diff(false), false
41 );
42 P.e.btnCommit.addEventListener(
43 "click",(e)=>stopEvent(e).commit(), false
44 );
45
46 /**
47 Cosmetic: jump through some hoops to enable/disable
48 certain preview options depending on the current
49 preview mode...
50 */
51 const selectPreviewMode =
52 P.e.selectPreviewModeWrap.querySelector('select');
53 console.debug('selectPreviewMode =',selectPreviewMode);
54 selectPreviewMode.addEventListener(
55 "change",function(e){
56 const mode = e.target.value,
57 name = P.previewModes[mode],
58 hide = [], unhide = [];
59 if('guess'===name){
60 unhide.push(P.e.cbLineNumbersWrap,
61 P.e.selectHtmlEmsWrap);
62 }else{
63 if('text'!==name) hide.push(P.e.cbLineNumbersWrap);
64 else unhide.push(P.e.cbLineNumbersWrap);
65 if('html'!==name) hide.push(P.e.selectHtmlEmsWrap);
66 else unhide.push(P.e.selectHtmlEmsWrap);
67 }
68 hide.forEach((e)=>e.classList.add('hidden'));
69 unhide.forEach((e)=>e.classList.remove('hidden'));
70 }, false
71 );
72 selectPreviewMode.dispatchEvent(
73 // Force UI update
74 new Event('change',{target:selectPreviewMode})
75 );
76 }, false);
77
78
79
80 /**
@@ -172,6 +207,84 @@
207 onload: updateView
208 });
209 return this;
210 };
211
212 /**
213 Performs an async commit based on the form contents and updates
214 the UI.
215
216 Returns this object.
217 */
218 fossil.page.commit = function f(){
219 if(!this.finfo){
220 fossil.error("No content is loaded.");
221 return this;
222 }
223 const self = this;
224 const content = this.e.editor.value,
225 target = this.e.ajaxContentTarget,
226 cbDryRun = E('[name=dry_run]'),
227 isDryRun = cbDryRun.checked,
228 filename = this.finfo.file;
229 if(!f.updateView){
230 f.updateView = function(c){
231 target.innerHTML = [
232 "<h3>Manifest",
233 (c.dryRun?" (dry run)":""),
234 ": ", c.uuid.substring(0,16),"</h3>",
235 "<code class='fileedit-manifest'>",
236 c.manifest,
237 "</code></pre>"
238 ].join('');
239 fossil.message(
240 c.dryRun ? 'Committed (dry run):' : 'Committed:',
241 c.uuid
242 );
243 if(!c.dryRun){
244 cbDryRun.checked = true;
245 fossil.page.updateVersion(filename, c.uuid);
246 }
247 };
248 }
249 if(!content){
250 f.updateView('');
251 return this;
252 }
253 const fd = new FormData();
254 fd.append('file',filename);
255 fd.append('r', this.finfo.r);
256 fd.append('content',content);
257 fd.append('dry_run',isDryRun ? 1 : 0);
258 /* Text fields or select lists... */
259 ['comment_mimetype',
260 'comment'
261 ].forEach(function(name){
262 var e = E('[name='+name+']');
263 if(e) fd.append(name,e.value);
264 });
265 /* Checkboxes: */
266 ['allow_fork',
267 'allow_older',
268 'exec_bit',
269 'allow_merge_conflict',
270 'prefer_delta'
271 ].forEach(function(name){
272 var e = E('[name='+name+']');
273 if(e){
274 if(e.checked) fd.append(name, 1);
275 }else{
276 console.error("Missing checkbox? name =",name);
277 }
278 });
279 fossil.message(
280 "Checking in..."
281 ).fetch('fileedit_commit',{
282 payload: fd,
283 responseType: 'json',
284 onload: f.updateView
285 });
286 return this;
287 };
288
289
290 })();
291
+30 -15
--- src/style.c
+++ src/style.c
@@ -1297,37 +1297,43 @@
12971297
#if INTERFACE
12981298
# define webpage_assert(T) if(!(T)){webpage_assert_page(__FILE__,__LINE__,#T);}
12991299
#endif
13001300
13011301
/*
1302
-** Outputs a labeled checkbox element. zFieldName is the form element
1303
-** name. zLabel is the label for the checkbox. zValue is the optional
1304
-** value for the checkbox. zTip is an optional tooltip, which gets set
1305
-** as the "title" attribute of the outermost element. If isChecked is
1306
-** true, the checkbox gets the "checked" attribute set, else it is
1307
-** not.
1302
+** Outputs a labeled checkbox element. zWrapperId is an optional ID
1303
+** value for the containing element (see below). zFieldName is the
1304
+** form element name. zLabel is the label for the checkbox. zValue is
1305
+** the optional value for the checkbox. zTip is an optional tooltip,
1306
+** which gets set as the "title" attribute of the outermost
1307
+** element. If isChecked is true, the checkbox gets the "checked"
1308
+** attribute set, else it is not.
13081309
**
13091310
** Resulting structure:
13101311
**
1311
-** <div class='input-with-label' title={{zTip}}>
1312
+** <div class='input-with-label' title={{zTip}} id={{zWrapperId}}>
13121313
** <input type='checkbox' name={{zFieldName}} value={{zValue}}
13131314
** {{isChecked ? " checked : ""}}/>
13141315
** <span>{{zLabel}}</span>
13151316
** </div>
13161317
**
1317
-** zFieldName, zLabel, and zValue are required. zTip is optional.
1318
+** zFieldName, zLabel, and zValue are required. zWrapperId and zTip
1319
+** are optional.
13181320
**
13191321
** Be sure that the input-with-label CSS class is defined sensibly, in
13201322
** particular, having its display:inline-block is useful for alignment
13211323
** purposes.
13221324
*/
1323
-void style_labeled_checkbox(const char *zFieldName, const char * zLabel,
1325
+void style_labeled_checkbox(const char * zWrapperId,
1326
+ const char *zFieldName, const char * zLabel,
13241327
const char * zValue, const char * zTip,
13251328
int isChecked){
13261329
CX("<div class='input-with-label'");
13271330
if(zTip && *zTip){
13281331
CX(" title='%h'", zTip);
1332
+ }
1333
+ if(zWrapperId && *zWrapperId){
1334
+ CX(" id='%s'",zWrapperId);
13291335
}
13301336
CX("><input type='checkbox' name='%s' value='%T'%s/>",
13311337
zFieldName,
13321338
zValue ? zValue : "", isChecked ? " checked" : "");
13331339
CX("<span>%h</span></div>", zLabel);
@@ -1347,41 +1353,50 @@
13471353
**
13481354
** Note that the pairs are not in (int, const char *) order because
13491355
** there is no well-known integer value which we can definitively use
13501356
** as a list terminator.
13511357
**
1352
-** zFieldName is the value of the form element's name attribute.
1358
+** zWrapperId is an optional ID value for the containing element (see
1359
+** below).
1360
+**
1361
+** zFieldName is the value of the form element's name attribute. Note
1362
+** that fossil prefers underscores over '-' for separators in form
1363
+** element names.
13531364
**
13541365
** zLabel is an optional string to use as a "label" for the element
13551366
** (see below).
13561367
**
13571368
** zTooltip is an optional value for the SELECT's title attribute.
13581369
**
13591370
** The structure of the emitted HTML is:
13601371
**
1361
-** <div class='input-with-label' title={{zToolTip}}>
1372
+** <div class='input-with-label' title={{zToolTip}} id={{zWrapperId}}>
13621373
** <span>{{zLabel}}</span>
13631374
** <select>...</select>
13641375
** </div>
13651376
**
13661377
** Example:
13671378
**
1368
-** style_select_list_int("my_field", "Grapes",
1379
+** style_select_list_int("my-grapes", "my_grapes", "Grapes",
13691380
** "Select the number of grapes",
13701381
** atoi(PD("my_field","0")),
13711382
** "", 1, "2", 2, "Three", 3,
13721383
** NULL);
13731384
**
13741385
*/
1375
-void style_select_list_int(const char *zFieldName, const char * zLabel,
1386
+void style_select_list_int(const char * zWrapperId,
1387
+ const char *zFieldName, const char * zLabel,
13761388
const char * zToolTip, int selectedVal,
13771389
... ){
13781390
va_list vargs;
13791391
va_start(vargs,selectedVal);
1380
- CX("<div class='input-with-label'");
1392
+ CX("<span class='input-with-label'");
13811393
if(zToolTip && *zToolTip){
13821394
CX(" title='%h'",zToolTip);
1395
+ }
1396
+ if(zWrapperId && *zWrapperId){
1397
+ CX(" id='%s'",zWrapperId);
13831398
}
13841399
CX(">");
13851400
if(zLabel && *zLabel){
13861401
CX("<span>%h</span>", zLabel);
13871402
}
@@ -1402,11 +1417,11 @@
14021417
}
14031418
CX("</option>\n");
14041419
}
14051420
CX("</select>\n");
14061421
if(zLabel && *zLabel){
1407
- CX("</div>\n");
1422
+ CX("</span>\n");
14081423
}
14091424
va_end(vargs);
14101425
}
14111426
14121427
14131428
--- src/style.c
+++ src/style.c
@@ -1297,37 +1297,43 @@
1297 #if INTERFACE
1298 # define webpage_assert(T) if(!(T)){webpage_assert_page(__FILE__,__LINE__,#T);}
1299 #endif
1300
1301 /*
1302 ** Outputs a labeled checkbox element. zFieldName is the form element
1303 ** name. zLabel is the label for the checkbox. zValue is the optional
1304 ** value for the checkbox. zTip is an optional tooltip, which gets set
1305 ** as the "title" attribute of the outermost element. If isChecked is
1306 ** true, the checkbox gets the "checked" attribute set, else it is
1307 ** not.
 
1308 **
1309 ** Resulting structure:
1310 **
1311 ** <div class='input-with-label' title={{zTip}}>
1312 ** <input type='checkbox' name={{zFieldName}} value={{zValue}}
1313 ** {{isChecked ? " checked : ""}}/>
1314 ** <span>{{zLabel}}</span>
1315 ** </div>
1316 **
1317 ** zFieldName, zLabel, and zValue are required. zTip is optional.
 
1318 **
1319 ** Be sure that the input-with-label CSS class is defined sensibly, in
1320 ** particular, having its display:inline-block is useful for alignment
1321 ** purposes.
1322 */
1323 void style_labeled_checkbox(const char *zFieldName, const char * zLabel,
 
1324 const char * zValue, const char * zTip,
1325 int isChecked){
1326 CX("<div class='input-with-label'");
1327 if(zTip && *zTip){
1328 CX(" title='%h'", zTip);
 
 
 
1329 }
1330 CX("><input type='checkbox' name='%s' value='%T'%s/>",
1331 zFieldName,
1332 zValue ? zValue : "", isChecked ? " checked" : "");
1333 CX("<span>%h</span></div>", zLabel);
@@ -1347,41 +1353,50 @@
1347 **
1348 ** Note that the pairs are not in (int, const char *) order because
1349 ** there is no well-known integer value which we can definitively use
1350 ** as a list terminator.
1351 **
1352 ** zFieldName is the value of the form element's name attribute.
 
 
 
 
 
1353 **
1354 ** zLabel is an optional string to use as a "label" for the element
1355 ** (see below).
1356 **
1357 ** zTooltip is an optional value for the SELECT's title attribute.
1358 **
1359 ** The structure of the emitted HTML is:
1360 **
1361 ** <div class='input-with-label' title={{zToolTip}}>
1362 ** <span>{{zLabel}}</span>
1363 ** <select>...</select>
1364 ** </div>
1365 **
1366 ** Example:
1367 **
1368 ** style_select_list_int("my_field", "Grapes",
1369 ** "Select the number of grapes",
1370 ** atoi(PD("my_field","0")),
1371 ** "", 1, "2", 2, "Three", 3,
1372 ** NULL);
1373 **
1374 */
1375 void style_select_list_int(const char *zFieldName, const char * zLabel,
 
1376 const char * zToolTip, int selectedVal,
1377 ... ){
1378 va_list vargs;
1379 va_start(vargs,selectedVal);
1380 CX("<div class='input-with-label'");
1381 if(zToolTip && *zToolTip){
1382 CX(" title='%h'",zToolTip);
 
 
 
1383 }
1384 CX(">");
1385 if(zLabel && *zLabel){
1386 CX("<span>%h</span>", zLabel);
1387 }
@@ -1402,11 +1417,11 @@
1402 }
1403 CX("</option>\n");
1404 }
1405 CX("</select>\n");
1406 if(zLabel && *zLabel){
1407 CX("</div>\n");
1408 }
1409 va_end(vargs);
1410 }
1411
1412
1413
--- src/style.c
+++ src/style.c
@@ -1297,37 +1297,43 @@
1297 #if INTERFACE
1298 # define webpage_assert(T) if(!(T)){webpage_assert_page(__FILE__,__LINE__,#T);}
1299 #endif
1300
1301 /*
1302 ** Outputs a labeled checkbox element. zWrapperId is an optional ID
1303 ** value for the containing element (see below). zFieldName is the
1304 ** form element name. zLabel is the label for the checkbox. zValue is
1305 ** the optional value for the checkbox. zTip is an optional tooltip,
1306 ** which gets set as the "title" attribute of the outermost
1307 ** element. If isChecked is true, the checkbox gets the "checked"
1308 ** attribute set, else it is not.
1309 **
1310 ** Resulting structure:
1311 **
1312 ** <div class='input-with-label' title={{zTip}} id={{zWrapperId}}>
1313 ** <input type='checkbox' name={{zFieldName}} value={{zValue}}
1314 ** {{isChecked ? " checked : ""}}/>
1315 ** <span>{{zLabel}}</span>
1316 ** </div>
1317 **
1318 ** zFieldName, zLabel, and zValue are required. zWrapperId and zTip
1319 ** are optional.
1320 **
1321 ** Be sure that the input-with-label CSS class is defined sensibly, in
1322 ** particular, having its display:inline-block is useful for alignment
1323 ** purposes.
1324 */
1325 void style_labeled_checkbox(const char * zWrapperId,
1326 const char *zFieldName, const char * zLabel,
1327 const char * zValue, const char * zTip,
1328 int isChecked){
1329 CX("<div class='input-with-label'");
1330 if(zTip && *zTip){
1331 CX(" title='%h'", zTip);
1332 }
1333 if(zWrapperId && *zWrapperId){
1334 CX(" id='%s'",zWrapperId);
1335 }
1336 CX("><input type='checkbox' name='%s' value='%T'%s/>",
1337 zFieldName,
1338 zValue ? zValue : "", isChecked ? " checked" : "");
1339 CX("<span>%h</span></div>", zLabel);
@@ -1347,41 +1353,50 @@
1353 **
1354 ** Note that the pairs are not in (int, const char *) order because
1355 ** there is no well-known integer value which we can definitively use
1356 ** as a list terminator.
1357 **
1358 ** zWrapperId is an optional ID value for the containing element (see
1359 ** below).
1360 **
1361 ** zFieldName is the value of the form element's name attribute. Note
1362 ** that fossil prefers underscores over '-' for separators in form
1363 ** element names.
1364 **
1365 ** zLabel is an optional string to use as a "label" for the element
1366 ** (see below).
1367 **
1368 ** zTooltip is an optional value for the SELECT's title attribute.
1369 **
1370 ** The structure of the emitted HTML is:
1371 **
1372 ** <div class='input-with-label' title={{zToolTip}} id={{zWrapperId}}>
1373 ** <span>{{zLabel}}</span>
1374 ** <select>...</select>
1375 ** </div>
1376 **
1377 ** Example:
1378 **
1379 ** style_select_list_int("my-grapes", "my_grapes", "Grapes",
1380 ** "Select the number of grapes",
1381 ** atoi(PD("my_field","0")),
1382 ** "", 1, "2", 2, "Three", 3,
1383 ** NULL);
1384 **
1385 */
1386 void style_select_list_int(const char * zWrapperId,
1387 const char *zFieldName, const char * zLabel,
1388 const char * zToolTip, int selectedVal,
1389 ... ){
1390 va_list vargs;
1391 va_start(vargs,selectedVal);
1392 CX("<span class='input-with-label'");
1393 if(zToolTip && *zToolTip){
1394 CX(" title='%h'",zToolTip);
1395 }
1396 if(zWrapperId && *zWrapperId){
1397 CX(" id='%s'",zWrapperId);
1398 }
1399 CX(">");
1400 if(zLabel && *zLabel){
1401 CX("<span>%h</span>", zLabel);
1402 }
@@ -1402,11 +1417,11 @@
1417 }
1418 CX("</option>\n");
1419 }
1420 CX("</select>\n");
1421 if(zLabel && *zLabel){
1422 CX("</span>\n");
1423 }
1424 va_end(vargs);
1425 }
1426
1427
1428

Keyboard Shortcuts

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