Fossil SCM

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

Keyboard Shortcuts

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