Public: anyone can read this; the crew can change it.
"That file is too large to edit here." and "That could not be changed." live in one module each owner reads #5687
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/5625-that-file-is-too-large-to-edit-here-and → main 2 commits fork 1 thread · 1 unresolved
6 files
changed +59
−9
src/web/edit_words.ts added +11
| @@ -0,0 +1,11 @@ | ||
| 1 | /** | |
| 2 | * The sentences an edit says when it cannot go ahead, said from one place | |
| 3 | * so the blob editor, the proposal editor, settings and threads read the | |
| 4 | * same (#5625). | |
| 5 | */ | |
| 6 | ||
| 7 | /** A text file past the size the in-browser editor will load. */ | |
| 8 | export const TOO_LARGE_TO_EDIT = "That file is too large to edit here."; | |
| 9 | ||
| 10 | /** A setting or thread change that was refused for no more specific reason. */ | |
| 11 | export const NOT_CHANGED = "That could not be changed."; |
src/web/pages/browse.ts +2 −1
| @@ -45,6 +45,7 @@ import { commentCard } from "../comments.ts"; | ||
| 45 | 45 | import { renderDiff } from "../diff-view.ts"; |
| 46 | 46 | import { readForm } from "../form.ts"; |
| 47 | 47 | import { html, when, type Html } from "../html.ts"; |
| 48 | import { TOO_LARGE_TO_EDIT } from "../edit_words.ts"; | |
| 48 | 49 | import { flashTo, fragment, page, redirect } from "../layout.ts"; |
| 49 | 50 | import { codeJs, mirrorJs } from "../client.ts"; |
| 50 | 51 | import { json, notFound } from "../../http/respond.ts"; |
| @@ -887,7 +888,7 @@ export async function browseEditPage(ctx: Ctx): Promise<Response> { | ||
| 887 | 888 | message: |
| 888 | 889 | blob.value.kind === "binary" |
| 889 | 890 | ? "That file is binary — propose it with git, not a textarea." |
| 890 | : "That file is too large to edit here.", | |
| 891 | : TOO_LARGE_TO_EDIT, | |
| 891 | 892 | }, |
| 892 | 893 | ); |
| 893 | 894 | return editorPage( |
src/web/pages/edit.ts +2 −1
| @@ -31,6 +31,7 @@ import { highlightToHtml } from "../highlight-html.ts"; | ||
| 31 | 31 | import { loadRepo, notFoundPage, pageHead, signInFirst } from "../chrome.ts"; |
| 32 | 32 | import { field as formValue, readForm, type Form } from "../form.ts"; |
| 33 | 33 | import { html, raw, when, type Html } from "../html.ts"; |
| 34 | import { TOO_LARGE_TO_EDIT } from "../edit_words.ts"; | |
| 34 | 35 | import { flashTo, fragment, page } from "../layout.ts"; |
| 35 | 36 | import { renderMarkdown } from "../markdown.ts"; |
| 36 | 37 | import { |
| @@ -617,7 +618,7 @@ export async function editPage(ctx: Ctx): Promise<Response> { | ||
| 617 | 618 | message: |
| 618 | 619 | blob.value.kind === "binary" |
| 619 | 620 | ? "That file is binary — edit it with git, not a textarea." |
| 620 | : "That file is too large to edit here.", | |
| 621 | : TOO_LARGE_TO_EDIT, | |
| 621 | 622 | }, |
| 622 | 623 | ); |
| 623 | 624 | } |
src/web/pages/settings.ts +5 −4
| @@ -96,6 +96,7 @@ import { pageTitle } from "../studio/nouns.ts"; | ||
| 96 | 96 | import { appHref } from "./apps.ts"; |
| 97 | 97 | import { field, readForm, type Form } from "../form.ts"; |
| 98 | 98 | import { html, when, type Html } from "../html.ts"; |
| 99 | import { NOT_CHANGED } from "../edit_words.ts"; | |
| 99 | 100 | import { DELETE_REPO, STOP_APP } from "../danger_words.ts"; |
| 100 | 101 | import { icon } from "../icons.ts"; |
| 101 | 102 | import { flashTo, page, redirect } from "../layout.ts"; |
| @@ -488,7 +489,7 @@ export async function archiveSubmit(ctx: Ctx): Promise<Response> { | ||
| 488 | 489 | if (asked !== "0" && asked !== "1") |
| 489 | 490 | return flashTo(ctx, `${repoBase(repo)}/settings`, { |
| 490 | 491 | tone: "danger", |
| 491 | message: "That could not be changed.", | |
| 492 | message: NOT_CHANGED, | |
| 492 | 493 | }); |
| 493 | 494 | const archived = asked === "1"; |
| 494 | 495 | const updated = await updateRepoSettings(ctx.app, ctx.auth.user, repo, { |
| @@ -500,7 +501,7 @@ export async function archiveSubmit(ctx: Ctx): Promise<Response> { | ||
| 500 | 501 | back, |
| 501 | 502 | updated.ok |
| 502 | 503 | ? { message: archived ? "Archived." : "Awake again." } |
| 503 | : { tone: "danger", message: "That could not be changed." }, | |
| 504 | : { tone: "danger", message: NOT_CHANGED }, | |
| 504 | 505 | ); |
| 505 | 506 | } |
| 506 | 507 | |
| @@ -549,14 +550,14 @@ export async function provenanceSubmit(ctx: Ctx): Promise<Response> { | ||
| 549 | 550 | const asked = field(form, "shown"); |
| 550 | 551 | const back = `${repoBase(repo)}/settings`; |
| 551 | 552 | if (asked !== "0" && asked !== "1") |
| 552 | return flashTo(ctx, back, { tone: "danger", message: "That could not be changed." }); | |
| 553 | return flashTo(ctx, back, { tone: "danger", message: NOT_CHANGED }); | |
| 553 | 554 | const set = showProvenance(ctx.app, ctx.auth.user, repo, asked === "1"); |
| 554 | 555 | return flashTo( |
| 555 | 556 | ctx, |
| 556 | 557 | back, |
| 557 | 558 | set.ok |
| 558 | 559 | ? { message: set.value.shown ? "Where this came from is shown." : "Where this came from is hidden." } |
| 559 | : { tone: "danger", message: "That could not be changed." }, | |
| 560 | : { tone: "danger", message: NOT_CHANGED }, | |
| 560 | 561 | ); |
| 561 | 562 | } |
| 562 | 563 |
src/web/pages/thread.ts +4 −3
| @@ -56,6 +56,7 @@ import type { Ctx } from "../../http/context.ts"; | ||
| 56 | 56 | import { commentCard } from "../comments.ts"; |
| 57 | 57 | import { backTo, field, readForm } from "../form.ts"; |
| 58 | 58 | import { html, raw, when, type Html } from "../html.ts"; |
| 59 | import { NOT_CHANGED } from "../edit_words.ts"; | |
| 59 | 60 | import { flashOrJson, flashTo } from "../layout.ts"; |
| 60 | 61 | import { |
| 61 | 62 | btn, |
| @@ -821,7 +822,7 @@ export async function stateSubmit( | ||
| 821 | 822 | ? merged |
| 822 | 823 | : changed.error === "forbidden" |
| 823 | 824 | ? "Only the author or someone with write access can do that." |
| 824 | : "That could not be changed."); | |
| 825 | : NOT_CHANGED); | |
| 825 | 826 | return flashOrJson(ctx, dest, { tone: "danger", message }); |
| 826 | 827 | } |
| 827 | 828 | return flashOrJson(ctx, dest, { |
| @@ -858,7 +859,7 @@ export async function wordsSubmit( | ||
| 858 | 859 | ? `A title is at most ${MAX_TITLE} characters.` |
| 859 | 860 | : edited.error === "body_too_long" |
| 860 | 861 | ? "That description is too long." |
| 861 | : "That could not be changed."); | |
| 862 | : NOT_CHANGED); | |
| 862 | 863 | return flashOrJson(ctx, dest, { tone: "danger", message }); |
| 863 | 864 | } |
| 864 | 865 | return flashOrJson(ctx, dest, { message: "Saved." }); |
| @@ -947,7 +948,7 @@ export async function teamsSubmit(ctx: Ctx): Promise<Response> { | ||
| 947 | 948 | const chosen = form.getAll("teams").filter((v): v is string => typeof v === "string"); |
| 948 | 949 | const set = nameTeams(ctx.app, ctx.auth.user, found.repo, found.issue, chosen); |
| 949 | 950 | if (!set.ok) |
| 950 | return flashTo(ctx, dest, { tone: "danger", message: ISSUE_TEAMS_SAY[set.error] ?? "That could not be changed." }); | |
| 951 | return flashTo(ctx, dest, { tone: "danger", message: ISSUE_TEAMS_SAY[set.error] ?? NOT_CHANGED }); | |
| 951 | 952 | return flashTo(ctx, dest, { |
| 952 | 953 | message: set.value.length === 0 ? "Teams cleared." : `Teams set to ${set.value.join(", ")}.`, |
| 953 | 954 | }); |
test/web-edit-words.test.ts added +35
| @@ -0,0 +1,35 @@ | ||
| 1 | /** | |
| 2 | * "That file is too large to edit here." and "That could not be changed." | |
| 3 | * are said from src/web/edit_words.ts alone (#5625): every page that says | |
| 4 | * one imports it, and a copy typed back into a page fails here. | |
| 5 | */ | |
| 6 | ||
| 7 | import { describe, expect, test } from "bun:test"; | |
| 8 | import { readFileSync } from "node:fs"; | |
| 9 | import { join } from "node:path"; | |
| 10 | import { NOT_CHANGED, TOO_LARGE_TO_EDIT } from "../src/web/edit_words.ts"; | |
| 11 | ||
| 12 | const root = join(import.meta.dir, ".."); | |
| 13 | const read = (path: string): string => readFileSync(join(root, path), "utf8"); | |
| 14 | ||
| 15 | const SAYERS: ReadonlyArray<{ path: string; words: string; name: string }> = [ | |
| 16 | { path: "src/web/pages/browse.ts", words: TOO_LARGE_TO_EDIT, name: "TOO_LARGE_TO_EDIT" }, | |
| 17 | { path: "src/web/pages/edit.ts", words: TOO_LARGE_TO_EDIT, name: "TOO_LARGE_TO_EDIT" }, | |
| 18 | { path: "src/web/pages/settings.ts", words: NOT_CHANGED, name: "NOT_CHANGED" }, | |
| 19 | { path: "src/web/pages/thread.ts", words: NOT_CHANGED, name: "NOT_CHANGED" }, | |
| 20 | ]; | |
| 21 | ||
| 22 | describe("edit words live in one module", () => { | |
| 23 | test("the sentences read as they always have", () => { | |
| 24 | expect(TOO_LARGE_TO_EDIT).toBe("That file is too large to edit here."); | |
| 25 | expect(NOT_CHANGED).toBe("That could not be changed."); | |
| 26 | }); | |
| 27 | ||
| 28 | for (const { path, words, name } of SAYERS) { | |
| 29 | test(`${path} imports ${name} and types no copy of it`, () => { | |
| 30 | const text = read(path); | |
| 31 | expect(text).not.toContain(words); | |
| 32 | expect(text).toMatch(new RegExp(`import \\{[^}]*\\b${name}\\b[^}]*\\} from "\\.\\./edit_words\\.ts"`)); | |
| 33 | }); | |
| 34 | } | |
| 35 | }); |
new above
offline
The check reads source as text. It is good enough here, but it would miss a copy that differs by a comma. The two exact-text assertions above cover the sentence itself.