A lone link's preview is looked up where the server filed it
The server unfurls what detect_urls finds, trailing .,;:!? trimmed, and files the preview under that. The web and Android cards looked a lone link's preview up under body.trim(), punctuation included, so a note reading "https://example.com/a." never showed its card. - grammar.json gains a urls section: what the server finds in a body, and the link a lone-link note is filed under. - The web's rule moves out of NoteCard into notes/links.ts loneUrl(); Android's LinkPreviewRow gets the same loneUrl(); both trim like the server. - The server and web suites run the cases; Android's JVM test pins them by hand, as it does the tint. Fixes #5399. DRY pass #2, batch 3 (#5372). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import { computed, ref, watch } from "vue";
|
||||
import { useNotesStore } from "../stores/notes";
|
||||
import { NOTE_CARD_SURFACE, labelChipClasses } from "../notes/colors";
|
||||
import { rendersInline } from "../notes/attachments";
|
||||
import { loneUrl } from "../notes/links";
|
||||
import type { Note } from "../stores/notes";
|
||||
import Icon from "./Icon.vue";
|
||||
import LinkPreview from "./LinkPreview.vue";
|
||||
@@ -84,18 +85,14 @@ const PREVIEW_LINES = 8;
|
||||
// A note that is NOTHING but a URL is a link, and its preview is the whole card —
|
||||
// showing the raw URL underneath a card that already says where it goes is saying the
|
||||
// same thing twice, badly. A URL mentioned *inside* a note is a footnote to it, and
|
||||
// gets a compact strip at the bottom instead.
|
||||
//
|
||||
// Whitespace either side still counts as lone: someone pasting a link rarely trims it.
|
||||
const LONE_URL_RE = /^\s*(https?:\/\/[^\s<>"'\])]+)\s*$/;
|
||||
|
||||
const isLoneUrl = computed(() => LONE_URL_RE.test(props.note.body) && !props.note.items.length);
|
||||
// gets a compact strip at the bottom instead. What counts as lone is notes/links.ts.
|
||||
const isLoneUrl = computed(() => loneUrl(props.note.body) !== null && !props.note.items.length);
|
||||
|
||||
/** The preview for a lone-URL note — null while it is still being fetched, or if it
|
||||
* could never be fetched at all. */
|
||||
const loneUrlPreview = computed(() => {
|
||||
if (!isLoneUrl.value) return null;
|
||||
const url = props.note.body.trim();
|
||||
const url = loneUrl(props.note.body);
|
||||
return props.note.previews.find((p) => p.url === url) ?? null;
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { describe, expect, it } from "vitest";
|
||||
import fixture from "../../../core/testdata/grammar.json";
|
||||
import { DERIVED_TINT_KEYS, derivedTint, resolveLabelColor, tintHash } from "./colors";
|
||||
import { parseInline, parseTaskLine, renderTaskLine } from "./markdown";
|
||||
import { loneUrl } from "./links";
|
||||
import { RECURRENCES } from "./recurrence";
|
||||
|
||||
describe("checklist lines", () => {
|
||||
@@ -59,3 +60,9 @@ describe("recurrences", () => {
|
||||
expect(RECURRENCES.map((r) => r.value)).toEqual(fixture.recurrences);
|
||||
});
|
||||
});
|
||||
|
||||
describe("links", () => {
|
||||
it.each(fixture.urls)("lone link in $body", ({ body, lone }) => {
|
||||
expect(loneUrl(body)).toBe(lone);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// A note that is nothing but a link renders as a link card.
|
||||
//
|
||||
// The characters a link runs over, and the punctuation trimmed off its end, are the
|
||||
// server's (unfurl_queue.detect_urls): the server files the preview under the trimmed
|
||||
// link, so the card has to look it up under the same one (#5399). Android's
|
||||
// LinkPreviewRow holds the same rule, and all three run the `urls` cases in
|
||||
// core/testdata/grammar.json.
|
||||
//
|
||||
// Whitespace either side still counts as lone: someone pasting a link rarely trims it.
|
||||
const LONE_URL_RE = /^\s*(https?:\/\/[^\s<>"'\])]+)\s*$/;
|
||||
|
||||
/** The link a lone-link note's preview is filed under, or null for any other body. */
|
||||
export function loneUrl(body: string): string | null {
|
||||
const match = LONE_URL_RE.exec(body);
|
||||
if (!match) return null;
|
||||
return match[1].replace(/[.,;:!?]+$/, "") || null;
|
||||
}
|
||||
Reference in New Issue
Block a user