({ notePath }: { notePath: string })
| 192 | } |
| 193 | |
| 194 | export function FloatingNoteApp({ notePath }: { notePath: string }): JSX.Element { |
| 195 | const prefs = useMemo(() => loadFloatingPrefs(), []) |
| 196 | const [content, setContent] = useState<NoteContent | null>(null) |
| 197 | const [dirty, setDirty] = useState(false) |
| 198 | const [mode, setMode] = useState<'edit' | 'preview'>('edit') |
| 199 | const viewRef = useRef<EditorView | null>(null) |
| 200 | const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null) |
| 201 | const dirtyBodyRef = useRef<string | null>(null) |
| 202 | /** The body we most recently wrote. Used to suppress watcher echoes |
| 203 | * of our own saves — comparing to disk is more robust than a |
| 204 | * single-shot ignore flag, since a save can echo more than once |
| 205 | * and additional edits can land in between. */ |
| 206 | const lastWrittenBodyRef = useRef<string | null>(null) |
| 207 | |
| 208 | // Apply theme + font vars before paint. |
| 209 | useEffect(() => { |
| 210 | applyTheme(prefs) |
| 211 | const html = document.documentElement |
| 212 | const mql = window.matchMedia('(prefers-color-scheme: dark)') |
| 213 | if (prefs.themeMode === 'auto') { |
| 214 | const onChange = (): void => applyTheme(prefs) |
| 215 | mql.addEventListener('change', onChange) |
| 216 | return () => mql.removeEventListener('change', onChange) |
| 217 | } |
| 218 | void html |
| 219 | return undefined |
| 220 | }, [prefs]) |
| 221 | |
| 222 | // Initial load. |
| 223 | useEffect(() => { |
| 224 | let alive = true |
| 225 | void window.zen.readNote(notePath).then((c) => { |
| 226 | if (!alive) return |
| 227 | dirtyBodyRef.current = c.body |
| 228 | setContent(c) |
| 229 | }) |
| 230 | return () => { |
| 231 | alive = false |
| 232 | } |
| 233 | }, [notePath]) |
| 234 | |
| 235 | // Sync to external changes (file watcher broadcasts to all windows). |
| 236 | useEffect(() => { |
| 237 | const off = window.zen.onVaultChange((ev: VaultChangeEvent) => { |
| 238 | if (ev.path !== notePath) return |
| 239 | if (ev.kind === 'unlink') { |
| 240 | // Note deleted — close the window. |
| 241 | window.zen.windowClose() |
| 242 | return |
| 243 | } |
| 244 | // Only refresh when the disk content differs from what we last |
| 245 | // wrote ourselves. Skipping the echo prevents the editor from |
| 246 | // rolling back to an older body when the save echo arrives |
| 247 | // after the user has typed more characters. |
| 248 | void window.zen.readNote(notePath).then((c) => { |
| 249 | if (lastWrittenBodyRef.current === c.body) return |
| 250 | dirtyBodyRef.current = c.body |
| 251 | setContent(c) |
nothing calls this directly
no test coverage detected