feat(web): add link preview view setting

Add a persisted "Link preview" toggle (default on) to the memo
display-settings popover, mirroring the compact-mode setting. When turned
off, link-preview cards are hidden across all memo views and the metadata
request is skipped (the gate sits at the paragraph renderer and reads the
preference from ViewContext). Resolves the request in #6045.
pull/6051/head
boojack 2 weeks ago
parent a5c10a7221
commit e3e4ae1051

@ -1,4 +1,5 @@
import type { Element } from "hast"; import type { Element } from "hast";
import { useLinkPreviewEnabled } from "@/contexts/ViewContext";
import { markdownStyles } from "@/lib/markdownStyles"; import { markdownStyles } from "@/lib/markdownStyles";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import LinkMetadataCard from "../LinkMetadataCard"; import LinkMetadataCard from "../LinkMetadataCard";
@ -46,7 +47,8 @@ export function getSingleLinkHref(node?: Element): string | undefined {
export const Paragraph = ({ children, className, node, ...props }: ParagraphProps) => { export const Paragraph = ({ children, className, node, ...props }: ParagraphProps) => {
const { blockDepth } = useMarkdownRenderContext(); const { blockDepth } = useMarkdownRenderContext();
const href = blockDepth === 0 ? getSingleLinkHref(node) : undefined; const linkPreviewEnabled = useLinkPreviewEnabled();
const href = blockDepth === 0 && linkPreviewEnabled ? getSingleLinkHref(node) : undefined;
const paragraph = ( const paragraph = (
<p className={cn(markdownStyles.paragraph, className)} {...props}> <p className={cn(markdownStyles.paragraph, className)} {...props}>
{children} {children}

@ -12,8 +12,8 @@ interface Props {
function MemoDisplaySettingMenu({ className }: Props) { function MemoDisplaySettingMenu({ className }: Props) {
const t = useTranslate(); const t = useTranslate();
const { orderByTimeAsc, timeBasis, compactMode, setTimeBasis, toggleSortOrder, setCompactMode } = useView(); const { orderByTimeAsc, timeBasis, compactMode, linkPreview, setTimeBasis, toggleSortOrder, setCompactMode, setLinkPreview } = useView();
const isApplying = orderByTimeAsc !== false || timeBasis !== "create_time" || compactMode; const isApplying = orderByTimeAsc !== false || timeBasis !== "create_time" || compactMode || !linkPreview;
return ( return (
<Popover> <Popover>
@ -57,6 +57,10 @@ function MemoDisplaySettingMenu({ className }: Props) {
<span className="text-sm shrink-0 mr-3 text-foreground">{t("memo.compact-mode")}</span> <span className="text-sm shrink-0 mr-3 text-foreground">{t("memo.compact-mode")}</span>
<Switch checked={compactMode} onCheckedChange={setCompactMode} /> <Switch checked={compactMode} onCheckedChange={setCompactMode} />
</div> </div>
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm shrink-0 mr-3 text-foreground">{t("memo.link-preview")}</span>
<Switch checked={linkPreview} onCheckedChange={setLinkPreview} />
</div>
</div> </div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>

@ -7,21 +7,26 @@ interface ViewState {
timeBasis?: MemoTimeBasis; timeBasis?: MemoTimeBasis;
sortTimeField?: MemoTimeBasis; sortTimeField?: MemoTimeBasis;
compactMode: boolean; compactMode: boolean;
linkPreview: boolean;
} }
interface ViewContextValue { interface ViewContextValue {
orderByTimeAsc: boolean; orderByTimeAsc: boolean;
timeBasis: MemoTimeBasis; timeBasis: MemoTimeBasis;
compactMode: boolean; compactMode: boolean;
linkPreview: boolean;
toggleSortOrder: () => void; toggleSortOrder: () => void;
setTimeBasis: (field: MemoTimeBasis) => void; setTimeBasis: (field: MemoTimeBasis) => void;
setCompactMode: (value: boolean) => void; setCompactMode: (value: boolean) => void;
setLinkPreview: (value: boolean) => void;
} }
const ViewContext = createContext<ViewContextValue | null>(null); const ViewContext = createContext<ViewContextValue | null>(null);
const LOCAL_STORAGE_KEY = "memos-view-setting"; const LOCAL_STORAGE_KEY = "memos-view-setting";
const DEFAULT_VIEW_STATE: ViewState = { orderByTimeAsc: false, compactMode: false, linkPreview: true };
export function ViewProvider({ children }: { children: ReactNode }) { export function ViewProvider({ children }: { children: ReactNode }) {
const getInitialState = (): ViewState => { const getInitialState = (): ViewState => {
try { try {
@ -31,15 +36,16 @@ export function ViewProvider({ children }: { children: ReactNode }) {
const cachedTimeBasis = data.timeBasis ?? data.sortTimeField; const cachedTimeBasis = data.timeBasis ?? data.sortTimeField;
const timeBasis = cachedTimeBasis === "create_time" || cachedTimeBasis === "update_time" ? cachedTimeBasis : undefined; const timeBasis = cachedTimeBasis === "create_time" || cachedTimeBasis === "update_time" ? cachedTimeBasis : undefined;
return { return {
orderByTimeAsc: Boolean(data.orderByTimeAsc ?? false), orderByTimeAsc: Boolean(data.orderByTimeAsc ?? DEFAULT_VIEW_STATE.orderByTimeAsc),
timeBasis, timeBasis,
compactMode: Boolean(data.compactMode ?? false), compactMode: Boolean(data.compactMode ?? DEFAULT_VIEW_STATE.compactMode),
linkPreview: Boolean(data.linkPreview ?? DEFAULT_VIEW_STATE.linkPreview),
}; };
} }
} catch (error) { } catch (error) {
console.warn("Failed to load view settings from localStorage:", error); console.warn("Failed to load view settings from localStorage:", error);
} }
return { orderByTimeAsc: false, compactMode: false }; return { ...DEFAULT_VIEW_STATE };
}; };
const [viewState, setViewState] = useState(getInitialState); const [viewState, setViewState] = useState(getInitialState);
@ -53,29 +59,18 @@ export function ViewProvider({ children }: { children: ReactNode }) {
} }
}; };
const toggleSortOrder = () => { const updateState = (patch: Partial<ViewState> | ((prev: ViewState) => Partial<ViewState>)) => {
setViewState((prev) => {
const newState = { ...prev, orderByTimeAsc: !prev.orderByTimeAsc };
persistToStorage(newState);
return newState;
});
};
const setTimeBasis = (field: MemoTimeBasis) => {
setViewState((prev) => { setViewState((prev) => {
const newState = { ...prev, timeBasis: field }; const newState = { ...prev, ...(typeof patch === "function" ? patch(prev) : patch) };
persistToStorage(newState); persistToStorage(newState);
return newState; return newState;
}); });
}; };
const setCompactMode = (value: boolean) => { const toggleSortOrder = () => updateState((prev) => ({ orderByTimeAsc: !prev.orderByTimeAsc }));
setViewState((prev) => { const setTimeBasis = (field: MemoTimeBasis) => updateState({ timeBasis: field });
const newState = { ...prev, compactMode: value }; const setCompactMode = (value: boolean) => updateState({ compactMode: value });
persistToStorage(newState); const setLinkPreview = (value: boolean) => updateState({ linkPreview: value });
return newState;
});
};
return ( return (
<ViewContext.Provider <ViewContext.Provider
@ -83,9 +78,11 @@ export function ViewProvider({ children }: { children: ReactNode }) {
orderByTimeAsc: viewState.orderByTimeAsc, orderByTimeAsc: viewState.orderByTimeAsc,
timeBasis, timeBasis,
compactMode: viewState.compactMode, compactMode: viewState.compactMode,
linkPreview: viewState.linkPreview,
toggleSortOrder, toggleSortOrder,
setTimeBasis, setTimeBasis,
setCompactMode, setCompactMode,
setLinkPreview,
}} }}
> >
{children} {children}
@ -100,3 +97,10 @@ export function useView() {
} }
return context; return context;
} }
// Read the link-preview preference from deep inside the markdown renderer, which
// can render outside a ViewProvider (tests, isolated previews). Defaults to
// enabled when no provider is present, preserving the historical behavior.
export function useLinkPreviewEnabled() {
return useContext(ViewContext)?.linkPreview ?? DEFAULT_VIEW_STATE.linkPreview;
}

@ -195,6 +195,7 @@
"label": "المرشحات" "label": "المرشحات"
}, },
"compact-mode": "الوضع المضغوط", "compact-mode": "الوضع المضغوط",
"link-preview": "معاينة الرابط",
"links": "روابط", "links": "روابط",
"load-more": "تحميل المزيد", "load-more": "تحميل المزيد",
"no-archived-memos": "لا يوجد مذكرات مؤرشفة", "no-archived-memos": "لا يوجد مذكرات مؤرشفة",

@ -247,6 +247,7 @@
"label": "Филтри" "label": "Филтри"
}, },
"compact-mode": "Компактен режим", "compact-mode": "Компактен режим",
"link-preview": "Преглед на връзка",
"links": "Връзки", "links": "Връзки",
"load-more": "Заредете повече", "load-more": "Заредете повече",
"no-archived-memos": "Няма архивирани бележки.", "no-archived-memos": "Няма архивирани бележки.",

