From eb28e7eb463624cf9262631d4badf631a3bff531 Mon Sep 17 00:00:00 2001 From: ocavue Date: Thu, 18 Jun 2026 23:45:50 +1000 Subject: [PATCH 1/5] feat(website): persist editor mode in sessionStorage --- website/src/app.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/website/src/app.tsx b/website/src/app.tsx index 10f49e1..a514248 100644 --- a/website/src/app.tsx +++ b/website/src/app.tsx @@ -1,5 +1,5 @@ import { MeowdownEditor, type EditorMode } from '@meowdown/react' -import { type CSSProperties, useLayoutEffect, useState } from 'react' +import { type CSSProperties, useEffect, useLayoutEffect, useState } from 'react' import { uploadFile } from './upload-file.ts' @@ -48,6 +48,17 @@ const MODES: ModeOption[] = [ }, ] +const MODE_STORAGE_KEY = 'meowdown:mode' + +function isEditorMode(value: string | null): value is EditorMode { + return value != null && MODES.some((option) => option.value === value) +} + +function readStoredMode(): EditorMode { + const stored = sessionStorage.getItem(MODE_STORAGE_KEY) + return isEditorMode(stored) ? stored : 'focus' +} + const INITIAL_CONTENT = ` # Welcome to Meowdown @@ -199,9 +210,13 @@ function Brand() { } export function App() { - const [mode, setMode] = useState('focus') + const [mode, setMode] = useState(readStoredMode) const activeMode = MODES.find((option) => option.value === mode) ?? MODES[0] + useEffect(() => { + sessionStorage.setItem(MODE_STORAGE_KEY, mode) + }, [mode]) + return (
From 7b895acf9c2ebd34e4b9e809aa16ae277bfdc505 Mon Sep 17 00:00:00 2001 From: ocavue Date: Thu, 18 Jun 2026 23:49:51 +1000 Subject: [PATCH 2/5] refactor --- website/src/app.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/website/src/app.tsx b/website/src/app.tsx index a514248..f2a79ed 100644 --- a/website/src/app.tsx +++ b/website/src/app.tsx @@ -50,15 +50,24 @@ const MODES: ModeOption[] = [ const MODE_STORAGE_KEY = 'meowdown:mode' -function isEditorMode(value: string | null): value is EditorMode { - return value != null && MODES.some((option) => option.value === value) -} function readStoredMode(): EditorMode { const stored = sessionStorage.getItem(MODE_STORAGE_KEY) - return isEditorMode(stored) ? stored : 'focus' + if (stored) { + for (const option of MODES) { + if (option.value === stored) { + return option.value + } + } + } + return "focus" } +function writeStoredMode(mode: EditorMode): void { + sessionStorage.setItem(MODE_STORAGE_KEY, mode) +} + + const INITIAL_CONTENT = ` # Welcome to Meowdown @@ -214,7 +223,7 @@ export function App() { const activeMode = MODES.find((option) => option.value === mode) ?? MODES[0] useEffect(() => { - sessionStorage.setItem(MODE_STORAGE_KEY, mode) + writeStoredMode(mode) }, [mode]) return ( From 046fb9c5d72bbebe6d4acda38ba8b8c6c772eed8 Mon Sep 17 00:00:00 2001 From: ocavue Date: Fri, 19 Jun 2026 00:00:00 +1000 Subject: [PATCH 3/5] refactor(website): extract useEditorMode hook --- website/src/app.tsx | 61 +++------------------------------- website/src/use-editor-mode.ts | 61 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 57 deletions(-) create mode 100644 website/src/use-editor-mode.ts diff --git a/website/src/app.tsx b/website/src/app.tsx index f2a79ed..1ab5bcf 100644 --- a/website/src/app.tsx +++ b/website/src/app.tsx @@ -1,7 +1,8 @@ -import { MeowdownEditor, type EditorMode } from '@meowdown/react' -import { type CSSProperties, useEffect, useLayoutEffect, useState } from 'react' +import { MeowdownEditor } from '@meowdown/react' +import { type CSSProperties, useLayoutEffect, useState } from 'react' import { uploadFile } from './upload-file.ts' +import { MODES, useEditorMode } from './use-editor-mode.ts' // Confirm, then open the target in a new tab. Shared by the link and image // click handlers below. @@ -19,55 +20,6 @@ function handleImageClick({ src }: { src: string }): void { confirmAndOpen('this image', src) } -interface ModeOption { - value: EditorMode - label: string - description: string -} - -const MODES: ModeOption[] = [ - { - value: 'focus', - label: 'Focus', - description: 'Syntax stays hidden and peeks out only where your cursor rests.', - }, - { - value: 'show', - label: 'Show', - description: 'Every Markdown character stays visible, dimmed in soft grey.', - }, - { - value: 'hide', - label: 'Hide', - description: 'Markdown characters disappear for a clean, fully rendered view.', - }, - { - value: 'source', - label: 'Source', - description: 'Raw Markdown with syntax highlighting, like an IDE.', - }, -] - -const MODE_STORAGE_KEY = 'meowdown:mode' - - -function readStoredMode(): EditorMode { - const stored = sessionStorage.getItem(MODE_STORAGE_KEY) - if (stored) { - for (const option of MODES) { - if (option.value === stored) { - return option.value - } - } - } - return "focus" -} - -function writeStoredMode(mode: EditorMode): void { - sessionStorage.setItem(MODE_STORAGE_KEY, mode) -} - - const INITIAL_CONTENT = ` # Welcome to Meowdown @@ -219,12 +171,7 @@ function Brand() { } export function App() { - const [mode, setMode] = useState(readStoredMode) - const activeMode = MODES.find((option) => option.value === mode) ?? MODES[0] - - useEffect(() => { - writeStoredMode(mode) - }, [mode]) + const { mode, setMode, activeMode } = useEditorMode() return (
diff --git a/website/src/use-editor-mode.ts b/website/src/use-editor-mode.ts new file mode 100644 index 0000000..410ad95 --- /dev/null +++ b/website/src/use-editor-mode.ts @@ -0,0 +1,61 @@ +import type { EditorMode } from '@meowdown/react' +import { useEffect, useState } from 'react' + +interface ModeOption { + value: EditorMode + label: string + description: string +} + +export const MODES: ModeOption[] = [ + { + value: 'focus', + label: 'Focus', + description: 'Syntax stays hidden and peeks out only where your cursor rests.', + }, + { + value: 'show', + label: 'Show', + description: 'Every Markdown character stays visible, dimmed in soft grey.', + }, + { + value: 'hide', + label: 'Hide', + description: 'Markdown characters disappear for a clean, fully rendered view.', + }, + { + value: 'source', + label: 'Source', + description: 'Raw Markdown with syntax highlighting, like an IDE.', + }, +] + +const MODE_STORAGE_KEY = 'meowdown:mode' + +function readStoredMode(): EditorMode { + const stored = sessionStorage.getItem(MODE_STORAGE_KEY) + if (stored) { + for (const option of MODES) { + if (option.value === stored) { + return option.value + } + } + } + return 'focus' +} + +function writeStoredMode(mode: EditorMode): void { + sessionStorage.setItem(MODE_STORAGE_KEY, mode) +} + +export function useEditorMode() { + const [mode, setMode] = useState(readStoredMode) + + useEffect(() => { + writeStoredMode(mode) + }, [mode]) + + const activeMode = MODES.find((option) => option.value === mode) ?? MODES[0] + + return { mode, setMode, activeMode } +} From e3772b3c1241a2fa94b51ae06ab63820b8e4acee Mon Sep 17 00:00:00 2001 From: ocavue Date: Fri, 19 Jun 2026 00:00:31 +1000 Subject: [PATCH 4/5] fix(website): return tag and wikilink items from demo search handlers --- website/src/app.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/website/src/app.tsx b/website/src/app.tsx index 1ab5bcf..e4ebae3 100644 --- a/website/src/app.tsx +++ b/website/src/app.tsx @@ -1,4 +1,4 @@ -import { MeowdownEditor } from '@meowdown/react' +import { MeowdownEditor, type TagItem, type WikilinkItem } from '@meowdown/react' import { type CSSProperties, useLayoutEffect, useState } from 'react' import { uploadFile } from './upload-file.ts' @@ -62,10 +62,10 @@ function greet(name: string): string { const TAGS = ['cats', 'editor', 'ideas', 'markdown', 'meow', 'notes', 'react', 'todo', 'work'] -async function searchTags(query: string): Promise { +async function searchTags(query: string): Promise { // Simulate network latency so the tag menu's loading state shows up. await new Promise((resolve) => setTimeout(resolve, 200)) - return TAGS.filter((tag) => tag.includes(query)) + return TAGS.filter((tag) => tag.includes(query)).map((tag) => ({ tag })) } const NOTES = [ @@ -77,10 +77,12 @@ const NOTES = [ 'Travel plans', ] -async function searchNotes(query: string): Promise { +async function searchNotes(query: string): Promise { // Simulate network latency so the wikilink menu's loading state shows up. await new Promise((resolve) => setTimeout(resolve, 200)) - return NOTES.filter((note) => note.toLowerCase().includes(query)) + return NOTES.filter((note) => note.toLowerCase().includes(query)).map((note) => ({ + target: note, + })) } const ICON_BUTTON_CLASS = From 090c491443929fe27e9e486079b8013a465707c0 Mon Sep 17 00:00:00 2001 From: ocavue Date: Fri, 19 Jun 2026 00:04:08 +1000 Subject: [PATCH 5/5] chore: typecheck react and website in root build --- tsconfig.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index f236fad..5fbc154 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,8 @@ { "extends": "@ocavue/tsconfig/dom/root.json", - "references": [{ "path": "./packages/core/tsconfig.json" }] + "references": [ + { "path": "./packages/core/tsconfig.json" }, + { "path": "./packages/react/tsconfig.json" }, + { "path": "./website/tsconfig.json" } + ] }