diff --git a/services/web/frontend/js/features/source-editor/extensions/doc-folder.ts b/services/web/frontend/js/features/source-editor/extensions/doc-folder.ts new file mode 100644 index 0000000000..77bb0817c2 --- /dev/null +++ b/services/web/frontend/js/features/source-editor/extensions/doc-folder.ts @@ -0,0 +1,23 @@ +import { + Compartment, + Extension, + Facet, + TransactionSpec, +} from '@codemirror/state' + +const docFolderConf = new Compartment() + +/** + * Folder of the open document (null at the project root), for resolving + * document-relative file paths. + */ +export const docFolderFacet = Facet.define({ + combine: values => values[0] ?? null, +}) + +export const docFolder = (docFolder: string | null): Extension => + docFolderConf.of(docFolderFacet.of(docFolder)) + +export const setDocFolder = (docFolder: string | null): TransactionSpec => ({ + effects: docFolderConf.reconfigure(docFolderFacet.of(docFolder)), +}) diff --git a/services/web/frontend/js/features/source-editor/extensions/index.ts b/services/web/frontend/js/features/source-editor/extensions/index.ts index 47905f2a5c..ca583acf90 100644 --- a/services/web/frontend/js/features/source-editor/extensions/index.ts +++ b/services/web/frontend/js/features/source-editor/extensions/index.ts @@ -49,6 +49,7 @@ import { breadcrumbPanel } from './breadcrumbs-panel' import { geometryChangeEvent } from './geometry-change-event' import { docName } from './doc-name' import { filePreview } from './file-preview' +import { docFolder } from './doc-folder' import { fileTreeItemDrop } from './file-tree-item-drop' import { mathPreview } from './math-preview' import { ranges } from './ranges' @@ -135,6 +136,7 @@ export const createExtensions = (options: Record): Extension[] => [ docName(options.docName), filePreview(options.previewByPath), + docFolder(options.currentDocFolder), // NOTE: `annotations` needs to be before `language` annotations(), diff --git a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts index 89d15b07d8..fee5edc806 100644 --- a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts +++ b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts @@ -33,6 +33,7 @@ import { EditorView } from '@codemirror/view' import { useErrorBoundary } from 'react-error-boundary' import { isVisual, setVisual } from '../extensions/visual/visual' import { setFilePreview } from '../extensions/file-preview' +import { setDocFolder } from '../extensions/doc-folder' import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path' import { useUserSettingsContext } from '@/shared/context/user-settings-context' import { setDocName } from '@/features/source-editor/extensions/doc-name' @@ -287,7 +288,7 @@ function useCodeMirrorScope(view: EditorView) { const editableRef = useRef(permissions.write || permissions.trackedWrite) - const { previewByPath } = useFileTreePathContext() + const { previewByPath, dirname } = useFileTreePathContext() const showVisual = visual && !!openDocName && isCmVisualEditorAvailable(openDocName) @@ -296,6 +297,14 @@ function useCodeMirrorScope(view: EditorView) { const previewByPathRef = useRef(previewByPath) + const dirnameRef = useRef(dirname) + + // Kept fresh here, above the state-creation effect, so that effect sees the + // new file tree on a simultaneous tree-change + doc-switch. + useEffect(() => { + dirnameRef.current = dirname + }, [dirname]) + // Persist the search query in this hook when the document changes by keeping // a reference to the search query in sync with the editor state const searchQueryRef = useRef(null) @@ -355,6 +364,7 @@ function useCodeMirrorScope(view: EditorView) { spelling: spellingRef.current, showVisual: showVisualRef.current, previewByPath: previewByPathRef.current, + currentDocFolder: dirnameRef.current(currentDocument.doc_id), projectFeatures: projectFeaturesRef.current, initialSearchQuery: searchQueryRef.current, showBoundary, @@ -423,9 +433,12 @@ function useCodeMirrorScope(view: EditorView) { useEffect(() => { previewByPathRef.current = previewByPath window.setTimeout(() => { - view.dispatch(setFilePreview(previewByPath)) + view.dispatch( + setFilePreview(previewByPath), + setDocFolder(currentDocument ? dirname(currentDocument.doc_id) : null) + ) }) - }, [view, previewByPath]) + }, [view, previewByPath, dirname, currentDocument]) useEffect(() => { editableRef.current = permissions.write || permissions.trackedWrite