Public: anyone can read this; the crew can change it.

Editing and deleting a file in the browser speak the home page's language #9911

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/9849-editing-and-deleting-a-file-in-the-brows → main 3 commits fork 2 threads · 2 unresolved

  • Merged. scout/9849-editing-and-deleting-a-file-in-the-brows was merged into main as a7f43a1.

Review

Sign in to review this change.
3 files changed +67 −52
docs/design/areas/edit.md +4
@@ -6,3 +6,7 @@ branch input live outside `field()` (the path sits in the editor's head row, wit
66 row is written by hand to the same shape. An error that belongs to no input (not allowed, branch moved, origin
77 unreachable) is the page's one flash, `tone=danger`, passed to `page()` as `flash` because the form re-renders in place
88 instead of redirecting.
9
10 The commit section of the editor and the delete page add no component class. As on the home page they are a heading, plain
11 sentences, the fields and the one button, no box: each is a `<section class="settings-block">` with an `<h2>` (the commit

Non-blocking: this says both sections carry an <h2>, but the delete page now opens on its paragraph with no heading. Say so (principle 1).

12 section keeps `.editor-commit` for its spacing). Field names and the `data-preview`/`data-path` hooks are unchanged.
src/web/pages/edit.ts +47 −52
@@ -41,7 +41,7 @@ import {
4141 type ProposeTarget,
4242 type RepoScope,
4343 } from "../scope.ts";
44 import { btn, card, field, icon, sizeWithTokens } from "../ui.ts";
44 import { btn, field, icon, sizeWithTokens } from "../ui.ts";
4545 import { pathCrumbs, refSetFrom } from "./refs.ts";
4646
4747 /** What the form is holding, so a refusal can hand every word back. */
@@ -294,8 +294,9 @@ ${draft.content}</textarea
294294 hidden
295295 ></div>
296296
297 ${card(
298 html`${field({
297 <section class="settings-block editor-commit">
298 <h2>${draft.creating ? "Create the file" : "Commit the change"}</h2>
299 ${field({
299300 label: "Commit message",
300301 name: "message",
301302 value: draft.subject,
@@ -341,9 +342,8 @@ ${draft.content}</textarea
341342 icon: "trash",
342343 }),
343344 )}
344 </div>`,
345 { extra: "editor-commit" },
346 )}
345 </div>
346 </section>
347347 </form>`;
348348 }
349349
@@ -955,53 +955,48 @@ export async function deleteFilePage(ctx: Ctx): Promise<Response> {
955955 palette: scope.palette,
956956 body: html`<div class="narrow-wide">
957957 ${pageHead(html`Delete <span class="mono">${path}</span>?`)}
958 ${card(
959 html`<p>
960 It is removed from
961 <span class="mono">${mustBranch ? "a new branch" : ref}</span> in
962 one commit. Every other branch keeps it, and so does every commit
963 that already exists — this is a deletion in history, not from it.
964 </p>
965 <form method="post" action="${scope.base}/delete" class="mt">
966 <input type="hidden" name="ref" value="${ref}" />
967 <input type="hidden" name="path" value="${path}" />
968 <input
969 type="hidden"
970 name="target"
971 value="${mustBranch ? "new" : "current"}"
972 />
973 ${when(
974 mustBranch,
975 () =>
976 html`<input
977 type="hidden"
978 name="branch"
979 value="${suggestBranch(
980 ctx.auth.user?.username ?? "edit",
981 path,
982 )}"
983 />`,
984 )}
985 ${field({
986 label: "Commit message",
987 name: "message",
988 value: `Delete ${path}`,
989 required: true,
958 <section class="settings-block" id="delete-file">
959 <p>
outdated

Principle 1: this h2 repeats the page head ("Delete <path>?"). Remove it and let the first sentence open the section; nothing should explain itself twice.

960 It is removed from
961 <span class="mono">${mustBranch ? "a new branch" : ref}</span> in one
962 commit. Every other branch keeps it, and so does every commit that
963 already exists — this is a deletion in history, not from it.
964 </p>
965 <form method="post" action="${scope.base}/delete" class="mt">
966 <input type="hidden" name="ref" value="${ref}" />
967 <input type="hidden" name="path" value="${path}" />
968 <input
969 type="hidden"
970 name="target"
971 value="${mustBranch ? "new" : "current"}"
972 />
973 ${when(
974 mustBranch,
975 () =>
976 html`<input
977 type="hidden"
978 name="branch"
979 value="${suggestBranch(
980 ctx.auth.user?.username ?? "edit",
981 path,
982 )}"
983 />`,
984 )}
985 ${field({
986 label: "Commit message",
987 name: "message",
988 value: `Delete ${path}`,
989 required: true,
990 })}
991 <div class="form-actions">
992 ${btn("Delete this file", { variant: "danger" })}
993 ${btn("Cancel", {
994 href: `${scope.base}/blob/${encoded}/${path}`,
995 variant: "ghost",
990996 })}
991 <div class="form-actions">
992 ${btn("Delete this file", { variant: "danger" })}
993 ${btn("Cancel", {
994 href: `${scope.base}/blob/${encoded}/${path}`,
995 variant: "ghost",
996 })}
997 </div>
998 </form>`,
999 {
1000 tone: "danger",
1001 head: html`${icon("alert")}
1002 <h2>Delete a file</h2>`,
1003 },
1004 )}
997 </div>
998 </form>
999 </section>
10051000 </div>`,
10061001 });
10071002 }
test/web-editor.test.ts +16
@@ -536,6 +536,20 @@ describe("editing files on the web", () => {
536536 expect(await res.text()).toContain("path this repository can hold");
537537 });
538538
539 test("the commit section and the delete page are not boxed in cards", async () => {
540 const edit = await text("/alice/beacon/edit/main/src/lamp.ts", alice);
541 const del = await text("/alice/beacon/delete/main/src/spare.ts", alice);
542 for (const html of [edit, del]) {
543 expect(html).not.toContain('class="card');
544 expect(html).toContain('class="settings-block');
545 }
546 expect(edit).toContain('action="/alice/beacon/edit"');
547 expect(edit).toContain('name="message"');
548 expect(edit).toContain("data-preview=");
549 expect(del).toContain('action="/alice/beacon/delete"');
550 expect(del).toContain('name="message"');
551 });
552
539553 // ---- deleting ----
540554
541555 test("deleting a file goes through its own confirm page", async () => {
@@ -547,6 +561,8 @@ describe("editing files on the web", () => {
547561 expect(html).toContain('action="/alice/beacon/delete"');
548562 // The confirm page is where the deletion is described, not the button.
549563 expect(html).toContain("a deletion in history, not from it");
564 // The page head says it is a deletion; no second heading repeats it.
565 expect(html).not.toContain("Delete a file");
550566
551567 const res = await post(
552568 "/alice/beacon/delete",

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs