Fossil SCM

fossil-scm / www / fileedit-page.md
Source Blame History 317 lines
1243bf3… stephan 1 # The fileedit Page
1243bf3… stephan 2
1243bf3… stephan 3 This document describes the limitations of, caveats for, and
1243bf3… stephan 4 disclaimers for the [](/fileedit) page, which provides users with
285430a… drh 5 basic editing features for files via the web interface when they
285430a… drh 6 have [checkin privileges](./caps/index.md).
1243bf3… stephan 7
1243bf3… stephan 8 # Important Caveats and Disclaimers
1243bf3… stephan 9
1243bf3… stephan 10 Predictably, the ability to edit files in a repository from a web
1243bf3… stephan 11 browser halfway around the world comes with several obligatory caveats
1243bf3… stephan 12 and disclaimers...
1243bf3… stephan 13
66851cd… wyoung 14 ## <a id="cap"></a> `/fileedit` Does *Nothing* by Default.
1243bf3… stephan 15
1243bf3… stephan 16 In order to "activate" it, a user with [the "setup"
1243bf3… stephan 17 permission](./caps/index.md) must set the
c64f28d… drh 18 [fileedit-glob](/help/fileedit-glob) repository setting to a
1243bf3… stephan 19 comma- or newline-delimited list of globs representing a whitelist of
1243bf3… stephan 20 files which may be edited online. Any user with commit access may then
1243bf3… stephan 21 edit files matching one of those globs. Certain pages within the UI
1243bf3… stephan 22 get an "edit" link added to them when the current user's permissions
1243bf3… stephan 23 and the whitelist both permit editing of that file.
1243bf3… stephan 24
66851cd… wyoung 25 ## <a id="csrf"></a> CSRF & HTTP Referrer Headers
1243bf3… stephan 26
1243bf3… stephan 27 In order to protect against [Cross-site Request Forgery (CSRF)][csrf]
1243bf3… stephan 28 attacks, Fossil UI features which write to the database require that
1243bf3… stephan 29 the browser send the so-called [HTTP `Referer` header][referer]
1243bf3… stephan 30 (noting that the misspelling of "referrer" is a historical accident
1243bf3… stephan 31 which has long-since been standardized!). Modern browsers, by default,
1243bf3… stephan 32 include such information automatically for *interactive* actions which
1243bf3… stephan 33 lead to a request, e.g. clicking on a link back to the same
1243bf3… stephan 34 server. However, `/fileedit` uses asynchronous ["XHR"][xhr]
1243bf3… stephan 35 connections, which browsers *may* treat differently than strictly
1243bf3… stephan 36 interactive elements.
1243bf3… stephan 37
1243bf3… stephan 38 - **Firefox**: configuration option `network.http.sendRefererHeader`
1243bf3… stephan 39 specifies whether the `Referer` header is sent. It must have a value
1243bf3… stephan 40 of 2 (which is the default) for XHR requests to get the `Referer`
1243bf3… stephan 41 header. Purely interactive Fossil features, in which users directly
1243bf3… stephan 42 activate links or forms, work with a level of 1 or higher.
1243bf3… stephan 43 - **Chrome**: apparently requires an add-on in order to change this
1243bf3… stephan 44 policy, so Chrome without such an add-on will not suppress this
1243bf3… stephan 45 header.
1243bf3… stephan 46 - **Safari**: ???
1243bf3… stephan 47 - **Other browsers**: ???
1243bf3… stephan 48
d400f40… stephan 49 If `/fileedit` shows an error message saying "CSRF violation," the
1243bf3… stephan 50 problem is that the browser is not sending a `Referer` header to XHR
1243bf3… stephan 51 connections. Fossil does not offer a way to disable its CSRF
1243bf3… stephan 52 protections.
1243bf3… stephan 53
1243bf3… stephan 54 [referer]: https://en.wikipedia.org/wiki/HTTP_referer
1243bf3… stephan 55 [csrf]: https://en.wikipedia.org/wiki/Cross-site_request_forgery
1243bf3… stephan 56 [xhr]: https://en.wikipedia.org/wiki/XMLHttpRequest
1243bf3… stephan 57
66851cd… wyoung 58 ## <a id="commit"></a> `/fileedit` **Works by Creating Commits**
1243bf3… stephan 59
1243bf3… stephan 60 Thus any edits made via that page become a normal part of the
1ddb400… wyoung 61 repository.
1243bf3… stephan 62
66851cd… wyoung 63 ## <a id="intent"></a> `/fileedit` is *Intended* for use with Embedded Docs
1243bf3… stephan 64
1243bf3… stephan 65 ... and similar text files, and is most certainly
1243bf3… stephan 66 **not intended for editing code**.
1243bf3… stephan 67
1243bf3… stephan 68 Editing files with unusual syntax requirements, e.g. hard tabs in
1243bf3… stephan 69 makefiles, may break them. *You Have Been Warned.*
1243bf3… stephan 70
1243bf3… stephan 71 Similarly, though every effort is made to retain the end-of-line
1243bf3… stephan 72 style used by being-edited files, the round-trip through an HTML
1243bf3… stephan 73 textarea element may change the EOLs. The Commit section of the page
1243bf3… stephan 74 offers three different options for how to treat newlines when saving
1243bf3… stephan 75 changes. **Files with mixed EOL styles** *will be normalized to a single
1243bf3… stephan 76 EOL style* when modified using `/fileedit`. When "inheriting" the EOL
1243bf3… stephan 77 style from a previous version which has mixed styles, the first EOL
1243bf3… stephan 78 style detected in the previous version of the file is used.
1243bf3… stephan 79
66851cd… wyoung 80 ## <a id="checkout"></a> `/fileedit` **is Not a Replacement for a Checkout**
1243bf3… stephan 81
1243bf3… stephan 82 A full-featured checkout allows far more possibilities than this basic
1243bf3… stephan 83 online editor permits, and the feature scope of `/fileedit` is
1243bf3… stephan 84 intentionally kept small, implementing only the bare necessities
1243bf3… stephan 85 needed for performing basic edits online. It *is not, and will never
1243bf3… stephan 86 be, a replacement for a checkout.*
1243bf3… stephan 87
1243bf3… stephan 88 It is to be expected that users will want to do "more" with this
1243bf3… stephan 89 page, and we generally encourage feature requests, but be aware that
1243bf3… stephan 90 certain types of ostensibly sensible feature requests *will be
1243bf3… stephan 91 rejected* for `/fileedit`. These include, but are not limited to:
1243bf3… stephan 92
1243bf3… stephan 93 - Features which are already provided by other pages, e.g.
1243bf3… stephan 94 the ability to create a new named branch or add tags.
1243bf3… stephan 95 - Features which would require re-implementing significant
1243bf3… stephan 96 capabilities provided only within a checkout (e.g. merging files).
1243bf3… stephan 97 - The ability to edit/manipulate files which are in a local
1243bf3… stephan 98 checkout. (If you have a checkout, use your local editor, not
1243bf3… stephan 99 `/fileedit`.)
1243bf3… stephan 100 - Editing of non-text files, e.g. images. Use a checkout and your
1243bf3… stephan 101 preferred graphics editor.
1243bf3… stephan 102 - Support for syncing/pulling/pushing of a repository before and/or
1243bf3… stephan 103 after edits. Those features cannot be *reliably* provided via a web
1243bf3… stephan 104 interface for several reasons.
1243bf3… stephan 105
1243bf3… stephan 106 Similarly, some *potential* features have significant downsides,
1243bf3… stephan 107 abuses, and/or implementation hurdles which make the decision of
1243bf3… stephan 108 whether or not to implement them subject to notable contributor
1243bf3… stephan 109 debate. e.g. the ability to add new files or remove/rename older
1243bf3… stephan 110 files.
1243bf3… stephan 111
1243bf3… stephan 112
66851cd… wyoung 113 ## <a id="storage"></a> `/fileedit` **Stores Only Limited Local Edits While Working**
1243bf3… stephan 114
1243bf3… stephan 115 When changes are made to a given checkin/file combination,
1243bf3… stephan 116 `/fileedit` will, if possible, store them in [`window.localStorage`
1243bf3… stephan 117 or `window.sessionStorage`][html5storage], if available, but...
1243bf3… stephan 118
1243bf3… stephan 119 - Which storage is used is unspecified and may differ across
1243bf3… stephan 120 environments.
1243bf3… stephan 121 - If neither of those is available, the storage is transient and
1243bf3… stephan 122 will not survive a page reload. In this case, the UI issues a clear
1243bf3… stephan 123 warning in the editor tab.
1243bf3… stephan 124 - It stores only the most recent checkin/file combinations which have
1243bf3… stephan 125 been modified (exactly how many may differ - the number will be
1243bf3… stephan 126 noted somewhere in the UI). Note that changing the "executable bit"
1243bf3… stephan 127 is counted as a modification, but the checkin *comment* is *not*
1243bf3… stephan 128 and is reset after a commit.
1243bf3… stephan 129 - If its internal limit on the number of modified files is exceeded,
1243bf3… stephan 130 it silently discards the oldest edits to keep the list at its limit.
1243bf3… stephan 131
1243bf3… stephan 132 Edits are saved whenever the editor component fires its "change"
1243bf3… stephan 133 event, which essentially means as soon as it loses input focus. Thus
1243bf3… stephan 134 to force the browser to save any pending changes, simply click
1243bf3… stephan 135 somwhere on the page outside of the editor.
1243bf3… stephan 136
1243bf3… stephan 137 Exactly how long `localStorage` will survive, and how much it or
1243bf3… stephan 138 `sessionStorage` can hold, is environment-dependent. `sessionStorage`
1243bf3… stephan 139 will survive until the current browser tab is closed, but it survives
1243bf3… stephan 140 across reloads of the same tab.
1243bf3… stephan 141
d400f40… stephan 142 If `/fileedit` determines that no persistent storage is available a
1243bf3… stephan 143 warning is displayed on the editor page.
1243bf3… stephan 144
1243bf3… stephan 145 [html5storage]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API
1243bf3… stephan 146
66851cd… wyoung 147 ## <a id="power"></a> The Power is Yours, but...
1243bf3… stephan 148
1243bf3… stephan 149 > "With great power comes great responsibility."
1243bf3… stephan 150
1243bf3… stephan 151 **Use this feature judiciously, *if at all*.**
1243bf3… stephan 152
1243bf3… stephan 153 Now, with those warnings and caveats out of the way...
1243bf3… stephan 154
1243bf3… stephan 155 -----
1243bf3… stephan 156
66851cd… wyoung 157 # <a id="tips"></a> Tips and Tricks
1243bf3… stephan 158
66851cd… wyoung 159 ## <a id="global-js"></a> `fossil` Global-scope JS Object
1243bf3… stephan 160
1243bf3… stephan 161 `/fileedit` is largely implemented in JavaScript, and makes heavy use
1243bf3… stephan 162 of the global-scope `fossil` object, which provides
1243bf3… stephan 163 infrastructure-level features intended for use by Fossil UI pages.
1243bf3… stephan 164 (That said, that infrastructure was introduced with `/fileedit`, and
1243bf3… stephan 165 most pages do not use it.)
1243bf3… stephan 166
1243bf3… stephan 167 The `fossil.page` object represents the UI's current page (on pages
1243bf3… stephan 168 which make use of this API - most do not). That object supports
1243bf3… stephan 169 listening to page-specific events so that JS code installed via
1243bf3… stephan 170 [client-side edits to the site skin's footer](customskin.md) may react
1243bf3… stephan 171 to those changes somehow. The next section describes one such use for
1243bf3… stephan 172 such events...
1243bf3… stephan 173
66851cd… wyoung 174 ## <a id="syn-hl"></a> Integrating Syntax Highlighting
1243bf3… stephan 175
1243bf3… stephan 176 Assuming a repository has integrated a 3rd-party syntax highlighting
1243bf3… stephan 177 solution, it can probably (depending on its API) be told how to
1243bf3… stephan 178 highlight `/fileedit`'s wiki/markdown-format previews. Here are
1243bf3… stephan 179 instructions for doing so with [highlightjs](https://highlightjs.org/):
1243bf3… stephan 180
1243bf3… stephan 181 At the very bottom of the [site skin's footer](customskin.md), add a
1243bf3… stephan 182 script tag similar to the following:
1243bf3… stephan 183
1243bf3… stephan 184 ```javascript
1243bf3… stephan 185 <script nonce="$<nonce>">
1243bf3… stephan 186 if(window.fossil && fossil.page && fossil.page.name==='fileedit'){
1243bf3… stephan 187 fossil.page.addEventListener(
1243bf3… stephan 188 'fileedit-preview-updated',
1243bf3… stephan 189 (ev)=>{
1243bf3… stephan 190 if(ev.detail.previewMode==='wiki'){
1243bf3… stephan 191 ev.detail.element.querySelectorAll(
1243bf3… stephan 192 'code[class^=language-]'
1243bf3… stephan 193 ).forEach((e)=>hljs.highlightBlock(e));
1243bf3… stephan 194 }
1243bf3… stephan 195 }
1243bf3… stephan 196 );
1243bf3… stephan 197 }
1243bf3… stephan 198 </script>
1243bf3… stephan 199 ```
1243bf3… stephan 200
1243bf3… stephan 201 Note that the `nonce="$<nonce>"` part is intended to be entered
1243bf3… stephan 202 literally as shown above. It will be expanded to contain the current
1243bf3… stephan 203 request's nonce value when the page is rendered.
1243bf3… stephan 204
1243bf3… stephan 205 The first line of the script just ensures that the expected JS-level
1243bf3… stephan 206 infrastructure is loaded. It's only loaded in the `/fileedit` page and
1243bf3… stephan 207 possibly pages added or "upgraded" since `/fileedit`'s introduction.
1243bf3… stephan 208
d400f40… stephan 209 The part in the `if` block adds an event listener to the `/fileedit`
1243bf3… stephan 210 app which gets called when the preview is refreshed. That event
1243bf3… stephan 211 contains 3 properties:
1243bf3… stephan 212
1243bf3… stephan 213 - `previewMode`: a string describing the current preview mode: `wiki`
1243bf3… stephan 214 (which includes Fossil-native wiki and markdown), `text`,
1243bf3… stephan 215 `htmlInline`, `htmlIframe`. We should "probably" only highlight wiki
1243bf3… stephan 216 text, and thus the example above limits its work to that type of
1243bf3… stephan 217 preview. It won't work with `htmlIframe`, as that represents an
1243bf3… stephan 218 iframe element which contains a complete HTML document.
1243bf3… stephan 219 - `element`: the DOM element in which the preview is rendered.
1243bf3… stephan 220 - `mimetype`: the mimetype of the being-previewed content, as determined
1243bf3… stephan 221 by Fossil (by its file extension).
1243bf3… stephan 222
1243bf3… stephan 223 The event listener callback shown above doesn't use the `mimetype`,
285430a… drh 224 but makes use of the other two. It fishes all `code` blocks out of
1243bf3… stephan 225 the preview which explicitly have a CSS class named
1243bf3… stephan 226 `language-`something, and then asks highlightjs to highlight them.
1243bf3… stephan 227
66851cd… wyoung 228 ## <a id="editor"></a> Integrating a Custom Editor Widget
88703f0… stephan 229
d400f40… stephan 230 (These instructions also work for the `/wikiedit` page by replacing
88703f0… stephan 231 "fileedit" with "wikiedit" in any strings or symbol names!)
1243bf3… stephan 232
d400f40… stephan 233 It is possible to replace `/fileedit`'s basic text-editing widget (a
1243bf3… stephan 234 `textarea` element) with a fancy 3rd-party editor widget by following
1243bf3… stephan 235 these instructions...
1243bf3… stephan 236
1243bf3… stephan 237 All JavaScript code which follows is assumed to be in a script tag
1243bf3… stephan 238 similar to the one shown in the previous section:
1243bf3… stephan 239
1243bf3… stephan 240 ```javascript
1243bf3… stephan 241 <script nonce="$<nonce>">
1243bf3… stephan 242 if(window.fossil && fossil.page && fossil.page.name==='fileedit'){
1243bf3… stephan 243 // code specific to the fileedit page goes here
1243bf3… stephan 244 }
1243bf3… stephan 245 </script>
1243bf3… stephan 246 ```
1243bf3… stephan 247
1243bf3… stephan 248 First, install proxy functions so that `fossil.page.fileContent()`
1243bf3… stephan 249 can get and set your content:
1243bf3… stephan 250
1243bf3… stephan 251 ```
bdfbc9f… stephan 252 fossil.page.setContentMethods(
1243bf3… stephan 253 function(){ return text-form content of your widget },
1243bf3… stephan 254 function(content){ set text-form content of your widget }
1243bf3… stephan 255 };
1243bf3… stephan 256 ```
1243bf3… stephan 257
88703f0… stephan 258 Secondly, we need to alert the editor app when there are changes so
88703f0… stephan 259 that it can do things like store edits locally so that they are not
88703f0… stephan 260 lost on a page reload. How that is done is completely dependent on the
88703f0… stephan 261 3rd-party editor widget, but it generically looks something like:
88703f0… stephan 262
88703f0… stephan 263 ```
88703f0… stephan 264 myCustomWidget.on('eventName', ()=>fossil.page.notifyOfChange());
88703f0… stephan 265 ```
88703f0… stephan 266
88703f0… stephan 267 Lastly, if the 3rd-party editor does *not* hide or remove the native
88703f0… stephan 268 editor widget, and does not inject itself into the DOM on the caller's
88703f0… stephan 269 behalf, we can replace the native widget with the 3rd-party one with:
1243bf3… stephan 270
1243bf3… stephan 271 ```javascript
1243bf3… stephan 272 fossil.page.replaceEditorWidget(yourNewWidgetElement);
1243bf3… stephan 273 ```
1243bf3… stephan 274
1243bf3… stephan 275 That method must be passed a DOM element and may only be called once:
1243bf3… stephan 276 it *removes itself* the first time it is called.
1243bf3… stephan 277
88703f0… stephan 278 That should be all there is to it. When `fossil.page` needs to get the
88703f0… stephan 279 being-edited content, it will call the installed content-getter
88703f0… stephan 280 function with no arguments, and when it sets the content (immediately
88703f0… stephan 281 after (re)loading a file or grabbing local edits), it will pass that
88703f0… stephan 282 content to the installed content-setter method. Those, in turn will
88703f0… stephan 283 trigger the installed proxies and fire any relevant events.
88703f0… stephan 284
88703f0… stephan 285 Below is an example of Fossil skin footer content which plugs in the
88703f0… stephan 286 TinyMCE HTML editor into the `/wikiedit` page, but the process is
88703f0… stephan 287 identical for `/fileedit` (noting that `/fileedit` may need to be able
88703f0… stephan 288 to edit multiple types of files for which a special-purpose editor
88703f0… stephan 289 like TinyMCE may not be suitable). Note that any paths to CSS and JS
88703f0… stephan 290 resources of course need to be modified to suit one's own
88703f0… stephan 291 installation.
88703f0… stephan 292
88703f0… stephan 293 ```
88703f0… stephan 294 <!-- TinyMCE CSS and JS: -->
88703f0… stephan 295 <link href="$<home>/doc/ckout/skin.min.css" rel="stylesheet" type="text/css">
88703f0… stephan 296 <link href="$<home>/doc/ckout/content.min.css" rel="stylesheet" type="text/css">
88703f0… stephan 297 <script src='$<home>/doc/ckout/tinymce.min.js'></script>
88703f0… stephan 298 <script src='$<home>/doc/ckout/theme.min.js'></script>
88703f0… stephan 299 <script src='$<home>/doc/ckout/icons.min.js'></script>
88703f0… stephan 300 <!-- Integrate TinyMCE into /wikiedit: -->
88703f0… stephan 301 <script nonce="$<nonce>">
88703f0… stephan 302 if(window.fossil && window.fossil.page.name==='wikiedit'){
88703f0… stephan 303 window.fossil.onPageLoad( function(){
88703f0… stephan 304 const elemId = 'wikiedit-content-editor';
88703f0… stephan 305 tinymce.init({selector: 'textarea#'+elemId});
88703f0… stephan 306 const widget = tinymce.get(elemId);
88703f0… stephan 307 fossil.page.setContentMethods(
88703f0… stephan 308 function(){return widget.getContent()},
88703f0… stephan 309 function(content){widget.setContent(content)}
88703f0… stephan 310 );
88703f0… stephan 311 widget.on('change', function(){
88703f0… stephan 312 if(widget.isDirty()) fossil.page.notifyOfChange();
88703f0… stephan 313 });
88703f0… stephan 314 });
88703f0… stephan 315 }
88703f0… stephan 316 </script>
88703f0… stephan 317 ```

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button