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

The duplicate-address notice says "open it, or launch another anyway" with both choices inside it #82

Merged opened this

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

Merged

mason/listing-launch:mason/80-the-duplicate-address-notice-says-open-i → main 1 commit fork

  • Merged. mason/80-the-duplicate-address-notice-says-open-i was merged into main as a4bd6cc.

Review

Sign in to review this change.
4 files changed +29 −8
docs/design/areas/duplicate.md added +4
@@ -0,0 +1,4 @@
1 # Duplicate-address notice
2
3 - `link-button`: a submit button styled as an inline link, so a choice can sit inside a sentence ("launch another anyway").
4 - The existing `sr` class keeps a default Launch button first in the form, so Enter in a field never submits `again`.
src/duplicate.test.ts +10 −2
@@ -51,10 +51,18 @@ test("the author is asked first: the form comes back with every value, the link
5151 const page = await r.text();
5252 expect(page).toContain("You launched 412 Alder Lane on");
5353 expect(page).toContain(`href="/listings/${first}"`);
54 expect(page).toContain('name="again" value="1"');
54 const notice = page.slice(page.indexOf('<div class="card read-only"'), page.indexOf("</div>", page.indexOf('<div class="card read-only"')));
55 expect(notice.replace(/<[^>]*>/g, "")).toContain("You launched 412 Alder Lane on");
56 expect(notice.replace(/<[^>]*>/g, "")).toMatch(/^You launched 412 Alder Lane on [A-Z][a-z]{2} \d{1,2} — open it, or launch another anyway/);
57 expect(notice).toContain(`<a href="/listings/${first}">open it</a>`);
58 expect(notice).toMatch(/<button class="link-button" name="again" value="1">launch another anyway<\/button>/);
59 expect(notice).toContain("Choose the photos again");
5560 // Enter in a field submits with the form's first submit button: it must not be the again one.
56 const form = page.slice(page.indexOf('<form class="card form"'));
61 const form = page.slice(page.indexOf('<form class="card form"'), page.indexOf("</form>"));
5762 expect(form.match(/<button[^>]*>/)![0]).not.toContain("again");
63 // No second button after Launch.
64 expect(form.slice(form.indexOf('class="button button-launch"'))).not.toContain("<button");
65 expect(form.match(/name="again"/g)!.length).toBe(1);
5866 expect(page).toContain('value="Ann &lt;b&gt;"');
5967 expect(photos()).toBe(before);
6068 const again = await post("ann", "412 Alder Lane", { again: "1" });
src/pages/home.ts +4 −6
@@ -125,11 +125,9 @@ export function form(
125125 const again =
126126 duplicate === undefined
127127 ? ""
128 : `<p class="card read-only" role="status">You launched ${escape(duplicate.address)} on ${escape(new Date(duplicate.created_at).toLocaleDateString("en", { month: "short", day: "numeric" }))} — <a href="/listings/${duplicate.id}">open it</a>, or launch another anyway with the button after Launch <small class="muted">(choose any photos again)</small></p>`;
129 const anyway =
130 duplicate === undefined
131 ? ""
132 : `<button class="button button-quiet" name="again" value="1">Launch another anyway</button>`;
128 : `<button class="sr" tabindex="-1" aria-hidden="true">Launch</button>
129 <div class="card read-only" role="status"><p>You launched ${escape(duplicate.address)} on ${escape(new Date(duplicate.created_at).toLocaleDateString("en", { month: "short", day: "numeric" }))} — <a href="/listings/${duplicate.id}">open it</a>, or <button class="link-button" name="again" value="1">launch another anyway</button></p>
130 <p class="muted"><small>Choose the photos again: a browser cannot keep the files you chose.</small></p></div>`;
133131 const launch =
134132 editing !== undefined
135133 ? `<button class="button button-launch">Save and write all again</button>
@@ -137,7 +135,7 @@ export function form(
137135 <span class="muted">Every asset is written again from these facts: your edits are replaced and every approval is cleared.</span>`
138136 : who.role === "anon"
139137 ? `<a class="button" href="/__forge/signin?to=/">Sign in to launch</a>`
140 : `<button class="button button-launch">Launch</button>${anyway}`;
138 : `<button class="button button-launch">Launch</button>`;
141139 const photos = `<label class="field"><span>Photos <small class="muted">up to ${PHOTOS_MAX}, JPEG, PNG or WebP, 24 MB in all; the first is the hero</small></span>
142140 <input class="input file" type="file" name="photos" multiple accept="image/jpeg,image/png,image/webp"></label>`;
143141 const headshot = `<label class="field"><span>Headshot <small class="muted">${headshotFrom === undefined ? "optional; your last one carries over" : `Using your headshot from ${escape(headshotFrom.address)} — choose a file to replace it`}</small></span>
static/app.css +11
@@ -1277,3 +1277,14 @@ main:focus {
12771277 break-inside: avoid;
12781278 }
12791279 }
1280 .link-button {
1281 font: inherit;
1282 color: var(--color-accent);
1283 background: none;
1284 border: 0;
1285 padding: 0;
1286 text-decoration: underline;
1287 text-decoration-thickness: 1px;
1288 text-underline-offset: 0.15em;
1289 cursor: pointer;
1290 }

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs