Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | fileedit: now gets the file perms via the response header and updates the Is Executable checkbox accordingly. Similary, mimetype is now harvested from the response headers and is used, in place of file extensions, for the "is this a wiki page?" determination. Added fossil-level event infrastructure to allow pages to communicate page-specific events to skin-injected/user-supplied JS code, and fileedit now emits a fileedit-file-loaded event when it loads a new file. Replaced old uses of the term "save" with "commit", per forum feedback. |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | fileedit-ajaxify |
Files: | files | file ages | folders |
SHA3-256: |
f1b2e509e770b4be664c6b42ae022eab |
User & Date: | stephan 2020-05-14 03:00:31 |
Context
2020-05-14
| ||
03:39 | The filename/version block on the first fileedit tab is now generated completely dynamically, rather than being filled out with '???' with broken links before a file is loaded. ... (check-in: 371b162f user: stephan tags: fileedit-ajaxify) | |
03:00 | fileedit: now gets the file perms via the response header and updates the Is Executable checkbox accordingly. Similary, mimetype is now harvested from the response headers and is used, in place of file extensions, for the "is this a wiki page?" determination. Added fossil-level event infrastructure to allow pages to communicate page-specific events to skin-injected/user-supplied JS code, and fileedit now emits a fileedit-file-loaded event when it loads a new file. Replaced old uses of the term "save" with "commit", per forum feedback. ... (check-in: f1b2e509 user: stephan tags: fileedit-ajaxify) | |
2020-05-13
| ||
16:44 | Minor doc fix. ... (check-in: ae594780 user: stephan tags: fileedit-ajaxify) | |
Changes
Changes to src/fileedit.c.
︙ | ︙ | |||
1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 | content_get(frid, &content); if(0==zMime){ if(looks_like_binary(&content)){ zMime = "application/octet-stream"; }else{ zMime = "text/plain"; } } cgi_set_content_type(zMime); cgi_set_content(&content); } /* ** AJAX route /fileedit?ajax=preview | > > > > > > > > > | 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 | content_get(frid, &content); if(0==zMime){ if(looks_like_binary(&content)){ zMime = "application/octet-stream"; }else{ zMime = "text/plain"; } } { /* Send the is-exec bit via response header so that the UI can be ** updated to account for that. */ int fperm = 0; char * zFuuid = fileedit_file_uuid(zFilename, vid, &fperm); const char * zPerm = mfile_permint_mstring(fperm); assert(zFuuid); cgi_printf_header("x-fileedit-file-perm:%s\r\n", zPerm); fossil_free(zFuuid); } cgi_set_content_type(zMime); cgi_set_content(&content); } /* ** AJAX route /fileedit?ajax=preview |
︙ | ︙ | |||
1269 1270 1271 1272 1273 1274 1275 | case FE_RENDER_HTML_INLINE: zRenderMode = "htmlInline"; break; case FE_RENDER_HTML_IFRAME: zRenderMode = "htmlIframe"; break; case FE_RENDER_PLAIN_TEXT: zRenderMode = "text"; break; case FE_RENDER_GUESS: assert(!"cannot happen"); } if(zRenderMode!=0){ | | | 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 | case FE_RENDER_HTML_INLINE: zRenderMode = "htmlInline"; break; case FE_RENDER_HTML_IFRAME: zRenderMode = "htmlIframe"; break; case FE_RENDER_PLAIN_TEXT: zRenderMode = "text"; break; case FE_RENDER_GUESS: assert(!"cannot happen"); } if(zRenderMode!=0){ cgi_printf_header("x-fileedit-render-mode: %s\r\n", zRenderMode); } } /* ** AJAX route /fileedit?ajax=diff ** ** Required query parameters: |
︙ | ︙ | |||
1905 1906 1907 1908 1909 1910 1911 | "data-tab-label='Commit'" ">"); { /******* Commit flags/options *******/ CX("<div class='fileedit-options flex-container flex-row'>"); style_labeled_checkbox("cb-dry-run", "dry_run", "Dry-run?", "1", 1, | | | | | | | 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 | "data-tab-label='Commit'" ">"); { /******* Commit flags/options *******/ CX("<div class='fileedit-options flex-container flex-row'>"); style_labeled_checkbox("cb-dry-run", "dry_run", "Dry-run?", "1", 1, "In dry-run mode, the Commit button performs" "all work needed for committing changes but " "then rolls back the transaction, and thus " "does not really commit."); style_labeled_checkbox("cb-allow-fork", "allow_fork", "Allow fork?", "1", cimi.flags & CIMINI_ALLOW_FORK, "Allow committing to create a fork?"); style_labeled_checkbox("cb-allow-older", "allow_older", "Allow older?", "1", cimi.flags & CIMINI_ALLOW_OLDER, "Allow saving against a parent version " "which has a newer timestamp?"); style_labeled_checkbox("cb-exec-bit", "exec_bit", "Executable?", "1", |
︙ | ︙ |
Changes to src/fossil.bootstrap.js.
︙ | ︙ | |||
272 273 274 275 276 277 278 279 280 | (r)=>eTo[asText ? 'textContent' : 'innerHTML'] = r||'' ); }, false ); }); return this; }; })(window); | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | (r)=>eTo[asText ? 'textContent' : 'innerHTML'] = r||'' ); }, false ); }); return this; }; /** Adds a listener for fossil-level custom events. Events are delivered to their callbacks as CustomEvent objects with a 'detail' property holding the event's app-level data. The exact events fired differ by page, and not all pages trigger events. Pedantic sidebar: the custom event's 'target' property is an unspecified DOM element. Clients must not rely on its value being anything specific or useful. Returns this object. */ F.page.addEventListener = function f(eventName, callback){ if(!f.proxy){ f.proxy = document.createElement('span'); } f.proxy.addEventListener(eventName, callback, false); return this; }; /** Internal. Dispatches a new CustomEvent to all listeners registered for the given eventName via fossil.page.addEventListener(), passing on a new CustomEvent with a 'detail' property equal to the 2nd argument's value. Returns this object. */ F.page.dispatchEvent = function(eventName, eventDetail){ if(this.addEventListener.proxy){ try{ this.addEventListener.proxy.dispatchEvent( new CustomEvent(eventName,{detail: eventDetail}) ); }catch(e){ console.error(eventName,"event listener threw:",e); } } return this; }; })(window); |
Changes to src/fossil.page.fileedit.js.
1 2 3 4 5 6 7 8 9 10 11 12 | (function(F/*the fossil object*/){ "use strict"; /** Code for the /filepage app. Requires that the fossil JS bootstrapping is complete and fossil.fetch() has been installed. */ const E = (s)=>document.querySelector(s), D = F.dom, P = F.page; /** Widget for the checkin/file selection list. | > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | (function(F/*the fossil object*/){ "use strict"; /** Code for the /filepage app. Requires that the fossil JS bootstrapping is complete and fossil.fetch() has been installed. Custom events, handled via fossil.page.addEventListener(): - 'fileedit-file-loaded': passes on information when it loads a file, in the form of an object: { filename: string, checkin: UUID string, isExe: bool, mimetype: mimetype stringas determined by the fossil server. } The fossil.page.value() method gets or sets the current file content for the page. Hypothetically, this can be overridden by skin-level JS in order to use a custom 3rd-party editing widget in place of the built-in textarea, but that is as yet untested. In order to do so the client would need to replace DOM element #fileedit-content-editor with their custom widget. */ const E = (s)=>document.querySelector(s), D = F.dom, P = F.page; /** Widget for the checkin/file selection list. |
︙ | ︙ | |||
185 186 187 188 189 190 191 192 193 194 195 196 197 198 | btnReload: E("#fileedit-tab-content > .fileedit-options > " +"button.fileedit-content-reload"), selectPreviewMode: E('#select-preview-mode select'), selectHtmlEmsWrap: E('#select-preview-html-ems'), selectEolWrap: E('#select-preview-html-ems'), cbLineNumbersWrap: E('#cb-line-numbers'), cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'), tabs:{ content: E('#fileedit-tab-content'), preview: E('#fileedit-tab-preview'), diff: E('#fileedit-tab-diff'), commit: E('#fileedit-tab-commit') } }; | > | 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | btnReload: E("#fileedit-tab-content > .fileedit-options > " +"button.fileedit-content-reload"), selectPreviewMode: E('#select-preview-mode select'), selectHtmlEmsWrap: E('#select-preview-html-ems'), selectEolWrap: E('#select-preview-html-ems'), cbLineNumbersWrap: E('#cb-line-numbers'), cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'), cbIsExe: E('input[type=checkbox][name=exec_bit]'), tabs:{ content: E('#fileedit-tab-content'), preview: E('#fileedit-tab-preview'), diff: E('#fileedit-tab-diff'), commit: E('#fileedit-tab-commit') } }; |
︙ | ︙ | |||
297 298 299 300 301 302 303 304 305 306 307 308 | }, false ); selectFontSize.dispatchEvent( // Force UI update new Event('change',{target:selectFontSize}) ); } }, false)/*onload event handler*/; /** Getter (if called with no args) or setter (if passed an arg) for the current file content. We use a function, rather than direct | > > > > > > > > | | | | > | | | | | | 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 | }, false ); selectFontSize.dispatchEvent( // Force UI update new Event('change',{target:selectFontSize}) ); } if(0){ // only for testing P.addEventListener( 'fileedit-file-loaded', (e)=>console.debug('fileedit-file-loaded ==>',e) ); } }, false)/*onload event handler*/; /** Getter (if called with no args) or setter (if passed an arg) for the current file content. We use a function, rather than direct access, so that clients can hypothetically swap out this method from their skin in order to facilitate plugging-in of a fancy 3rd-party editor widget. The setter form returns this object, and re-implementations must do the same. */ P.value = function(){ if(0===arguments.length){ return this.e.taEditor.value; }else{ this.e.taEditor.value = arguments[0]; return this; } }; /** If either of... - P.previewModes.current==='wiki' - P.previewModes.current==='guess' AND the currently-loaded file has a mimetype of "text/x-fossil-wiki" or "text/x-markdown". ... then this function updates the document's base.href to a repo-relative /doc/{{this.finfo.checkin}}/{{directory part of this.finfo.filename}}/ If neither of those conditions applies, this is a no-op. */ P.baseHrefForFile = function f(){ const fn = this.finfo ? this.finfo.filename : undefined; if(!fn) return this; if(!f.wikiMimeTypes){ f.wikiMimeTypes = ["text/x-fossil-wiki", "text/x-markdown"]; } if('wiki'===P.previewModes.current || ('guess'===P.previewModes.current && f.wikiMimeTypes.indexOf(this.finfo.mimetype)>=0)){ const a = fn.split('/'); a.pop(); this.base.tag.href = F.repoUrl( 'doc/'+F.hashDigits(this.finfo.checkin) +'/'+(a.length ? a.join('/')+'/' : '') ); } |
︙ | ︙ | |||
434 435 436 437 438 439 440 | const affirmHasFile = function(){ if(!P.finfo) F.error("No file is loaded."); return !!P.finfo; }; /** loadFile() loads (file,checkinVersion) and updates the relevant | | > > | > > > | < > > > > > | 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 | const affirmHasFile = function(){ if(!P.finfo) F.error("No file is loaded."); return !!P.finfo; }; /** loadFile() loads (file,checkinVersion) and updates the relevant UI elements to reflect the loaded state. If passed no arguments then it re-uses the values from the currently-loaded file (becoming a no-op if no file is loaded). Returns this object, noting that the load is async. After loading it triggers a 'fileedit-file-loaded' event, passing it this.finfo. */ P.loadFile = function(file,rev){ if(0===arguments.length){ if(!affirmHasFile()) return this; file = this.finfo.filename; rev = this.finfo.checkin; } delete this.finfo; const self = this; F.message("Loading content..."); F.fetch('fileedit',{ urlParams: { ajax: 'content', filename:file, checkin:rev }, responseHeaders: ['x-fileedit-file-perm', 'content-type'], onload:(r,headers)=>{ F.message('Loaded content.'); self.updateVersion(file,rev); self.finfo.isExe = ('x'===headers['x-fileedit-file-perm']); self.finfo.mimetype = headers['content-type'].split(';').shift(); self.tabs.switchToTab(self.e.tabs.content); self.e.cbIsExe.checked = self.finfo.isExe; self.value(r); self.dispatchEvent('fileedit-file-loaded', self.finfo); } }); return this; }; /** Fetches the page preview based on the contents and settings of |
︙ | ︙ | |||
522 523 524 525 526 527 528 | fossil.fetch.onerror(e); callback("Error fetching preview: "+e); } }); return this; }; | < | | | 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 | fossil.fetch.onerror(e); callback("Error fetching preview: "+e); } }); return this; }; /** Fetches the content diff based on the contents and settings of this page's input fields, and updates the UI with the diff view. Returns this object, noting that the operation is async. */ P.diff = function f(sbs){ if(!affirmHasFile()) return this; const content = this.value(), self = this; |
︙ | ︙ | |||
642 643 644 645 646 647 648 | urlParams: {ajax: 'commit'}, payload: fd, responseType: 'json', onload: f.updateView }); return this; }; | | < | 680 681 682 683 684 685 686 687 688 | urlParams: {ajax: 'commit'}, payload: fd, responseType: 'json', onload: f.updateView }); return this; }; })(window.fossil); |