@ -209,6 +209,7 @@
"label": "Filtres" "label": "Filtres"
}, },
"compact-mode": "Mode compacte", "compact-mode": "Mode compacte",
"link-preview": "Previsualització d'enllaços",
"links": "Enllaços", "links": "Enllaços",
"load-more": "Carrega més", "load-more": "Carrega més",
"no-archived-memos": "No hi ha notes arxivades.", "no-archived-memos": "No hi ha notes arxivades.",

@ -209,6 +209,7 @@
"label": "Filtry" "label": "Filtry"
}, },
"compact-mode": "Kompaktní režim", "compact-mode": "Kompaktní režim",
"link-preview": "Náhled odkazu",
"links": "Odkazy", "links": "Odkazy",
"load-more": "Načíst více", "load-more": "Načíst více",
"no-archived-memos": "Žádné archivované poznámky.", "no-archived-memos": "Žádné archivované poznámky.",

@ -247,6 +247,7 @@
"label": "Filtre" "label": "Filtre"
}, },
"compact-mode": "Kompakt tilstand", "compact-mode": "Kompakt tilstand",
"link-preview": "Linkforhåndsvisning",
"links": "Links", "links": "Links",
"load-more": "Indlæs mere", "load-more": "Indlæs mere",
"no-archived-memos": "Ingen arkiverede notater.", "no-archived-memos": "Ingen arkiverede notater.",

