diff --git a/src/main.ts b/src/main.ts index c9358a06..a7814e86 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { App, Component, debounce, MarkdownPostProcessorContext, Plugin, PluginSettingTab, Setting } from "obsidian"; +import { App, Component, debounce, MarkdownPostProcessorContext, MarkdownView, Plugin, PluginSettingTab, Setting } from "obsidian"; import { renderErrorPre } from "ui/render"; import { FullIndex } from "data-index/index"; import { parseField } from "expression/parse"; @@ -11,7 +11,7 @@ import { currentLocale } from "util/locale"; import { DateTime } from "luxon"; import { DataviewInlineApi } from "api/inline-api"; import { replaceInlineFields } from "ui/views/inline-field"; -import { inlineFieldsField, replaceInlineFieldsInLivePreview } from "./ui/views/inline-field-live-preview"; +import { inlineFieldsField, replaceInlineFieldsInLivePreview, workspaceLayoutChangeEffect } from "./ui/views/inline-field-live-preview"; import { DataviewInit } from "ui/markdown"; import { inlinePlugin } from "./ui/lp-render"; import { Extension } from "@codemirror/state"; @@ -121,8 +121,20 @@ export default class DataviewPlugin extends Plugin { this.app.metadataCache.trigger("dataview:api-ready", this.api); console.log(`Dataview: version ${this.manifest.version} (requires obsidian ${this.manifest.minAppVersion})`); + // Mainly intended to detect when the user switches between live preview and source mode. + this.registerEvent( + this.app.workspace.on("layout-change", () => { + this.app.workspace.iterateAllLeaves((leaf) => { + if (leaf.view instanceof MarkdownView && leaf.view.editor.cm) { + leaf.view.editor.cm.dispatch({ + effects: workspaceLayoutChangeEffect.of(null) + }) + } + }) + }) + ) this.registerEditorExtension(inlineFieldsField); - this.registerEditorExtension(replaceInlineFieldsInLivePreview(this.app)); + this.registerEditorExtension(replaceInlineFieldsInLivePreview(this.app, this.settings)); } private debouncedRefresh: () => void = () => null; diff --git a/src/typings/obsidian-ex.d.ts b/src/typings/obsidian-ex.d.ts index b74deafd..22fb5aee 100644 --- a/src/typings/obsidian-ex.d.ts +++ b/src/typings/obsidian-ex.d.ts @@ -1,5 +1,6 @@ import type { DataviewApi } from "api/plugin-api"; import "obsidian"; +import { EditorView } from "@codemirror/view"; declare module "obsidian" { interface MetadataCache { @@ -23,6 +24,13 @@ declare module "obsidian" { /** Sent to rendered dataview components to tell them to possibly refresh */ on(name: "dataview:refresh-views", callback: () => void, ctx?: any): EventRef; } + + interface Editor { + /** + * CodeMirror editor instance + */ + cm?: EditorView; + } } declare global { diff --git a/src/ui/views/inline-field-live-preview.ts b/src/ui/views/inline-field-live-preview.ts index 113edb76..fd225938 100644 --- a/src/ui/views/inline-field-live-preview.ts +++ b/src/ui/views/inline-field-live-preview.ts @@ -1,5 +1,5 @@ -import { App, Component, MarkdownRenderer, editorInfoField } from "obsidian"; -import { EditorState, RangeSet, RangeSetBuilder, RangeValue, StateField } from "@codemirror/state"; +import { App, Component, MarkdownRenderer, editorInfoField, editorLivePreviewField } from "obsidian"; +import { EditorState, RangeSet, RangeSetBuilder, RangeValue, StateEffect, StateField } from "@codemirror/state"; import { Decoration, DecorationSet, @@ -9,8 +9,10 @@ import { ViewUpdate, WidgetType, } from "@codemirror/view"; -import { InlineField, extractInlineFields } from "data-import/inline-field"; +import { InlineField, extractInlineFields, parseInlineValue } from "data-import/inline-field"; import { canonicalizeVarName } from "util/normalize"; +import { renderValue } from "ui/render"; +import { DataviewSettings } from "settings"; class InlineFieldValue extends RangeValue { constructor(public field: InlineField) { @@ -40,7 +42,7 @@ export const inlineFieldsField = StateField.define>({ }); /** Create a view plugin that renders inline fields in live preview just as in the reading view. */ -export const replaceInlineFieldsInLivePreview = (app: App) => +export const replaceInlineFieldsInLivePreview = (app: App, settings: DataviewSettings) => ViewPlugin.fromClass( class implements PluginValue { decorations: DecorationSet; @@ -59,18 +61,31 @@ export const replaceInlineFieldsInLivePreview = (app: App) => const oldIndices = this.overlappingIndices; const newIndices = this.getOverlappingIndices(update.state); - let overlapChanged = + const overlapChanged = update.startState.field(inlineFieldsField).size != update.state.field(inlineFieldsField).size || JSON.stringify(oldIndices) != JSON.stringify(newIndices); this.overlappingIndices = newIndices; - if (update.docChanged || update.viewportChanged || overlapChanged) { - this.decorations = this.buildDecoration(update.view); + const layoutChanged = update.transactions.some( + transaction => transaction.effects.some( + effect => effect.is(workspaceLayoutChangeEffect) + ) + ); + + if (update.state.field(editorLivePreviewField)) { + if (update.docChanged || update.viewportChanged || layoutChanged || overlapChanged) { + this.decorations = this.buildDecoration(update.view); + } + } else { + this.decorations = Decoration.none; } } buildDecoration(view: EditorView): DecorationSet { + // Disable in the source mode + if (!view.state.field(editorLivePreviewField)) return Decoration.none; + const markdownView = view.state.field(editorInfoField); if (!(markdownView instanceof Component)) { // For a canvas card not assosiated with a note in the vault, @@ -95,7 +110,7 @@ export const replaceInlineFieldsInLivePreview = (app: App) => start, end, Decoration.replace({ - widget: new InlineFieldWidget(app, field, x++, file.path, markdownView), + widget: new InlineFieldWidget(app, field, x++, file.path, markdownView, settings), }) ); } @@ -131,7 +146,8 @@ class InlineFieldWidget extends WidgetType { public field: InlineField, public id: number, public sourcePath: string, - public parentComponent: Component + public parentComponent: Component, + public settings: DataviewSettings ) { super(); } @@ -161,13 +177,27 @@ class InlineFieldWidget extends WidgetType { cls: ["dataview", "inline-field-value"], attr: { id: "dataview-inline-field-" + this.id }, }); - this.renderMarkdown(value, this.field.value); + renderValue( + parseInlineValue(this.field.value), + value, + this.sourcePath, + this.parentComponent, + this.settings, + false + ); } else { const value = renderContainer.createSpan({ cls: ["dataview", "inline-field-standalone-value"], attr: { id: "dataview-inline-field-" + this.id }, }); - this.renderMarkdown(value, this.field.value); + renderValue( + parseInlineValue(this.field.value), + value, + this.sourcePath, + this.parentComponent, + this.settings, + false + ); } return renderContainer; @@ -195,3 +225,9 @@ export async function renderMarkdown( } return null; } + +/** + * A state effect that represents the workspace's layout change. + * Mainly intended to detect when the user switches between live preview and source mode. + */ +export const workspaceLayoutChangeEffect = StateEffect.define();