Fossil

Check-in [1a6c5090ce]
Login

Check-in [1a6c5090ce]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Ajaxified commit. All that's left is cleanup and prettification.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | fileedit-ajaxify
Files: files | file ages | folders
SHA3-256: 1a6c5090ce31697a4f5d92806dff0bab9b53ae20d4ef9f8530fa184b9af60a73
User & Date: stephan 2020-05-05 06:48:52.949
Context
2020-05-05
08:41
Numerous minor cleanups. ... (check-in: f54ac21745 user: stephan tags: fileedit-ajaxify)
06:48
Ajaxified commit. All that's left is cleanup and prettification. ... (check-in: 1a6c5090ce user: stephan tags: fileedit-ajaxify)
04:06
Initial work on ajaxifying /fileedit. Fetching content, preview, and diffs are ajax'd, but save is not yet. ... (check-in: 8edf9dbfc2 user: stephan tags: fileedit-ajaxify)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/default_css.txt.
900
901
902
903
904
905
906

907
908
909
910
911
912
913
  padding: 0.5em;
  border-radius: 0.5em;
}
code.fileedit-manifest {
  display: block;
  height: 16em;
  overflow: auto;

}
div.fileedit-preview {
  margin: 0;
  padding: 0;
}
.fileedit-preview > div:first-child {
  margin: 1em 0 0 0;







>







900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
  padding: 0.5em;
  border-radius: 0.5em;
}
code.fileedit-manifest {
  display: block;
  height: 16em;
  overflow: auto;
  white-space: pre;
}
div.fileedit-preview {
  margin: 0;
  padding: 0;
}
.fileedit-preview > div:first-child {
  margin: 1em 0 0 0;
963
964
965
966
967
968
969



.input-with-label > input[type=radio] {
    vertical-align: sub;
}
.input-with-label > span {
    margin: 0 0.25em 0 0.25em;
    vertical-align: middle;
}










>
>
>
964
965
966
967
968
969
970
971
972
973
.input-with-label > input[type=radio] {
    vertical-align: sub;
}
.input-with-label > span {
    margin: 0 0.25em 0 0.25em;
    vertical-align: middle;
}
.hidden {
  display: none;
}
Changes to src/fileedit.c.
1218
1219
1220
1221
1222
1223
1224




































































































































































































1225
1226
1227
1228
1229
1230
1231
  cgi_set_content_type("text/html");
  blob_init(&content, zContent, -1);
  fileedit_render_diff(&content, frid, zRevUuid, isSbs);
  fossil_free(zRevUuid);
  blob_reset(&content);
}






































































































































































































/*
** Emits utility script code specific to the /fileedit page.
*/
static void fileedit_emit_page_script(){
  style_emit_script_tag(0);
  CX("%s\n", builtin_text("fossil.page.fileedit.js"));







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
  cgi_set_content_type("text/html");
  blob_init(&content, zContent, -1);
  fileedit_render_diff(&content, frid, zRevUuid, isSbs);
  fossil_free(zRevUuid);
  blob_reset(&content);
}

/*
** Sets up and validates must, but not all, of p's checkin-related
** state from the CGI environment. Returns 0 on success or a suggested
** HTTP result code on error, in which case a message will have been
** written to pErr.
**
** It always fails if it cannot completely resolve the 'file' and 'r'
** parameters, including verifying that the refer to a real
** file/version combination. Most others are optional.
**
** Intended to be used only by /filepage and /filepage_commit.
*/
static int fileedit_setup_cimi_from_p(CheckinMiniInfo * p, Blob * pErr){
  char * zFileUuid = 0;
  const char * zFlag;
  int rc = 0, vid = 0, frid = 0;

#define fail(EXPR) blob_appendf EXPR; goto end_fail
  zFlag = PD("file",P("name"));
  if(zFlag==0 || !*zFlag){
    rc = 400;
    fail((pErr,"Missing required 'file' parameter."));
  }
  p->zFilename = mprintf("%s",zFlag);

  if(0==fileedit_is_editable(p->zFilename)){
    rc = 403;
    fail((pErr,"Filename [%h] is disallowed "
          "by the [fileedit-glob] repository "
          "setting.",
          p->zFilename));
  }

  zFlag = P("r");
  if(!zFlag){
    rc = 400;
    fail((pErr,"Missing required 'r' parameter."));
  }
  vid = symbolic_name_to_rid(zFlag, "ci");
  if(0==vid){
    rc = 404;
    fail((pErr,"Could not resolve checkin version."));
  }
  p->zParentUuid = rid_to_uuid(vid)/*fully expand it*/;

  zFileUuid = fileedit_file_uuid(p->zFilename, vid, &p->filePerm);
  if(!zFileUuid){
    rc = 404;
    fail((pErr,"Checkin [%S] does not contain file: "
          "[%h]", p->zParentUuid, p->zFilename));
  }else if(PERM_LNK==p->filePerm){
    rc = 400;
    fail((pErr,"Editing symlinks is not permitted."));
  }

  /* Find the repo-side file entry or fail... */
  frid = fast_uuid_to_rid(zFileUuid);
  assert(frid);

  /* Read file content from submit request or repo... */
  zFlag = P("content");
  if(zFlag==0){
    content_get(frid, &p->fileContent);
  }else{
    blob_init(&p->fileContent,zFlag,-1);
  }
  if(looks_like_binary(&p->fileContent)){
    rc = 400;
    fail((pErr,"File appears to be binary. Cannot edit: "
          "[%h]",p->zFilename));
  }

  zFlag = PT("comment");
  if(zFlag!=0 && *zFlag!=0){
    blob_append(&p->comment, zFlag, -1);
  }
  zFlag = P("comment_mimetype");
  if(zFlag){
    p->zCommentMimetype = mprintf("%s",zFlag);
    zFlag = 0;
  }
  p->zUser = mprintf("%s",g.zLogin);
#define p_int(K) atoi(PD(K,"0"))
  if(p_int("dry_run")!=0){
    p->flags |= CIMINI_DRY_RUN;
  }
  if(p_int("allow_fork")!=0){
    p->flags |= CIMINI_ALLOW_FORK;
  }
  if(p_int("allow_older")!=0){
    p->flags |= CIMINI_ALLOW_OLDER;
  }
  if(p_int("exec_bit")!=0){
    p->filePerm = PERM_EXE;
  }
  if(p_int("allow_merge_conflict")!=0){
    p->flags |= CIMINI_ALLOW_MERGE_MARKER;
  }
  if(p_int("prefer_delta")!=0){
    p->flags |= CIMINI_PREFER_DELTA;
  }

  /* EOL conversion policy... */
  {
    const int eolMode = p_int("eol");
    switch(eolMode){
      case 1: p->flags |= CIMINI_CONVERT_EOL_UNIX; break;
      case 2: p->flags |= CIMINI_CONVERT_EOL_WINDOWS; break;
      default: p->flags |= CIMINI_CONVERT_EOL_INHERIT; break;
    }
  }
#undef p_int
  /*
  ** TODO?: date-override date selection field. Maybe use
  ** an input[type=datetime-local].
  */
  return 0;
end_fail:
#undef fail
  fossil_free(zFileUuid);
  return rc ? rc : 500;
}