@ -209,6 +209,7 @@
"label": "Filter" "label": "Filter"
}, },
"compact-mode": "Kompaktmodus", "compact-mode": "Kompaktmodus",
"link-preview": "Linkvorschau",
"links": "Links", "links": "Links",
"load-more": "Mehr laden", "load-more": "Mehr laden",
"no-archived-memos": "Keine archivierten Notizen.", "no-archived-memos": "Keine archivierten Notizen.",

@ -247,6 +247,7 @@
"label": "Φίλτρα" "label": "Φίλτρα"
}, },
"compact-mode": "Συμπαγής λειτουργία", "compact-mode": "Συμπαγής λειτουργία",
"link-preview": "Προεπισκόπηση συνδέσμου",
"links": "Εδαφος διά παιγνίδι γκολφ", "links": "Εδαφος διά παιγνίδι γκολφ",
"load-more": "Φορτώστε περισσότερα", "load-more": "Φορτώστε περισσότερα",
"no-archived-memos": "Δεν υπάρχουν αρχειοθετημένα σημειώματα.", "no-archived-memos": "Δεν υπάρχουν αρχειοθετημένα σημειώματα.",

@ -209,6 +209,7 @@
"label": "Filters" "label": "Filters"
}, },
"compact-mode": "Compact mode", "compact-mode": "Compact mode",
"link-preview": "Link preview",
"links": "Links", "links": "Links",
"load-more": "Load more", "load-more": "Load more",
"no-archived-memos": "No archived memos.", "no-archived-memos": "No archived memos.",

