()
| 43 | } |
| 44 | |
| 45 | export function ExternalFileApp(): JSX.Element { |
| 46 | const prefs = useMemo(() => loadFloatingPrefs(), []) |
| 47 | const [content, setContent] = useState<ExternalFileContent | null>(null) |
| 48 | const [dirty, setDirty] = useState(false) |
| 49 | const [mode, setMode] = useState<'edit' | 'preview'>('edit') |
| 50 | const [moving, setMoving] = useState(false) |
| 51 | const [moveError, setMoveError] = useState<string | null>(null) |
| 52 | const viewRef = useRef<EditorView | null>(null) |
| 53 | const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null) |
| 54 | // Source of truth for the body: seeded on load and updated on every |
| 55 | // edit. The editor is recreated on each edit/preview toggle, so it must |
| 56 | // re-seed from here — `content` is captured stale in setContainerRef. |
| 57 | const bodyRef = useRef<string | null>(null) |
| 58 | |
| 59 | // Apply theme + font vars before paint. |
| 60 | useEffect(() => { |
| 61 | applyTheme(prefs) |
| 62 | const mql = window.matchMedia('(prefers-color-scheme: dark)') |
| 63 | if (prefs.themeMode === 'auto') { |
| 64 | const onChange = (): void => applyTheme(prefs) |
| 65 | mql.addEventListener('change', onChange) |
| 66 | return () => mql.removeEventListener('change', onChange) |
| 67 | } |
| 68 | return undefined |
| 69 | }, [prefs]) |
| 70 | |
| 71 | // Initial load. |
| 72 | useEffect(() => { |
| 73 | let alive = true |
| 74 | void window.zen |
| 75 | .readExternalFile() |
| 76 | .then((c) => { |
| 77 | if (!alive) return |
| 78 | bodyRef.current = c.body |
| 79 | setContent(c) |
| 80 | }) |
| 81 | .catch((err) => { |
| 82 | console.error('readExternalFile failed', err) |
| 83 | }) |
| 84 | return () => { |
| 85 | alive = false |
| 86 | } |
| 87 | }, []) |
| 88 | |
| 89 | const persist = useCallback(async (body: string) => { |
| 90 | try { |
| 91 | await window.zen.writeExternalFile(body) |
| 92 | setDirty(false) |
| 93 | } catch (err) { |
| 94 | console.error('writeExternalFile failed', err) |
| 95 | } |
| 96 | }, []) |
| 97 | |
| 98 | // Mount CodeMirror once content is loaded. |
| 99 | const setContainerRef = useCallback( |
| 100 | (el: HTMLDivElement | null) => { |
| 101 | if (!el) { |
| 102 | viewRef.current?.destroy() |
nothing calls this directly
no test coverage detected