diff --git a/web/src/components/MemoContent/markdown/Paragraph.tsx b/web/src/components/MemoContent/markdown/Paragraph.tsx index a64ee3c94..f0ffeee7f 100644 --- a/web/src/components/MemoContent/markdown/Paragraph.tsx +++ b/web/src/components/MemoContent/markdown/Paragraph.tsx @@ -1,4 +1,5 @@ import type { Element } from "hast"; +import { useLinkPreviewEnabled } from "@/contexts/ViewContext"; import { markdownStyles } from "@/lib/markdownStyles"; import { cn } from "@/lib/utils"; import LinkMetadataCard from "../LinkMetadataCard"; @@ -46,7 +47,8 @@ export function getSingleLinkHref(node?: Element): string | undefined { export const Paragraph = ({ children, className, node, ...props }: ParagraphProps) => { const { blockDepth } = useMarkdownRenderContext(); - const href = blockDepth === 0 ? getSingleLinkHref(node) : undefined; + const linkPreviewEnabled = useLinkPreviewEnabled(); + const href = blockDepth === 0 && linkPreviewEnabled ? getSingleLinkHref(node) : undefined; const paragraph = (

{children} diff --git a/web/src/components/MemoDisplaySettingMenu.tsx b/web/src/components/MemoDisplaySettingMenu.tsx index 806179442..a4599c6cf 100644 --- a/web/src/components/MemoDisplaySettingMenu.tsx +++ b/web/src/components/MemoDisplaySettingMenu.tsx @@ -12,8 +12,8 @@ interface Props { function MemoDisplaySettingMenu({ className }: Props) { const t = useTranslate(); - const { orderByTimeAsc, timeBasis, compactMode, setTimeBasis, toggleSortOrder, setCompactMode } = useView(); - const isApplying = orderByTimeAsc !== false || timeBasis !== "create_time" || compactMode; + const { orderByTimeAsc, timeBasis, compactMode, linkPreview, setTimeBasis, toggleSortOrder, setCompactMode, setLinkPreview } = useView(); + const isApplying = orderByTimeAsc !== false || timeBasis !== "create_time" || compactMode || !linkPreview; return ( @@ -57,6 +57,10 @@ function MemoDisplaySettingMenu({ className }: Props) { {t("memo.compact-mode")} +

+ {t("memo.link-preview")} + +
diff --git a/web/src/contexts/ViewContext.tsx b/web/src/contexts/ViewContext.tsx index 89366b109..90857c391 100644 --- a/web/src/contexts/ViewContext.tsx +++ b/web/src/contexts/ViewContext.tsx @@ -7,21 +7,26 @@ interface ViewState { timeBasis?: MemoTimeBasis; sortTimeField?: MemoTimeBasis; compactMode: boolean; + linkPreview: boolean; } interface ViewContextValue { orderByTimeAsc: boolean; timeBasis: MemoTimeBasis; compactMode: boolean; + linkPreview: boolean; toggleSortOrder: () => void; setTimeBasis: (field: MemoTimeBasis) => void; setCompactMode: (value: boolean) => void; + setLinkPreview: (value: boolean) => void; } const ViewContext = createContext(null); const LOCAL_STORAGE_KEY = "memos-view-setting"; +const DEFAULT_VIEW_STATE: ViewState = { orderByTimeAsc: false, compactMode: false, linkPreview: true }; + export function ViewProvider({ children }: { children: ReactNode }) { const getInitialState = (): ViewState => { try { @@ -31,15 +36,16 @@ export function ViewProvider({ children }: { children: ReactNode }) { const cachedTimeBasis = data.timeBasis ?? data.sortTimeField; const timeBasis = cachedTimeBasis === "create_time" || cachedTimeBasis === "update_time" ? cachedTimeBasis : undefined; return { - orderByTimeAsc: Boolean(data.orderByTimeAsc ?? false), + orderByTimeAsc: Boolean(data.orderByTimeAsc ?? DEFAULT_VIEW_STATE.orderByTimeAsc), timeBasis, - compactMode: Boolean(data.compactMode ?? false), + compactMode: Boolean(data.compactMode ?? DEFAULT_VIEW_STATE.compactMode), + linkPreview: Boolean(data.linkPreview ?? DEFAULT_VIEW_STATE.linkPreview), }; } } catch (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); @@ -53,29 +59,18 @@ export function ViewProvider({ children }: { children: ReactNode }) { } }; - const toggleSortOrder = () => { - setViewState((prev) => { - const newState = { ...prev, orderByTimeAsc: !prev.orderByTimeAsc }; - persistToStorage(newState); - return newState; - }); - }; - - const setTimeBasis = (field: MemoTimeBasis) => { + const updateState = (patch: Partial | ((prev: ViewState) => Partial)) => { setViewState((prev) => { - const newState = { ...prev, timeBasis: field }; + const newState = { ...prev, ...(typeof patch === "function" ? patch(prev) : patch) }; persistToStorage(newState); return newState; }); }; - const setCompactMode = (value: boolean) => { - setViewState((prev) => { - const newState = { ...prev, compactMode: value }; - persistToStorage(newState); - return newState; - }); - }; + const toggleSortOrder = () => updateState((prev) => ({ orderByTimeAsc: !prev.orderByTimeAsc })); + const setTimeBasis = (field: MemoTimeBasis) => updateState({ timeBasis: field }); + const setCompactMode = (value: boolean) => updateState({ compactMode: value }); + const setLinkPreview = (value: boolean) => updateState({ linkPreview: value }); return ( {children} @@ -100,3 +97,10 @@ export function useView() { } 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; +} diff --git a/web/src/locales/ar.json b/web/src/locales/ar.json index e693aa67b..bb52ad0b7 100644 --- a/web/src/locales/ar.json +++ b/web/src/locales/ar.json @@ -195,6 +195,7 @@ "label": "المرشحات" }, "compact-mode": "الوضع المضغوط", + "link-preview": "معاينة الرابط", "links": "روابط", "load-more": "تحميل المزيد", "no-archived-memos": "لا يوجد مذكرات مؤرشفة", diff --git a/web/src/locales/bg.json b/web/src/locales/bg.json index 0197cbfe2..a25b735e9 100644 --- a/web/src/locales/bg.json +++ b/web/src/locales/bg.json @@ -247,6 +247,7 @@ "label": "Филтри" }, "compact-mode": "Компактен режим", + "link-preview": "Преглед на връзка", "links": "Връзки", "load-more": "Заредете повече", "no-archived-memos": "Няма архивирани бележки.", diff --git a/web/src/locales/ca.json b/web/src/locales/ca.json index 2756ff5d2..bd443afc2 100644 --- a/web/src/locales/ca.json +++ b/web/src/locales/ca.json @@ -209,6 +209,7 @@ "label": "Filtres" }, "compact-mode": "Mode compacte", + "link-preview": "Previsualització d'enllaços", "links": "Enllaços", "load-more": "Carrega més", "no-archived-memos": "No hi ha notes arxivades.", diff --git a/web/src/locales/cs.json b/web/src/locales/cs.json index 8a52ef81c..8aa0d643e 100644 --- a/web/src/locales/cs.json +++ b/web/src/locales/cs.json @@ -209,6 +209,7 @@ "label": "Filtry" }, "compact-mode": "Kompaktní režim", + "link-preview": "Náhled odkazu", "links": "Odkazy", "load-more": "Načíst více", "no-archived-memos": "Žádné archivované poznámky.", diff --git a/web/src/locales/da.json b/web/src/locales/da.json index 0cfb44059..3b00b95fd 100644 --- a/web/src/locales/da.json +++ b/web/src/locales/da.json @@ -247,6 +247,7 @@ "label": "Filtre" }, "compact-mode": "Kompakt tilstand", + "link-preview": "Linkforhåndsvisning", "links": "Links", "load-more": "Indlæs mere", "no-archived-memos": "Ingen arkiverede notater.", diff --git a/web/src/locales/de.json b/web/src/locales/de.json index 272fb22b0..3e7cf72dd 100644 --- a/web/src/locales/de.json +++ b/web/src/locales/de.json @@ -209,6 +209,7 @@ "label": "Filter" }, "compact-mode": "Kompaktmodus", + "link-preview": "Linkvorschau", "links": "Links", "load-more": "Mehr laden", "no-archived-memos": "Keine archivierten Notizen.", diff --git a/web/src/locales/el.json b/web/src/locales/el.json index 29f827ed0..cfb0b408a 100644 --- a/web/src/locales/el.json +++ b/web/src/locales/el.json @@ -247,6 +247,7 @@ "label": "Φίλτρα" }, "compact-mode": "Συμπαγής λειτουργία", + "link-preview": "Προεπισκόπηση συνδέσμου", "links": "Εδαφος διά παιγνίδι γκολφ", "load-more": "Φορτώστε περισσότερα", "no-archived-memos": "Δεν υπάρχουν αρχειοθετημένα σημειώματα.", diff --git a/web/src/locales/en-GB.json b/web/src/locales/en-GB.json index 2c8e07459..5b7aa1152 100644 --- a/web/src/locales/en-GB.json +++ b/web/src/locales/en-GB.json @@ -209,6 +209,7 @@ "label": "Filters" }, "compact-mode": "Compact mode", + "link-preview": "Link preview", "links": "Links", "load-more": "Load more", "no-archived-memos": "No archived memos.", diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 16adf1874..86903f307 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -265,6 +265,7 @@ "label": "Filters" }, "compact-mode": "Compact mode", + "link-preview": "Link preview", "links": "Links", "load-more": "Load more", "no-archived-memos": "No archived memos.", diff --git a/web/src/locales/es.json b/web/src/locales/es.json index 200237b6c..afb66f412 100644 --- a/web/src/locales/es.json +++ b/web/src/locales/es.json @@ -209,6 +209,7 @@ "label": "Filtros" }, "compact-mode": "Modo compacto", + "link-preview": "Vista previa de enlaces", "links": "Enlaces", "load-more": "Cargar más", "no-archived-memos": "No hay memos archivados.", diff --git a/web/src/locales/et.json b/web/src/locales/et.json index bf5d498aa..4f67a3e54 100644 --- a/web/src/locales/et.json +++ b/web/src/locales/et.json @@ -247,6 +247,7 @@ "label": "Filtrid" }, "compact-mode": "Kompaktne režiim", + "link-preview": "Lingi eelvaade", "links": "Lingid", "load-more": "Laadige rohkem", "no-archived-memos": "Arhiveeritud memosid pole.", diff --git a/web/src/locales/fa.json b/web/src/locales/fa.json index abb4ba98e..0009a0a97 100644 --- a/web/src/locales/fa.json +++ b/web/src/locales/fa.json @@ -195,6 +195,7 @@ "label": "فیلترها" }, "compact-mode": "حالت فشرده", + "link-preview": "پیش‌نمایش پیوند", "links": "پیوندها", "load-more": "بارگذاری بیشتر", "no-archived-memos": "یادداشت آرشیو شده‌ای وجود ندارد.", diff --git a/web/src/locales/fi.json b/web/src/locales/fi.json index 355d627e0..cf8a0559d 100644 --- a/web/src/locales/fi.json +++ b/web/src/locales/fi.json @@ -247,6 +247,7 @@ "label": "Suodattimet" }, "compact-mode": "Tiivis tila", + "link-preview": "Linkin esikatselu", "links": "Linkit", "load-more": "Lataa lisää", "no-archived-memos": "Ei arkistoituja muistioita.", diff --git a/web/src/locales/fr.json b/web/src/locales/fr.json index 0e2f5b2e5..1f3ff3c1a 100644 --- a/web/src/locales/fr.json +++ b/web/src/locales/fr.json @@ -209,6 +209,7 @@ "label": "Filtres" }, "compact-mode": "Mode compact", + "link-preview": "Aperçu des liens", "links": "Liens", "load-more": "Charger plus", "no-archived-memos": "Aucune note archivée.", diff --git a/web/src/locales/gl.json b/web/src/locales/gl.json index def58081c..81d0d1923 100644 --- a/web/src/locales/gl.json +++ b/web/src/locales/gl.json @@ -209,6 +209,7 @@ "label": "Filtros" }, "compact-mode": "Modo compacto", + "link-preview": "Vista previa de ligazóns", "links": "Ligazóns", "load-more": "Cargar máis", "no-archived-memos": "Sen memos arquivadas.", diff --git a/web/src/locales/hi.json b/web/src/locales/hi.json index 27810ab29..f67589563 100644 --- a/web/src/locales/hi.json +++ b/web/src/locales/hi.json @@ -195,6 +195,7 @@ "label": "फिल्टर" }, "compact-mode": "कॉम्पैक्ट मोड", + "link-preview": "लिंक पूर्वावलोकन", "links": "लिंक", "load-more": "और लोड करें", "no-archived-memos": "कोई संग्रहीत मेमो नहीं।", diff --git a/web/src/locales/hr.json b/web/src/locales/hr.json index b12ab10a3..540e33557 100644 --- a/web/src/locales/hr.json +++ b/web/src/locales/hr.json @@ -209,6 +209,7 @@ "label": "Filteri" }, "compact-mode": "Kompaktni način", + "link-preview": "Pregled poveznice", "links": "Linkovi", "load-more": "Učitaj više", "no-archived-memos": "Nema arhiviranih memoa.", diff --git a/web/src/locales/hu.json b/web/src/locales/hu.json index 15065f9b9..5af02e5e5 100644 --- a/web/src/locales/hu.json +++ b/web/src/locales/hu.json @@ -209,6 +209,7 @@ "label": "Szűrők" }, "compact-mode": "Kompakt mód", + "link-preview": "Hivatkozás előnézete", "links": "Hivatkozások", "load-more": "Több betöltése", "no-archived-memos": "Nincsenek archivált jegyzetek.", diff --git a/web/src/locales/id.json b/web/src/locales/id.json index b315c0963..7fe656903 100644 --- a/web/src/locales/id.json +++ b/web/src/locales/id.json @@ -195,6 +195,7 @@ "label": "Filter" }, "compact-mode": "Mode ringkas", + "link-preview": "Pratinjau tautan", "links": "Tautan", "load-more": "Muat lebih banyak", "no-archived-memos": "Tidak ada memo yang diarsipkan.", diff --git a/web/src/locales/it.json b/web/src/locales/it.json index f1cd046d6..26d65e087 100644 --- a/web/src/locales/it.json +++ b/web/src/locales/it.json @@ -209,6 +209,7 @@ "label": "Filtri" }, "compact-mode": "Modalità compatta", + "link-preview": "Anteprima dei link", "links": "Link", "load-more": "Carica altro", "no-archived-memos": "Nessun memo archiviato.", diff --git a/web/src/locales/ja.json b/web/src/locales/ja.json index f9e40c895..a243c62d0 100644 --- a/web/src/locales/ja.json +++ b/web/src/locales/ja.json @@ -227,6 +227,7 @@ "label": "フィルター" }, "compact-mode": "コンパクト表示", + "link-preview": "リンクプレビュー", "links": "リンク", "load-more": "さらに読み込む", "no-archived-memos": "アーカイブされたメモはありません。", diff --git a/web/src/locales/ka-GE.json b/web/src/locales/ka-GE.json index 1173e6589..0e26588b8 100644 --- a/web/src/locales/ka-GE.json +++ b/web/src/locales/ka-GE.json @@ -209,6 +209,7 @@ "label": "ფილტრები" }, "compact-mode": "კომპაქტური რეჟიმი", + "link-preview": "ბმულის გადახედვა", "links": "ლინკები", "load-more": "მეტის ჩატვირთვა", "no-archived-memos": "დაარქივებული მემოები არ არის.", diff --git a/web/src/locales/ko.json b/web/src/locales/ko.json index 12da053cb..22317e0f1 100644 --- a/web/src/locales/ko.json +++ b/web/src/locales/ko.json @@ -195,6 +195,7 @@ "label": "필터" }, "compact-mode": "간략 보기", + "link-preview": "링크 미리보기", "links": "링크", "load-more": "더보기", "no-archived-memos": "보관처리된 메모가 없습니다.", diff --git a/web/src/locales/lt.json b/web/src/locales/lt.json index 8993a58e0..9004e528f 100644 --- a/web/src/locales/lt.json +++ b/web/src/locales/lt.json @@ -247,6 +247,7 @@ "label": "Filtrai" }, "compact-mode": "Kompaktiškas režimas", + "link-preview": "Nuorodos peržiūra", "links": "Nuorodos", "load-more": "Įkelti daugiau", "no-archived-memos": "Nėra archyvuotų atmintinių.", diff --git a/web/src/locales/lv.json b/web/src/locales/lv.json index af417861f..743024fd2 100644 --- a/web/src/locales/lv.json +++ b/web/src/locales/lv.json @@ -247,6 +247,7 @@ "label": "Filtri" }, "compact-mode": "Kompaktais režīms", + "link-preview": "Saites priekšskatījums", "links": "Saites", "load-more": "Ielādēt vairāk", "no-archived-memos": "Nav arhivētu piezīmju.", diff --git a/web/src/locales/mr.json b/web/src/locales/mr.json index 608feacea..bb95b667b 100644 --- a/web/src/locales/mr.json +++ b/web/src/locales/mr.json @@ -195,6 +195,7 @@ "label": "फिल्टर" }, "compact-mode": "संक्षिप्त मोड", + "link-preview": "दुवा पूर्वावलोकन", "links": "लिंक्स", "load-more": "अधिक लोड करा", "no-archived-memos": "कोणतेही संग्रहित मेमो नाहीत.", diff --git a/web/src/locales/nb.json b/web/src/locales/nb.json index bda70ba5b..b1806254d 100644 --- a/web/src/locales/nb.json +++ b/web/src/locales/nb.json @@ -209,6 +209,7 @@ "label": "Filtre" }, "compact-mode": "Kompakt modus", + "link-preview": "Forhåndsvisning av lenke", "links": "Linker", "load-more": "Last inn mer", "no-archived-memos": "Ingen arkiverte memoer.", diff --git a/web/src/locales/nl.json b/web/src/locales/nl.json index 61e99ee41..8a4f575f7 100644 --- a/web/src/locales/nl.json +++ b/web/src/locales/nl.json @@ -209,6 +209,7 @@ "label": "Filters" }, "compact-mode": "Compacte modus", + "link-preview": "Linkvoorbeeld", "links": "Links", "load-more": "Meer laden", "no-archived-memos": "Geen gearchiveerde memos.", diff --git a/web/src/locales/pl.json b/web/src/locales/pl.json index 6ce09b79b..3503da277 100644 --- a/web/src/locales/pl.json +++ b/web/src/locales/pl.json @@ -209,6 +209,7 @@ "label": "Filtry" }, "compact-mode": "Tryb kompaktowy", + "link-preview": "Podgląd linków", "links": "Linki", "load-more": "Załaduj więcej", "no-archived-memos": "Brak zarchiwizowanych notatek.", diff --git a/web/src/locales/pt-BR.json b/web/src/locales/pt-BR.json index ba0ae9396..a0460c806 100644 --- a/web/src/locales/pt-BR.json +++ b/web/src/locales/pt-BR.json @@ -195,6 +195,7 @@ "label": "Filtros" }, "compact-mode": "Modo compacto", + "link-preview": "Pré-visualização de links", "links": "Links", "load-more": "Carregar mais", "no-archived-memos": "Nenhum memo arquivado.", diff --git a/web/src/locales/pt-PT.json b/web/src/locales/pt-PT.json index b1dcc5843..908fc9f25 100644 --- a/web/src/locales/pt-PT.json +++ b/web/src/locales/pt-PT.json @@ -209,6 +209,7 @@ "label": "Filtros" }, "compact-mode": "Modo compacto", + "link-preview": "Pré-visualização de hiperligações", "links": "Links", "load-more": "Carregar mais", "no-archived-memos": "Não existem memos arquivados.", diff --git a/web/src/locales/ro.json b/web/src/locales/ro.json index 6e5223eea..3bb264345 100644 --- a/web/src/locales/ro.json +++ b/web/src/locales/ro.json @@ -247,6 +247,7 @@ "label": "Filtre" }, "compact-mode": "Mod compact", + "link-preview": "Previzualizare link", "links": "Legături", "load-more": "Încărcați mai multe", "no-archived-memos": "Fără memorii arhivate.", diff --git a/web/src/locales/ru.json b/web/src/locales/ru.json index 0786fb598..a98759803 100644 --- a/web/src/locales/ru.json +++ b/web/src/locales/ru.json @@ -209,6 +209,7 @@ "label": "Фильтры" }, "compact-mode": "Компактный режим", + "link-preview": "Предпросмотр ссылок", "links": "Ссылки", "load-more": "Загрузить еще", "no-archived-memos": "Нет заархивированных записей.", diff --git a/web/src/locales/sk.json b/web/src/locales/sk.json index c2523cb2e..36163314c 100644 --- a/web/src/locales/sk.json +++ b/web/src/locales/sk.json @@ -247,6 +247,7 @@ "label": "Filtre" }, "compact-mode": "Kompaktný režim", + "link-preview": "Náhľad odkazu", "links": "Odkazy", "load-more": "Načítať viac", "no-archived-memos": "Žiadne archivované poznámky.", diff --git a/web/src/locales/sl.json b/web/src/locales/sl.json index dd73a5a93..0c8c4e0ef 100644 --- a/web/src/locales/sl.json +++ b/web/src/locales/sl.json @@ -209,6 +209,7 @@ "label": "Filtri" }, "compact-mode": "Strnjeni način", + "link-preview": "Predogled povezave", "links": "Povezave", "load-more": "Naloži več", "no-archived-memos": "Ni arhiviranih beležk.", diff --git a/web/src/locales/sr.json b/web/src/locales/sr.json index 4700cc3d4..63ad8c4c9 100644 --- a/web/src/locales/sr.json +++ b/web/src/locales/sr.json @@ -247,6 +247,7 @@ "label": "Филтери" }, "compact-mode": "Компактни режим", + "link-preview": "Преглед везе", "links": "Линкови", "load-more": "Учитај више", "no-archived-memos": "Нема архивираних белешки.", diff --git a/web/src/locales/sv.json b/web/src/locales/sv.json index b1d7dfbdb..eb16253b8 100644 --- a/web/src/locales/sv.json +++ b/web/src/locales/sv.json @@ -209,6 +209,7 @@ "label": "Filter" }, "compact-mode": "Kompakt läge", + "link-preview": "Förhandsvisning av länk", "links": "Länkar", "load-more": "Ladda mer", "no-archived-memos": "Inga arkiverade anteckningar.", diff --git a/web/src/locales/th.json b/web/src/locales/th.json index 5c63c974c..5765628c0 100644 --- a/web/src/locales/th.json +++ b/web/src/locales/th.json @@ -195,6 +195,7 @@ "label": "ตัวกรอง" }, "compact-mode": "โหมดกะทัดรัด", + "link-preview": "ตัวอย่างลิงก์", "links": "ลิงก์", "load-more": "โหลดเพิ่มเติม", "no-archived-memos": "ไม่มีบันทึกช่วยจำที่ถูกเก็บถาวร", diff --git a/web/src/locales/tr.json b/web/src/locales/tr.json index ed9edb035..6111103cc 100644 --- a/web/src/locales/tr.json +++ b/web/src/locales/tr.json @@ -209,6 +209,7 @@ "label": "Filtreler" }, "compact-mode": "Kompakt mod", + "link-preview": "Bağlantı önizlemesi", "links": "Bağlantılar", "load-more": "Daha fazla yükle", "no-archived-memos": "Arşivlenmiş not yok.", diff --git a/web/src/locales/uk.json b/web/src/locales/uk.json index a7020ae61..ab6d92ed1 100644 --- a/web/src/locales/uk.json +++ b/web/src/locales/uk.json @@ -209,6 +209,7 @@ "label": "Фільтри" }, "compact-mode": "Компактний режим", + "link-preview": "Попередній перегляд посилань", "links": "Посилання", "load-more": "Завантажити більше", "no-archived-memos": "Немає архівованих нотаток.", diff --git a/web/src/locales/vi.json b/web/src/locales/vi.json index c096a7969..a15f91d94 100644 --- a/web/src/locales/vi.json +++ b/web/src/locales/vi.json @@ -195,6 +195,7 @@ "label": "Bộ lọc" }, "compact-mode": "Chế độ thu gọn", + "link-preview": "Xem trước liên kết", "links": "Liên kết", "load-more": "Tải thêm", "no-archived-memos": "Không có ghi chú nào được lưu trữ.", diff --git a/web/src/locales/zh-Hans.json b/web/src/locales/zh-Hans.json index 6f3bd5266..127264a74 100644 --- a/web/src/locales/zh-Hans.json +++ b/web/src/locales/zh-Hans.json @@ -211,6 +211,7 @@ "label": "过滤器" }, "compact-mode": "紧凑模式", + "link-preview": "链接预览", "links": "链接", "load-more": "加载更多", "no-archived-memos": "没有已归档备忘录。", diff --git a/web/src/locales/zh-Hant.json b/web/src/locales/zh-Hant.json index e80c39141..34f0b29e5 100644 --- a/web/src/locales/zh-Hant.json +++ b/web/src/locales/zh-Hant.json @@ -247,6 +247,7 @@ "label": "過濾器" }, "compact-mode": "精簡模式", + "link-preview": "連結預覽", "links": "連結", "load-more": "載入更多", "no-archived-memos": "無已封存的備忘錄", diff --git a/web/tests/memo-content-link-preview.test.tsx b/web/tests/memo-content-link-preview.test.tsx new file mode 100644 index 000000000..5ac028971 --- /dev/null +++ b/web/tests/memo-content-link-preview.test.tsx @@ -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( + + https://example.com + , + ); + +describe(" 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(); + }); +});