@ -265,6 +265,7 @@
"label": "Filters" "label": "Filters"
}, },
"compact-mode": "Compact mode", "compact-mode": "Compact mode",
"link-preview": "Link preview",
"links": "Links", "links": "Links",
"load-more": "Load more", "load-more": "Load more",
"no-archived-memos": "No archived memos.", "no-archived-memos": "No archived memos.",

@ -209,6 +209,7 @@
"label": "Filtros" "label": "Filtros"
}, },
"compact-mode": "Modo compacto", "compact-mode": "Modo compacto",
"link-preview": "Vista previa de enlaces",
"links": "Enlaces", "links": "Enlaces",
"load-more": "Cargar más", "load-more": "Cargar más",
"no-archived-memos": "No hay memos archivados.", "no-archived-memos": "No hay memos archivados.",

@ -247,6 +247,7 @@
"label": "Filtrid" "label": "Filtrid"
}, },
"compact-mode": "Kompaktne režiim", "compact-mode": "Kompaktne režiim",
"link-preview": "Lingi eelvaade",
"links": "Lingid", "links": "Lingid",
"load-more": "Laadige rohkem", "load-more": "Laadige rohkem",
"no-archived-memos": "Arhiveeritud memosid pole.", "no-archived-memos": "Arhiveeritud memosid pole.",

@ -195,6 +195,7 @@
"label": "فیلترها" "label": "فیلترها"
}, },
"compact-mode": "حالت فشرده", "compact-mode": "حالت فشرده",
"link-preview": "پیش‌نمایش پیوند",
"links": "پیوندها", "links": "پیوندها",
"load-more": "بارگذاری بیشتر", "load-more": "بارگذاری بیشتر",
"no-archived-memos": "یادداشت آرشیو شده‌ای وجود ندارد.", "no-archived-memos": "یادداشت آرشیو شده‌ای وجود ندارد.",

@ -247,6 +247,7 @@
"label": "Suodattimet" "label": "Suodattimet"
}, },
"compact-mode": "Tiivis tila", "compact-mode": "Tiivis tila",
"link-preview": "Linkin esikatselu",
"links": "Linkit", "links": "Linkit",
"load-more": "Lataa lisää", "load-more": "Lataa lisää",
"no-archived-memos": "Ei arkistoituja muistioita.", "no-archived-memos": "Ei arkistoituja muistioita.",

@ -209,6 +209,7 @@
"label": "Filtres" "label": "Filtres"
}, },
"compact-mode": "Mode compact", "compact-mode": "Mode compact",
"link-preview": "Aperçu des liens",
"links": "Liens", "links": "Liens",
"load-more": "Charger plus", "load-more": "Charger plus",
"no-archived-memos": "Aucune note archivée.", "no-archived-memos": "Aucune note archivée.",

@ -209,6 +209,7 @@
"label": "Filtros" "label": "Filtros"
}, },
"compact-mode": "Modo compacto", "compact-mode": "Modo compacto",
"link-preview": "Vista previa de ligazóns",
"links": "Ligazóns", "links": "Ligazóns",
"load-more": "Cargar máis", "load-more": "Cargar máis",
"no-archived-memos": "Sen memos arquivadas.", "no-archived-memos": "Sen memos arquivadas.",

