Diff
Not logged in

Differences From Artifact [5175c583dc]:

To Artifact [b7cc73cb31]:


896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912

913
914
915
916
917
918
919
920
921




922
923


924
925
926
927
928

929
930
931
932
933







934

935

936
937
938


939
940
941





942
943
944
945
946
947
948
949
950























951
952
953

















954
955
956
957
958
959
960
961
962
963

964

965
966

967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
896
897
898
899
900
901
902








903

904
905
906
907
908
909
910
911
912
913
914
915
916
917


918
919
920
921
922
923

924
925
926
927


928
929
930
931
932
933
934
935
936

937
938


939
940
941


942
943
944
945
946
947
948







949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972


973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000

1001
1002

1003
1004
1005

























1006
1007
1008
1009
1010
1011
1012







-
-
-
-
-
-
-
-

-
+









+
+
+
+
-
-
+
+




-
+



-
-
+
+
+
+
+
+
+

+
-
+

-
-
+
+

-
-
+
+
+
+
+


-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+










+
-
+

-
+


-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







    if(0==zGlobs) return 0;
    pGlobs = glob_create(zGlobs);
    fossil_free(zGlobs);
  }
  return glob_match(pGlobs, zFilename);
}

static void fileedit_emit_script(int phase){
  if(0==phase){
    CX("<script nonce='%s'>", style_nonce());
  }else{
    CX("</script>\n");
  }
}

/*
** Emits a script tag which defines window.fossilFetch(), which works
** Emits a script tag which defines window.fossil.fetch(), which works
** similarly (not identically) to the not-quite-ubiquitous global
** fetch().
**
** JS usages:
**
** fossilFetch( URI, onLoadCallback );
**
** fossilFetch( URI, optionsObject );
**
** Noting that URI must be relative to the top of the repository and
** must not start with a slash (if it does, it is stripped). It gets
** %R/ prepended to it.
**
** Where the optionsObject may be an object with any of these
** properties:
** The optionsObject may be an onload callback or an object with any
** of these properties:
**
** - onload: callback(responseData) (default = output response to
**   console).
**
** - onerror: callback(XHR onload event) (default = no-op)
** - onerror: callback(XHR onload event) (default = console output)
**
** - method: 'POST' | 'GET' (default = 'GET')
**
** Noting that URI must be relative to the top of the repository and
** must not start with a slash. It gets %R/ prepended to it.
** - payload: anything acceptable by XHR2.send(ARG) (DOMString,
**   Document, FormData, Blob, File, ArrayBuffer), or a plain object
**   or array, either of which gets JSON.stringify()'d. If set then
**   the method is automatically set to 'POST'. If an object/array is
**   converted to JSON, the content-type is set to 'application/json'.
**   By default XHR2 will set the content type based on the payload
**   type.
**
** - contentType: Optional request content type when POSTing. Ignored
** TODOs, if needed, include:
**   if the method is not 'POST'.
**
** optionsObject.params: object map of key/value pairs to append to the
** URI.
** - responseType: optional string. One of ("text", "arraybuffer",
**   "blob", or "document") (as specified by XHR2). Default = "text".
**
** optionsObject.payload: string or JSON-able object to POST as the
** payload.
** - urlParams: string|object. If a string, it is assumed to be a
**   URI-encoded list of params in the form "key1=val1&key2=val2...",
**   with NO leading '?'.  If it is an object, all of its properties
**   get converted to that form. Either way, the parameters get
**   appended to the URL.
**
*/
static void fileedit_emit_script_fetch(){
  fileedit_emit_script(0);
  CX("window.fossilFetch = function(path,opt){\n");
  CX("  if('function'===typeof opt){\n");
  CX("    opt={onload:opt};\n");
  CX("  }else{\n");
  CX("    opt=opt||{onload:function(r){console.debug('response:',r)}}\n");
void fileedit_emit_script_fetch(){
  style_emit_script_tag(0);
  CX("fossil.fetch = function(path,opt){\n");
  CX("  if('/'===path[0]) path = path.substr(1);\n");
  CX("  if(!opt){\n");
  CX("    opt = {onload:(r)=>console.debug('response:',r)};\n");
  CX("  }else if('function'===typeof opt){\n");
  CX("    opt={onload:opt,\n");
  CX("         onerror:(e)=>console.error('ajax error:',e)};\n");
  CX("  }\n");
  CX("  let payload = opt.payload;\n");
  CX("  if(payload){\n");
  CX("    opt.method = 'POST';\n");
  CX("    if(!(payload instanceof FormData)\n");
  CX("       && !(payload instanceof Document)\n");
  CX("       && !(payload instanceof Blob)\n");
  CX("       && !(payload instanceof File)\n");
  CX("       && !(payload instanceof ArrayBuffer)){\n");
  CX("      if('object'===typeof payload || payload instanceof Array){\n");
  CX("        payload = JSON.stringify(payload);\n");
  CX("        opt.contentType = 'application/json';\n");
  CX("      }\n");
  CX("    }\n");
  CX("  }\n");
  CX("  const url='%R/'+path, x=new XMLHttpRequest();\n");
  CX("  x.open(opt.method||'GET', url, true);\n");
  CX("  const url=['%R/'+path], x=new XMLHttpRequest();\n");
  CX("  if(opt.urlParams){\n");
  CX("    url.push('?');\n");
  CX("    if('string'===typeof opt.urlParams){\n");
  CX("      url.push(opt.urlParams);\n");
  CX("    }else{/*assume object*/\n");
  CX("      let k, i = 0;\n");
  CX("      for( k in opt.urlParams ){\n");
  CX("        if(i++) url.push('&');\n");
  CX("        url.push(k,'=',encodeURIComponent(opt.urlParams[k]));\n");
  CX("      }\n");
  CX("    }\n");
  CX("  }\n");
  CX("  if('POST'===opt.method && 'string'===typeof opt.contentType){\n");
  CX("    x.setRequestHeader('Content-Type',opt.contentType);\n");
  CX("  }\n");
  CX("  x.open(opt.method||'GET', url.join(''), true);\n");
  CX("  x.responseType=opt.responseType||'text';\n");
  CX("  if(opt.onload){\n");
  CX("    x.onload = function(e){\n");
  CX("      if(200!==this.status){\n");
  CX("        if(opt.onerror) opt.onerror(e);\n");
  CX("        return;\n");
  CX("      }\n");
  CX("      opt.onload(this.response);\n");
  CX("    }\n");
  CX("  }\n");
  CX("  if(payload) x.send(payload);\n");
  CX("  x.send();");
  CX("  else x.send();\n");
  CX("};\n");
  fileedit_emit_script(1);
  style_emit_script_tag(1);
};

/*
** Outputs a labeled checkbox element:
**
** <span class='input-with-label' title={{zTip}}>
**   <input type='checkbox' name={{zFieldName}} value={{zValue}}
**          {{isChecked ? " checked : ""}}/>
**   <span>{{zLabel}}</span>
** </span>
**
** zFieldName, zLabel, and zValue are required. zTip is optional.
*/
static 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);
}

