2026-03-03 18:42:05 +01:00
|
|
|
import { useRef, useEffect } from 'react'
|
|
|
|
|
import { EditorView, lineNumbers, highlightActiveLine, keymap } from '@codemirror/view'
|
|
|
|
|
import { EditorState } from '@codemirror/state'
|
|
|
|
|
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'
|
|
|
|
|
import { frontmatterHighlightPlugin, frontmatterHighlightTheme } from '../extensions/frontmatterHighlight'
|
2026-03-09 10:43:46 +01:00
|
|
|
import { zoomCursorFix } from '../extensions/zoomCursorFix'
|
2026-03-03 18:42:05 +01:00
|
|
|
|
|
|
|
|
const FONT_FAMILY = '"Berkeley Mono", "JetBrains Mono", "Fira Mono", ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
|
|
|
|
|
|
|
|
|
|
export interface CodeMirrorCallbacks {
|
|
|
|
|
onDocChange: (doc: string) => void
|
|
|
|
|
onCursorActivity: (view: EditorView) => void
|
|
|
|
|
onSave: () => void
|
|
|
|
|
onEscape: () => boolean
|
|
|
|
|
}
|
|
|
|
|
|
refactor: remove theming system entirely
Remove all theme switching, creation, and management functionality
from the app — backend (Rust theme module, Tauri commands, menu items,
startup tasks, vault seeding), frontend (useThemeManager, ThemePropertyEditor,
themeSchema, command palette Appearance group, settings panel appearance
section, isDarkTheme prop chain), mock data, and all related tests.
Editor styling via theme.json/EditorTheme.css is preserved (not user theming).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 19:57:58 +01:00
|
|
|
function buildBaseTheme() {
|
|
|
|
|
const bg = '#ffffff'
|
|
|
|
|
const fg = '#1e1e1e'
|
|
|
|
|
const gutterBg = '#ffffff'
|
|
|
|
|
const gutterColor = '#aaa'
|
|
|
|
|
const activeLineBg = 'rgba(0,100,255,0.06)'
|
|
|
|
|
const gutterBorder = '#eee'
|
2026-03-03 18:42:05 +01:00
|
|
|
|
|
|
|
|
return EditorView.theme({
|
|
|
|
|
'&': {
|
|
|
|
|
fontSize: '13px',
|
|
|
|
|
fontFamily: FONT_FAMILY,
|
|
|
|
|
backgroundColor: bg,
|
|
|
|
|
color: fg,
|
|
|
|
|
flex: '1',
|
|
|
|
|
minHeight: '0',
|
|
|
|
|
},
|
|
|
|
|
'.cm-scroller': {
|
|
|
|
|
fontFamily: FONT_FAMILY,
|
|
|
|
|
lineHeight: '1.6',
|
|
|
|
|
padding: '16px 0',
|
|
|
|
|
overflow: 'auto',
|
|
|
|
|
},
|
|
|
|
|
'.cm-content': {
|
|
|
|
|
padding: '0 32px 0 16px',
|
|
|
|
|
caretColor: fg,
|
|
|
|
|
},
|
|
|
|
|
'.cm-gutters': {
|
|
|
|
|
backgroundColor: gutterBg,
|
|
|
|
|
color: gutterColor,
|
|
|
|
|
borderRight: `1px solid ${gutterBorder}`,
|
|
|
|
|
paddingLeft: '16px',
|
|
|
|
|
},
|
|
|
|
|
'.cm-lineNumbers .cm-gutterElement': {
|
|
|
|
|
paddingRight: '12px',
|
|
|
|
|
minWidth: '28px',
|
|
|
|
|
textAlign: 'right',
|
|
|
|
|
},
|
|
|
|
|
'.cm-activeLine': {
|
|
|
|
|
backgroundColor: activeLineBg,
|
|
|
|
|
},
|
|
|
|
|
'.cm-activeLineGutter': {
|
|
|
|
|
backgroundColor: activeLineBg,
|
|
|
|
|
},
|
|
|
|
|
'&.cm-focused': { outline: 'none' },
|
|
|
|
|
'.cm-line': { padding: '0' },
|
refactor: remove theming system entirely
Remove all theme switching, creation, and management functionality
from the app — backend (Rust theme module, Tauri commands, menu items,
startup tasks, vault seeding), frontend (useThemeManager, ThemePropertyEditor,
themeSchema, command palette Appearance group, settings panel appearance
section, isDarkTheme prop chain), mock data, and all related tests.
Editor styling via theme.json/EditorTheme.css is preserved (not user theming).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 19:57:58 +01:00
|
|
|
})
|
2026-03-03 18:42:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildSaveKeymap(callbacks: { current: CodeMirrorCallbacks }) {
|
|
|
|
|
return keymap.of([{
|
|
|
|
|
key: 'Mod-s',
|
|
|
|
|
run: () => { callbacks.current.onSave(); return true },
|
|
|
|
|
}, {
|
|
|
|
|
key: 'Escape',
|
|
|
|
|
run: () => callbacks.current.onEscape(),
|
|
|
|
|
}])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useCodeMirror(
|
|
|
|
|
containerRef: React.RefObject<HTMLDivElement | null>,
|
|
|
|
|
content: string,
|
|
|
|
|
callbacks: CodeMirrorCallbacks,
|
|
|
|
|
) {
|
|
|
|
|
const viewRef = useRef<EditorView | null>(null)
|
|
|
|
|
const callbacksRef = useRef(callbacks)
|
|
|
|
|
callbacksRef.current = callbacks
|
2026-03-18 13:32:07 +01:00
|
|
|
// Track whether we're dispatching an external sync so the updateListener skips it
|
|
|
|
|
const externalSyncRef = useRef(false)
|
|
|
|
|
|
|
|
|
|
// Sync content prop changes to the editor (e.g. after frontmatter update on disk)
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const view = viewRef.current
|
|
|
|
|
if (!view) return
|
|
|
|
|
const current = view.state.doc.toString()
|
|
|
|
|
if (current === content) return
|
|
|
|
|
externalSyncRef.current = true
|
|
|
|
|
view.dispatch({ changes: { from: 0, to: current.length, insert: content } })
|
|
|
|
|
externalSyncRef.current = false
|
|
|
|
|
}, [content])
|
2026-03-03 18:42:05 +01:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const parent = containerRef.current
|
|
|
|
|
if (!parent) return
|
|
|
|
|
|
|
|
|
|
const state = EditorState.create({
|
|
|
|
|
doc: content,
|
|
|
|
|
extensions: [
|
|
|
|
|
lineNumbers(),
|
|
|
|
|
highlightActiveLine(),
|
2026-03-05 12:57:16 +01:00
|
|
|
EditorView.lineWrapping,
|
2026-03-03 18:42:05 +01:00
|
|
|
history(),
|
|
|
|
|
keymap.of([...defaultKeymap, ...historyKeymap]),
|
|
|
|
|
buildSaveKeymap(callbacksRef),
|
refactor: remove theming system entirely
Remove all theme switching, creation, and management functionality
from the app — backend (Rust theme module, Tauri commands, menu items,
startup tasks, vault seeding), frontend (useThemeManager, ThemePropertyEditor,
themeSchema, command palette Appearance group, settings panel appearance
section, isDarkTheme prop chain), mock data, and all related tests.
Editor styling via theme.json/EditorTheme.css is preserved (not user theming).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 19:57:58 +01:00
|
|
|
buildBaseTheme(),
|
|
|
|
|
frontmatterHighlightTheme(),
|
2026-03-03 18:42:05 +01:00
|
|
|
frontmatterHighlightPlugin,
|
2026-03-09 10:43:46 +01:00
|
|
|
zoomCursorFix(),
|
2026-03-03 18:42:05 +01:00
|
|
|
EditorView.updateListener.of((update) => {
|
2026-03-18 13:32:07 +01:00
|
|
|
if (update.docChanged && !externalSyncRef.current) {
|
2026-03-03 18:42:05 +01:00
|
|
|
callbacksRef.current.onDocChange(update.state.doc.toString())
|
|
|
|
|
}
|
|
|
|
|
if (update.selectionSet || update.docChanged) {
|
|
|
|
|
callbacksRef.current.onCursorActivity(update.view)
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const view = new EditorView({ state, parent })
|
|
|
|
|
viewRef.current = view
|
2026-03-19 00:57:34 +01:00
|
|
|
// Expose EditorView on the parent DOM for Playwright test access
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
|
;(parent as any).__cmView = view
|
2026-03-08 14:54:50 +01:00
|
|
|
|
|
|
|
|
// When CSS zoom changes on the document, CodeMirror's cached measurements
|
|
|
|
|
// (scaleX/scaleY, line heights, character widths) become stale because
|
|
|
|
|
// ResizeObserver doesn't fire for ancestor zoom changes. Force a re-measure
|
|
|
|
|
// so cursor placement stays accurate at any zoom level.
|
|
|
|
|
const handleZoomChange = () => { view.requestMeasure() }
|
|
|
|
|
window.addEventListener('laputa-zoom-change', handleZoomChange)
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
window.removeEventListener('laputa-zoom-change', handleZoomChange)
|
2026-03-19 00:57:34 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
|
delete (parent as any).__cmView
|
2026-03-08 14:54:50 +01:00
|
|
|
view.destroy()
|
|
|
|
|
viewRef.current = null
|
|
|
|
|
}
|
2026-03-03 18:42:05 +01:00
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
refactor: remove theming system entirely
Remove all theme switching, creation, and management functionality
from the app — backend (Rust theme module, Tauri commands, menu items,
startup tasks, vault seeding), frontend (useThemeManager, ThemePropertyEditor,
themeSchema, command palette Appearance group, settings panel appearance
section, isDarkTheme prop chain), mock data, and all related tests.
Editor styling via theme.json/EditorTheme.css is preserved (not user theming).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 19:57:58 +01:00
|
|
|
}, [])
|
2026-03-03 18:42:05 +01:00
|
|
|
|
|
|
|
|
return viewRef
|
|
|
|
|
}
|