/*
** WEBPAGE: fileedit_commit
**
** Required query parameters:
** 
** file=FILENAME
** r=Parent checkin UUID
** content=text
** comment=text
**
** Optional query parameters:
**
** comment_mimetype=text
** dry_run=int (1 or 0)
** 
**
** User must have Write access to use this page.
**
** Responds with JSON:
**
** {uuid: newUUID,
**  manifest: text of manifest,
**  dryRun: bool
** }
**
** On error it produces a JSON response as documented for
** fileedit_ajax_error().
*/
void fileedit_ajax_commit(){
  int newVid = 0;
  Blob err = empty_blob;
  Blob manifest = empty_blob;
  CheckinMiniInfo cimi;
  int rc;
  char * zNewUuid = 0;

  if(!fileedit_ajax_boostrap()){
    goto end_cleanup;
  }
  db_begin_transaction();
  CheckinMiniInfo_init(&cimi);
  rc = fileedit_setup_cimi_from_p(&cimi,&err);
  if(0!=rc){
    fileedit_ajax_error(rc,"%b",&err);
    goto end_cleanup;
  }
  if(blob_size(&cimi.comment)==0){
    fileedit_ajax_error(400,"Empty checkin comment is not permitted.");
    goto end_cleanup;
  }
  cimi.pMfOut = &manifest;
  checkin_mini(&cimi, &newVid, &err);
  if(blob_size(&err)){
    fileedit_ajax_error(500,"%b",&err);
    goto end_cleanup;
  }
  assert(newVid>0);
  zNewUuid = rid_to_uuid(newVid);
  cgi_set_content_type("application/json");
  CX("{");
  CX("\"uuid\":\"%j\",", zNewUuid);
  CX("\"dryRun\": %s,",
     (CIMINI_DRY_RUN & cimi.flags) ? "true" : "false");
  CX("\"manifest\": \"%j\"", blob_str(&manifest));
  CX("}");
  db_end_transaction(0);
end_cleanup:
  fossil_free(zNewUuid);
  blob_reset(&err);
  blob_reset(&manifest);
  CheckinMiniInfo_cleanup(&cimi);
}