enum fileedit_render_preview_flags {
FE_PREVIEW_LINE_NUMBERS = 1
};
enum fileedit_render_modes {
FE_RENDER_GUESS = 0,
FE_RENDER_PLAIN_TEXT,
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039






1040
1041
1042
1043
1044
1045
1046
1040
1041
1042
1043
1044
1045
1046





1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059







-
-
-
-
-
+
+
+
+
+
+







    renderMode = fileedit_render_mode_for_mimetype(zMime);
  }
  CX("<div class='fileedit-preview'>");
  CX("<div>Preview</div>");
  switch(renderMode){
    case FE_RENDER_HTML:{
      char * z64 = encode64(blob_str(pContent), blob_size(pContent));
      CX("<iframe width='100%%' frameborder='0' marginwidth='0' "
         "style='height:%dem' "
         "marginheight='0' sandbox='allow-same-origin' id='ifm1' "
         "src='data:text/html;base64,%z'"
         "></iframe>", nIframeHeightEm ? nIframeHeightEm : 40,
      CX("<iframe width='100%%' frameborder='0' "
         "marginwidth='0' style='height:%dem' "
         "marginheight='0' sandbox='allow-same-origin' "
         "id='ifm1' src='data:text/html;base64,%z'"
         "></iframe>",
         nIframeHeightEm ? nIframeHeightEm : 40,
         z64);
      break;
    }
    case FE_RENDER_WIKI:
      wiki_render_by_mimetype(pContent, zMime);
      break;
    default:{
1059
1060
1061
1062
1063
1064
1065
1066

1067
1068
1069
1070
1071
1072
1073
1074
1075
1076


1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136

1137
1138
1139




1140
1141
1142
1143
1144
1145





1146
1147
1148
1149




1150
1151
1152


1153
1154
1155
1156
1157
1158
1159

1160
1161
1162
1163




1164
1165
1166
1167
1168

1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1072
1073
1074
1075
1076
1077
1078

1079
1080
1081
1082
1083
1084
1085
1086
1087


1088
1089
1090





1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102

1103








































1104



1105
1106
1107
1108






1109
1110
1111
1112
1113




1114
1115
1116
1117



1118
1119







1120




1121
1122
1123
1124





1125







1126
1127
1128
1129
1130
1131
1132







-
+








-
-
+
+

-
-
-
-
-












-

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
-
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
-
-
-
-
-
-
-







  }
  CX("</div><!--.fileedit-preview-->\n");
}

/*
** Renders diffs for the /fileedit page. pContent is the
** locally-edited content.  frid is the RID of the file's blob entry
** from which pContent is based.  zManifestUuid is the checkin version
** from which pContent is based. zManifestUuid is the checkin version
** to which RID belongs - it is purely informational, for labeling the
** diff view. isSbs is true for side-by-side diffs, false for unified.
*/
static void fileedit_render_diff(Blob * pContent, int frid,
                                 const char * zManifestUuid,
                                 int isSbs){
  Blob orig = empty_blob;
  Blob out = empty_blob;
  u64 diffFlags = DIFF_HTML | DIFF_NOTTOOBIG | DIFF_STRIP_EOLCR;

  u64 diffFlags = DIFF_HTML | DIFF_NOTTOOBIG | DIFF_STRIP_EOLCR
    | (isSbs ? DIFF_SIDEBYSIDE : DIFF_LINENO);
  content_get(frid, &orig);
  if(isSbs){
    diffFlags |=  DIFF_SIDEBYSIDE;
  }else{
    diffFlags |= DIFF_LINENO;
  }
  text_diff(&orig, pContent, &out, 0, diffFlags);
  CX("<div class='fileedit-diff'>");
  CX("<div>Diff <code>[%S]</code> &rarr; Local Edits</div>",
     zManifestUuid);
  if(isSbs){
    CX("%b",&out);
  }else{
    CX("<pre class='udiff'>%b</pre>",&out);
  }
  CX("</div><!--.fileedit-diff-->\n");
  blob_reset(&orig);
  blob_reset(&out);
  /* Wow, that was *easy*. */
}

/*
** Outputs a SELECT list from a compile-time list of integers.
** The vargs must be a list of (const char *, int) pairs, terminated
** with a single NULL. Each pair is interpreted as...
**
** If the (const char *) is NULL, it is the end of the list, else
** a new OPTION entry is created. If the string is empty, the
** label and value of the OPTION is the integer part of the pair.
** If the string is not empty, it becomes the label and the integer
** 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 emited HTML is:
**
** <div class='input-with-label'>
**   <span>{{zLabel}}</span>
**   <select>...</select>
** </div>
** 
*/
static void style_select_list_int_v(const char *zFieldName,
                                    const char * zLabel,
                                    const char * zToolTip,
                                    int selectedVal, va_list vargs){
  CX("<div class='input-with-label'");
  if(zToolTip && *zToolTip){
    CX(" title='%h'",zToolTip);
  }

  CX(">");
  if(zLabel && *zLabel){
    CX("<span>%h</span>", zLabel);
/*
** Given a repo-relative filename and a manifest RID, returns the UUID
** of the corresponding file entry.  Returns NULL if no match is
** found.  If pFilePerm is not NULL, the file's permission flag value
  }
  CX("<select name='%s'>",zFieldName);
  while(1){
    const char * zOption = va_arg(vargs,char *);
    int v;
    if(NULL==zOption){
** is written to *pFilePerm.
*/
static char *fileedit_file_uuid(char const *zFilename,
                                int vid, int *pFilePerm){
  Stmt stmt = empty_Stmt;
      break;
    }
    v = va_arg(vargs,int);
    CX("<option value='%d'%s>",
  char * zFileUuid = 0;
  db_prepare(&stmt, "SELECT uuid, perm FROM files_of_checkin "
             "WHERE filename=%Q %s AND checkinID=%d",
             zFilename, filename_collation(), vid);
         v, v==selectedVal ? " selected" : "");
    if(*zOption){
      CX("%s", zOption);
  if(SQLITE_ROW==db_step(&stmt)){
    zFileUuid = mprintf("%s",db_column_text(&stmt, 0));
    }else{
      CX("%d",v);
    }
    CX("</option>\n");
  }
  CX("</select>\n");
  if(zLabel && *zLabel){
    if(pFilePerm){
    CX("</div>\n");
  }
}

      *pFilePerm = mfile_permstr_int(db_column_text(&stmt, 1));
    }
  }
  db_finalize(&stmt);
/*
** The ellipsis-args counterpart of style_select_list_int_v().
*/
void style_select_list_int(const char *zFieldName,
                           const char * zLabel,
  return zFileUuid;
                           const char * zToolTip,
                           int selectedVal, ... ){
  va_list vargs;
  va_start(vargs,selectedVal);
  style_select_list_int_v(zFieldName, zLabel, zToolTip,
                          selectedVal, vargs);
  va_end(vargs);
}

/*
** WEBPAGE: fileedit
**
** EXPERIMENTAL and subject to change and removal at any time. The goal
** is to allow online edits of files.
1210
1211
1212
1213
1214
1215
1216

1217
1218
1219
1220
1221
1222
1223
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174







+







  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. */
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
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







-
-
-
-
-
-
-
+
-
-
-
-
-





+
+
+
















+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



















-
-
+
+







    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);
  db_prepare(&stmt, "SELECT uuid, perm FROM files_of_checkin "
             "WHERE filename=%Q %s AND checkinID=%d",
             zFilename, filename_collation(), vid);
  if(SQLITE_ROW==db_step(&stmt)){
    const char * zPerm = db_column_text(&stmt, 1);
    cimi.filePerm = mfile_permstr_int(zPerm);
    if(PERM_LNK==cimi.filePerm){
  zFileUuid = fileedit_file_uuid(zFilename, vid, &cimi.filePerm);
      fail((&err,"Editing symlinks is not permitted."));
    }
    zFileUuid = mprintf("%s",db_column_text(&stmt, 0));
  }
  db_finalize(&stmt);
  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 set. Here we go... */
  
  /********************************************************************
  ** 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("<h1>Editing:</h1>");
  CX("<p class='fileedit-hint'>");
  CX("File: "
     "[<a id='finfo-link' href='%R/finfo?name=%T&m=%!S'>info</a>] "
     "<code>%h</code><br>",
     zFilename, zFileUuid, zFilename);
  CX("Checkin Version: "
     "[<a id='r-link' href='%R/info/%!S'>info</a>] "
     "<code id='r-label'>%s</code><br>",
     cimi.zParentUuid, cimi.zParentUuid);
  CX("Permalink: <code>"
     "<a id='permalink' href='%R/fileedit?file=%T&r=%!S'>"
     "/fileedit?file=%T&r=%!S</a></code><br>"
     "(Clicking the permalink will reload the page and discard "
     "all edits!)",
     zFilename, cimi.zParentUuid,
     zFilename, cimi.zParentUuid);
  CX("</p>");
  CX("<p>This page is <em>far from complete</em> and may still have "
     "significant bugs. USE AT YOUR OWN RISK, preferably on a test "
  CX("<p>This page is <em>NEW AND EXPERIMENTAL</em>. "
     "USE AT YOUR OWN RISK, preferably on a test "
     "repo.</p>\n");
  
  CX("<form action='%R/fileedit#options' method='POST' "
     "class='fileedit'>\n");

  /******* Hidden fields *******/
  CX("<input type='hidden' name='r' value='%s'>",
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

1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465

1466
1467
1468
1469
1470
1471
1472
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
1452
1453

1454
1455
1456
1457
1458
1459
1460


1461
1462
1463
1464
1465

1466
1467
1468
1469
1470
1471
1472
1473







-
-
-
-
-
-
-






-
-
-



-
-
-




-
-
-



-
-
-






-
-
-






-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-










-
+






-
-





-
+







  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. */);
  /*
  ** 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;
  }
  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);
  if(P("allow_fork")!=0){
    cimi.flags |= CIMINI_ALLOW_FORK;
  }
  style_labeled_checkbox("allow_fork", "Allow fork?", "1",
                         "Allow saving to create a fork?",
                         cimi.flags & CIMINI_ALLOW_FORK);
  if(P("allow_older")!=0){
    cimi.flags |= CIMINI_ALLOW_OLDER;
  }
  style_labeled_checkbox("allow_older", "Allow older?", "1",
                         "Allow saving against a parent version "
                         "which has a newer timestamp?",
                         cimi.flags & CIMINI_ALLOW_OLDER);
  if(P("exec_bit")!=0){
    cimi.filePerm = PERM_EXE;
  }
  style_labeled_checkbox("exec_bit", "Executable?", "1",
                         "Set the executable bit?",
                         PERM_EXE==cimi.filePerm);
  if(P("allow_merge_conflict")!=0){
    cimi.flags |= CIMINI_ALLOW_MERGE_MARKER;
  }
  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);
  if(P("prefer_delta")!=0){
    cimi.flags |= CIMINI_PREFER_DELTA;
  }
  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);
  {/* 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;
    }
    style_select_list_int("eol", "EOL Style",
                          "EOL conversion policy, noting that "
                          "form-processing may implicitly change the "
                          "line endings of the input.",
                          eolMode==1||eolMode==2 ? eolMode : 0,
                          "Inherit", 0,
                          "Unix", 1,
                          "Windows", 2,
                          NULL);
  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"/*%h? %s?*/, 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>Tell the server to...</legend><div>");
  CX("<button type='submit' name='submit' value='%d'>"
     "Save</button>", SUBMIT_SAVE);
     "Commit</button>", SUBMIT_SAVE);
  CX("<button type='submit' name='submit' value='%d'>"
     "Preview</button>", SUBMIT_PREVIEW);
  {
    /* Preview rendering mode selection... */
    previewRenderMode = atoi(PD("preview_render_mode","0"));
    if(0==previewRenderMode){
      previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime);
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
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
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








+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
















-

-
+








-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







-
-
+
+
+
+

+



-
+




-
+





     "Diff (SBS)</button>", SUBMIT_DIFF_SBS);
  CX("<button type='submit' name='submit' value='%d'>"
     "Diff (Unified)</button>", SUBMIT_DIFF_UNIFIED);
  CX("</div></fieldset>");

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

  /*
  ** We cannot intercept the output for PREVIEW
  ** and DIFF modes, and therefore have to render those
  ** last.
  */
  if(SUBMIT_PREVIEW==submitMode){
    int pflags = 0;
    if(previewLn) pflags |= FE_PREVIEW_LINE_NUMBERS;
    fileedit_render_preview(&cimi.fileContent, cimi.zFilename, pflags,
                            previewRenderMode, previewHtmlHeight);
  }else if(SUBMIT_DIFF_SBS==submitMode
           || SUBMIT_DIFF_UNIFIED==submitMode){
    fileedit_render_diff(&cimi.fileContent, frid, cimi.zParentUuid,
                         SUBMIT_DIFF_SBS==submitMode);
  }

  /* Dynamically populate the editor... */
  fileedit_emit_script_fetch();
  if(1==loadMode || (2==loadMode && submitMode>SUBMIT_NONE)){
    char const * zQuoted = 0;
    if(blob_size(&cimi.fileContent)>0){
      db_prepare(&stmt, "SELECT json_quote(%B)", &cimi.fileContent);
      db_step(&stmt);
      zQuoted = db_column_text(&stmt,0);
    }
    blob_appendf(&endScript,
                 "/* populate editor form */\n"
                 "document.getElementById('fileedit-content')"
                 ".value=%s;", zQuoted ? zQuoted : "'';\n");
    if(stmt.pStmt){
      db_finalize(&stmt);
    }
  }else if(2==loadMode){
    assert(submitMode==SUBMIT_NONE);
    fileedit_emit_script_fetch();
    blob_appendf(&endScript,
                 "window.fossilFetch('raw/%s',{"
                 "window.fossil.fetch('raw/%s',{"
                 "onload: (r)=>document.getElementById('fileedit-content')"
                 ".value=r,"
                 "onerror:()=>document.getElementById('fileedit-content')"
                 ".value="
                 "'Error loading content'"
                 "});\n", zFileUuid);
  }

  if(SUBMIT_SAVE==submitMode){
    Blob manifest = empty_blob;
    char * zNewUuid = 0;
    /*cimi.flags |= CIMINI_STRONGLY_PREFER_DELTA;*/
    if(zComment && *zComment){
      blob_append(&cimi.comment, zComment, -1);
    }else{
      fail((&err,"Empty comment is not permitted."));
    }
    /*cimi.pParent = manifest_get(vid, CFTYPE_MANIFEST, 0);
      assert(cimi.pParent && "We know vid is valid.");*/
    cimi.pMfOut = &manifest;
    checkin_mini(&cimi, &newVid, &err);
    if(newVid!=0){
      zNewUuid = rid_to_uuid(newVid);
      CX("<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)){
        /* We need to update certain form fields and UI elements so
        ** they're not left pointing to the previous version. While
        ** we're at it, we'll re-enable dry-run mode for sanity's
        ** sake.
        */
        blob_appendf(&endScript,
                     "/* Toggle dry-run back on */\n"
                     "document.querySelector('input[type=checkbox]"
                     "[name=dry_run]').checked=true;\n");
        blob_appendf(&endScript,
                     "/* Update version number */\n"
                     "document.querySelector('input[name=r]')"
                     ".value=%Q;\n"
                     "document.querySelector('#r-label')"
                     ".innerText=%Q;\n"
                     "document.querySelector('#r-link')"
                     ".setAttribute('href', '%R/info/%!S');\n"
                     "document.querySelector('#finfo-link')"
                     ".setAttribute('href','%R/finfo?name=%T&m=%!S');\n",
                     /*input[name=r]:*/zNewUuid, /*#r-label:*/ zNewUuid,
                     /*#r-link:*/ zNewUuid,
                     /*#finfo-link:*/zFilename, zNewUuid);
        blob_appendf(&endScript,
                     "/* Updated finfo link */"
                     );
        blob_appendf(&endScript,
                     "/* Update permalink */\n"
                     "const urlFull='%R/fileedit?file=%T&r=%!S';\n"
                     "const urlShort='/fileedit?file=%T&r=%!S';\n"
                     "let link=document.querySelector('#permalink');\n"
                     "link.innerText=urlShort;\n"
                     "link.setAttribute('href',urlFull);\n",
                     cimi.zFilename, zNewUuid,
                     cimi.zFilename, zNewUuid);
      }
      fossil_free(zNewUuid);
      zNewUuid = 0;
    }
    /* On error, the error message is in the err blob and will
    ** be emitted below. */
    cimi.pMfOut = 0;
    blob_reset(&manifest);
  }else if(SUBMIT_PREVIEW==submitMode){
    int pflags = 0;
    if(previewLn) pflags |= FE_PREVIEW_LINE_NUMBERS;
    fileedit_render_preview(&cimi.fileContent, cimi.zFilename, pflags,
                            previewRenderMode, previewHtmlHeight);
  }else if(SUBMIT_DIFF_SBS==submitMode
           || SUBMIT_DIFF_UNIFIED==submitMode){
    fileedit_render_diff(&cimi.fileContent, frid, cimi.zParentUuid,
                         SUBMIT_DIFF_SBS==submitMode);
  }else{
    /* Ignore invalid submitMode value */
    goto end_footer;
  }

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));
    CX("<div class='fileedit-error-report'>%s</div>",
       blob_str(&err));
  }else if(blob_size(&submitResult)){
    CX("%b",&submitResult);
  }
  blob_reset(&submitResult);
  blob_reset(&err);
  CheckinMiniInfo_cleanup(&cimi);
  if(blob_size(&endScript)>0){
    fileedit_emit_script(0);
    style_emit_script_tag(0);
    CX("(function(){\n");
    CX("try{\n%b\n}catch(e){console.error('Exception:',e)}\n",
       &endScript);
    CX("})();");
    fileedit_emit_script(1);
    style_emit_script_tag(1);
  }
  db_end_transaction(0/*noting that dry-run mode will have already
                      ** set this to rollback mode. */);
  style_footer();
}