Fossil SCM
Implemented HTML-in-iframe preview. Related internal cleanups.
Commit
ac309281e61e03a5cd38e81bc019d1102080c5db8bb8193bd8b27be81e66ffd4
Parent
079030faaf762c2…
1 file changed
+88
-58
+88
-58
| --- src/fileedit.c | ||
| +++ src/fileedit.c | ||
| @@ -992,64 +992,57 @@ | ||
| 992 | 992 | } |
| 993 | 993 | |
| 994 | 994 | enum fileedit_render_preview_flags { |
| 995 | 995 | FE_PREVIEW_LINE_NUMBERS = 1 |
| 996 | 996 | }; |
| 997 | +enum fileedit_render_modes { | |
| 998 | +FE_RENDER_PLAIN_TEXT = 0, | |
| 999 | +FE_RENDER_HTML, | |
| 1000 | +FE_RENDER_WIKI | |
| 1001 | +}; | |
| 1002 | + | |
| 1003 | +static int fileedit_render_mode_for_mimetype(const char * zMimetype){ | |
| 1004 | + int rc = FE_RENDER_PLAIN_TEXT; | |
| 1005 | + if( zMimetype ){ | |
| 1006 | + if( fossil_strcmp(zMimetype, "text/html")==0 ){ | |
| 1007 | + rc = FE_RENDER_HTML; | |
| 1008 | + }else if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 | |
| 1009 | + || fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ | |
| 1010 | + rc = FE_RENDER_WIKI; | |
| 1011 | + } | |
| 1012 | + } | |
| 1013 | + return rc; | |
| 1014 | +} | |
| 997 | 1015 | |
| 998 | 1016 | /* |
| 999 | 1017 | ** Performs the PREVIEW mode for /filepage. |
| 1000 | 1018 | */ |
| 1001 | 1019 | static void fileedit_render_preview(Blob * pContent, |
| 1002 | 1020 | const char *zFilename, |
| 1003 | - int flags){ | |
| 1021 | + int flags, | |
| 1022 | + int nIframeHeightEm){ | |
| 1004 | 1023 | const char * zMime; |
| 1005 | - enum render_modes {PLAIN_TEXT = 0, HTML, WIKI}; | |
| 1006 | - int renderMode = PLAIN_TEXT; | |
| 1024 | + int renderMode = FE_RENDER_PLAIN_TEXT; | |
| 1007 | 1025 | zMime = mimetype_from_name(zFilename); |
| 1008 | - if( zMime ){ | |
| 1009 | - if( fossil_strcmp(zMime, "text/html")==0 ){ | |
| 1010 | - renderMode = HTML; | |
| 1011 | - }else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 | |
| 1012 | - || fossil_strcmp(zMime, "text/x-markdown")==0 ){ | |
| 1013 | - renderMode = WIKI; | |
| 1014 | - } | |
| 1015 | - } | |
| 1026 | + renderMode = fileedit_render_mode_for_mimetype(zMime); | |
| 1016 | 1027 | CX("<div class='fileedit-preview'>"); |
| 1017 | 1028 | CX("<div>Preview</div>"); |
| 1018 | 1029 | switch(renderMode){ |
| 1019 | - case HTML:{ | |
| 1030 | + case FE_RENDER_HTML:{ | |
| 1031 | + char * z64 = encode64(blob_str(pContent), blob_size(pContent)); | |
| 1020 | 1032 | CX("<iframe width='100%%' frameborder='0' marginwidth='0' " |
| 1033 | + "style='height:%dem' " | |
| 1021 | 1034 | "marginheight='0' sandbox='allow-same-origin' id='ifm1' " |
| 1022 | - "srcdoc='Not yet working: not sure how to " | |
| 1023 | - "populate the iframe.'" | |
| 1024 | - "></iframe>"); | |
| 1025 | -#if 0 | |
| 1026 | - fileedit_emit_script(0); | |
| 1027 | - CX("document.getElementById('ifm1').addEventListener('load'," | |
| 1028 | - "function(){\n" | |
| 1029 | - "console.debug('iframe=',this);\n" | |
| 1030 | - "this.height=this.contentDocument.documentElement." | |
| 1031 | - "scrollHeight + 75;\n" | |
| 1032 | - "this.contentDocument.body.innerHTML=`%h`;\n" | |
| 1033 | - "});\n", | |
| 1034 | - blob_str(pContent)); | |
| 1035 | - /* Potential TODO: use iframe.srcdoc: | |
| 1036 | - ** | |
| 1037 | - ** https://caniuse.com/#search=srcdoc | |
| 1038 | - ** https://stackoverflow.com/questions/22381216/escape-quotes-in-an-iframe-srcdoc-value | |
| 1039 | - ** | |
| 1040 | - ** Doing so would require escaping the quote characters which match | |
| 1041 | - ** the srcdoc='xyz' quotes. | |
| 1042 | - */ | |
| 1043 | - fileedit_emit_script(1); | |
| 1044 | -#endif | |
| 1035 | + "src='data:text/html;base64,%z'" | |
| 1036 | + "></iframe>", nIframeHeightEm ? nIframeHeightEm : 40, | |
| 1037 | + z64); | |
| 1045 | 1038 | break; |
| 1046 | 1039 | } |
| 1047 | - case WIKI: | |
| 1040 | + case FE_RENDER_WIKI: | |
| 1048 | 1041 | wiki_render_by_mimetype(pContent, zMime); |
| 1049 | 1042 | break; |
| 1050 | - case PLAIN_TEXT: | |
| 1043 | + case FE_RENDER_PLAIN_TEXT: | |
| 1051 | 1044 | default:{ |
| 1052 | 1045 | const char *zExt = strrchr(zFilename,'.'); |
| 1053 | 1046 | const char *zContent = blob_str(pContent); |
| 1054 | 1047 | if(FE_PREVIEW_LINE_NUMBERS & flags){ |
| 1055 | 1048 | output_text_with_line_numbers(zContent, "on"); |
| @@ -1080,22 +1073,28 @@ | ||
| 1080 | 1073 | ** All other parameters are for internal use only, submitted via the |
| 1081 | 1074 | ** form-submission process, and may change with any given revision of |
| 1082 | 1075 | ** this code. |
| 1083 | 1076 | */ |
| 1084 | 1077 | void fileedit_page(){ |
| 1078 | + enum submit_modes { | |
| 1079 | + SUBMIT_NONE = 0, SUBMIT_SAVE = 1, SUBMIT_PREVIEW = 2, | |
| 1080 | + SUBMIT_DIFF = 3 | |
| 1081 | + }; | |
| 1085 | 1082 | const char * zFilename = PD("file",P("name")); |
| 1086 | 1083 | /* filename. We'll accept 'name' |
| 1087 | 1084 | because that param is handled |
| 1088 | 1085 | specially by the core. */ |
| 1089 | 1086 | const char * zRev = P("r"); /* checkin version */ |
| 1090 | 1087 | const char * zContent = P("content"); /* file content */ |
| 1091 | 1088 | const char * zComment = P("comment"); /* checkin comment */ |
| 1089 | + const char * zFileMime = 0; /* File mime type guess */ | |
| 1092 | 1090 | CheckinMiniInfo cimi; /* Checkin state */ |
| 1093 | - int submitMode = 0; /* See mapping below */ | |
| 1091 | + int submitMode = SUBMIT_NONE; /* See mapping below */ | |
| 1094 | 1092 | int vid, newVid = 0; /* checkin rid */ |
| 1095 | 1093 | int frid = 0; /* File content rid */ |
| 1096 | 1094 | int previewLn = P("preview_ln")!=0; /* Line number mode */ |
| 1095 | + int previewHtmlHeight = 0; /* iframe height (EMs) */ | |
| 1097 | 1096 | char * zFileUuid = 0; /* File content UUID */ |
| 1098 | 1097 | Blob err = empty_blob; /* Error report */ |
| 1099 | 1098 | const char * zFlagCheck = 0; /* Temp url flag holder */ |
| 1100 | 1099 | Blob endScript = empty_blob; /* Script code to run at the |
| 1101 | 1100 | end. This content will be |
| @@ -1132,13 +1131,14 @@ | ||
| 1132 | 1131 | login_needed(g.anon.Write); |
| 1133 | 1132 | return; |
| 1134 | 1133 | } |
| 1135 | 1134 | db_begin_transaction(); |
| 1136 | 1135 | CheckinMiniInfo_init(&cimi); |
| 1137 | - submitMode = atoi(PD("submit","0")) | |
| 1138 | - /* Submit modes: 0=initial request, | |
| 1139 | - ** 1=submit (save), 2=preview, 3=diff */; | |
| 1136 | + submitMode = atoi(PD("submit","0")); | |
| 1137 | + if(submitMode < SUBMIT_NONE || submitMode > SUBMIT_DIFF){ | |
| 1138 | + submitMode = 0; | |
| 1139 | + } | |
| 1140 | 1140 | zFlagCheck = P("comment_mimetype"); |
| 1141 | 1141 | if(zFlagCheck){ |
| 1142 | 1142 | cimi.zMimetype = mprintf("%s",zFlagCheck); |
| 1143 | 1143 | zFlagCheck = 0; |
| 1144 | 1144 | } |
| @@ -1162,10 +1162,11 @@ | ||
| 1162 | 1162 | vid = symbolic_name_to_rid(zRev, "ci"); |
| 1163 | 1163 | if(0==vid){ |
| 1164 | 1164 | fail((&err,"Could not resolve checkin version.")); |
| 1165 | 1165 | } |
| 1166 | 1166 | cimi.zFilename = mprintf("%s",zFilename); |
| 1167 | + zFileMime = mimetype_from_name(zFilename); | |
| 1167 | 1168 | |
| 1168 | 1169 | /* Find the repo-side file entry or fail... */ |
| 1169 | 1170 | cimi.zParentUuid = rid_to_uuid(vid); |
| 1170 | 1171 | db_prepare(&stmt, "SELECT uuid, perm FROM files_of_checkin " |
| 1171 | 1172 | "WHERE filename=%Q %s AND checkinID=%d", |
| @@ -1222,13 +1223,12 @@ | ||
| 1222 | 1223 | CX("</p>"); |
| 1223 | 1224 | CX("<p>This page is <em>far from complete</em> and may still have " |
| 1224 | 1225 | "significant bugs. USE AT YOUR OWN RISK, preferably on a test " |
| 1225 | 1226 | "repo.</p>\n"); |
| 1226 | 1227 | |
| 1227 | - CX("<form action='%R/fileedit%s' method='POST' " | |
| 1228 | - "class='fileedit-form'>\n", | |
| 1229 | - submitMode>0 ? "#options" : ""); | |
| 1228 | + CX("<form action='%R/fileedit#options' method='POST' " | |
| 1229 | + "class='fileedit-form'>\n"); | |
| 1230 | 1230 | |
| 1231 | 1231 | /******* Hidden fields *******/ |
| 1232 | 1232 | CX("<input type='hidden' name='r' value='%s'>", |
| 1233 | 1233 | cimi.zParentUuid); |
| 1234 | 1234 | CX("<input type='hidden' name='file' value='%T'>", |
| @@ -1263,11 +1263,11 @@ | ||
| 1263 | 1263 | ** a containing div is necessary. */); |
| 1264 | 1264 | /* |
| 1265 | 1265 | ** TODO?: date-override date selection field. Maybe use |
| 1266 | 1266 | ** an input[type=datetime-local]. |
| 1267 | 1267 | */ |
| 1268 | - if(0==submitMode || P("dry_run")!=0){ | |
| 1268 | + if(SUBMIT_NONE==submitMode || P("dry_run")!=0){ | |
| 1269 | 1269 | cimi.flags |= CIMINI_DRY_RUN; |
| 1270 | 1270 | } |
| 1271 | 1271 | style_labeled_checkbox("dry_run", "Dry-run?", "1", |
| 1272 | 1272 | "In dry-run mode, the Save button performs " |
| 1273 | 1273 | "all work needed for saving but then rolls " |
| @@ -1310,11 +1310,12 @@ | ||
| 1310 | 1310 | "Will create a delta manifest, instead of " |
| 1311 | 1311 | "baseline, if conditions are favorable to do " |
| 1312 | 1312 | "so. This option is only a suggestion.", |
| 1313 | 1313 | cimi.flags & CIMINI_PREFER_DELTA); |
| 1314 | 1314 | {/* EOL conversion policy... */ |
| 1315 | - const int eolMode = submitMode==0 ? 0 : atoi(PD("eol","0")); | |
| 1315 | + const int eolMode = submitMode==SUBMIT_NONE | |
| 1316 | + ? 0 : atoi(PD("eol","0")); | |
| 1316 | 1317 | switch(eolMode){ |
| 1317 | 1318 | case 1: cimi.flags |= CIMINI_CONVERT_EOL_UNIX; break; |
| 1318 | 1319 | case 2: cimi.flags |= CIMINI_CONVERT_EOL_WINDOWS; break; |
| 1319 | 1320 | default: cimi.flags |= CIMINI_CONVERT_EOL_INHERIT; break; |
| 1320 | 1321 | } |
| @@ -1340,23 +1341,51 @@ | ||
| 1340 | 1341 | "Save</button>"); |
| 1341 | 1342 | CX("<button type='submit' name='submit' value='2'>" |
| 1342 | 1343 | "Preview</button>"); |
| 1343 | 1344 | CX("<button type='submit' name='submit' value='3'>" |
| 1344 | 1345 | "Diff (TODO)</button>"); |
| 1345 | - CX("<br>"); | |
| 1346 | - style_labeled_checkbox("preview_ln", | |
| 1347 | - "Add line numbers to plain-text previews?", "1", | |
| 1348 | - "If on, plain-text files (only) will get " | |
| 1349 | - "line numbers added to the preview.", | |
| 1350 | - previewLn); | |
| 1346 | + { | |
| 1347 | + /* Options which depend on the current submitMode or | |
| 1348 | + the file's preview rendering mode. */ | |
| 1349 | + const int renderMode = fileedit_render_mode_for_mimetype(zFileMime); | |
| 1350 | + CX("<br>"); | |
| 1351 | + if(FE_RENDER_HTML==renderMode){ | |
| 1352 | + /* HTML preview mode iframe height... */ | |
| 1353 | + int i; | |
| 1354 | + if(submitMode==SUBMIT_PREVIEW){ | |
| 1355 | + previewHtmlHeight = atoi(PD("preview_html_ems","0")); | |
| 1356 | + }else{ | |
| 1357 | + previewHtmlHeight = 40; | |
| 1358 | + } | |
| 1359 | + /* Allow selection of HTML preview iframe height */ | |
| 1360 | + CX("<select name='preview_html_ems' " | |
| 1361 | + "title='Height (in EMs) of the iframe used for HTML " | |
| 1362 | + "preview.'>\n"); | |
| 1363 | + CX("<option disabled value='40'>HTML Preview Height (EMs)" | |
| 1364 | + "</option>\n"); | |
| 1365 | + for( i = 20; i <= 100; i+=20 ){ | |
| 1366 | + CX("<option value='%d'%s>%d</option>\n", | |
| 1367 | + i, (previewHtmlHeight==i) ? " selected" : "", i); | |
| 1368 | + } | |
| 1369 | + CX("</select>\n"); | |
| 1370 | + } | |
| 1371 | + else if(FE_RENDER_PLAIN_TEXT == renderMode){ | |
| 1372 | + style_labeled_checkbox("preview_ln", | |
| 1373 | + "Add line numbers to plain-text previews?", | |
| 1374 | + "1", | |
| 1375 | + "If on, plain-text files (only) will get " | |
| 1376 | + "line numbers added to the preview.", | |
| 1377 | + previewLn); | |
| 1378 | + } | |
| 1379 | + } | |
| 1351 | 1380 | CX("</div></fieldset>"); |
| 1352 | 1381 | |
| 1353 | 1382 | /******* End of form *******/ |
| 1354 | 1383 | CX("</form>\n"); |
| 1355 | 1384 | |
| 1356 | 1385 | /* Dynamically populate the editor... */ |
| 1357 | - if(1==loadMode || (2==loadMode && submitMode>0)){ | |
| 1386 | + if(1==loadMode || (2==loadMode && submitMode>SUBMIT_NONE)){ | |
| 1358 | 1387 | char const * zQuoted = 0; |
| 1359 | 1388 | if(blob_size(&cimi.fileContent)>0){ |
| 1360 | 1389 | db_prepare(&stmt, "SELECT json_quote(%B)", &cimi.fileContent); |
| 1361 | 1390 | db_step(&stmt); |
| 1362 | 1391 | zQuoted = db_column_text(&stmt,0); |
| @@ -1367,11 +1396,11 @@ | ||
| 1367 | 1396 | ".value=%s;", zQuoted ? zQuoted : "'';\n"); |
| 1368 | 1397 | if(stmt.pStmt){ |
| 1369 | 1398 | db_finalize(&stmt); |
| 1370 | 1399 | } |
| 1371 | 1400 | }else if(2==loadMode){ |
| 1372 | - assert(submitMode==0); | |
| 1401 | + assert(submitMode==SUBMIT_NONE); | |
| 1373 | 1402 | fileedit_emit_script_fetch(); |
| 1374 | 1403 | blob_appendf(&endScript, |
| 1375 | 1404 | "window.fossilFetch('raw/%s',{" |
| 1376 | 1405 | "onload: (r)=>document.getElementById('fileedit-content')" |
| 1377 | 1406 | ".value=r," |
| @@ -1379,11 +1408,11 @@ | ||
| 1379 | 1408 | ".value=" |
| 1380 | 1409 | "'Error loading content'" |
| 1381 | 1410 | "});\n", zFileUuid); |
| 1382 | 1411 | } |
| 1383 | 1412 | |
| 1384 | - if(1==submitMode/*save*/){ | |
| 1413 | + if(SUBMIT_SAVE==submitMode){ | |
| 1385 | 1414 | Blob manifest = empty_blob; |
| 1386 | 1415 | char * zNewUuid = 0; |
| 1387 | 1416 | /*cimi.flags |= CIMINI_STRONGLY_PREFER_DELTA;*/ |
| 1388 | 1417 | if(zComment && *zComment){ |
| 1389 | 1418 | blob_append(&cimi.comment, zComment, -1); |
| @@ -1442,15 +1471,16 @@ | ||
| 1442 | 1471 | } |
| 1443 | 1472 | /* On error, the error message is in the err blob and will |
| 1444 | 1473 | ** be emitted below. */ |
| 1445 | 1474 | cimi.pMfOut = 0; |
| 1446 | 1475 | blob_reset(&manifest); |
| 1447 | - }else if(2==submitMode/*preview*/){ | |
| 1476 | + }else if(SUBMIT_PREVIEW==submitMode){ | |
| 1448 | 1477 | int pflags = 0; |
| 1449 | 1478 | if(previewLn) pflags |= FE_PREVIEW_LINE_NUMBERS; |
| 1450 | - fileedit_render_preview(&cimi.fileContent, cimi.zFilename, pflags); | |
| 1451 | - }else if(3==submitMode/*diff*/){ | |
| 1479 | + fileedit_render_preview(&cimi.fileContent, cimi.zFilename, pflags, | |
| 1480 | + previewHtmlHeight); | |
| 1481 | + }else if(SUBMIT_DIFF==submitMode/*diff*/){ | |
| 1452 | 1482 | fail((&err,"Diff mode is still TODO.")); |
| 1453 | 1483 | }else{ |
| 1454 | 1484 | /* Ignore invalid submitMode value */ |
| 1455 | 1485 | goto end_footer; |
| 1456 | 1486 | } |
| 1457 | 1487 |
| --- src/fileedit.c | |
| +++ src/fileedit.c | |
| @@ -992,64 +992,57 @@ | |
| 992 | } |
| 993 | |
| 994 | enum fileedit_render_preview_flags { |
| 995 | FE_PREVIEW_LINE_NUMBERS = 1 |
| 996 | }; |
| 997 | |
| 998 | /* |
| 999 | ** Performs the PREVIEW mode for /filepage. |
| 1000 | */ |
| 1001 | static void fileedit_render_preview(Blob * pContent, |
| 1002 | const char *zFilename, |
| 1003 | int flags){ |
| 1004 | const char * zMime; |
| 1005 | enum render_modes {PLAIN_TEXT = 0, HTML, WIKI}; |
| 1006 | int renderMode = PLAIN_TEXT; |
| 1007 | zMime = mimetype_from_name(zFilename); |
| 1008 | if( zMime ){ |
| 1009 | if( fossil_strcmp(zMime, "text/html")==0 ){ |
| 1010 | renderMode = HTML; |
| 1011 | }else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 |
| 1012 | || fossil_strcmp(zMime, "text/x-markdown")==0 ){ |
| 1013 | renderMode = WIKI; |
| 1014 | } |
| 1015 | } |
| 1016 | CX("<div class='fileedit-preview'>"); |
| 1017 | CX("<div>Preview</div>"); |
| 1018 | switch(renderMode){ |
| 1019 | case HTML:{ |
| 1020 | CX("<iframe width='100%%' frameborder='0' marginwidth='0' " |
| 1021 | "marginheight='0' sandbox='allow-same-origin' id='ifm1' " |
| 1022 | "srcdoc='Not yet working: not sure how to " |
| 1023 | "populate the iframe.'" |
| 1024 | "></iframe>"); |
| 1025 | #if 0 |
| 1026 | fileedit_emit_script(0); |
| 1027 | CX("document.getElementById('ifm1').addEventListener('load'," |
| 1028 | "function(){\n" |
| 1029 | "console.debug('iframe=',this);\n" |
| 1030 | "this.height=this.contentDocument.documentElement." |
| 1031 | "scrollHeight + 75;\n" |
| 1032 | "this.contentDocument.body.innerHTML=`%h`;\n" |
| 1033 | "});\n", |
| 1034 | blob_str(pContent)); |
| 1035 | /* Potential TODO: use iframe.srcdoc: |
| 1036 | ** |
| 1037 | ** https://caniuse.com/#search=srcdoc |
| 1038 | ** https://stackoverflow.com/questions/22381216/escape-quotes-in-an-iframe-srcdoc-value |
| 1039 | ** |
| 1040 | ** Doing so would require escaping the quote characters which match |
| 1041 | ** the srcdoc='xyz' quotes. |
| 1042 | */ |
| 1043 | fileedit_emit_script(1); |
| 1044 | #endif |
| 1045 | break; |
| 1046 | } |
| 1047 | case WIKI: |
| 1048 | wiki_render_by_mimetype(pContent, zMime); |
| 1049 | break; |
| 1050 | case PLAIN_TEXT: |
| 1051 | default:{ |
| 1052 | const char *zExt = strrchr(zFilename,'.'); |
| 1053 | const char *zContent = blob_str(pContent); |
| 1054 | if(FE_PREVIEW_LINE_NUMBERS & flags){ |
| 1055 | output_text_with_line_numbers(zContent, "on"); |
| @@ -1080,22 +1073,28 @@ | |
| 1080 | ** All other parameters are for internal use only, submitted via the |
| 1081 | ** form-submission process, and may change with any given revision of |
| 1082 | ** this code. |
| 1083 | */ |
| 1084 | void fileedit_page(){ |
| 1085 | const char * zFilename = PD("file",P("name")); |
| 1086 | /* filename. We'll accept 'name' |
| 1087 | because that param is handled |
| 1088 | specially by the core. */ |
| 1089 | const char * zRev = P("r"); /* checkin version */ |
| 1090 | const char * zContent = P("content"); /* file content */ |
| 1091 | const char * zComment = P("comment"); /* checkin comment */ |
| 1092 | CheckinMiniInfo cimi; /* Checkin state */ |
| 1093 | int submitMode = 0; /* See mapping below */ |
| 1094 | int vid, newVid = 0; /* checkin rid */ |
| 1095 | int frid = 0; /* File content rid */ |
| 1096 | int previewLn = P("preview_ln")!=0; /* Line number mode */ |
| 1097 | char * zFileUuid = 0; /* File content UUID */ |
| 1098 | Blob err = empty_blob; /* Error report */ |
| 1099 | const char * zFlagCheck = 0; /* Temp url flag holder */ |
| 1100 | Blob endScript = empty_blob; /* Script code to run at the |
| 1101 | end. This content will be |
| @@ -1132,13 +1131,14 @@ | |
| 1132 | login_needed(g.anon.Write); |
| 1133 | return; |
| 1134 | } |
| 1135 | db_begin_transaction(); |
| 1136 | CheckinMiniInfo_init(&cimi); |
| 1137 | submitMode = atoi(PD("submit","0")) |
| 1138 | /* Submit modes: 0=initial request, |
| 1139 | ** 1=submit (save), 2=preview, 3=diff */; |
| 1140 | zFlagCheck = P("comment_mimetype"); |
| 1141 | if(zFlagCheck){ |
| 1142 | cimi.zMimetype = mprintf("%s",zFlagCheck); |
| 1143 | zFlagCheck = 0; |
| 1144 | } |
| @@ -1162,10 +1162,11 @@ | |
| 1162 | vid = symbolic_name_to_rid(zRev, "ci"); |
| 1163 | if(0==vid){ |
| 1164 | fail((&err,"Could not resolve checkin version.")); |
| 1165 | } |
| 1166 | cimi.zFilename = mprintf("%s",zFilename); |
| 1167 | |
| 1168 | /* Find the repo-side file entry or fail... */ |
| 1169 | cimi.zParentUuid = rid_to_uuid(vid); |
| 1170 | db_prepare(&stmt, "SELECT uuid, perm FROM files_of_checkin " |
| 1171 | "WHERE filename=%Q %s AND checkinID=%d", |
| @@ -1222,13 +1223,12 @@ | |
| 1222 | CX("</p>"); |
| 1223 | CX("<p>This page is <em>far from complete</em> and may still have " |
| 1224 | "significant bugs. USE AT YOUR OWN RISK, preferably on a test " |
| 1225 | "repo.</p>\n"); |
| 1226 | |
| 1227 | CX("<form action='%R/fileedit%s' method='POST' " |
| 1228 | "class='fileedit-form'>\n", |
| 1229 | submitMode>0 ? "#options" : ""); |
| 1230 | |
| 1231 | /******* Hidden fields *******/ |
| 1232 | CX("<input type='hidden' name='r' value='%s'>", |
| 1233 | cimi.zParentUuid); |
| 1234 | CX("<input type='hidden' name='file' value='%T'>", |
| @@ -1263,11 +1263,11 @@ | |
| 1263 | ** a containing div is necessary. */); |
| 1264 | /* |
| 1265 | ** TODO?: date-override date selection field. Maybe use |
| 1266 | ** an input[type=datetime-local]. |
| 1267 | */ |
| 1268 | if(0==submitMode || P("dry_run")!=0){ |
| 1269 | cimi.flags |= CIMINI_DRY_RUN; |
| 1270 | } |
| 1271 | style_labeled_checkbox("dry_run", "Dry-run?", "1", |
| 1272 | "In dry-run mode, the Save button performs " |
| 1273 | "all work needed for saving but then rolls " |
| @@ -1310,11 +1310,12 @@ | |
| 1310 | "Will create a delta manifest, instead of " |
| 1311 | "baseline, if conditions are favorable to do " |
| 1312 | "so. This option is only a suggestion.", |
| 1313 | cimi.flags & CIMINI_PREFER_DELTA); |
| 1314 | {/* EOL conversion policy... */ |
| 1315 | const int eolMode = submitMode==0 ? 0 : atoi(PD("eol","0")); |
| 1316 | switch(eolMode){ |
| 1317 | case 1: cimi.flags |= CIMINI_CONVERT_EOL_UNIX; break; |
| 1318 | case 2: cimi.flags |= CIMINI_CONVERT_EOL_WINDOWS; break; |
| 1319 | default: cimi.flags |= CIMINI_CONVERT_EOL_INHERIT; break; |
| 1320 | } |
| @@ -1340,23 +1341,51 @@ | |
| 1340 | "Save</button>"); |
| 1341 | CX("<button type='submit' name='submit' value='2'>" |
| 1342 | "Preview</button>"); |
| 1343 | CX("<button type='submit' name='submit' value='3'>" |
| 1344 | "Diff (TODO)</button>"); |
| 1345 | CX("<br>"); |
| 1346 | style_labeled_checkbox("preview_ln", |
| 1347 | "Add line numbers to plain-text previews?", "1", |
| 1348 | "If on, plain-text files (only) will get " |
| 1349 | "line numbers added to the preview.", |
| 1350 | previewLn); |
| 1351 | CX("</div></fieldset>"); |
| 1352 | |
| 1353 | /******* End of form *******/ |
| 1354 | CX("</form>\n"); |
| 1355 | |
| 1356 | /* Dynamically populate the editor... */ |
| 1357 | if(1==loadMode || (2==loadMode && submitMode>0)){ |
| 1358 | char const * zQuoted = 0; |
| 1359 | if(blob_size(&cimi.fileContent)>0){ |
| 1360 | db_prepare(&stmt, "SELECT json_quote(%B)", &cimi.fileContent); |
| 1361 | db_step(&stmt); |
| 1362 | zQuoted = db_column_text(&stmt,0); |
| @@ -1367,11 +1396,11 @@ | |
| 1367 | ".value=%s;", zQuoted ? zQuoted : "'';\n"); |
| 1368 | if(stmt.pStmt){ |
| 1369 | db_finalize(&stmt); |
| 1370 | } |
| 1371 | }else if(2==loadMode){ |
| 1372 | assert(submitMode==0); |
| 1373 | fileedit_emit_script_fetch(); |
| 1374 | blob_appendf(&endScript, |
| 1375 | "window.fossilFetch('raw/%s',{" |
| 1376 | "onload: (r)=>document.getElementById('fileedit-content')" |
| 1377 | ".value=r," |
| @@ -1379,11 +1408,11 @@ | |
| 1379 | ".value=" |
| 1380 | "'Error loading content'" |
| 1381 | "});\n", zFileUuid); |
| 1382 | } |
| 1383 | |
| 1384 | if(1==submitMode/*save*/){ |
| 1385 | Blob manifest = empty_blob; |
| 1386 | char * zNewUuid = 0; |
| 1387 | /*cimi.flags |= CIMINI_STRONGLY_PREFER_DELTA;*/ |
| 1388 | if(zComment && *zComment){ |
| 1389 | blob_append(&cimi.comment, zComment, -1); |
| @@ -1442,15 +1471,16 @@ | |
| 1442 | } |
| 1443 | /* On error, the error message is in the err blob and will |
| 1444 | ** be emitted below. */ |
| 1445 | cimi.pMfOut = 0; |
| 1446 | blob_reset(&manifest); |
| 1447 | }else if(2==submitMode/*preview*/){ |
| 1448 | int pflags = 0; |
| 1449 | if(previewLn) pflags |= FE_PREVIEW_LINE_NUMBERS; |
| 1450 | fileedit_render_preview(&cimi.fileContent, cimi.zFilename, pflags); |
| 1451 | }else if(3==submitMode/*diff*/){ |
| 1452 | fail((&err,"Diff mode is still TODO.")); |
| 1453 | }else{ |
| 1454 | /* Ignore invalid submitMode value */ |
| 1455 | goto end_footer; |
| 1456 | } |
| 1457 |
| --- src/fileedit.c | |
| +++ src/fileedit.c | |
| @@ -992,64 +992,57 @@ | |
| 992 | } |
| 993 | |
| 994 | enum fileedit_render_preview_flags { |
| 995 | FE_PREVIEW_LINE_NUMBERS = 1 |
| 996 | }; |
| 997 | enum fileedit_render_modes { |
| 998 | FE_RENDER_PLAIN_TEXT = 0, |
| 999 | FE_RENDER_HTML, |
| 1000 | FE_RENDER_WIKI |
| 1001 | }; |
| 1002 | |
| 1003 | static int fileedit_render_mode_for_mimetype(const char * zMimetype){ |
| 1004 | int rc = FE_RENDER_PLAIN_TEXT; |
| 1005 | if( zMimetype ){ |
| 1006 | if( fossil_strcmp(zMimetype, "text/html")==0 ){ |
| 1007 | rc = FE_RENDER_HTML; |
| 1008 | }else if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 |
| 1009 | || fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ |
| 1010 | rc = FE_RENDER_WIKI; |
| 1011 | } |
| 1012 | } |
| 1013 | return rc; |
| 1014 | } |
| 1015 | |
| 1016 | /* |
| 1017 | ** Performs the PREVIEW mode for /filepage. |
| 1018 | */ |
| 1019 | static void fileedit_render_preview(Blob * pContent, |
| 1020 | const char *zFilename, |
| 1021 | int flags, |
| 1022 | int nIframeHeightEm){ |
| 1023 | const char * zMime; |
| 1024 | int renderMode = FE_RENDER_PLAIN_TEXT; |
| 1025 | zMime = mimetype_from_name(zFilename); |
| 1026 | renderMode = fileedit_render_mode_for_mimetype(zMime); |
| 1027 | CX("<div class='fileedit-preview'>"); |
| 1028 | CX("<div>Preview</div>"); |
| 1029 | switch(renderMode){ |
| 1030 | case FE_RENDER_HTML:{ |
| 1031 | char * z64 = encode64(blob_str(pContent), blob_size(pContent)); |
| 1032 | CX("<iframe width='100%%' frameborder='0' marginwidth='0' " |
| 1033 | "style='height:%dem' " |
| 1034 | "marginheight='0' sandbox='allow-same-origin' id='ifm1' " |
| 1035 | "src='data:text/html;base64,%z'" |
| 1036 | "></iframe>", nIframeHeightEm ? nIframeHeightEm : 40, |
| 1037 | z64); |
| 1038 | break; |
| 1039 | } |
| 1040 | case FE_RENDER_WIKI: |
| 1041 | wiki_render_by_mimetype(pContent, zMime); |
| 1042 | break; |
| 1043 | case FE_RENDER_PLAIN_TEXT: |
| 1044 | default:{ |
| 1045 | const char *zExt = strrchr(zFilename,'.'); |
| 1046 | const char *zContent = blob_str(pContent); |
| 1047 | if(FE_PREVIEW_LINE_NUMBERS & flags){ |
| 1048 | output_text_with_line_numbers(zContent, "on"); |
| @@ -1080,22 +1073,28 @@ | |
| 1073 | ** All other parameters are for internal use only, submitted via the |
| 1074 | ** form-submission process, and may change with any given revision of |
| 1075 | ** this code. |
| 1076 | */ |
| 1077 | void fileedit_page(){ |
| 1078 | enum submit_modes { |
| 1079 | SUBMIT_NONE = 0, SUBMIT_SAVE = 1, SUBMIT_PREVIEW = 2, |
| 1080 | SUBMIT_DIFF = 3 |
| 1081 | }; |
| 1082 | const char * zFilename = PD("file",P("name")); |
| 1083 | /* filename. We'll accept 'name' |
| 1084 | because that param is handled |
| 1085 | specially by the core. */ |
| 1086 | const char * zRev = P("r"); /* checkin version */ |
| 1087 | const char * zContent = P("content"); /* file content */ |
| 1088 | const char * zComment = P("comment"); /* checkin comment */ |
| 1089 | const char * zFileMime = 0; /* File mime type guess */ |
| 1090 | CheckinMiniInfo cimi; /* Checkin state */ |
| 1091 | int submitMode = SUBMIT_NONE; /* See mapping below */ |
| 1092 | int vid, newVid = 0; /* checkin rid */ |
| 1093 | int frid = 0; /* File content rid */ |
| 1094 | int previewLn = P("preview_ln")!=0; /* Line number mode */ |
| 1095 | int previewHtmlHeight = 0; /* iframe height (EMs) */ |
| 1096 | char * zFileUuid = 0; /* File content UUID */ |
| 1097 | Blob err = empty_blob; /* Error report */ |
| 1098 | const char * zFlagCheck = 0; /* Temp url flag holder */ |
| 1099 | Blob endScript = empty_blob; /* Script code to run at the |
| 1100 | end. This content will be |
| @@ -1132,13 +1131,14 @@ | |
| 1131 | login_needed(g.anon.Write); |
| 1132 | return; |
| 1133 | } |
| 1134 | db_begin_transaction(); |
| 1135 | CheckinMiniInfo_init(&cimi); |
| 1136 | submitMode = atoi(PD("submit","0")); |
| 1137 | if(submitMode < SUBMIT_NONE || submitMode > SUBMIT_DIFF){ |
| 1138 | submitMode = 0; |
| 1139 | } |
| 1140 | zFlagCheck = P("comment_mimetype"); |
| 1141 | if(zFlagCheck){ |
| 1142 | cimi.zMimetype = mprintf("%s",zFlagCheck); |
| 1143 | zFlagCheck = 0; |
| 1144 | } |
| @@ -1162,10 +1162,11 @@ | |
| 1162 | vid = symbolic_name_to_rid(zRev, "ci"); |
| 1163 | if(0==vid){ |
| 1164 | fail((&err,"Could not resolve checkin version.")); |
| 1165 | } |
| 1166 | cimi.zFilename = mprintf("%s",zFilename); |
| 1167 | zFileMime = mimetype_from_name(zFilename); |
| 1168 | |
| 1169 | /* Find the repo-side file entry or fail... */ |
| 1170 | cimi.zParentUuid = rid_to_uuid(vid); |
| 1171 | db_prepare(&stmt, "SELECT uuid, perm FROM files_of_checkin " |
| 1172 | "WHERE filename=%Q %s AND checkinID=%d", |
| @@ -1222,13 +1223,12 @@ | |
| 1223 | CX("</p>"); |
| 1224 | CX("<p>This page is <em>far from complete</em> and may still have " |
| 1225 | "significant bugs. USE AT YOUR OWN RISK, preferably on a test " |
| 1226 | "repo.</p>\n"); |
| 1227 | |
| 1228 | CX("<form action='%R/fileedit#options' method='POST' " |
| 1229 | "class='fileedit-form'>\n"); |
| 1230 | |
| 1231 | /******* Hidden fields *******/ |
| 1232 | CX("<input type='hidden' name='r' value='%s'>", |
| 1233 | cimi.zParentUuid); |
| 1234 | CX("<input type='hidden' name='file' value='%T'>", |
| @@ -1263,11 +1263,11 @@ | |
| 1263 | ** a containing div is necessary. */); |
| 1264 | /* |
| 1265 | ** TODO?: date-override date selection field. Maybe use |
| 1266 | ** an input[type=datetime-local]. |
| 1267 | */ |
| 1268 | if(SUBMIT_NONE==submitMode || P("dry_run")!=0){ |
| 1269 | cimi.flags |= CIMINI_DRY_RUN; |
| 1270 | } |
| 1271 | style_labeled_checkbox("dry_run", "Dry-run?", "1", |
| 1272 | "In dry-run mode, the Save button performs " |
| 1273 | "all work needed for saving but then rolls " |
| @@ -1310,11 +1310,12 @@ | |
| 1310 | "Will create a delta manifest, instead of " |
| 1311 | "baseline, if conditions are favorable to do " |
| 1312 | "so. This option is only a suggestion.", |
| 1313 | cimi.flags & CIMINI_PREFER_DELTA); |
| 1314 | {/* EOL conversion policy... */ |
| 1315 | const int eolMode = submitMode==SUBMIT_NONE |
| 1316 | ? 0 : atoi(PD("eol","0")); |
| 1317 | switch(eolMode){ |
| 1318 | case 1: cimi.flags |= CIMINI_CONVERT_EOL_UNIX; break; |
| 1319 | case 2: cimi.flags |= CIMINI_CONVERT_EOL_WINDOWS; break; |
| 1320 | default: cimi.flags |= CIMINI_CONVERT_EOL_INHERIT; break; |
| 1321 | } |
| @@ -1340,23 +1341,51 @@ | |
| 1341 | "Save</button>"); |
| 1342 | CX("<button type='submit' name='submit' value='2'>" |
| 1343 | "Preview</button>"); |
| 1344 | CX("<button type='submit' name='submit' value='3'>" |
| 1345 | "Diff (TODO)</button>"); |
| 1346 | { |
| 1347 | /* Options which depend on the current submitMode or |
| 1348 | the file's preview rendering mode. */ |
| 1349 | const int renderMode = fileedit_render_mode_for_mimetype(zFileMime); |
| 1350 | CX("<br>"); |
| 1351 | if(FE_RENDER_HTML==renderMode){ |
| 1352 | /* HTML preview mode iframe height... */ |
| 1353 | int i; |
| 1354 | if(submitMode==SUBMIT_PREVIEW){ |
| 1355 | previewHtmlHeight = atoi(PD("preview_html_ems","0")); |
| 1356 | }else{ |
| 1357 | previewHtmlHeight = 40; |
| 1358 | } |
| 1359 | /* Allow selection of HTML preview iframe height */ |
| 1360 | CX("<select name='preview_html_ems' " |
| 1361 | "title='Height (in EMs) of the iframe used for HTML " |
| 1362 | "preview.'>\n"); |
| 1363 | CX("<option disabled value='40'>HTML Preview Height (EMs)" |
| 1364 | "</option>\n"); |
| 1365 | for( i = 20; i <= 100; i+=20 ){ |
| 1366 | CX("<option value='%d'%s>%d</option>\n", |
| 1367 | i, (previewHtmlHeight==i) ? " selected" : "", i); |
| 1368 | } |
| 1369 | CX("</select>\n"); |
| 1370 | } |
| 1371 | else if(FE_RENDER_PLAIN_TEXT == renderMode){ |
| 1372 | style_labeled_checkbox("preview_ln", |
| 1373 | "Add line numbers to plain-text previews?", |
| 1374 | "1", |
| 1375 | "If on, plain-text files (only) will get " |
| 1376 | "line numbers added to the preview.", |
| 1377 | previewLn); |
| 1378 | } |
| 1379 | } |
| 1380 | CX("</div></fieldset>"); |
| 1381 | |
| 1382 | /******* End of form *******/ |
| 1383 | CX("</form>\n"); |
| 1384 | |
| 1385 | /* Dynamically populate the editor... */ |
| 1386 | if(1==loadMode || (2==loadMode && submitMode>SUBMIT_NONE)){ |
| 1387 | char const * zQuoted = 0; |
| 1388 | if(blob_size(&cimi.fileContent)>0){ |
| 1389 | db_prepare(&stmt, "SELECT json_quote(%B)", &cimi.fileContent); |
| 1390 | db_step(&stmt); |
| 1391 | zQuoted = db_column_text(&stmt,0); |
| @@ -1367,11 +1396,11 @@ | |
| 1396 | ".value=%s;", zQuoted ? zQuoted : "'';\n"); |
| 1397 | if(stmt.pStmt){ |
| 1398 | db_finalize(&stmt); |
| 1399 | } |
| 1400 | }else if(2==loadMode){ |
| 1401 | assert(submitMode==SUBMIT_NONE); |
| 1402 | fileedit_emit_script_fetch(); |
| 1403 | blob_appendf(&endScript, |
| 1404 | "window.fossilFetch('raw/%s',{" |
| 1405 | "onload: (r)=>document.getElementById('fileedit-content')" |
| 1406 | ".value=r," |
| @@ -1379,11 +1408,11 @@ | |
| 1408 | ".value=" |
| 1409 | "'Error loading content'" |
| 1410 | "});\n", zFileUuid); |
| 1411 | } |
| 1412 | |
| 1413 | if(SUBMIT_SAVE==submitMode){ |
| 1414 | Blob manifest = empty_blob; |
| 1415 | char * zNewUuid = 0; |
| 1416 | /*cimi.flags |= CIMINI_STRONGLY_PREFER_DELTA;*/ |
| 1417 | if(zComment && *zComment){ |
| 1418 | blob_append(&cimi.comment, zComment, -1); |
| @@ -1442,15 +1471,16 @@ | |
| 1471 | } |
| 1472 | /* On error, the error message is in the err blob and will |
| 1473 | ** be emitted below. */ |
| 1474 | cimi.pMfOut = 0; |
| 1475 | blob_reset(&manifest); |
| 1476 | }else if(SUBMIT_PREVIEW==submitMode){ |
| 1477 | int pflags = 0; |
| 1478 | if(previewLn) pflags |= FE_PREVIEW_LINE_NUMBERS; |
| 1479 | fileedit_render_preview(&cimi.fileContent, cimi.zFilename, pflags, |
| 1480 | previewHtmlHeight); |
| 1481 | }else if(SUBMIT_DIFF==submitMode/*diff*/){ |
| 1482 | fail((&err,"Diff mode is still TODO.")); |
| 1483 | }else{ |
| 1484 | /* Ignore invalid submitMode value */ |
| 1485 | goto end_footer; |
| 1486 | } |
| 1487 |