| | @@ -898,118 +898,16 @@ |
| 898 | 898 | fossil_free(zGlobs); |
| 899 | 899 | } |
| 900 | 900 | return glob_match(pGlobs, zFilename); |
| 901 | 901 | } |
| 902 | 902 | |
| 903 | | -/* |
| 904 | | -** Emits a script tag which defines window.fossil.fetch(), which works |
| 905 | | -** similarly (not identically) to the not-quite-ubiquitous global |
| 906 | | -** fetch(). |
| 907 | | -** |
| 908 | | -** JS usages: |
| 909 | | -** |
| 910 | | -** fossilFetch( URI, onLoadCallback ); |
| 911 | | -** |
| 912 | | -** fossilFetch( URI, optionsObject ); |
| 913 | | -** |
| 914 | | -** Noting that URI must be relative to the top of the repository and |
| 915 | | -** must not start with a slash (if it does, it is stripped). It gets |
| 916 | | -** %R/ prepended to it. |
| 917 | | -** |
| 918 | | -** The optionsObject may be an onload callback or an object with any |
| 919 | | -** of these properties: |
| 920 | | -** |
| 921 | | -** - onload: callback(responseData) (default = output response to |
| 922 | | -** console). |
| 923 | | -** |
| 924 | | -** - onerror: callback(XHR onload event) (default = console output) |
| 925 | | -** |
| 926 | | -** - method: 'POST' | 'GET' (default = 'GET') |
| 927 | | -** |
| 928 | | -** - payload: anything acceptable by XHR2.send(ARG) (DOMString, |
| 929 | | -** Document, FormData, Blob, File, ArrayBuffer), or a plain object |
| 930 | | -** or array, either of which gets JSON.stringify()'d. If set then |
| 931 | | -** the method is automatically set to 'POST'. If an object/array is |
| 932 | | -** converted to JSON, the content-type is set to 'application/json'. |
| 933 | | -** By default XHR2 will set the content type based on the payload |
| 934 | | -** type. |
| 935 | | -** |
| 936 | | -** - contentType: Optional request content type when POSTing. Ignored |
| 937 | | -** if the method is not 'POST'. |
| 938 | | -** |
| 939 | | -** - responseType: optional string. One of ("text", "arraybuffer", |
| 940 | | -** "blob", or "document") (as specified by XHR2). Default = "text". |
| 941 | | -** |
| 942 | | -** - urlParams: string|object. If a string, it is assumed to be a |
| 943 | | -** URI-encoded list of params in the form "key1=val1&key2=val2...", |
| 944 | | -** with NO leading '?'. If it is an object, all of its properties |
| 945 | | -** get converted to that form. Either way, the parameters get |
| 946 | | -** appended to the URL. |
| 947 | | -** |
| 948 | | -*/ |
| 949 | | -void fileedit_emit_script_fetch(){ |
| 950 | | - style_emit_script_tag(0); |
| 951 | | - CX("fossil.fetch = function(path,opt){\n"); |
| 952 | | - CX(" if('/'===path[0]) path = path.substr(1);\n"); |
| 953 | | - CX(" if(!opt){\n"); |
| 954 | | - CX(" opt = {onload:(r)=>console.debug('response:',r)};\n"); |
| 955 | | - CX(" }else if('function'===typeof opt){\n"); |
| 956 | | - CX(" opt={onload:opt,\n"); |
| 957 | | - CX(" onerror:(e)=>console.error('ajax error:',e)};\n"); |
| 958 | | - CX(" }\n"); |
| 959 | | - CX(" let payload = opt.payload;\n"); |
| 960 | | - CX(" if(payload){\n"); |
| 961 | | - CX(" opt.method = 'POST';\n"); |
| 962 | | - CX(" if(!(payload instanceof FormData)\n"); |
| 963 | | - CX(" && !(payload instanceof Document)\n"); |
| 964 | | - CX(" && !(payload instanceof Blob)\n"); |
| 965 | | - CX(" && !(payload instanceof File)\n"); |
| 966 | | - CX(" && !(payload instanceof ArrayBuffer)){\n"); |
| 967 | | - CX(" if('object'===typeof payload || payload instanceof Array){\n"); |
| 968 | | - CX(" payload = JSON.stringify(payload);\n"); |
| 969 | | - CX(" opt.contentType = 'application/json';\n"); |
| 970 | | - CX(" }\n"); |
| 971 | | - CX(" }\n"); |
| 972 | | - CX(" }\n"); |
| 973 | | - CX(" const url=['%R/'+path], x=new XMLHttpRequest();\n"); |
| 974 | | - CX(" if(opt.urlParams){\n"); |
| 975 | | - CX(" url.push('?');\n"); |
| 976 | | - CX(" if('string'===typeof opt.urlParams){\n"); |
| 977 | | - CX(" url.push(opt.urlParams);\n"); |
| 978 | | - CX(" }else{/*assume object*/\n"); |
| 979 | | - CX(" let k, i = 0;\n"); |
| 980 | | - CX(" for( k in opt.urlParams ){\n"); |
| 981 | | - CX(" if(i++) url.push('&');\n"); |
| 982 | | - CX(" url.push(k,'=',encodeURIComponent(opt.urlParams[k]));\n"); |
| 983 | | - CX(" }\n"); |
| 984 | | - CX(" }\n"); |
| 985 | | - CX(" }\n"); |
| 986 | | - CX(" if('POST'===opt.method && 'string'===typeof opt.contentType){\n"); |
| 987 | | - CX(" x.setRequestHeader('Content-Type',opt.contentType);\n"); |
| 988 | | - CX(" }\n"); |
| 989 | | - CX(" x.open(opt.method||'GET', url.join(''), true);\n"); |
| 990 | | - CX(" x.responseType=opt.responseType||'text';\n"); |
| 991 | | - CX(" if(opt.onload){\n"); |
| 992 | | - CX(" x.onload = function(e){\n"); |
| 993 | | - CX(" if(200!==this.status){\n"); |
| 994 | | - CX(" if(opt.onerror) opt.onerror(e);\n"); |
| 995 | | - CX(" return;\n"); |
| 996 | | - CX(" }\n"); |
| 997 | | - CX(" opt.onload(this.response);\n"); |
| 998 | | - CX(" }\n"); |
| 999 | | - CX(" }\n"); |
| 1000 | | - CX(" if(payload) x.send(payload);\n"); |
| 1001 | | - CX(" else x.send();\n"); |
| 1002 | | - CX("};\n"); |
| 1003 | | - style_emit_script_tag(1); |
| 1004 | | -}; |
| 1005 | | - |
| 1006 | 903 | |
| 1007 | 904 | enum fileedit_render_preview_flags { |
| 1008 | 905 | FE_PREVIEW_LINE_NUMBERS = 1 |
| 1009 | 906 | }; |
| 1010 | 907 | enum fileedit_render_modes { |
| 908 | +/* GUESS must be 0. All others have specified values. */ |
| 1011 | 909 | FE_RENDER_GUESS = 0, |
| 1012 | 910 | FE_RENDER_PLAIN_TEXT, |
| 1013 | 911 | FE_RENDER_HTML, |
| 1014 | 912 | FE_RENDER_WIKI |
| 1015 | 913 | }; |
| | @@ -1037,12 +935,10 @@ |
| 1037 | 935 | const char * zMime; |
| 1038 | 936 | zMime = mimetype_from_name(zFilename); |
| 1039 | 937 | if(FE_RENDER_GUESS==renderMode){ |
| 1040 | 938 | renderMode = fileedit_render_mode_for_mimetype(zMime); |
| 1041 | 939 | } |
| 1042 | | - CX("<div class='fileedit-preview'>"); |
| 1043 | | - CX("<div>Preview</div>"); |
| 1044 | 940 | switch(renderMode){ |
| 1045 | 941 | case FE_RENDER_HTML:{ |
| 1046 | 942 | char * z64 = encode64(blob_str(pContent), blob_size(pContent)); |
| 1047 | 943 | CX("<iframe width='100%%' frameborder='0' " |
| 1048 | 944 | "marginwidth='0' style='height:%dem' " |
| | @@ -1068,11 +964,10 @@ |
| 1068 | 964 | CX("<pre>%h</pre>", zExt+1, zContent); |
| 1069 | 965 | } |
| 1070 | 966 | break; |
| 1071 | 967 | } |
| 1072 | 968 | } |
| 1073 | | - CX("</div><!--.fileedit-preview-->\n"); |
| 1074 | 969 | } |
| 1075 | 970 | |
| 1076 | 971 | /* |
| 1077 | 972 | ** Renders diffs for the /fileedit page. pContent is the |
| 1078 | 973 | ** locally-edited content. frid is the RID of the file's blob entry |
| | @@ -1087,19 +982,15 @@ |
| 1087 | 982 | Blob out = empty_blob; |
| 1088 | 983 | u64 diffFlags = DIFF_HTML | DIFF_NOTTOOBIG | DIFF_STRIP_EOLCR |
| 1089 | 984 | | (isSbs ? DIFF_SIDEBYSIDE : DIFF_LINENO); |
| 1090 | 985 | content_get(frid, &orig); |
| 1091 | 986 | text_diff(&orig, pContent, &out, 0, diffFlags); |
| 1092 | | - CX("<div class='fileedit-diff'>"); |
| 1093 | | - CX("<div>Diff <code>[%S]</code> → Local Edits</div>", |
| 1094 | | - zManifestUuid); |
| 1095 | 987 | if(isSbs){ |
| 1096 | 988 | CX("%b",&out); |
| 1097 | 989 | }else{ |
| 1098 | 990 | CX("<pre class='udiff'>%b</pre>",&out); |
| 1099 | 991 | } |
| 1100 | | - CX("</div><!--.fileedit-diff-->\n"); |
| 1101 | 992 | blob_reset(&orig); |
| 1102 | 993 | blob_reset(&out); |
| 1103 | 994 | } |
| 1104 | 995 | |
| 1105 | 996 | /* |
| | @@ -1122,10 +1013,226 @@ |
| 1122 | 1013 | } |
| 1123 | 1014 | } |
| 1124 | 1015 | db_finalize(&stmt); |
| 1125 | 1016 | return zFileUuid; |
| 1126 | 1017 | } |
| 1018 | + |
| 1019 | +/* |
| 1020 | +** Helper for /filepage_xyz routes. Clears the CGI content buffer, |
| 1021 | +** sets an error status code, and queues up a JSON response in the |
| 1022 | +** form of an object: |
| 1023 | +** |
| 1024 | +** {error: formatted message} |
| 1025 | +** |
| 1026 | +** After calling this, the caller should immediately return. |
| 1027 | + */ |
| 1028 | +static void fileedit_ajax_error(int httpCode, const char * zFmt, ...){ |
| 1029 | + Blob msg = empty_blob; |
| 1030 | + Blob content = empty_blob; |
| 1031 | + va_list vargs; |
| 1032 | + va_start(vargs,zFmt); |
| 1033 | + blob_vappendf(&msg, zFmt, vargs); |
| 1034 | + va_end(vargs); |
| 1035 | + blob_appendf(&content,"{\"error\":\"%j\"}", blob_str(&msg)); |
| 1036 | + blob_reset(&msg); |
| 1037 | + cgi_set_content(&content); |
| 1038 | + cgi_set_status(httpCode, "Error"); |
| 1039 | + cgi_set_content_type("application/json"); |
| 1040 | +} |
| 1041 | + |
| 1042 | +/* |
| 1043 | +** Performs bootstrapping common to the /fileedit_xyz AJAX routes. |
| 1044 | +** Returns 0 if bootstrapping fails (wrong permissions), in which |
| 1045 | +** case it has reported the error and the route should immediately |
| 1046 | +** return. Returns true on success. |
| 1047 | +*/ |
| 1048 | +static int fileedit_ajax_boostrap(){ |
| 1049 | + login_check_credentials(); |
| 1050 | + if( !g.perm.Write ){ |
| 1051 | + fileedit_ajax_error(403,"Write permissions required."); |
| 1052 | + return 0; |
| 1053 | + } |
| 1054 | + return 1; |
| 1055 | +} |
| 1056 | +/* |
| 1057 | +** Returns true if the current user is allowed to edit the given |
| 1058 | +** filename, as determined by fileedit_is_editable(), else false, |
| 1059 | +** in which case it queues up an error response and the caller |
| 1060 | +** must return immediately. |
| 1061 | +*/ |
| 1062 | +static int fileedit_ajax_check_filename(const char * zFilename){ |
| 1063 | + if(0==fileedit_is_editable(zFilename)){ |
| 1064 | + fileedit_ajax_error(403, "File is disallowed by the " |
| 1065 | + "fileedit-glob setting."); |
| 1066 | + return 0; |
| 1067 | + } |
| 1068 | + return 1; |
| 1069 | +} |
| 1070 | + |
| 1071 | +/* |
| 1072 | +** Passed the values of the "r" and "file" request properties, |
| 1073 | +** this function verifies that they are valid and populates: |
| 1074 | +** |
| 1075 | +** - *zRevUuid = the fully-expanded value of zRev (owned by the |
| 1076 | +** caller). zRevUuid may be NULL. |
| 1077 | +** |
| 1078 | +** - *vid = the RID of zRevUuid. May not be NULL. |
| 1079 | +** |
| 1080 | +** - *frid = the RID of zFilename's blob content. May not be NULL. |
| 1081 | +** |
| 1082 | +** Returns 0 if the given file is not in the given checkin or if |
| 1083 | +** fileedit_ajax_check_filename() fails, else returns true. If it |
| 1084 | +** returns false, it queues up an error response and the caller must |
| 1085 | +** return immediately. |
| 1086 | +*/ |
| 1087 | +static int fileedit_ajax_setup_filerev(const char * zRev, |
| 1088 | + char ** zRevUuid, |
| 1089 | + int * vid, |
| 1090 | + const char * zFilename, |
| 1091 | + int * frid){ |
| 1092 | + char * zCi = 0; /* fully-resolved checkin UUID */ |
| 1093 | + char * zFileUuid; /* file UUID */ |
| 1094 | + |
| 1095 | + if(!fileedit_ajax_check_filename(zFilename)){ |
| 1096 | + return 0; |
| 1097 | + } |
| 1098 | + *vid = symbolic_name_to_rid(zRev, "ci"); |
| 1099 | + if(0==*vid){ |
| 1100 | + fileedit_ajax_error(404,"Cannot resolve name as a checkin: %s", |
| 1101 | + zRev); |
| 1102 | + return 0; |
| 1103 | + } |
| 1104 | + zFileUuid = fileedit_file_uuid(zFilename, *vid, 0); |
| 1105 | + if(zFileUuid==0){ |
| 1106 | + fileedit_ajax_error(404,"Checkin does not contain file."); |
| 1107 | + return 0; |
| 1108 | + } |
| 1109 | + zCi = rid_to_uuid(*vid); |
| 1110 | + *frid = fast_uuid_to_rid(zFileUuid); |
| 1111 | + fossil_free(zFileUuid); |
| 1112 | + if(zRevUuid!=0){ |
| 1113 | + *zRevUuid = zCi; |
| 1114 | + }else{ |
| 1115 | + fossil_free(zCi); |
| 1116 | + } |
| 1117 | + return 1; |
| 1118 | +} |
| 1119 | + |
| 1120 | + |
| 1121 | +/* |
| 1122 | +** WEBPAGE: fileedit_content |
| 1123 | +** |
| 1124 | +** Query parameters: |
| 1125 | +** |
| 1126 | +** file=FILENAME |
| 1127 | +** r=CHECKIN_NAME |
| 1128 | +** |
| 1129 | +** User must have Write access to use this page. |
| 1130 | +** |
| 1131 | +** Responds with the raw content of the given page. On error it |
| 1132 | +** produces a JSON response as documented for fileedit_ajax_error(). |
| 1133 | +*/ |
| 1134 | +void fileedit_ajax_content(){ |
| 1135 | + const char * zFilename = PD("file",P("name")); |
| 1136 | + const char * zRev = P("r"); |
| 1137 | + int vid, frid; |
| 1138 | + Blob content = empty_blob; |
| 1139 | + const char * zMime; |
| 1140 | + if(!fileedit_ajax_boostrap() |
| 1141 | + || !fileedit_ajax_setup_filerev(zRev, 0, &vid, |
| 1142 | + zFilename, &frid)){ |
| 1143 | + return; |
| 1144 | + } |
| 1145 | + zMime = mimetype_from_name(zFilename); |
| 1146 | + content_get(frid, &content); |
| 1147 | + cgi_set_content_type(zMime ? zMime : "application/octet-stream"); |
| 1148 | + cgi_set_content(&content); |
| 1149 | +} |
| 1150 | + |
| 1151 | +/* |
| 1152 | +** WEBPAGE: fileedit_preview |
| 1153 | +** |
| 1154 | +** Query parameters: |
| 1155 | +** |
| 1156 | +** file=FILENAME |
| 1157 | +** render_mode=integer (FE_RENDER_xxx) (default=FE_RENDER_GUESS) |
| 1158 | +** content=text |
| 1159 | +** |
| 1160 | +** User must have Write access to use this page. |
| 1161 | +** |
| 1162 | +** Responds with the HTML content of the preview. On error it produces |
| 1163 | +** a JSON response as documented for fileedit_ajax_error(). |
| 1164 | +*/ |
| 1165 | +void fileedit_ajax_preview(){ |
| 1166 | + const char * zFilename = PD("file",P("name")); |
| 1167 | + const char * zContent = P("content"); |
| 1168 | + int renderMode = atoi(PD("render_mode","0")); |
| 1169 | + int ln = atoi(PD("ln","0")); |
| 1170 | + int iframeHeight = atoi(PD("iframe_height","40")); |
| 1171 | + Blob content = empty_blob; |
| 1172 | + if(!fileedit_ajax_boostrap() |
| 1173 | + || !fileedit_ajax_check_filename(zFilename)){ |
| 1174 | + return; |
| 1175 | + } |
| 1176 | + cgi_set_content_type("text/html"); |
| 1177 | + blob_init(&content, zContent, -1); |
| 1178 | + fileedit_render_preview(&content, zFilename, |
| 1179 | + ln ? FE_PREVIEW_LINE_NUMBERS : 0, |
| 1180 | + renderMode, iframeHeight); |
| 1181 | +} |
| 1182 | + |
| 1183 | +/* |
| 1184 | +** WEBPAGE: fileedit_diff |
| 1185 | +** |
| 1186 | +** file=FILENAME |
| 1187 | +** sbs=integer (1=side-by-side or 0=unified) |
| 1188 | +** content=text |
| 1189 | +** r=checkin version |
| 1190 | +** |
| 1191 | +** User must have Write access to use this page. |
| 1192 | +** |
| 1193 | +** Responds with the HTML content of the diff. On error it produces a |
| 1194 | +** JSON response as documented for fileedit_ajax_error(). |
| 1195 | +*/ |
| 1196 | +void fileedit_ajax_diff(){ |
| 1197 | + /* |
| 1198 | + ** Reminder: we only need the filename to perform valdiation |
| 1199 | + ** against fileedit_is_editable(), else this route could be |
| 1200 | + ** abused to get diffs against content disallowed by the |
| 1201 | + ** whitelist. |
| 1202 | + */ |
| 1203 | + const char * zFilename = PD("file",P("name")); |
| 1204 | + const char * zRev = P("r"); |
| 1205 | + const char * zContent = P("content"); |
| 1206 | + char * zRevUuid = 0; |
| 1207 | + int isSbs = atoi(PD("sbs","0")); |
| 1208 | + int vid, frid; |
| 1209 | + Blob content = empty_blob; |
| 1210 | + if(!fileedit_ajax_boostrap() |
| 1211 | + || !fileedit_ajax_setup_filerev(zRev, &zRevUuid, &vid, |
| 1212 | + zFilename, &frid)){ |
| 1213 | + return; |
| 1214 | + } |
| 1215 | + if(!zContent){ |
| 1216 | + zContent = ""; |
| 1217 | + } |
| 1218 | + cgi_set_content_type("text/html"); |
| 1219 | + blob_init(&content, zContent, -1); |
| 1220 | + fileedit_render_diff(&content, frid, zRevUuid, isSbs); |
| 1221 | + fossil_free(zRevUuid); |
| 1222 | + blob_reset(&content); |
| 1223 | +} |
| 1224 | + |
| 1225 | + |
| 1226 | +/* |
| 1227 | +** Emits utility script code specific to the /fileedit page. |
| 1228 | +*/ |
| 1229 | +static void fileedit_emit_page_script(){ |
| 1230 | + style_emit_script_tag(0); |
| 1231 | + CX("%s\n", builtin_text("fossil.page.fileedit.js")); |
| 1232 | + style_emit_script_tag(1); |
| 1233 | +} |
| 1127 | 1234 | |
| 1128 | 1235 | /* |
| 1129 | 1236 | ** WEBPAGE: fileedit |
| 1130 | 1237 | ** |
| 1131 | 1238 | ** EXPERIMENTAL and subject to change and removal at any time. The goal |
| | @@ -1171,33 +1278,12 @@ |
| 1171 | 1278 | combined into a single JS |
| 1172 | 1279 | function call, thus each |
| 1173 | 1280 | entry must end with a |
| 1174 | 1281 | semicolon. */ |
| 1175 | 1282 | Stmt stmt = empty_Stmt; |
| 1176 | | - const int loadMode = 0; /* See next comment block */ |
| 1177 | | - /* loadMode: How to populate the TEXTAREA: |
| 1178 | | - ** |
| 1179 | | - ** 0: HTML encode: despite my personal reservations regarding HTML |
| 1180 | | - ** escaping, this seems to be the only reliable approach |
| 1181 | | - ** until/unless we completely AJAXify this page. |
| 1182 | | - ** |
| 1183 | | - ** 1: JSON mode: JSON-izes the file content and injects it, via JS, |
| 1184 | | - ** into the editor TEXTAREA. This works wonderfully until the input |
| 1185 | | - ** file contains an raw <SCRIPT> tag, at which points the HTML |
| 1186 | | - ** parser chokes on it. |
| 1187 | | - ** |
| 1188 | | - ** 2: AJAX mode: can only load content from the db, not preview/dry-run |
| 1189 | | - ** content. Unless this page is refactored to work solely over AJAX |
| 1190 | | - ** (which is a potential TODO), this method is only useful on the |
| 1191 | | - ** initial hit to this page, where the file is loaded. |
| 1192 | | - ** |
| 1193 | | - ** loadMode is not generally configurable: change it only for |
| 1194 | | - ** testing/development purposes. |
| 1195 | | - */ |
| 1196 | 1283 | #define fail(EXPR) blob_appendf EXPR; goto end_footer |
| 1197 | 1284 | |
| 1198 | | - assert(loadMode==0 || loadMode==1 || loadMode==2); |
| 1199 | 1285 | login_check_credentials(); |
| 1200 | 1286 | if( !g.perm.Write ){ |
| 1201 | 1287 | login_needed(g.anon.Write); |
| 1202 | 1288 | return; |
| 1203 | 1289 | } |
| | @@ -1352,34 +1438,39 @@ |
| 1352 | 1438 | cimi.pMfOut = 0; |
| 1353 | 1439 | blob_reset(&manifest); |
| 1354 | 1440 | break; |
| 1355 | 1441 | } |
| 1356 | 1442 | |
| 1443 | + CX("<div id='fossil-status-bar'>Async. status messages will go " |
| 1444 | + "here.</div>\n"); |
| 1357 | 1445 | CX("<h1>Editing:</h1>"); |
| 1358 | 1446 | CX("<p class='fileedit-hint'>"); |
| 1359 | 1447 | CX("File: " |
| 1360 | | - "[<a id='finfo-link' href='%R/finfo?name=%T&m=%!S'>info</a>] " |
| 1361 | | - "<code>%h</code><br>", |
| 1362 | | - zFilename, zFileUuid, zFilename); |
| 1448 | + "[<a id='finfo-link' href='#'>info</a>] " |
| 1449 | + /* %R/finfo?name=%T&m=%!S */ |
| 1450 | + "<code id='finfo-file-name'>(loading)</code><br>"); |
| 1363 | 1451 | CX("Checkin Version: " |
| 1364 | | - "[<a id='r-link' href='%R/info/%!S'>info</a>] " |
| 1365 | | - "<code id='r-label'>%s</code><br>", |
| 1366 | | - cimi.zParentUuid, cimi.zParentUuid); |
| 1452 | + "[<a id='r-link' href='#'>info</a>] " |
| 1453 | + /* %R/info/%!S */ |
| 1454 | + "<code id='r-label'>(loading...)</code><br>" |
| 1455 | + ); |
| 1367 | 1456 | CX("Permalink: <code>" |
| 1368 | | - "<a id='permalink' href='%R/fileedit?file=%T&r=%!S'>" |
| 1369 | | - "/fileedit?file=%T&r=%!S</a></code><br>" |
| 1457 | + "<a id='permalink' href='#'>(loading...)</a></code><br>" |
| 1370 | 1458 | "(Clicking the permalink will reload the page and discard " |
| 1371 | 1459 | "all edits!)", |
| 1372 | 1460 | zFilename, cimi.zParentUuid, |
| 1373 | 1461 | zFilename, cimi.zParentUuid); |
| 1374 | 1462 | CX("</p>"); |
| 1375 | 1463 | CX("<p>This page is <em>NEW AND EXPERIMENTAL</em>. " |
| 1376 | 1464 | "USE AT YOUR OWN RISK, preferably on a test " |
| 1377 | 1465 | "repo.</p>\n"); |
| 1378 | 1466 | |
| 1379 | | - CX("<form action='%R/fileedit#options' method='POST' " |
| 1380 | | - "class='fileedit'>\n"); |
| 1467 | + CX("<form action='#' method='POST' " |
| 1468 | + "class='fileedit' id='fileedit-form' " |
| 1469 | + "onsubmit='function(e){" |
| 1470 | + "e.preventDefault(); e.stopPropagation(); return false;" |
| 1471 | + "}'>\n"); |
| 1381 | 1472 | |
| 1382 | 1473 | /******* Hidden fields *******/ |
| 1383 | 1474 | CX("<input type='hidden' name='r' value='%s'>", |
| 1384 | 1475 | cimi.zParentUuid); |
| 1385 | 1476 | CX("<input type='hidden' name='file' value='%T'>", |
| | @@ -1387,16 +1478,11 @@ |
| 1387 | 1478 | |
| 1388 | 1479 | /******* Content *******/ |
| 1389 | 1480 | CX("<h3>File Content</h3>\n"); |
| 1390 | 1481 | CX("<textarea name='content' id='fileedit-content' " |
| 1391 | 1482 | "rows='20' cols='80'>"); |
| 1392 | | - if(0==loadMode){ |
| 1393 | | - CX("%h",blob_str(&cimi.fileContent)); |
| 1394 | | - }else{ |
| 1395 | | - CX("Loading..."); |
| 1396 | | - /* Performed via JS later on */ |
| 1397 | | - } |
| 1483 | + CX("Loading..."); |
| 1398 | 1484 | CX("</textarea>\n"); |
| 1399 | 1485 | /******* Flags/options *******/ |
| 1400 | 1486 | CX("<fieldset class='fileedit-options' id='options'>" |
| 1401 | 1487 | "<legend>Options</legend><div>" |
| 1402 | 1488 | /* Chrome does not sanely lay out multiple |
| | @@ -1459,108 +1545,60 @@ |
| 1459 | 1545 | CX("</div></fieldset>\n"); |
| 1460 | 1546 | |
| 1461 | 1547 | /******* Buttons *******/ |
| 1462 | 1548 | CX("<a id='buttons'></a>"); |
| 1463 | 1549 | CX("<fieldset class='fileedit-options'>" |
| 1464 | | - "<legend>Tell the server to...</legend><div>"); |
| 1465 | | - CX("<button type='submit' name='submit' value='%d'>" |
| 1466 | | - "Commit</button>", SUBMIT_SAVE); |
| 1467 | | - CX("<button type='submit' name='submit' value='%d'>" |
| 1468 | | - "Preview</button>", SUBMIT_PREVIEW); |
| 1550 | + "<legend>Ask the server to...</legend><div>"); |
| 1551 | + CX("<button id='fileedit-btn-commit'>Commit</button>"); |
| 1552 | + CX("<button id='fileedit-btn-preview'>Preview</button>"); |
| 1469 | 1553 | { |
| 1470 | 1554 | /* Preview rendering mode selection... */ |
| 1471 | 1555 | previewRenderMode = atoi(PD("preview_render_mode","0")); |
| 1472 | | - if(0==previewRenderMode){ |
| 1473 | | - previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime); |
| 1474 | | - } |
| 1556 | + previewRenderMode = fileedit_render_mode_for_mimetype(zFileMime); |
| 1475 | 1557 | style_select_list_int("preview_render_mode", |
| 1476 | 1558 | "Preview Mode", |
| 1477 | 1559 | "Preview mode format.", |
| 1478 | 1560 | previewRenderMode, |
| 1479 | 1561 | "Guess", FE_RENDER_GUESS, |
| 1480 | 1562 | "Wiki/Markdown", FE_RENDER_WIKI, |
| 1481 | 1563 | "HTML (iframe)", FE_RENDER_HTML, |
| 1482 | 1564 | "Plain Text", FE_RENDER_PLAIN_TEXT, |
| 1483 | 1565 | NULL); |
| 1484 | | - if(FE_RENDER_HTML==previewRenderMode){ |
| 1485 | | - /* HTML preview mode iframe height... */ |
| 1486 | | - if(submitMode==SUBMIT_PREVIEW){ |
| 1487 | | - previewHtmlHeight = atoi(PD("preview_html_ems","0")); |
| 1488 | | - }else{ |
| 1489 | | - previewHtmlHeight = 40; |
| 1490 | | - } |
| 1491 | | - /* Allow selection of HTML preview iframe height */ |
| 1492 | | - style_select_list_int("preview_html_ems", |
| 1493 | | - "Preview IFrame Height (EMs)", |
| 1494 | | - "Height (in EMs) of the iframe used for " |
| 1495 | | - "HTML preview", |
| 1496 | | - previewHtmlHeight, |
| 1497 | | - "", 20, "", 40, |
| 1498 | | - "", 60, "", 80, |
| 1499 | | - "", 100, NULL); |
| 1500 | | - } |
| 1501 | | - else if(FE_RENDER_PLAIN_TEXT==previewRenderMode){ |
| 1502 | | - style_labeled_checkbox("preview_ln", |
| 1503 | | - "Add line numbers to plain-text previews?", |
| 1504 | | - "1", |
| 1505 | | - "If on, plain-text files (only) will get " |
| 1506 | | - "line numbers added to the preview.", |
| 1507 | | - previewLn); |
| 1508 | | - } |
| 1509 | | - } |
| 1510 | | - CX("<button type='submit' name='submit' value='%d'>" |
| 1511 | | - "Diff (SBS)</button>", SUBMIT_DIFF_SBS); |
| 1512 | | - CX("<button type='submit' name='submit' value='%d'>" |
| 1513 | | - "Diff (Unified)</button>", SUBMIT_DIFF_UNIFIED); |
| 1566 | + previewHtmlHeight = atoi(PD("preview_html_ems","0")); |
| 1567 | + if(!previewHtmlHeight){ |
| 1568 | + previewHtmlHeight = 40; |
| 1569 | + } |
| 1570 | + /* Allow selection of HTML preview iframe height */ |
| 1571 | + style_select_list_int("preview_html_ems", |
| 1572 | + "HTML Preview IFrame Height (EMs)", |
| 1573 | + "Height (in EMs) of the iframe used for " |
| 1574 | + "HTML preview", |
| 1575 | + previewHtmlHeight, |
| 1576 | + "", 20, "", 40, |
| 1577 | + "", 60, "", 80, |
| 1578 | + "", 100, NULL); |
| 1579 | + style_labeled_checkbox("preview_ln", |
| 1580 | + "Add line numbers to plain-text previews?", |
| 1581 | + "1", |
| 1582 | + "If on, plain-text files (only) will get " |
| 1583 | + "line numbers added to the preview.", |
| 1584 | + previewLn); |
| 1585 | + } |
| 1586 | + CX("<button id='fileedit-btn-diffsbs'>Diff (SBS)</button>"); |
| 1587 | + CX("<button id='fileedit-btn-diffu'>Diff (Unified)</button>"); |
| 1514 | 1588 | CX("</div></fieldset>"); |
| 1515 | 1589 | |
| 1516 | 1590 | /******* End of form *******/ |
| 1517 | 1591 | CX("</form>\n"); |
| 1518 | 1592 | |
| 1519 | | - /* |
| 1520 | | - ** We cannot intercept the output for PREVIEW |
| 1521 | | - ** and DIFF modes, and therefore have to render those |
| 1522 | | - ** last. |
| 1523 | | - */ |
| 1524 | | - if(SUBMIT_PREVIEW==submitMode){ |
| 1525 | | - int pflags = 0; |
| 1526 | | - if(previewLn) pflags |= FE_PREVIEW_LINE_NUMBERS; |
| 1527 | | - fileedit_render_preview(&cimi.fileContent, cimi.zFilename, pflags, |
| 1528 | | - previewRenderMode, previewHtmlHeight); |
| 1529 | | - }else if(SUBMIT_DIFF_SBS==submitMode |
| 1530 | | - || SUBMIT_DIFF_UNIFIED==submitMode){ |
| 1531 | | - fileedit_render_diff(&cimi.fileContent, frid, cimi.zParentUuid, |
| 1532 | | - SUBMIT_DIFF_SBS==submitMode); |
| 1533 | | - } |
| 1534 | | - |
| 1593 | + CX("<div id='ajax-target'></div>" |
| 1594 | + /* this is where preview/diff go */); |
| 1595 | + |
| 1535 | 1596 | /* Dynamically populate the editor... */ |
| 1536 | | - fileedit_emit_script_fetch(); |
| 1537 | | - if(1==loadMode || (2==loadMode && submitMode>SUBMIT_NONE)){ |
| 1538 | | - char const * zQuoted = 0; |
| 1539 | | - if(blob_size(&cimi.fileContent)>0){ |
| 1540 | | - db_prepare(&stmt, "SELECT json_quote(%B)", &cimi.fileContent); |
| 1541 | | - db_step(&stmt); |
| 1542 | | - zQuoted = db_column_text(&stmt,0); |
| 1543 | | - } |
| 1544 | | - blob_appendf(&endScript, |
| 1545 | | - "/* populate editor form */\n" |
| 1546 | | - "document.getElementById('fileedit-content')" |
| 1547 | | - ".value=%s;", zQuoted ? zQuoted : "'';\n"); |
| 1548 | | - if(stmt.pStmt){ |
| 1549 | | - db_finalize(&stmt); |
| 1550 | | - } |
| 1551 | | - }else if(2==loadMode){ |
| 1552 | | - assert(submitMode==SUBMIT_NONE); |
| 1553 | | - blob_appendf(&endScript, |
| 1554 | | - "window.fossil.fetch('raw/%s',{" |
| 1555 | | - "onload: (r)=>document.getElementById('fileedit-content')" |
| 1556 | | - ".value=r," |
| 1557 | | - "onerror:()=>document.getElementById('fileedit-content')" |
| 1558 | | - ".value=" |
| 1559 | | - "'Error loading content'" |
| 1560 | | - "});\n", zFileUuid); |
| 1561 | | - } |
| 1597 | + blob_appendf(&endScript, |
| 1598 | + "fossil.page.loadFile('%j','%j');", |
| 1599 | + zFilename, cimi.zParentUuid); |
| 1562 | 1600 | |
| 1563 | 1601 | end_footer: |
| 1564 | 1602 | zContent = 0; |
| 1565 | 1603 | fossil_free(zFileUuid); |
| 1566 | 1604 | if(stmt.pStmt){ |
| | @@ -1573,10 +1611,12 @@ |
| 1573 | 1611 | CX("%b",&submitResult); |
| 1574 | 1612 | } |
| 1575 | 1613 | blob_reset(&submitResult); |
| 1576 | 1614 | blob_reset(&err); |
| 1577 | 1615 | CheckinMiniInfo_cleanup(&cimi); |
| 1616 | + style_emit_script_fetch(); |
| 1617 | + fileedit_emit_page_script(); |
| 1578 | 1618 | if(blob_size(&endScript)>0){ |
| 1579 | 1619 | style_emit_script_tag(0); |
| 1580 | 1620 | CX("(function(){\n"); |
| 1581 | 1621 | CX("try{\n%b\n}catch(e){console.error('Exception:',e)}\n", |
| 1582 | 1622 | &endScript); |
| 1583 | 1623 | |
| 1584 | 1624 | ADDED src/fossil.bootstrap.js |
| 1585 | 1625 | ADDED src/fossil.fetch.js |
| 1586 | 1626 | ADDED src/fossil.page.fileedit.js |