/*
** Emits utility script code specific to the /fileedit page.
*/
static void fileedit_emit_page_script(){
  style_emit_script_tag(0);
  CX("%s\n", builtin_text("fossil.page.fileedit.js"));
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
**                     supported symbolic version name.
**
** All other parameters are for internal use only, submitted via the
** form-submission process, and may change with any given revision of
** this code.
*/
void fileedit_page(){
  enum submit_modes {
  SUBMIT_NONE = 0, SUBMIT_SAVE, SUBMIT_PREVIEW,
  SUBMIT_DIFF_SBS, SUBMIT_DIFF_UNIFIED,
  SUBMIT_end /* sentinel for range validation */
  };
  const char * zFilename = PD("file",P("name"));
                                        /* filename. We'll accept 'name'
                                           because that param is handled
                                           specially by the core. */
  const char * zRev = P("r");           /* checkin version */
  const char * zContent = P("content"); /* file content */
  const char * zComment = P("comment"); /* checkin comment */
  const char * zFileMime = 0;           /* File mime type guess */
  CheckinMiniInfo cimi;                 /* Checkin state */
  int submitMode = SUBMIT_NONE;         /* See mapping below */
  int vid, newVid = 0;                  /* checkin rid */
  int frid = 0;                         /* File content rid */
  int previewLn = P("preview_ln")!=0;   /* Line number mode */
  int previewHtmlHeight = 0;            /* iframe height (EMs) */
  int previewRenderMode = FE_RENDER_GUESS; /* preview mode */
  char * zFileUuid = 0;                 /* File content UUID */
  Blob err = empty_blob;                /* Error report */
  Blob submitResult = empty_blob;       /* Error report */
  const char * zFlagCheck = 0;          /* Temp url flag holder */
  Blob endScript = empty_blob;          /* Script code to run at the
                                           end. This content will be
                                           combined into a single JS
                                           function call, thus each
                                           entry must end with a
                                           semicolon. */
  Stmt stmt = empty_Stmt;
#define fail(EXPR) blob_appendf EXPR; goto end_footer

  login_check_credentials();
  if( !g.perm.Write ){
    login_needed(g.anon.Write);
    return;
  }
  db_begin_transaction();
  CheckinMiniInfo_init(&cimi);
  submitMode = atoi(PD("submit","0"));
  if(submitMode < SUBMIT_NONE || submitMode >= SUBMIT_end){
    submitMode = 0;
  }
  zFlagCheck = P("comment_mimetype");
  if(zFlagCheck){
    cimi.zCommentMimetype = mprintf("%s",zFlagCheck);
    zFlagCheck = 0;
  }
  cimi.zUser = mprintf("%s",g.zLogin);

  style_header("File Editor");
  /* As of this point, don't use return or fossil_fatal(), use
  ** fail((&err,...))  instead so that we can be sure to do any
  ** cleanup and end the transaction cleanly.
  */
  if(!zRev || !*zRev || !zFilename || !*zFilename){
    fail((&err,"Missing required URL parameters: "
          "file=FILE and r=CHECKIN"));
  }
  if(0==fileedit_is_editable(zFilename)){
    fail((&err,"Filename <code>%h</code> is disallowed "
          "by the <code>fileedit-glob</code> repository "
          "setting.",
          zFilename));
  }
  vid = symbolic_name_to_rid(zRev, "ci");
  if(0==vid){
    fail((&err,"Could not resolve checkin version."));
  }
  cimi.zFilename = mprintf("%s",zFilename);
  zFileMime = mimetype_from_name(zFilename);

  /* Find the repo-side file entry or fail... */
  cimi.zParentUuid = rid_to_uuid(vid);
  zFileUuid = fileedit_file_uuid(zFilename, vid, &cimi.filePerm);
  if(!zFileUuid){
    fail((&err,"Checkin [%S] does not contain file: "
          "<code>%h</code>",
          cimi.zParentUuid, zFilename));
  }
  else if(PERM_LNK==cimi.filePerm){
    fail((&err,"Editing symlinks is not permitted."));
  }
  frid = fast_uuid_to_rid(zFileUuid);
  assert(frid);

  /* Read file content from submit request or repo... */
  if(zContent==0){
    content_get(frid, &cimi.fileContent);
    zContent = blob_size(&cimi.fileContent)
      ? blob_str(&cimi.fileContent) : NULL;
  }else{
    blob_init(&cimi.fileContent,zContent,-1);
  }
  if(looks_like_binary(&cimi.fileContent)){
    fail((&err,"File appears to be binary. Cannot edit: "
          "<code>%h</code>",zFilename));
  }

  /*
  ** TODO?: date-override date selection field. Maybe use
  ** an input[type=datetime-local].
  */
  if(SUBMIT_NONE==submitMode || P("dry_run")!=0){
    cimi.flags |= CIMINI_DRY_RUN;
  }
  if(P("allow_fork")!=0){
    cimi.flags |= CIMINI_ALLOW_FORK;
  }
  if(P("allow_older")!=0){
    cimi.flags |= CIMINI_ALLOW_OLDER;
  }
  if(P("exec_bit")!=0){
    cimi.filePerm = PERM_EXE;
  }
  if(P("allow_merge_conflict")!=0){
    cimi.flags |= CIMINI_ALLOW_MERGE_MARKER;
  }
  if(P("prefer_delta")!=0){
    cimi.flags |= CIMINI_PREFER_DELTA;
  }
  /* EOL conversion policy... */
  {
    const int eolMode = submitMode==SUBMIT_NONE
      ? 0 : atoi(PD("eol","0"));
    switch(eolMode){
      case 1: cimi.flags |= CIMINI_CONVERT_EOL_UNIX; break;
      case 2: cimi.flags |= CIMINI_CONVERT_EOL_WINDOWS; break;
      default: cimi.flags |= CIMINI_CONVERT_EOL_INHERIT; break;
    }
  }
  
  /********************************************************************
  ** All errors which "could" have happened up to this point are of a
  ** degree which keep us from rendering the rest of the page, and
  ** thus fail() has already skipped to the end of the page to render
  ** the errors. Any up-coming errors, barring malloc failure or
  ** similar, are not "that" fatal. We can/should continue rendering
  ** the page, then output the error message at the end.
  **
  ** Because we cannot intercept the output of the PREVIEW and DIFF
  ** rendering, we have to delay the "real work" for those modes until
  ** after the rest of the page has been rendered. In the case of
  ** SAVE, we can capture all of the output, and thus can perform that
  ** work before rendering, which is important so that we have the
  ** proper version information when rendering the rest of the page.
  ********************************************************************/
#undef fail
  while(SUBMIT_SAVE==submitMode){
    Blob manifest = empty_blob;
    /*cimi.flags |= CIMINI_STRONGLY_PREFER_DELTA;*/
    if(zComment && *zComment){
      blob_append(&cimi.comment, zComment, -1);
    }else{
      blob_append(&err,"Empty checkin comment is not permitted.",-1);
      break;
    }
    cimi.pMfOut = &manifest;
    checkin_mini(&cimi, &newVid, &err);
    if(newVid!=0){
      char * zNewUuid = rid_to_uuid(newVid);
      blob_appendf(&submitResult,
                   "<h3>Manifest%s: %S</h3><pre>"
                   "<code class='fileedit-manifest'>%h</code>"
                   "</pre>",
                   (cimi.flags & CIMINI_DRY_RUN) ? " (dry run)" : "",
                   zNewUuid, blob_str(&manifest));
      if(CIMINI_DRY_RUN & cimi.flags){
        fossil_free(zNewUuid);
      }else{
        /* Update cimi version info... */
        assert(cimi.pParent);
        assert(cimi.zParentUuid);
        fossil_free(zFileUuid);
        zFileUuid = fileedit_file_uuid(cimi.zFilename, newVid, 0);
        manifest_destroy(cimi.pParent);
        cimi.pParent = 0;
        fossil_free(cimi.zParentUuid);
        cimi.zParentUuid = zNewUuid;
        zComment = 0;
        cimi.flags |= CIMINI_DRY_RUN /* for sanity's sake */;
      }
    }
    /* On error, the error message is in the err blob and will
    ** be emitted at the end. */
    cimi.pMfOut = 0;
    blob_reset(&manifest);
    break;
  }

  CX("<div id='fossil-status-bar'>Async. status messages will go "
     "here.</div>\n");
  CX("<h1>Editing:</h1>");
  CX("<p class='fileedit-hint'>");
  CX("File: "
     "[<a id='finfo-link' href='#'>info</a>] "
     /* %R/finfo?name=%T&m=%!S */
     "<code id='finfo-file-name'>(loading)</code><br>");
  CX("Checkin Version: "







<
<
<
<
<
<
|


|
<
<


<
<
<
<





<
















<
<
<
<
<
<
<
<
<
<






|
|
<

<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
|
<
|
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
















<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1441
1442
1443
1444
1445
1446
1447






1448
1449
1450
1451


1452
1453




1454
1455
1456
1457
1458

1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474










1475
1476
1477
1478
1479
1480
1481
1482

1483




1484














1485
1486




1487











1488

1489






1490


























1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506












































1507
1508
1509
1510
1511
1512
1513
**                     supported symbolic version name.
**
** All other parameters are for internal use only, submitted via the
** form-submission process, and may change with any given revision of
** this code.
*/
void fileedit_page(){






  const char * zFilename;               /* filename. We'll accept 'name'
                                           because that param is handled
                                           specially by the core. */
  const char * zRev;                    /* checkin version */


  const char * zFileMime = 0;           /* File mime type guess */
  CheckinMiniInfo cimi;                 /* Checkin state */




  int previewHtmlHeight = 0;            /* iframe height (EMs) */
  int previewRenderMode = FE_RENDER_GUESS; /* preview mode */
  char * zFileUuid = 0;                 /* File content UUID */
  Blob err = empty_blob;                /* Error report */
  Blob submitResult = empty_blob;       /* Error report */

  Blob endScript = empty_blob;          /* Script code to run at the
                                           end. This content will be
                                           combined into a single JS
                                           function call, thus each
                                           entry must end with a
                                           semicolon. */
  Stmt stmt = empty_Stmt;
#define fail(EXPR) blob_appendf EXPR; goto end_footer

  login_check_credentials();
  if( !g.perm.Write ){
    login_needed(g.anon.Write);
    return;
  }
  db_begin_transaction();
  CheckinMiniInfo_init(&cimi);











  style_header("File Editor");
  /* As of this point, don't use return or fossil_fatal(), use
  ** fail((&err,...))  instead so that we can be sure to do any
  ** cleanup and end the transaction cleanly.
  */
  if(fileedit_setup_cimi_from_p(&cimi, &err)!=0){
    goto end_footer;

  }




  zFilename = cimi.zFilename;














  zRev = cimi.zParentUuid;





  assert(zRev);











  assert(zFilename);

  zFileMime = mimetype_from_name(cimi.zFilename);

































  /********************************************************************
  ** All errors which "could" have happened up to this point are of a
  ** degree which keep us from rendering the rest of the page, and
  ** thus fail() has already skipped to the end of the page to render
  ** the errors. Any up-coming errors, barring malloc failure or
  ** similar, are not "that" fatal. We can/should continue rendering
  ** the page, then output the error message at the end.
  **
  ** Because we cannot intercept the output of the PREVIEW and DIFF
  ** rendering, we have to delay the "real work" for those modes until
  ** after the rest of the page has been rendered. In the case of
  ** SAVE, we can capture all of the output, and thus can perform that
  ** work before rendering, which is important so that we have the
  ** proper version information when rendering the rest of the page.
  ********************************************************************/
#undef fail












































  CX("<h1>Editing:</h1>");
  CX("<p class='fileedit-hint'>");
  CX("File: "
     "[<a id='finfo-link' href='#'>info</a>] "
     /* %R/finfo?name=%T&m=%!S */
     "<code id='finfo-file-name'>(loading)</code><br>");
  CX("Checkin Version: "
1478
1479
1480
1481
1482
1483
1484




1485
1486
1487
1488
1489
1490
1491

1492
1493
1494
1495
1496
1497

1498
1499

1500
1501
1502
1503
1504

1505
1506
1507

1508
1509
1510
1511
1512
1513

1514
1515
1516
1517
1518
1519

1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
















































1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609

  /******* Content *******/
  CX("<h3>File Content</h3>\n");
  CX("<textarea name='content' id='fileedit-content' "
     "rows='20' cols='80'>");
  CX("Loading...");
  CX("</textarea>\n");




  /******* Flags/options *******/
  CX("<fieldset class='fileedit-options' id='options'>"
     "<legend>Options</legend><div>"
     /* Chrome does not sanely lay out multiple
     ** fieldset children after the <legend>, so
     ** a containing div is necessary. */);
  style_labeled_checkbox("dry_run", "Dry-run?", "1",

                         "In dry-run mode, the Save button performs "
                         "all work needed for saving but then rolls "
                         "back the transaction, and thus does not "
                         "really save.",
                         cimi.flags & CIMINI_DRY_RUN);
  style_labeled_checkbox("allow_fork", "Allow fork?", "1",

                         "Allow saving to create a fork?",
                         cimi.flags & CIMINI_ALLOW_FORK);

  style_labeled_checkbox("allow_older", "Allow older?", "1",
                         "Allow saving against a parent version "
                         "which has a newer timestamp?",
                         cimi.flags & CIMINI_ALLOW_OLDER);
  style_labeled_checkbox("exec_bit", "Executable?", "1",

                         "Set the executable bit?",
                         PERM_EXE==cimi.filePerm);
  style_labeled_checkbox("allow_merge_conflict",

                         "Allow merge conflict markers?", "1",
                         "Allow saving even if the content contains "
                         "what appear to be fossil merge conflict "
                         "markers?",
                         cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
  style_labeled_checkbox("prefer_delta",

                         "Prefer delta manifest?", "1",
                         "Will create a delta manifest, instead of "
                         "baseline, if conditions are favorable to do "
                         "so. This option is only a suggestion.",
                         cimi.flags & CIMINI_PREFER_DELTA);
  style_select_list_int("eol", "EOL Style",

                        "EOL conversion policy, noting that "
                        "form-processing may implicitly change the "
                        "line endings of the input.",
                        (cimi.flags & CIMINI_CONVERT_EOL_UNIX)
                        ? 1 : (cimi.flags & CIMINI_CONVERT_EOL_WINDOWS
                               ? 2 : 0),
                        "Inherit", 0,
                        "Unix", 1,
                        "Windows", 2,
                        NULL);

  CX("</div></fieldset>") /* end of checkboxes */;

  /******* Comment *******/
  CX("<a id='comment'></a>");
  CX("<fieldset><legend>Commit message</legend><div>");
  CX("<textarea name='comment' rows='3' cols='80'>");
  /* ^^^ adding the 'required' attribute means we cannot even submit
  ** for PREVIEW mode if it's empty :/. */
  if(zComment && *zComment){
    CX("%h", zComment);
  }
  CX("</textarea>\n");
  CX("<div class='fileedit-hint'>Comments use the Fossil wiki markup "
     "syntax.</div>\n"/*TODO: select for fossil/md/plain text*/);
  CX("</div></fieldset>\n");

  /******* Buttons *******/
  CX("<a id='buttons'></a>");
  CX("<fieldset class='fileedit-options'>"
     "<legend>Ask the server to...</legend><div>");
  CX("<button id='fileedit-btn-commit'>Commit</button>");
  CX("<button id='fileedit-btn-preview'>Preview</button>");
  {
    /* Preview rendering mode selection... */
    previewRenderMode = atoi(PD("preview_render_mode","0"));
    previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime);
    style_select_list_int("preview_render_mode",
                          "Preview Mode",
                          "Preview mode format.",
                          previewRenderMode,
                          "Guess", FE_RENDER_GUESS,
                          "Wiki/Markdown", FE_RENDER_WIKI,
                          "HTML (iframe)", FE_RENDER_HTML,
                          "Plain Text", FE_RENDER_PLAIN_TEXT,
                          NULL);
    previewHtmlHeight = atoi(PD("preview_html_ems","0"));
    if(!previewHtmlHeight){
      previewHtmlHeight = 40;
    }
    /* Allow selection of HTML preview iframe height */
    style_select_list_int("preview_html_ems",
                          "HTML Preview IFrame Height (EMs)",
                          "Height (in EMs) of the iframe used for "
                          "HTML preview",
                          previewHtmlHeight,
                          "", 20, "", 40,
                          "", 60, "", 80,
                          "", 100, NULL);
    style_labeled_checkbox("preview_ln",
                           "Add line numbers to plain-text previews?",
                           "1",
                           "If on, plain-text files (only) will get "
                           "line numbers added to the preview.",
                           previewLn);
  }
  CX("<button id='fileedit-btn-diffsbs'>Diff (SBS)</button>");
  CX("<button id='fileedit-btn-diffu'>Diff (Unified)</button>");
















































  CX("</div></fieldset>");

  /******* End of form *******/    
  CX("</form>\n");

  CX("<div id='ajax-target'></div>"
     /* this is where preview/diff go */);
  
  /* Dynamically populate the editor... */
  blob_appendf(&endScript,
               "fossil.page.loadFile('%j','%j');",
               zFilename, cimi.zParentUuid);

end_footer:
  zContent = 0;
  fossil_free(zFileUuid);
  if(stmt.pStmt){
    db_finalize(&stmt);
  }
  if(blob_size(&err)){
    CX("<div class='fileedit-error-report'>%s</div>",
       blob_str(&err));







>
>
>
>






|
>




|
|
>


>
|



|
>


|
>





|
>





|
>



















|
|











<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





|








<







1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624


































1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688

1689
1690
1691
1692
1693
1694
1695

  /******* Content *******/
  CX("<h3>File Content</h3>\n");
  CX("<textarea name='content' id='fileedit-content' "
     "rows='20' cols='80'>");
  CX("Loading...");
  CX("</textarea>\n");

  CX("<div id='fossil-status-bar'>Async. status messages will go "
     "here.</div>\n");

  /******* Flags/options *******/
  CX("<fieldset class='fileedit-options' id='options'>"
     "<legend>Options</legend><div>"
     /* Chrome does not sanely lay out multiple
     ** fieldset children after the <legend>, so
     ** a containing div is necessary. */);
  style_labeled_checkbox("cb-dry-run",
                         "dry_run", "Dry-run?", "1",
                         "In dry-run mode, the Save button performs "
                         "all work needed for saving but then rolls "
                         "back the transaction, and thus does not "
                         "really save.",
                         1);
  style_labeled_checkbox("cb-allow-fork",
                         "allow_fork", "Allow fork?", "1",
                         "Allow saving to create a fork?",
                         cimi.flags & CIMINI_ALLOW_FORK);
  style_labeled_checkbox("cb-allow-older",
                         "allow_older", "Allow older?", "1",
                         "Allow saving against a parent version "
                         "which has a newer timestamp?",
                         cimi.flags & CIMINI_ALLOW_OLDER);
  style_labeled_checkbox("cb-exec-bit",
                         "exec_bit", "Executable?", "1",
                         "Set the executable bit?",
                         PERM_EXE==cimi.filePerm);
  style_labeled_checkbox("cb-allow-merge-conflict",
                         "allow_merge_conflict",
                         "Allow merge conflict markers?", "1",
                         "Allow saving even if the content contains "
                         "what appear to be fossil merge conflict "
                         "markers?",
                         cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
  style_labeled_checkbox("cb-prefer-delta",
                         "prefer_delta",
                         "Prefer delta manifest?", "1",
                         "Will create a delta manifest, instead of "
                         "baseline, if conditions are favorable to do "
                         "so. This option is only a suggestion.",
                         cimi.flags & CIMINI_PREFER_DELTA);
  style_select_list_int("select-eol-style",
                        "eol", "EOL Style",
                        "EOL conversion policy, noting that "
                        "form-processing may implicitly change the "
                        "line endings of the input.",
                        (cimi.flags & CIMINI_CONVERT_EOL_UNIX)
                        ? 1 : (cimi.flags & CIMINI_CONVERT_EOL_WINDOWS
                               ? 2 : 0),
                        "Inherit", 0,
                        "Unix", 1,
                        "Windows", 2,
                        NULL);

  CX("</div></fieldset>") /* end of checkboxes */;

  /******* Comment *******/
  CX("<a id='comment'></a>");
  CX("<fieldset><legend>Commit message</legend><div>");
  CX("<textarea name='comment' rows='3' cols='80'>");
  /* ^^^ adding the 'required' attribute means we cannot even submit
  ** for PREVIEW mode if it's empty :/. */
  if(blob_size(&cimi.comment)){
    CX("%h", blob_str(&cimi.comment));
  }
  CX("</textarea>\n");
  CX("<div class='fileedit-hint'>Comments use the Fossil wiki markup "
     "syntax.</div>\n"/*TODO: select for fossil/md/plain text*/);
  CX("</div></fieldset>\n");

  /******* Buttons *******/
  CX("<a id='buttons'></a>");
  CX("<fieldset class='fileedit-options'>"
     "<legend>Ask the server to...</legend><div>");
  CX("<button id='fileedit-btn-commit'>Commit</button>");


































  CX("<button id='fileedit-btn-diffsbs'>Diff (SBS)</button>");
  CX("<button id='fileedit-btn-diffu'>Diff (Unified)</button>");
  CX("<button id='fileedit-btn-preview'>Preview</button>");
  /* Default preview rendering mode selection... */
  previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime);
  style_select_list_int("select-preview-mode",
                        "preview_render_mode",
                        "Preview Mode",
                        "Preview mode format.",
                        previewRenderMode,
                        "Guess", FE_RENDER_GUESS,
                        "Wiki/Markdown", FE_RENDER_WIKI,
                        "HTML (iframe)", FE_RENDER_HTML,
                        "Plain Text", FE_RENDER_PLAIN_TEXT,
                        NULL);
  /*
  ** Set up a JS-side mapping of the FE_RENDER_xyz values
  */
  blob_appendf(&endScript, "fossil.page.previewModes={"
               "guess: %d, %d: 'guess', wiki: %d, %d: 'wiki',"
               "html: %d, %d: 'html', text: %d, %d: 'text'"
               "};\n",
               FE_RENDER_GUESS, FE_RENDER_GUESS,
               FE_RENDER_WIKI, FE_RENDER_WIKI,
               FE_RENDER_HTML, FE_RENDER_HTML,
               FE_RENDER_PLAIN_TEXT, FE_RENDER_PLAIN_TEXT);

  /* Allow selection of HTML preview iframe height */
  previewHtmlHeight = atoi(PD("preview_html_ems","0"));
  if(!previewHtmlHeight){
    previewHtmlHeight = 40;
  }
  style_select_list_int("select-preview-html-ems",
                        "preview_html_ems",
                        "HTML Preview IFrame Height (EMs)",
                        "Height (in EMs) of the iframe used for "
                        "HTML preview",
                        previewHtmlHeight,
                        "", 20, "", 40,
                        "", 60, "", 80,
                        "", 100, NULL);
  /* Selection of line numbers for text preview */
  style_labeled_checkbox("cb-line-numbers",
                         "preview_ln",
                         "Add line numbers to plain-text previews?",
                         "1",
                         "If on, plain-text files (only) will get "
                         "line numbers added to the preview.",
                         P("preview_ln")!=0);

  CX("</div></fieldset>");

  /******* End of form *******/    
  CX("</form>\n");

  CX("<div id='ajax-target'>%s</div>"
     /* this is where preview/diff go */);
  
  /* Dynamically populate the editor... */
  blob_appendf(&endScript,
               "fossil.page.loadFile('%j','%j');",
               zFilename, cimi.zParentUuid);

end_footer:

  fossil_free(zFileUuid);
  if(stmt.pStmt){
    db_finalize(&stmt);
  }
  if(blob_size(&err)){
    CX("<div class='fileedit-error-report'>%s</div>",
       blob_str(&err));
Changes to src/fossil.bootstrap.js.
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
** Returns this object.
*/
window.fossil.message = function f(msg){
  const args = Array.prototype.slice.call(arguments,0);
  const tgt = f.targetElement;
  if(tgt){
    tgt.classList.remove('error');
    tgt.innerText = msg || args.join(' ');
  }
  else{
    args.unshift('Fossil status:');
    console.debug.apply(console,args);
  }
  return this;
};







|







15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
** Returns this object.
*/
window.fossil.message = function f(msg){
  const args = Array.prototype.slice.call(arguments,0);
  const tgt = f.targetElement;
  if(tgt){
    tgt.classList.remove('error');
    tgt.innerText = args.join(' ');
  }
  else{
    args.unshift('Fossil status:');
    console.debug.apply(console,args);
  }
  return this;
};
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
** Returns this object.
*/
window.fossil.error = function f(msg){
  const args = Array.prototype.slice.call(arguments,0);
  const tgt = window.fossil.message.targetElement;
  if(tgt){
    tgt.classList.add('error');
    tgt.innerText = msg || args.join(' ');
  }
  else{
    args.unshift('Fossil error:');
    console.error.apply(console,args);
  }
  return this;
};







|







41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
** Returns this object.
*/
window.fossil.error = function f(msg){
  const args = Array.prototype.slice.call(arguments,0);
  const tgt = window.fossil.message.targetElement;
  if(tgt){
    tgt.classList.add('error');
    tgt.innerText = args.join(' ');
  }
  else{
    args.unshift('Fossil error:');
    console.error.apply(console,args);
  }
  return this;
};
Changes to src/fossil.page.fileedit.js.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20




21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40


































41
42
43
44
45
46
47
(function(){
  "use strict";
  /**
     Code for the /filepage app. Requires that the fossil JS bootstrappin
     is complete and fossil.fetch() has been installed.
  */

  const E = (s)=>document.querySelector(s);
        

  window.addEventListener("load", function() {
    const P = fossil.page;
    P.e = {
      editor: E('#fileedit-content'),
      ajaxContentTarget: E('#ajax-target'),
      form: E('#fileedit-form'),
      btnPreview: E("#fileedit-btn-preview"),
      btnDiffSbs: E("#fileedit-btn-diffsbs"),
      btnDiffU: E("#fileedit-btn-diffu"),
      btnCommit: E("#fileedit-btn-commit")




    };
    const stopEvent = function(e){
      e.preventDefault();
      e.stopPropagation();
      return P;
    };
      
    P.e.form.addEventListener("submit", function(e) {
      e.target.checkValidity();
      stopEvent(e);
    }, false);
    P.e.btnPreview.addEventListener(
      "click",(e)=>stopEvent(e).preview(),false
    );
    P.e.btnDiffSbs.addEventListener(
      "click",(e)=>stopEvent(e).diff(true),false
    );
    P.e.btnDiffU.addEventListener(
      "click",(e)=>stopEvent(e).diff(false), false
    );


































  }, false);


  
  /**
     updateVersion() updates filename and version in relevant UI
     elements...



|
|

<

<
<









|
>
>
>
>




















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6

7


8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
(function(){
  "use strict";
  /**
     Code for the /filepage app. Requires that the fossil JS
     bootstrapping is complete and fossil.fetch() has been installed.
  */

  const E = (s)=>document.querySelector(s);


  window.addEventListener("load", function() {
    const P = fossil.page;
    P.e = {
      editor: E('#fileedit-content'),
      ajaxContentTarget: E('#ajax-target'),
      form: E('#fileedit-form'),
      btnPreview: E("#fileedit-btn-preview"),
      btnDiffSbs: E("#fileedit-btn-diffsbs"),
      btnDiffU: E("#fileedit-btn-diffu"),
      btnCommit: E("#fileedit-btn-commit"),
      selectPreviewModeWrap: E('#select-preview-mode'),
      selectHtmlEmsWrap: E('#select-preview-html-ems'),
      selectEolWrap:  E('#select-preview-html-ems'),
      cbLineNumbersWrap: E('#cb-line-numbers')
    };
    const stopEvent = function(e){
      e.preventDefault();
      e.stopPropagation();
      return P;
    };
      
    P.e.form.addEventListener("submit", function(e) {
      e.target.checkValidity();
      stopEvent(e);
    }, false);
    P.e.btnPreview.addEventListener(
      "click",(e)=>stopEvent(e).preview(),false
    );
    P.e.btnDiffSbs.addEventListener(
      "click",(e)=>stopEvent(e).diff(true),false
    );
    P.e.btnDiffU.addEventListener(
      "click",(e)=>stopEvent(e).diff(false), false
    );
    P.e.btnCommit.addEventListener(
      "click",(e)=>stopEvent(e).commit(), false
    );

    /**
       Cosmetic: jump through some hoops to enable/disable
       certain preview options depending on the current
       preview mode...
    */
    const selectPreviewMode =
          P.e.selectPreviewModeWrap.querySelector('select');
    console.debug('selectPreviewMode =',selectPreviewMode);
    selectPreviewMode.addEventListener(
      "change",function(e){
        const mode = e.target.value,
              name = P.previewModes[mode],
              hide = [], unhide = [];
        if('guess'===name){
          unhide.push(P.e.cbLineNumbersWrap,
                      P.e.selectHtmlEmsWrap);
        }else{
          if('text'!==name) hide.push(P.e.cbLineNumbersWrap);
          else unhide.push(P.e.cbLineNumbersWrap);
          if('html'!==name) hide.push(P.e.selectHtmlEmsWrap);
          else unhide.push(P.e.selectHtmlEmsWrap);
        }
        hide.forEach((e)=>e.classList.add('hidden'));
        unhide.forEach((e)=>e.classList.remove('hidden'));
      }, false
    );
    selectPreviewMode.dispatchEvent(
      // Force UI update
      new Event('change',{target:selectPreviewMode})
    );
  }, false);


  
  /**
     updateVersion() updates filename and version in relevant UI
     elements...
170
171
172
173
174
175
176














































































177
    ).fetch('fileedit_diff',{
      payload: fd,
      onload: updateView
    });
    return this;
  };















































































})();







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
    ).fetch('fileedit_diff',{
      payload: fd,
      onload: updateView
    });
    return this;
  };

  /**
     Performs an async commit based on the form contents and updates
     the UI.

     Returns this object.
  */
  fossil.page.commit = function f(){
    if(!this.finfo){
      fossil.error("No content is loaded.");
      return this;
    }
    const self = this;
    const content = this.e.editor.value,
          target = this.e.ajaxContentTarget,
          cbDryRun = E('[name=dry_run]'),
          isDryRun = cbDryRun.checked,
          filename = this.finfo.file;
    if(!f.updateView){
      f.updateView = function(c){
        target.innerHTML = [
          "<h3>Manifest",
          (c.dryRun?" (dry run)":""),
          ": ", c.uuid.substring(0,16),"</h3>",
          "<code class='fileedit-manifest'>",
          c.manifest,
          "</code></pre>"
        ].join('');
        fossil.message(
          c.dryRun ? 'Committed (dry run):' : 'Committed:',
          c.uuid
        );
        if(!c.dryRun){
          cbDryRun.checked = true;
          fossil.page.updateVersion(filename, c.uuid);
        }
      };
    }
    if(!content){
      f.updateView('');
      return this;
    }
    const fd = new FormData();
    fd.append('file',filename);
    fd.append('r', this.finfo.r);
    fd.append('content',content);
    fd.append('dry_run',isDryRun ? 1 : 0);
    /* Text fields or select lists... */
    ['comment_mimetype',
     'comment'
    ].forEach(function(name){
      var e = E('[name='+name+']');
      if(e) fd.append(name,e.value);
    });
    /* Checkboxes: */
    ['allow_fork',
     'allow_older',
     'exec_bit',
     'allow_merge_conflict',
     'prefer_delta'
    ].forEach(function(name){
      var e = E('[name='+name+']');
      if(e){
        if(e.checked) fd.append(name, 1);
      }else{
        console.error("Missing checkbox? name =",name);
      }
    });
    fossil.message(
      "Checking in..."
    ).fetch('fileedit_commit',{
      payload: fd,
      responseType: 'json',
      onload: f.updateView
    });
    return this;
  };

  
})();
Changes to src/style.c.
1295
1296
1297
1298
1299
1300
1301
1302

1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317

1318
1319
1320
1321
1322
1323

1324
1325
1326
1327
1328



1329
1330
1331
1332
1333
1334
1335
}

#if INTERFACE
# define webpage_assert(T) if(!(T)){webpage_assert_page(__FILE__,__LINE__,#T);}
#endif

/*
** Outputs a labeled checkbox element. zFieldName is the form element

** name. zLabel is the label for the checkbox. zValue is the optional
** value for the checkbox. zTip is an optional tooltip, which gets set
** as the "title" attribute of the outermost element. If isChecked is
** true, the checkbox gets the "checked" attribute set, else it is
** not.
**
** Resulting structure:
**
** <div class='input-with-label' title={{zTip}}>
**   <input type='checkbox' name={{zFieldName}} value={{zValue}}
**          {{isChecked ? " checked : ""}}/>
**   <span>{{zLabel}}</span>
** </div>
**
** zFieldName, zLabel, and zValue are required. zTip is optional.

**
** Be sure that the input-with-label CSS class is defined sensibly, in
** particular, having its display:inline-block is useful for alignment
** purposes.
*/
void style_labeled_checkbox(const char *zFieldName, const char * zLabel,

                            const char * zValue, const char * zTip,
                            int isChecked){
  CX("<div class='input-with-label'");
  if(zTip && *zTip){
    CX(" title='%h'", zTip);



  }
  CX("><input type='checkbox' name='%s' value='%T'%s/>",
     zFieldName,
     zValue ? zValue : "", isChecked ? " checked" : "");
  CX("<span>%h</span></div>", zLabel);
}








|
>
|
|
|
|
|



|





|
>





|
>





>
>
>







1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
}

#if INTERFACE
# define webpage_assert(T) if(!(T)){webpage_assert_page(__FILE__,__LINE__,#T);}
#endif

/*
** Outputs a labeled checkbox element. zWrapperId is an optional ID
** value for the containing element (see below). zFieldName is the
** form element name. zLabel is the label for the checkbox. zValue is
** the optional value for the checkbox. zTip is an optional tooltip,
** which gets set as the "title" attribute of the outermost
** element. If isChecked is true, the checkbox gets the "checked"
** attribute set, else it is not.
**
** Resulting structure:
**
** <div class='input-with-label' title={{zTip}} id={{zWrapperId}}>
**   <input type='checkbox' name={{zFieldName}} value={{zValue}}
**          {{isChecked ? " checked : ""}}/>
**   <span>{{zLabel}}</span>
** </div>
**
** zFieldName, zLabel, and zValue are required. zWrapperId and zTip
** are optional.
**
** Be sure that the input-with-label CSS class is defined sensibly, in
** particular, having its display:inline-block is useful for alignment
** purposes.
*/
void style_labeled_checkbox(const char * zWrapperId,
                            const char *zFieldName, const char * zLabel,
                            const char * zValue, const char * zTip,
                            int isChecked){
  CX("<div class='input-with-label'");
  if(zTip && *zTip){
    CX(" title='%h'", zTip);
  }
  if(zWrapperId && *zWrapperId){
    CX(" id='%s'",zWrapperId);
  }
  CX("><input type='checkbox' name='%s' value='%T'%s/>",
     zFieldName,
     zValue ? zValue : "", isChecked ? " checked" : "");
  CX("<span>%h</span></div>", zLabel);
}

1345
1346
1347
1348
1349
1350
1351



1352


1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375

1376
1377
1378
1379
1380
1381
1382



1383
1384
1385
1386
1387
1388
1389
** the value. If that value == selectedValue then that OPTION
** element gets the 'selected' attribute.
**
** Note that the pairs are not in (int, const char *) order because
** there is no well-known integer value which we can definitively use
** as a list terminator.
**



** zFieldName is the value of the form element's name attribute.


**
** zLabel is an optional string to use as a "label" for the element
** (see below).
**
** zTooltip is an optional value for the SELECT's title attribute.
**
** The structure of the emitted HTML is:
**
** <div class='input-with-label' title={{zToolTip}}>
**   <span>{{zLabel}}</span>
**   <select>...</select>
** </div>
**
** Example:
**
** style_select_list_int("my_field", "Grapes",
**                      "Select the number of grapes",
**                       atoi(PD("my_field","0")),
**                       "", 1, "2", 2, "Three", 3,
**                       NULL);
** 
*/
void style_select_list_int(const char *zFieldName, const char * zLabel,

                           const char * zToolTip, int selectedVal,
                           ... ){
  va_list vargs;
  va_start(vargs,selectedVal);
  CX("<div class='input-with-label'");
  if(zToolTip && *zToolTip){
    CX(" title='%h'",zToolTip);



  }
  CX(">");
  if(zLabel && *zLabel){
    CX("<span>%h</span>", zLabel);
  }
  CX("<select name='%s'>",zFieldName);
  while(1){







>
>
>
|
>
>








|






|






|
>




|


>
>
>







1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
** the value. If that value == selectedValue then that OPTION
** element gets the 'selected' attribute.
**
** Note that the pairs are not in (int, const char *) order because
** there is no well-known integer value which we can definitively use
** as a list terminator.
**
** zWrapperId is an optional ID value for the containing element (see
** below).
**
** zFieldName is the value of the form element's name attribute. Note
** that fossil prefers underscores over '-' for separators in form
** element names.
**
** zLabel is an optional string to use as a "label" for the element
** (see below).
**
** zTooltip is an optional value for the SELECT's title attribute.
**
** The structure of the emitted HTML is:
**
** <div class='input-with-label' title={{zToolTip}} id={{zWrapperId}}>
**   <span>{{zLabel}}</span>
**   <select>...</select>
** </div>
**
** Example:
**
** style_select_list_int("my-grapes", "my_grapes", "Grapes",
**                      "Select the number of grapes",
**                       atoi(PD("my_field","0")),
**                       "", 1, "2", 2, "Three", 3,
**                       NULL);
** 
*/
void style_select_list_int(const char * zWrapperId,
                           const char *zFieldName, const char * zLabel,
                           const char * zToolTip, int selectedVal,
                           ... ){
  va_list vargs;
  va_start(vargs,selectedVal);
  CX("<span class='input-with-label'");
  if(zToolTip && *zToolTip){
    CX(" title='%h'",zToolTip);
  }
  if(zWrapperId && *zWrapperId){
    CX(" id='%s'",zWrapperId);
  }
  CX(">");
  if(zLabel && *zLabel){
    CX("<span>%h</span>", zLabel);
  }
  CX("<select name='%s'>",zFieldName);
  while(1){
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
    }else{
      CX("%d",v);
    }
    CX("</option>\n");
  }
  CX("</select>\n");
  if(zLabel && *zLabel){
    CX("</div>\n");
  }
  va_end(vargs);
}


/*
** If passed 0, it emits a script opener tag with this session's







|







1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
    }else{
      CX("%d",v);
    }
    CX("</option>\n");
  }
  CX("</select>\n");
  if(zLabel && *zLabel){
    CX("</span>\n");
  }
  va_end(vargs);
}


/*
** If passed 0, it emits a script opener tag with this session's