@ -195,6 +195,7 @@
"label": "फिल्टर" "label": "फिल्टर"
}, },
"compact-mode": "कॉम्पैक्ट मोड", "compact-mode": "कॉम्पैक्ट मोड",
"link-preview": "लिंक पूर्वावलोकन",
"links": "लिंक", "links": "लिंक",
"load-more": "और लोड करें", "load-more": "और लोड करें",
"no-archived-memos": "कोई संग्रहीत मेमो नहीं।", "no-archived-memos": "कोई संग्रहीत मेमो नहीं।",

@ -209,6 +209,7 @@
"label": "Filteri" "label": "Filteri"
}, },
"compact-mode": "Kompaktni način", "compact-mode": "Kompaktni način",
"link-preview": "Pregled poveznice",
"links": "Linkovi", "links": "Linkovi",
"load-more": "Učitaj više", "load-more": "Učitaj više",
"no-archived-memos": "Nema arhiviranih memoa.", "no-archived-memos": "Nema arhiviranih memoa.",

@ -209,6 +209,7 @@
"label": "Szűrők" "label": "Szűrők"
}, },
"compact-mode": "Kompakt mód", "compact-mode": "Kompakt mód",
"link-preview": "Hivatkozás előnézete",
"links": "Hivatkozások", "links": "Hivatkozások",
"load-more": "Több betöltése", "load-more": "Több betöltése",
"no-archived-memos": "Nincsenek archivált jegyzetek.", "no-archived-memos": "Nincsenek archivált jegyzetek.",

@ -195,6 +195,7 @@
"label": "Filter" "label": "Filter"
}, },
"compact-mode": "Mode ringkas", "compact-mode": "Mode ringkas",
"link-preview": "Pratinjau tautan",
"links": "Tautan", "links": "Tautan",
"load-more": "Muat lebih banyak", "load-more": "Muat lebih banyak",
"no-archived-memos": "Tidak ada memo yang diarsipkan.", "no-archived-memos": "Tidak ada memo yang diarsipkan.",

@ -209,6 +209,7 @@
"label": "Filtri" "label": "Filtri"
}, },
"compact-mode": "Modalità compatta", "compact-mode": "Modalità compatta",
"link-preview": "Anteprima dei link",
"links": "Link", "links": "Link",
"load-more": "Carica altro", "load-more": "Carica altro",
"no-archived-memos": "Nessun memo archiviato.", "no-archived-memos": "Nessun memo archiviato.",

@ -227,6 +227,7 @@
"label": "フィルター" "label": "フィルター"
}, },
"compact-mode": "コンパクト表示", "compact-mode": "コンパクト表示",
"link-preview": "リンクプレビュー",
"links": "リンク", "links": "リンク",
"load-more": "さらに読み込む", "load-more": "さらに読み込む",
"no-archived-memos": "アーカイブされたメモはありません。", "no-archived-memos": "アーカイブされたメモはありません。",

@ -209,6 +209,7 @@
"label": "ფილტრები" "label": "ფილტრები"
}, },
"compact-mode": "კომპაქტური რეჟიმი", "compact-mode": "კომპაქტური რეჟიმი",
"link-preview": "ბმულის გადახედვა",
"links": "ლინკები", "links": "ლინკები",
"load-more": "მეტის ჩატვირთვა", "load-more": "მეტის ჩატვირთვა",
"no-archived-memos": "დაარქივებული მემოები არ არის.", "no-archived-memos": "დაარქივებული მემოები არ არის.",

@ -195,6 +195,7 @@
"label": "필터" "label": "필터"
}, },
"compact-mode": "간략 보기", "compact-mode": "간략 보기",
"link-preview": "링크 미리보기",
"links": "링크", "links": "링크",
"load-more": "더보기", "load-more": "더보기",
"no-archived-memos": "보관처리된 메모가 없습니다.", "no-archived-memos": "보관처리된 메모가 없습니다.",

