Jump to…
snowfeat: given a new redesign and emulated terminal homepagerxkspqmsoknz1mo
1//! The in-browser file editor.
2//!
3//! A form with a `<textarea>` in it. That is the whole page, and it works with
4//! scripting disabled — CodeMirror replaces the textarea when `/assets/editor.js`
5//! loads and copies its content back on submit (spec §7: "Progressive
6//! enhancement is a hard requirement").
7//!
8//! Nothing here is conditional on JavaScript being available. There is no
9//! "loading" state and no hidden fallback: the fallback *is* the page.
10
11use maud::{html, Markup};
12
13use crate::repo_ctx::RepoContext;
14use crate::views::repo::breadcrumbs;
15
16pub struct Editor<'a> {
17 pub bookmark: &'a str,
18 /// The revision the edit is composed against, submitted back so a
19 /// concurrent edit is detected rather than overwritten.
20 pub tip: &'a str,
21 pub path: &'a str,
22 pub content: &'a str,
23 /// The grammar name CodeMirror should use, when we have one.
24 pub language: Option<&'a str>,
25 pub author: &'a str,
26 pub csrf: &'a str,
27 pub error: Option<&'a str>,
28}
29
30pub fn editor(ctx: &RepoContext, e: Editor<'_>) -> Markup {
31 let base = ctx.base();
32 let action = format!("{base}/edit/{}/{}", e.bookmark, e.path);
33 let cancel = format!("{base}/blob/{}/{}", e.bookmark, e.path);
34
35 html! {
36 div .tree-crumbs {
37 span .btn.btn-mono.is-static { (e.bookmark) }
38 (breadcrumbs(&base, e.bookmark, e.path))
39 span .spacer {}
40 a .btn.btn-mono href=(cancel) { "cancel" }
41 }
42
43 @if let Some(msg) = e.error {
44 div .banner.banner-error role="alert" { (msg) }
45 }
46
47 form method="post" action=(action) data-editor-form="1" {
48 input type="hidden" name="_csrf" value=(e.csrf);
49 // What the edit was composed against. The server refuses the save
50 // if the bookmark has moved since.
51 input type="hidden" name="tip" value=(e.tip);
52
53 div .editor-shell {
54 div .editor-bar {
55 span .mono { (e.path) }
56 @if let Some(l) = e.language {
57 span .label-condensed { (l) }
58 }
59 span .spacer {}
60 span .editor-status.mono { "editing on " (e.bookmark) }
61 }
62
63 label .sr-only for="content" { "Contents of " (e.path) }
64 div .editor-host {
65 textarea id="content" name="content" rows="24"
66 data-editor="1"
67 data-language=[e.language]
68 spellcheck="false"
69 autocapitalize="off" autocomplete="off"
70 wrap="off" { (e.content) }
71 }
72
73 div .editor-foot {
74 label .sr-only for="message" { "Commit message" }
75 input type="text" id="message" name="message" maxlength="500"
76 placeholder=(format!("Update {}", e.path));
77 button .btn.btn-primary type="submit" { "Commit changes" }
78 }
79 }
80 }
81
82 p .hint.measure {
83 "Committed to " code { (e.bookmark) } " as " (e.author)
84 ". Editing here writes a commit on top of the bookmark — it never rewrites \
85 history, so a protected bookmark accepts it, and it is indexed like any push."
86 }
87 }
88}

88 lines · Rust