Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153// src/plugin/views/markdown-view.tsximport type { ReactNode } from 'react'import { Profiler } from 'react'import { TextFileView } from './text-file-view'import { CM6EditorAdapter } from '../cm6-editor'import type { Editor } from '../editor'import type { WorkspaceLeaf } from '../workspace'import type { MarkdownFileInfo } from '../obsidian-cm6-stubs'import type { MarkdownSubView, HoverPopover } from '../obsidian-stubs'import { MarkdownPreviewView } from '../obsidian-stubs'import type { ReactItemView } from './item-view'import { MarkdownLeafBinding } from './markdown-leaf-binding'import { onRenderProfiler } from '../../lib/perf-profile'
/** * View mode type. */export type MarkdownViewModeType = 'source' | 'preview'
/** * Internal WeakMaps for private data that should not appear in declarations. * Using WeakMap ensures: * 1. No visible members in the emitted .d.ts * 2. Proper garbage collection when MarkdownView instances are collected * 3. Private data access only within this module */const modeMap = new WeakMap<MarkdownView, MarkdownViewModeType>()const cm6EditorMap = new WeakMap<MarkdownView, CM6EditorAdapter>()
/** * Markdown document view. * Provides editor access and file reference. * * The editor is a CM6EditorAdapter. It starts unbound (no EditorView) * and is wired by host-only helper functions. * * Implements `MarkdownFileInfo` for compatibility with Obsidian plugins * that check for this interface on editor contexts. * * @public */export class MarkdownView extends TextFileView implements MarkdownFileInfo, ReactItemView { /** * Editor instance — typed as abstract Editor for Obsidian-compatible API. * @public */ editor: Editor
/** * The preview mode subview. * @public */ previewMode: MarkdownPreviewView
/** * The current subview (source or preview). * @public */ currentMode: MarkdownSubView
/** * Hover popover for link previews. * @public */ hoverPopover: HoverPopover | null = null
constructor(leaf: WorkspaceLeaf) { super(leaf) const cm6Editor = new CM6EditorAdapter() cm6EditorMap.set(this, cm6Editor) modeMap.set(this, 'source') this.editor = cm6Editor // Initialize previewMode with a stub MarkdownPreviewView this.previewMode = new MarkdownPreviewView(this.containerEl) // currentMode starts as previewMode (or could be a source-mode stub) // For now, use previewMode as the default since it implements MarkdownSubView this.currentMode = this.previewMode }
/** View type identifier */ getViewType(): string { return 'markdown' }
/** Get current mode */ getMode(): MarkdownViewModeType { return modeMap.get(this) ?? 'source' }
/** * Get view data (markdown content). * Returns the editor content when bound, otherwise the backing store. */ getViewData(): string { const cm6Editor = cm6EditorMap.get(this) if (cm6Editor?.isBound()) return cm6Editor.getValue() return this.data }
/** Set view data. Updates the internal data store. */ setViewData(data: string, clear: boolean): void { if (clear) { this.clear() } this.data = data }
/** Clear view. Resets the internal data store. */ clear(): void { this.data = '' }
/** Show search in editor */ showSearch(_replace?: boolean): void { // Search implementation deferred }
/** * @internal React component that owns the editor binding for this leaf. * Consumed by MainAreaViewContainer (#406) so MarkdownView participates in * the same content-type routing path as every other view. */ getReactComponent(): ReactNode { const profilerId = `MarkdownLeafBinding[${this.leaf.file?.path ?? this.leaf.id}]` return ( <Profiler id={profilerId} onRender={onRenderProfiler}> <MarkdownLeafBinding leaf={this.leaf} key={this.leaf.file?.path ?? this.leaf.id} /> </Profiler> ) }}
/** @internal Host-only mode bridge for the singleton editor architecture. */export function setMarkdownViewMode(view: MarkdownView, mode: MarkdownViewModeType): void { modeMap.set(view, mode)}
/** @internal Host-only CM6 bridge for the singleton editor architecture. */export function attachMarkdownViewEditor(view: MarkdownView, adapter: CM6EditorAdapter): void { const editorView = adapter.getView() if (editorView) { ;(view.editor as CM6EditorAdapter).setView(editorView) }}
/** @internal Host-only CM6 bridge cleanup for the singleton editor architecture. */export function detachMarkdownViewEditor(view: MarkdownView): void { ;(view.editor as CM6EditorAdapter).clearView()}