@ -247,6 +247,7 @@
"label": "Filtrai" "label": "Filtrai"
}, },
"compact-mode": "Kompaktiškas režimas", "compact-mode": "Kompaktiškas režimas",
"link-preview": "Nuorodos peržiūra",
"links": "Nuorodos", "links": "Nuorodos",
"load-more": "Įkelti daugiau", "load-more": "Įkelti daugiau",
"no-archived-memos": "Nėra archyvuotų atmintinių.", "no-archived-memos": "Nėra archyvuotų atmintinių.",

@ -247,6 +247,7 @@
"label": "Filtri" "label": "Filtri"
}, },
"compact-mode": "Kompaktais režīms", "compact-mode": "Kompaktais režīms",
"link-preview": "Saites priekšskatījums",
"links": "Saites", "links": "Saites",
"load-more": "Ielādēt vairāk", "load-more": "Ielādēt vairāk",
"no-archived-memos": "Nav arhivētu piezīmju.", "no-archived-memos": "Nav arhivētu piezīmju.",

@ -195,6 +195,7 @@
"label": "फिल्टर" "label": "फिल्टर"
}, },
"compact-mode": "संक्षिप्त मोड", "compact-mode": "संक्षिप्त मोड",
"link-preview": "दुवा पूर्वावलोकन",
"links": "लिंक्स", "links": "लिंक्स",
"load-more": "अधिक लोड करा", "load-more": "अधिक लोड करा",
"no-archived-memos": "कोणतेही संग्रहित मेमो नाहीत.", "no-archived-memos": "कोणतेही संग्रहित मेमो नाहीत.",

@ -209,6 +209,7 @@
"label": "Filtre" "label": "Filtre"
}, },
"compact-mode": "Kompakt modus", "compact-mode": "Kompakt modus",
"link-preview": "Forhåndsvisning av lenke",
"links": "Linker", "links": "Linker",
"load-more": "Last inn mer", "load-more": "Last inn mer",
"no-archived-memos": "Ingen arkiverte memoer.", "no-archived-memos": "Ingen arkiverte memoer.",

@ -209,6 +209,7 @@
"label": "Filters" "label": "Filters"
}, },
"compact-mode": "Compacte modus", "compact-mode": "Compacte modus",
"link-preview": "Linkvoorbeeld",
"links": "Links", "links": "Links",
"load-more": "Meer laden", "load-more": "Meer laden",
"no-archived-memos": "Geen gearchiveerde memos.", "no-archived-memos": "Geen gearchiveerde memos.",

@ -209,6 +209,7 @@
"label": "Filtry" "label": "Filtry"
}, },
"compact-mode": "Tryb kompaktowy", "compact-mode": "Tryb kompaktowy",
"link-preview": "Podgląd linków",
"links": "Linki", "links": "Linki",
"load-more": "Załaduj więcej", "load-more": "Załaduj więcej",
"no-archived-memos": "Brak zarchiwizowanych notatek.", "no-archived-memos": "Brak zarchiwizowanych notatek.",

@ -195,6 +195,7 @@
"label": "Filtros" "label": "Filtros"
}, },
"compact-mode": "Modo compacto", "compact-mode": "Modo compacto",
"link-preview": "Pré-visualização de links",
"links": "Links", "links": "Links",
"load-more": "Carregar mais", "load-more": "Carregar mais",
"no-archived-memos": "Nenhum memo arquivado.", "no-archived-memos": "Nenhum memo arquivado.",

@ -209,6 +209,7 @@
"label": "Filtros" "label": "Filtros"
}, },
"compact-mode": "Modo compacto", "compact-mode": "Modo compacto",
"link-preview": "Pré-visualização de hiperligações",
"links": "Links", "links": "Links",
"load-more": "Carregar mais", "load-more": "Carregar mais",
"no-archived-memos": "Não existem memos arquivados.", "no-archived-memos": "Não existem memos arquivados.",

@ -247,6 +247,7 @@
"label": "Filtre" "label": "Filtre"
}, },
"compact-mode": "Mod compact", "compact-mode": "Mod compact",
"link-preview": "Previzualizare link",
"links": "Legături", "links": "Legături",
"load-more": "Încărcați mai multe", "load-more": "Încărcați mai multe",
"no-archived-memos": "Fără memorii arhivate.", "no-archived-memos": "Fără memorii arhivate.",

@ -209,6 +209,7 @@
"label": "Фильтры" "label": "Фильтры"
}, },
"compact-mode": "Компактный режим", "compact-mode": "Компактный режим",
"link-preview": "Предпросмотр ссылок",
"links": "Ссылки", "links": "Ссылки",
"load-more": "Загрузить еще", "load-more": "Загрузить еще",
"no-archived-memos": "Нет заархивированных записей.", "no-archived-memos": "Нет заархивированных записей.",

@ -247,6 +247,7 @@
"label": "Filtre" "label": "Filtre"
}, },
"compact-mode": "Kompaktný režim", "compact-mode": "Kompaktný režim",
"link-preview": "Náhľad odkazu",
"links": "Odkazy", "links": "Odkazy",
"load-more": "Načítať viac", "load-more": "Načítať viac",
"no-archived-memos": "Žiadne archivované poznámky.", "no-archived-memos": "Žiadne archivované poznámky.",

@ -209,6 +209,7 @@
"label": "Filtri" "label": "Filtri"
}, },
"compact-mode": "Strnjeni način", "compact-mode": "Strnjeni način",
"link-preview": "Predogled povezave",
"links": "Povezave", "links": "Povezave",
"load-more": "Naloži več", "load-more": "Naloži več",
"no-archived-memos": "Ni arhiviranih beležk.", "no-archived-memos": "Ni arhiviranih beležk.",

@ -247,6 +247,7 @@
"label": "Филтери" "label": "Филтери"
}, },
"compact-mode": "Компактни режим", "compact-mode": "Компактни режим",
"link-preview": "Преглед везе",
"links": "Линкови", "links": "Линкови",
"load-more": "Учитај више", "load-more": "Учитај више",
"no-archived-memos": "Нема архивираних белешки.", "no-archived-memos": "Нема архивираних белешки.",

@ -209,6 +209,7 @@
"label": "Filter" "label": "Filter"
}, },
"compact-mode": "Kompakt läge", "compact-mode": "Kompakt läge",
"link-preview": "Förhandsvisning av länk",
"links": "Länkar", "links": "Länkar",
"load-more": "Ladda mer", "load-more": "Ladda mer",
"no-archived-memos": "Inga arkiverade anteckningar.", "no-archived-memos": "Inga arkiverade anteckningar.",

@ -195,6 +195,7 @@
"label": "ตัวกรอง" "label": "ตัวกรอง"
}, },
"compact-mode": "โหมดกะทัดรัด", "compact-mode": "โหมดกะทัดรัด",
"link-preview": "ตัวอย่างลิงก์",
"links": "ลิงก์", "links": "ลิงก์",
"load-more": "โหลดเพิ่มเติม", "load-more": "โหลดเพิ่มเติม",
"no-archived-memos": "ไม่มีบันทึกช่วยจำที่ถูกเก็บถาวร", "no-archived-memos": "ไม่มีบันทึกช่วยจำที่ถูกเก็บถาวร",

@ -209,6 +209,7 @@
"label": "Filtreler" "label": "Filtreler"
}, },
"compact-mode": "Kompakt mod", "compact-mode": "Kompakt mod",
"link-preview": "Bağlantı önizlemesi",
"links": "Bağlantılar", "links": "Bağlantılar",
"load-more": "Daha fazla yükle", "load-more": "Daha fazla yükle",
"no-archived-memos": "Arşivlenmiş not yok.", "no-archived-memos": "Arşivlenmiş not yok.",

@ -209,6 +209,7 @@
"label": "Фільтри" "label": "Фільтри"
}, },
"compact-mode": "Компактний режим", "compact-mode": "Компактний режим",
"link-preview": "Попередній перегляд посилань",
"links": "Посилання", "links": "Посилання",
"load-more": "Завантажити більше", "load-more": "Завантажити більше",
"no-archived-memos": "Немає архівованих нотаток.", "no-archived-memos": "Немає архівованих нотаток.",

@ -195,6 +195,7 @@
"label": "Bộ lọc" "label": "Bộ lọc"
}, },
"compact-mode": "Chế độ thu gọn", "compact-mode": "Chế độ thu gọn",
"link-preview": "Xem trước liên kết",
"links": "Liên kết", "links": "Liên kết",
"load-more": "Tải thêm", "load-more": "Tải thêm",
"no-archived-memos": "Không có ghi chú nào được lưu trữ.", "no-archived-memos": "Không có ghi chú nào được lưu trữ.",

@ -211,6 +211,7 @@
"label": "过滤器" "label": "过滤器"
}, },
"compact-mode": "紧凑模式", "compact-mode": "紧凑模式",
"link-preview": "链接预览",
"links": "链接", "links": "链接",
"load-more": "加载更多", "load-more": "加载更多",
"no-archived-memos": "没有已归档备忘录。", "no-archived-memos": "没有已归档备忘录。",

@ -247,6 +247,7 @@
"label": "過濾器" "label": "過濾器"
}, },
"compact-mode": "精簡模式", "compact-mode": "精簡模式",
"link-preview": "連結預覽",
"links": "連結", "links": "連結",
"load-more": "載入更多", "load-more": "載入更多",
"no-archived-memos": "無已封存的備忘錄", "no-archived-memos": "無已封存的備忘錄",

@ -0,0 +1,57 @@
import { render, screen } from "@testing-library/react";
import type { Element } from "hast";
import { describe, expect, it, vi } from "vitest";
import { Paragraph } from "@/components/MemoContent/markdown/Paragraph";
const viewState = vi.hoisted(() => ({ linkPreview: true }));
// Paragraph reads the global preference via this hook.
vi.mock("@/contexts/ViewContext", () => ({
useLinkPreviewEnabled: () => viewState.linkPreview,
}));
// The link-preview card fetches metadata via this hook; stub it so the card would
// render whenever it is allowed to.
vi.mock("@/hooks/useMemoQueries", () => ({
useLinkMetadata: () => ({
data: { url: "https://example.com", title: "Example Title", description: "An example site", image: "" },
isSuccess: true,
}),
}));
// A single bare-link paragraph node, the shape that triggers a preview card.
const singleLinkNode = {
type: "element",
tagName: "p",
properties: {},
children: [
{
type: "element",
tagName: "a",
properties: { href: "https://example.com" },
children: [{ type: "text", value: "https://example.com" }],
},
],
} as unknown as Element;
const renderParagraph = () =>
render(
<Paragraph node={singleLinkNode}>
<a href="https://example.com">https://example.com</a>
</Paragraph>,
);
describe("<Paragraph /> link preview gating", () => {
it("renders the link preview card when the setting is enabled (default)", () => {
viewState.linkPreview = true;
renderParagraph();
expect(screen.getByText("Example Title")).toBeInTheDocument();
});
it("renders the plain link without a card when the setting is disabled", () => {
viewState.linkPreview = false;
renderParagraph();
expect(screen.queryByText("Example Title")).not.toBeInTheDocument();
expect(screen.getByText("https://example.com")).toBeInTheDocument();
});
});
Loading…
Cancel
Save