// src/plugin/loader/plugin-api-global.ts /** * Expose the Reams plugin API on the global scope so external plugins * loaded from blob URLs can access it via `require('obsidian')`. * * See ExternalPluginStore.createBlobUrl for how CJS plugins are wrapped. * Call installPluginApiGlobal() once during app init, before any * external plugins are loaded. */ // Import moment default export. At runtime, moment's default export is a callable // function with all namespace properties (duration, utc, etc.) attached. // The TYPE is `typeof Moment` (namespace) but VALUE is the callable default. import momentLib from 'moment' import { Plugin } from '../plugin' import { Component } from '../component' import { Events } from '../events' import { App } from '../app' import { Vault } from '../vault' import { Workspace, WorkspaceLeaf } from '../workspace' import { Editor } from '../editor' import { View } from '../views/view' import { ItemView } from '../views/item-view' import { TextFileView } from '../views/text-file-view' import { MarkdownView } from '../views/markdown-view' import { TAbstractFile, TFile, TFolder } from '../files' import { PluginSettingTab, SettingTab } from '../settings/setting-tab' import { FileManager } from '../file-manager' import { EditorSuggest, PopoverSuggest, AbstractInputSuggest } from '../editor-suggest' import { MetadataCache } from '../../services/metadata' // CodeMirror 6 re-exports — Obsidian bundles CM6 and exposes these primitives // for plugins that use registerEditorExtension(). See obsidian.d.ts imports. // // NOTE: Only exports that are VALUES (not type-only) are included here. // Type-only exports (Extension, DecorationSet, Tooltip, etc.) cannot be // re-exported at runtime — plugins get them from TypeScript declarations. // NOTE: ChangeSet, ChangeDesc, and SelectionRange are NOT part of Obsidian's // public API (i.e. `import { SelectionRange } from 'obsidian'` won't work). // However, SelectionRange IS available via `require('@codemirror/state')`. import { EditorState, EditorSelection, SelectionRange, Prec, Compartment, StateField, StateEffect, Facet, Transaction, RangeValue, RangeSetBuilder, Text, } from '@codemirror/state' // NOTE: Line is NOT part of Obsidian's public API. Internal code that needs it // should import directly from @codemirror/state. // Obsidian-private CM6 fields exposed for plugins. See obsidian-cm6-stubs.ts // for the rationale; the same instance must be added to every editor's // extension list and exposed on the plugin API surface so plugins reading // `state.field(editorInfoField)` find a value. import { editorInfoField, editorViewField, editorEditorField, editorLivePreviewField, livePreviewState, } from '../obsidian-cm6-stubs' // NOTE: MatchDecorator is NOT part of Obsidian's public API. Internal code // that needs it should import directly from @codemirror/view. // NOTE: highlightActiveLineGutter is NOT part of Obsidian's public API. // Internal code that needs it should import directly from @codemirror/view. // NOTE: highlightActiveLine is NOT part of Obsidian's public API. Internal code // that needs it should import directly from @codemirror/view. // NOTE: dropCursor is NOT part of Obsidian's public API. Internal code that // needs it should import directly from @codemirror/view. // NOTE: rectangularSelection is NOT part of Obsidian's public API. Internal // code that needs it should import directly from @codemirror/view. // NOTE: WidgetType is NOT part of Obsidian's public API. Internal code that // needs it should import directly from @codemirror/view. // NOTE: gutter, GutterMarker, and gutters are NOT part of Obsidian's public API. // Internal code that needs them should import directly from @codemirror/view. // NOTE: hoverTooltip is NOT part of Obsidian's public API. Internal code that // needs it should import directly from @codemirror/view. // NOTE: ViewUpdate is NOT part of Obsidian's public API. Internal code that // needs it should import directly from @codemirror/view. // NOTE: showTooltip is NOT part of Obsidian's public API (require('obsidian')), // but it IS available via require('@codemirror/view') since Obsidian bundles // full CM6. We import it for the codemirrorViewModule but NOT pluginApi. import { EditorView, ViewPlugin, Decoration, WidgetType, keymap, highlightSpecialChars, lineNumbers, showTooltip, BlockInfo, } from '@codemirror/view' import { syntaxTree, foldable, foldEffect, unfoldEffect, foldedRanges, foldAll, // NOTE: unfoldAll intentionally excluded — not in Obsidian's public API foldKeymap, // Exposed via require('@codemirror/language'), but NOT via require('obsidian') bracketMatching, // NOTE: indentOnInput intentionally excluded — not in Obsidian's public API indentUnit, Language, LRLanguage, LanguageDescription, HighlightStyle, defaultHighlightStyle, } from '@codemirror/language' // NOTE: foldable, foldEffect, unfoldEffect, foldedRanges, foldAll are imported // above for @codemirror/language require() compatibility, but NOT re-exported // via require('obsidian') since they are not part of Obsidian's public API. // NOTE: StreamLanguage is NOT part of Obsidian's public API. Internal code that // needs it should import directly from @codemirror/language. // NOTE: getIndentUnit is NOT part of Obsidian's public API. Internal code that // needs it should import directly from @codemirror/language. // NOTE: getIndentation is NOT part of Obsidian's public API. Internal code that // needs it should import directly from @codemirror/language. // NOTE: foldCode and unfoldCode are NOT part of Obsidian's public API. Internal // code that needs them should import directly from @codemirror/language. // NOTE: moveLineDown is NOT part of Obsidian's public API // NOTE: history is NOT part of Obsidian's public API // NOTE: undoDepth and redoDepth are NOT part of Obsidian's public API // NOTE: cursorDocStart is NOT part of Obsidian's public API (not in require('obsidian')), // but it IS available via require('@codemirror/commands') since Obsidian bundles full CM6. import { defaultKeymap, undo, redo, indentWithTab, insertNewlineAndIndent, indentMore, indentLess, insertTab, cursorCharLeft, cursorLineUp, cursorLineDown, cursorLineStart, cursorLineEnd, cursorDocStart, cursorDocEnd, selectCharRight, selectLineUp, selectLineDown, selectLineStart, selectLineEnd, selectDocStart, // Available via require('@codemirror/commands') but NOT via require('obsidian') selectDocEnd, // Available via require('@codemirror/commands') but NOT via require('obsidian') selectAll, deleteLine, moveLineUp, copyLineUp, copyLineDown, deleteCharBackward, deleteCharForward, } from '@codemirror/commands' // NOTE: snippet and snippetCompletion are NOT part of Obsidian's public API // (`import { snippet } from 'obsidian'`). However, they ARE available via // `require('@codemirror/autocomplete')` since Obsidian bundles full CM6. // NOTE: acceptCompletion is NOT part of Obsidian's public API. Internal code // that needs it should import directly from @codemirror/autocomplete. import { autocompletion, completionKeymap, startCompletion, CompletionContext, completeFromList, ifNotIn, snippet, snippetCompletion, } from '@codemirror/autocomplete' // NOTE: classHighlighter is NOT part of Obsidian's public API. Internal code // that needs it should import directly from @lezer/highlight. // NOTE: TreeCursor is NOT part of Obsidian's public API. Internal code that // needs it should import directly from @lezer/common. // NOTE: Tree is NOT part of Obsidian's public API. Internal code that needs // it should import directly from @lezer/common. import { NodeType, NodeProp } from '@lezer/common' import { tokenClassNodeProp } from '../../editor/markdown-language' // Import stubs from shared module (no more inline definitions) import { BaseComponent, ValueComponent, AbstractTextComponent, PopoverState, Modal, SuggestModal, FuzzySuggestModal, Menu, MenuItem, MenuSeparator, Setting, FileView, Notice, Tasks, Scope, Keymap, TextComponent, MomentFormatComponent, ToggleComponent, DropdownComponent, ButtonComponent, ExtraButtonComponent, SliderComponent, ColorComponent, TextAreaComponent, MarkdownRenderChild, debounce, parseFrontMatterEntry, parseFrontMatterStringArray, parseFrontMatterTags, parseFrontMatterAliases, getAllTags, getFrontMatterInfo, normalizePath, // YAML utilities (Obsidian 0.12.2+) parseYaml, stringifyYaml, arrayBufferToBase64, arrayBufferToHex, base64ToArrayBuffer, getBlobArrayBuffer, hexToArrayBuffer, parseLinktext, getLinkpath, stripHeading, stripHeadingForLink, setTooltip, displayTooltip, requestUrl, request, // NOTE: ReamsRequestUrlError is Reams-specific (not in upstream Obsidian API). // It is NOT exposed via require('obsidian'). Plugins needing it should // import from 'reams' instead. Platform, htmlToMarkdown, sanitizeHTMLToDom, MarkdownRenderer, // Bases utility functions and value types (Obsidian 1.10.0+) BasesEntry, BasesEntryGroup, QueryController, BasesQueryResult, BasesViewConfig, BasesView, parsePropertyId, Value, NullValue, NotNullValue, PrimitiveValue, BooleanValue, NumberValue, StringValue, HTMLValue, ImageValue, UrlValue, FileValue, LinkValue, IconValue, TagValue, ListValue, RegExpValue, // Search helpers (Obsidian-compatible) prepareFuzzySearch, prepareSimpleSearch, renderMatches, renderResults, sortSearchResults, // API version and version check apiVersion, requireApiVersion, // Library loading functions (stub implementations) loadMathJax, loadMermaid, loadPdfJs, loadPrism, // Math rendering functions (stub implementations since MathJax not bundled) renderMath, finishRenderMath, // Secret storage (Obsidian 1.11.4+) SecretStorage, // Workspace compat classes (F-COMPAT-9, chainlink #341) WorkspaceItem, WorkspaceParent, WorkspaceSplit, WorkspaceFloating, WorkspaceSidedock, WorkspaceContainer, WorkspaceRoot, WorkspaceRibbon, WorkspaceWindow, WorkspaceTabs, WorkspaceMobileDrawer, MarkdownPreviewView, MarkdownPreviewRenderer, MarkdownEditView, // Data adapter classes (Obsidian-compatible) FileSystemAdapter, // Subpath and reference iteration utilities resolveSubpath, iterateCacheRefs, iterateRefs, } from '../obsidian-stubs' import { setIcon, addIcon, removeIcon, getIcon, getIconIds } from '../ui/icon' // Base theme CSS for Obsidian plugin views. Provides styling for // standard Obsidian CSS classes (.nav-file, .tree-item, .setting-item, etc.) // that plugins expect to be available from the theme. import '../ui/obsidian-base-theme.css' /** * CodeMirror 6 module objects for require('@codemirror/state') etc. * Obsidian bundles CM6 and plugins can require these modules directly. * We expose the same primitives that obsidian.d.ts imports. */ export const codemirrorStateModule: Record = { EditorState, EditorSelection, SelectionRange, // Available via require('@codemirror/state'), but NOT via require('obsidian') Prec, Compartment, StateField, StateEffect, Facet, Transaction, // Available via require('@codemirror/state'), but NOT via require('obsidian') // Annotation, AnnotationType intentionally excluded — not in Obsidian's public API // ChangeSet, ChangeDesc, StateEffectType, RangeSet, MapMode intentionally excluded — not in Obsidian's public API RangeValue, RangeSetBuilder, Text, // Line intentionally excluded — not in Obsidian's public API } export const codemirrorViewModule: Record = { EditorView, ViewPlugin, // ViewUpdate intentionally excluded — not in Obsidian's public API Decoration, WidgetType, // MatchDecorator intentionally excluded — not in Obsidian's public API // highlightActiveLineGutter intentionally excluded — not in Obsidian's public API // highlightActiveLine intentionally excluded — not in Obsidian's public API // dropCursor intentionally excluded — not in Obsidian's public API // crosshairCursor intentionally excluded — not in Obsidian's public API // rectangularSelection intentionally excluded — not in Obsidian's public API // gutter, GutterMarker, gutters intentionally excluded — not in Obsidian's public API // hoverTooltip intentionally excluded — not in Obsidian's public API keymap, highlightSpecialChars, lineNumbers, // Available via require('@codemirror/view'), but NOT via require('obsidian') showTooltip, BlockInfo, } export const codemirrorLanguageModule: Record = { syntaxTree, foldable, foldEffect, unfoldEffect, foldedRanges, foldAll, // unfoldAll intentionally excluded — not in Obsidian's public API // foldCode and unfoldCode intentionally excluded — not in Obsidian's public API foldKeymap, // Exposed via require('@codemirror/language'), matching real Obsidian bracketMatching, // indentOnInput intentionally excluded — not in Obsidian's public API indentUnit, // getIndentation intentionally excluded — not in Obsidian's public API Language, LRLanguage, LanguageDescription, HighlightStyle, defaultHighlightStyle, tokenClassNodeProp, } // NOTE: moveLineDown is NOT part of Obsidian's public API // NOTE: history is NOT part of Obsidian's public API // NOTE: undoDepth and redoDepth are NOT part of Obsidian's public API // NOTE: cursorDocStart is exposed via require('@codemirror/commands') but NOT via require('obsidian') export const codemirrorCommandsModule: Record = { defaultKeymap, undo, redo, indentWithTab, insertNewlineAndIndent, indentMore, indentLess, insertTab, cursorCharLeft, cursorLineUp, cursorLineDown, cursorLineStart, cursorLineEnd, cursorDocStart, // Available in @codemirror/commands but NOT in obsidian module cursorDocEnd, selectCharRight, // Available in @codemirror/commands but NOT in obsidian module selectLineUp, // Available in @codemirror/commands but NOT in obsidian module selectLineDown, // Available in @codemirror/commands but NOT in obsidian module selectLineStart, // Available in @codemirror/commands but NOT in obsidian module selectLineEnd, // Available in @codemirror/commands but NOT in obsidian module selectDocStart, // Available in @codemirror/commands but NOT in obsidian module selectDocEnd, // Available in @codemirror/commands but NOT in obsidian module selectAll, deleteLine, moveLineUp, copyLineUp, copyLineDown, deleteCharBackward, deleteCharForward, } // NOTE: @codemirror/search and @codemirror/lint are not installed in this project. // If plugins need them, add them to package.json dependencies. export const codemirrorAutocompleteModule: Record = { autocompletion, completionKeymap, startCompletion, // acceptCompletion intentionally excluded — not in Obsidian's public API CompletionContext, completeFromList, ifNotIn, snippet, snippetCompletion, } // NOTE: @lezer/highlight exports (classHighlighter, Tag, tags, etc.) are NOT // part of Obsidian's public API. Removed lezerHighlightModule entirely. /** * Lezer common module shim for plugins that do `require('@lezer/common')`. * * NOTE: These exports (NodeType, NodeProp) are NOT part of Obsidian's public * `require('obsidian')` API surface. They are only available here for plugins * that directly import from '@lezer/common'. The contract report checks the * 'obsidian' module, not this separate module shim. */ export const lezerCommonModule: Record = { // NOTE: Tree removed - use require('@lezer/common').Tree directly if needed // NOTE: TreeCursor removed - use require('@lezer/common').TreeCursor directly if needed NodeType, NodeProp, } /** * Browser-safe shims for Node modules bundled into otherwise web-compatible * plugins. They cover pure parsing packages that retain unused Node imports. */ export const browserNodeModules: Record> = { fs: {}, path: { extname(path: string): string { const filename = path.slice(path.lastIndexOf('/') + 1) const dot = filename.lastIndexOf('.') return dot > 0 ? filename.slice(dot) : '' }, }, buffer: {}, } /** * Build an ES5-callable wrapper around an ES6 class. * * Many third-party plugins are bundled with TypeScript's ES5 target, which * uses the `__extends` helper. The derived constructor body looks like: * * function Derived() { * var _this = _super !== null && _super.apply(this, arguments) || this; * ... * } * * `_super.apply(this, arguments)` — calling the parent without `new` — * throws `TypeError: Class constructor X cannot be invoked without 'new'` * when `_super` is a real ES6 class (our `Plugin` / `Component`). * * This wrapper produces a regular function that: * - When called as a function (`Wrapper.apply(this, args)`), uses * `Reflect.construct` to construct an instance of the real class with * `this.constructor` (the derived class) as `new.target` so the * prototype chain matches the derived class — exactly what `__extends` * expects (it also sets `Derived.prototype = Object.create(Real.prototype)` * and `Object.setPrototypeOf(Derived, Real)` so `instanceof Real` still * works on derived instances). * - When called with `new Wrapper(...)`, constructs a real instance. * - Has the same `.prototype` reference as the real class, so * `derived.prototype = Object.create(Wrapper.prototype)` works and * `instance instanceof Real` returns true. * * Real Obsidian historically shipped its `Plugin` class compiled to ES5 * (callable as a regular function), which is why this pattern works in * Obsidian. Our codebase targets ES2020, so we synthesize the ES5 shape * only on the CJS shim used by external plugins. Native ESM consumers * (typed `import { Plugin } from 'obsidian'`) keep the real ES6 class. */ // Use a permissive constructor type so abstract classes (Plugin, View, ItemView, // PluginSettingTab) match. The wrapper itself never instantiates the // abstract class directly — only via Reflect.construct with a derived // new.target — so the abstract-ness is preserved at the type level. type AnyConstructor = abstract new (...args: never[]) => unknown function makeEs5Callable(RealClass: T): T { function Wrapper(this: unknown, ...args: unknown[]): unknown { // When called as a function (no new.target), the caller is doing // `Plugin.apply(this, args)` from a TS-ES5 __extends derived class. // Reflect.construct with `this.constructor` as new.target ensures the // resulting instance has the derived class's prototype chain. const Real = RealClass as unknown as new (...a: unknown[]) => unknown if (new.target === undefined) { const NewTarget = (this as { constructor?: new (...a: unknown[]) => unknown })?.constructor as | (new (...a: unknown[]) => unknown) | undefined return Reflect.construct(Real, args, NewTarget ?? Real) } return Reflect.construct(Real, args, new.target) } // Share the prototype so `instance instanceof Plugin` and __extends' // prototype = Object.create(Plugin.prototype) line up with the real class. Wrapper.prototype = (RealClass as unknown as { prototype: object }).prototype // Copy static properties (e.g. static methods on Component if any are added later) Object.setPrototypeOf(Wrapper, RealClass) return Wrapper as unknown as T } // ES5-callable wrappers for the classes plugins extend. TS-bundled-as-ES5 // plugins use __extends which calls these as functions (Plugin.apply(this, …)). // // We only wrap classes plugins are observed to subclass via `__extends`. // CM6 classes (EditorView, ViewPlugin, etc.) come from upstream packages compiled // to ES2017+ but still callable in practice; if a plugin trips on them we add // wrappers here driven by the actual error. const PluginCallable = makeEs5Callable(Plugin) const ComponentCallable = makeEs5Callable(Component) const BaseComponentCallable = makeEs5Callable(BaseComponent) const MarkdownRenderChildCallable = makeEs5Callable(MarkdownRenderChild) const ViewCallable = makeEs5Callable(View) const ItemViewCallable = makeEs5Callable(ItemView) const PluginSettingTabCallable = makeEs5Callable(PluginSettingTab) /** * The Obsidian-compatible API object exposed via require('obsidian'). */ const pluginApi: Record = { // Real implementations — exposed via ES5-callable wrappers so plugins // bundled with TypeScript's ES5 target (which uses __extends and // `_super.apply(this, args)`) can extend them. See makeEs5Callable. Plugin: PluginCallable, Component: ComponentCallable, BaseComponent: BaseComponentCallable, Events, App, Vault, Workspace, WorkspaceLeaf, Editor, View: ViewCallable, ItemView: ItemViewCallable, TextFileView, MarkdownView, TAbstractFile, TFile, TFolder, PluginSettingTab: PluginSettingTabCallable, SettingTab, FileManager, MetadataCache, // EditorSuggest framework — third-party plugins (e.g. obsidian-emoji-shortcodes) // subclass `EditorSuggest` and instantiate it inside `onload()`. Without // these on the runtime shim, `class X extends obsidian.EditorSuggest` // throws "class heritage … is not an object or null" at module evaluation // time, before `Plugin.onload()` ever runs — so install silently fails // (no row in the plugin list, no localStorage entry). EditorSuggest, PopoverSuggest, AbstractInputSuggest, // Stubs for APIs plugins commonly reference ValueComponent, AbstractTextComponent, PopoverState, Modal, SuggestModal, FuzzySuggestModal, Menu, MenuItem, MenuSeparator, Setting, FileView, Notice, Tasks, Scope, Keymap, TextComponent, MomentFormatComponent, ToggleComponent, DropdownComponent, ButtonComponent, ExtraButtonComponent, // Instruction is a type-only interface — not exported to CJS runtime SliderComponent, ColorComponent, TextAreaComponent, MarkdownRenderChild: MarkdownRenderChildCallable, // Utility functions debounce, parseFrontMatterEntry, parseFrontMatterStringArray, parseFrontMatterTags, parseFrontMatterAliases, getAllTags, getFrontMatterInfo, normalizePath, // YAML utilities (Obsidian 0.12.2+) parseYaml, stringifyYaml, arrayBufferToBase64, arrayBufferToHex, base64ToArrayBuffer, getBlobArrayBuffer, hexToArrayBuffer, parseLinktext, getLinkpath, stripHeading, stripHeadingForLink, setIcon, setTooltip, displayTooltip, addIcon, removeIcon, getIcon, getIconIds, requestUrl, request, // NOTE: ReamsRequestUrlError is Reams-specific (not in upstream Obsidian API). // It is NOT exposed via require('obsidian'). Plugins needing it should // import from 'reams' instead. Platform, moment: momentLib, htmlToMarkdown, sanitizeHTMLToDom, MarkdownRenderer, // Bases utility functions and value types (Obsidian 1.10.0+) BasesEntry, BasesEntryGroup, QueryController, BasesQueryResult, BasesViewConfig, BasesView, parsePropertyId, Value, NullValue, NotNullValue, PrimitiveValue, BooleanValue, NumberValue, StringValue, HTMLValue, ImageValue, UrlValue, FileValue, LinkValue, IconValue, TagValue, ListValue, RegExpValue, // Search helpers (Obsidian-compatible) prepareFuzzySearch, prepareSimpleSearch, renderMatches, renderResults, sortSearchResults, // API version and version check apiVersion, requireApiVersion, // Library loading functions (stub implementations) loadMathJax, loadMermaid, loadPdfJs, loadPrism, // Math rendering functions (stub implementations since MathJax not bundled) renderMath, finishRenderMath, // Secret storage (Obsidian 1.11.4+) SecretStorage, // CodeMirror 6 re-exports — Obsidian bundles CM6 and re-exports key primitives // via `import { EditorView, ... } from 'obsidian'`. // See obsidian.d.ts: `import { Extension, StateField } from '@codemirror/state'` // `import { EditorView, ViewPlugin } from '@codemirror/view'` // @codemirror/state // EditorState intentionally excluded — not in Obsidian's public API. Plugins use require('@codemirror/state') directly. // EditorSelection (CM6 class) intentionally excluded — Obsidian's EditorSelection is a simple {anchor, head} interface (type-only, no runtime value). // SelectionRange intentionally excluded — not in Obsidian's public API // Prec intentionally excluded — not in Obsidian's public API. Plugins use require('@codemirror/state') directly. // Compartment intentionally excluded — not in Obsidian's public API. Plugins use require('@codemirror/state') directly. // StateField intentionally excluded — not in Obsidian's public API. Plugins use require('@codemirror/state') directly. // StateEffect intentionally excluded — not in Obsidian's public API. Plugins use require('@codemirror/state') directly. // Facet intentionally excluded — not in Obsidian's public API. Plugins use require('@codemirror/state') directly. // Transaction intentionally excluded — not in Obsidian's public API // Annotation, AnnotationType intentionally excluded — not in Obsidian's public API // ChangeSet, ChangeDesc, StateEffectType, RangeSet, RangeSetBuilder, MapMode intentionally excluded — not in Obsidian's public API // Text intentionally excluded — not in Obsidian's public API // Line intentionally excluded — not in Obsidian's public API // @codemirror/view // EditorView intentionally excluded — not in Obsidian's public API. Plugins use require('@codemirror/view') directly. // ViewPlugin intentionally excluded — not in Obsidian's public API (but available via require('@codemirror/view')) // ViewUpdate intentionally excluded — not in Obsidian's public API // Decoration intentionally excluded — not in Obsidian's public API. Plugins use require('@codemirror/view') directly. // WidgetType intentionally excluded — not in Obsidian's public API // MatchDecorator intentionally excluded — not in Obsidian's public API // highlightActiveLineGutter intentionally excluded — not in Obsidian's public API // highlightActiveLine intentionally excluded — not in Obsidian's public API // dropCursor intentionally excluded — not in Obsidian's public API // crosshairCursor intentionally excluded — not in Obsidian's public API // rectangularSelection intentionally excluded — not in Obsidian's public API // gutter, GutterMarker, gutters intentionally excluded — not in Obsidian's public API // hoverTooltip intentionally excluded — not in Obsidian's public API // showTooltip intentionally excluded — not in Obsidian's public API (but available via require('@codemirror/view')) // lineNumbers intentionally excluded — not in Obsidian's public API (but available via require('@codemirror/view')) // keymap intentionally excluded — not in Obsidian's public API (but available via require('@codemirror/view')) // highlightSpecialChars intentionally excluded — not in Obsidian's public API (but available via require('@codemirror/view')) // BlockInfo intentionally excluded — not in Obsidian's public API (but available via require('@codemirror/view')) // @codemirror/language // NOTE: getIndentUnit intentionally excluded — not in Obsidian's public API // NOTE: syntaxTree intentionally excluded — not in Obsidian's public API // NOTE: syntaxTreeAvailable intentionally excluded — not in Obsidian's public API // NOTE: getIndentation intentionally excluded — not in Obsidian's public API // NOTE: foldCode and unfoldCode intentionally excluded — not in Obsidian's public API // NOTE: unfoldAll intentionally excluded — not in Obsidian's public API // NOTE: indentOnInput intentionally excluded — not in Obsidian's public API // NOTE: foldKeymap intentionally excluded — not in Obsidian's public API // NOTE: foldable intentionally excluded — not in Obsidian's public API // NOTE: foldEffect intentionally excluded — not in Obsidian's public API // NOTE: unfoldEffect intentionally excluded — not in Obsidian's public API // NOTE: foldedRanges intentionally excluded — not in Obsidian's public API // NOTE: foldAll intentionally excluded — not in Obsidian's public API // NOTE: bracketMatching intentionally excluded — not in Obsidian's public API // NOTE: indentUnit intentionally excluded — not in Obsidian's public API // NOTE: Language intentionally excluded — not in Obsidian's public API // NOTE: LRLanguage intentionally excluded — not in Obsidian's public API // NOTE: LanguageDescription intentionally excluded — not in Obsidian's public API // NOTE: HighlightStyle intentionally excluded — not in Obsidian's public API // NOTE: defaultHighlightStyle intentionally excluded — not in Obsidian's public API // @codemirror/commands // NOTE: history intentionally excluded — not in Obsidian's public API // NOTE: undoDepth and redoDepth intentionally excluded — not in Obsidian's public API // NOTE: indentMore intentionally excluded — not in Obsidian's public API // NOTE: insertTab intentionally excluded — not in Obsidian's public API // NOTE: undo intentionally excluded — not in Obsidian's public API // NOTE: redo intentionally excluded — not in Obsidian's public API // NOTE: defaultKeymap intentionally excluded — not in Obsidian's public API // indentWithTab intentionally excluded — not in Obsidian's public API. Plugins use require('@codemirror/commands') directly. // insertNewlineAndIndent intentionally excluded — not in Obsidian's public API // indentLess intentionally excluded — not in Obsidian's public API // cursorCharLeft intentionally excluded — not in Obsidian's public API // cursorLineUp intentionally excluded — not in Obsidian's public API // cursorLineDown intentionally excluded — not in Obsidian's public API // cursorLineStart intentionally excluded — not in Obsidian's public API // cursorLineEnd intentionally excluded — not in Obsidian's public API // cursorDocStart intentionally excluded — not in Obsidian's public API // cursorDocEnd intentionally excluded — not in Obsidian's public API // selectCharRight intentionally excluded — not in Obsidian's public API // selectLineUp intentionally excluded — not in Obsidian's public API // selectLineDown intentionally excluded — not in Obsidian's public API // selectLineStart intentionally excluded — not in Obsidian's public API // selectLineEnd intentionally excluded — not in Obsidian's public API // selectDocStart intentionally excluded — not in Obsidian's public API // selectDocEnd intentionally excluded — not in Obsidian's public API // selectAll intentionally excluded — not in Obsidian's public API // deleteLine intentionally excluded — not in Obsidian's public API // moveLineUp intentionally excluded — not in Obsidian's public API. Plugins use require('@codemirror/commands') directly. // copyLineUp intentionally excluded — not in Obsidian's public API. Plugins use require('@codemirror/commands') directly. // copyLineDown intentionally excluded — not in Obsidian's public API // deleteCharBackward intentionally excluded — not in Obsidian's public API. Plugins use require('@codemirror/commands') directly. // @codemirror/autocomplete // NOTE: autocompletion, moveCompletionSelection, CompletionContext, snippet, // snippetCompletion, acceptCompletion, startCompletion, ifNotIn, completionKeymap, // and completeFromList are NOT part of Obsidian's public API (but available via require('@codemirror/autocomplete')) // NOTE: @lezer/highlight exports (classHighlighter, etc.) are NOT part of // Obsidian's public API. Internal code should import directly from @lezer/highlight. // @lezer/common // NOTE: Tree removed from require('obsidian') - not part of Obsidian's public API. // Still available via require('@lezer/common') in lezerCommonModule. // NOTE: TreeCursor removed from require('obsidian') - not part of Obsidian's public API. // NOTE: NodeType removed from require('obsidian') - not part of Obsidian's public API. // Still available via require('@lezer/common') in lezerCommonModule. // NOTE: NodeProp removed from require('obsidian') - not part of Obsidian's public API. // Still available via require('@lezer/common') in lezerCommonModule. // Obsidian-private CM6 fields plugins read directly. SNW reads // `editorInfoField` to discover the active editor's file. editorInfoField, // Deprecated alias kept for public API parity with ESM exports. editorViewField, // `editorEditorField` to get the CM6 EditorView reference. editorEditorField, // `editorLivePreviewField` to discover whether the host editor is in // live preview mode. editorLivePreviewField, // `livePreviewState` tracks the editor's live preview mouse state. livePreviewState, // Workspace compat classes (F-COMPAT-9, chainlink #341) // Stub classes for API compatibility so plugins can: // - `import { WorkspaceSidedock } from 'obsidian'` // - `leaf.parent instanceof WorkspaceSidedock` WorkspaceItem, WorkspaceParent, WorkspaceSplit, WorkspaceFloating, WorkspaceSidedock, WorkspaceContainer, WorkspaceRoot, WorkspaceRibbon, WorkspaceWindow, WorkspaceTabs, WorkspaceMobileDrawer, MarkdownPreviewView, MarkdownPreviewRenderer, MarkdownEditView, // Data adapter classes (Obsidian-compatible) // Stub class for API compatibility so plugins can check // `adapter instanceof FileSystemAdapter` without crashing. FileSystemAdapter, // Subpath and reference iteration utilities resolveSubpath, iterateCacheRefs, iterateRefs, } /** * Options for createEl/createDiv/createSpan. * Matches Obsidian's DomElementInfo interface. */ interface DomElementInfo { text?: string cls?: string | string[] attr?: Record title?: string placeholder?: string href?: string type?: string value?: string prepend?: boolean parent?: HTMLElement } /** * Apply DomElementInfo options to an element. * Shared by HTMLElement.prototype methods and global window functions — DRY. */ function applyDomElementInfo(el: HTMLElement, o?: DomElementInfo | string): void { if (typeof o === 'string') { el.className = o } else if (o) { if (o.text != null) el.textContent = o.text if (o.cls != null) { if (Array.isArray(o.cls)) el.className = o.cls.join(' ') else el.className = o.cls } if (o.attr) { for (const [k, v] of Object.entries(o.attr)) { el.setAttribute(k, v) } } if (o.title != null) el.title = o.title if (o.placeholder != null) (el as any).placeholder = o.placeholder if (o.href != null) (el as any).href = o.href if (o.type != null) (el as any).type = o.type if (o.value != null) (el as any).value = o.value } } /** * Install Obsidian-compatible extensions on HTMLElement.prototype. * * In real Obsidian, these are global monkey-patches. Plugins depend on them * heavily (Calendar, Periodic Notes, Dataview, etc.). * * Idempotent — safe to call multiple times. */ function installHtmlElementExtensions(): void { if (typeof HTMLElement === 'undefined') return const proto = HTMLElement.prototype as any // Guard: only install once (non-enumerable to avoid polluting for...in) if (proto._reamsExtInstalled) return Object.defineProperty(proto, '_reamsExtInstalled', { value: true, writable: false, configurable: false, enumerable: false, }) proto.createEl = function createEl( tag: string, o?: DomElementInfo | string, callback?: (el: HTMLElement) => void ): HTMLElement { const el = document.createElement(tag) applyDomElementInfo(el, o) if (typeof o === 'object' && o?.prepend) { this.prepend(el) } else { this.appendChild(el) } if (callback) callback(el) return el } proto.createDiv = function createDiv( o?: DomElementInfo | string, callback?: (el: HTMLDivElement) => void ): HTMLDivElement { return this.createEl('div', o, callback) as HTMLDivElement } proto.createSpan = function createSpan( o?: DomElementInfo | string, callback?: (el: HTMLSpanElement) => void ): HTMLSpanElement { return this.createEl('span', o, callback) as HTMLSpanElement } proto.empty = function empty(): void { while (this.firstChild) this.removeChild(this.firstChild) } proto.toggleVisibility = function toggleVisibility(visible: boolean): void { this.style.display = visible ? '' : 'none' } // setText — set textContent. Obsidian plugins call el.setText("hello") // instead of el.textContent = "hello". proto.setText = function setText(text: string): void { this.textContent = text } // Dataview uses appendText for safe text output. Do not use innerHTML here: // values rendered from notes must remain text, not markup. proto.appendText = function appendText(text: string): globalThis.Text { const node = document.createTextNode(text) this.appendChild(node) return node } // addClass — add one or more CSS classes. Obsidian plugins call // el.addClass("is-active") instead of el.classList.add("is-active"). proto.addClass = function addClass(...classes: string[]): void { this.classList.add(...classes) } proto.addClasses = function addClasses(classes: string[]): void { this.classList.add(...classes) } // removeClass — remove one or more CSS classes. proto.removeClass = function removeClass(...classes: string[]): void { this.classList.remove(...classes) } // setAttr — setAttribute shorthand. Obsidian plugins call // el.setAttr("draggable", "true") instead of el.setAttribute(...). proto.setAttr = function setAttr(key: string, value: string): void { this.setAttribute(key, value) } // setChildrenInPlace — replace element children with a new set of nodes. // Obsidian plugins call this.contentEl.setChildrenInPlace([newRoot]) to // efficiently swap the entire content of a view. Reuses existing children // where possible, removes extras, appends new ones. proto.setChildrenInPlace = function setChildrenInPlace(children: Node[]): void { const existing = Array.from(this.childNodes) as Node[] // Remove children not in the new set for (const child of existing) { if (!children.includes(child)) { this.removeChild(child) } } // Append/reorder children to match the new set for (let i = 0; i < children.length; i++) { const child = children[i] if (this.childNodes[i] !== child) { this.insertBefore(child, this.childNodes[i] || null) } } } // Obsidian's delegated event system. Plugins call // el.on('click', '.selector', handler) for CSS-selector-based event delegation, // or el.on('click', handler) for direct event binding. // Uses addEventListener + event.target.closest(selector) for delegation. const DELEGATED_LISTENERS = new WeakMap void wrappedHandler: (e: Event) => void }>>>() proto.on = function on( event: string, selectorOrHandler: string | ((...args: unknown[]) => void), handler?: (...args: unknown[]) => void, _options?: unknown ): void { const selector = typeof selectorOrHandler === 'string' ? selectorOrHandler : null const actualHandler = typeof selectorOrHandler === 'function' ? selectorOrHandler : handler! const wrappedHandler = (e: Event) => { if (selector) { // Delegated: check if target matches selector const target = (e.target as HTMLElement)?.closest?.(selector) if (target && this.contains(target)) { actualHandler.call(target, e) } } else { actualHandler.call(this, e) } } this.addEventListener(event, wrappedHandler) // Store for later removal via .off() if (!DELEGATED_LISTENERS.has(this)) { DELEGATED_LISTENERS.set(this, new Map()) } const listenersMap = DELEGATED_LISTENERS.get(this)! if (!listenersMap.has(event)) { listenersMap.set(event, []) } listenersMap.get(event)!.push({ selector, handler: actualHandler, wrappedHandler }) } proto.off = function off( event: string, selectorOrHandler: string | ((...args: unknown[]) => void), handler?: (...args: unknown[]) => void, _options?: unknown ): void { const selector = typeof selectorOrHandler === 'string' ? selectorOrHandler : null const actualHandler = typeof selectorOrHandler === 'function' ? selectorOrHandler : handler! const listenersMap = DELEGATED_LISTENERS.get(this) if (!listenersMap) return const entries = listenersMap.get(event) if (!entries) return const idx = entries.findIndex(e => e.selector === selector && e.handler === actualHandler) if (idx !== -1) { this.removeEventListener(event, entries[idx].wrappedHandler) entries.splice(idx, 1) } } proto.trigger = function trigger(event: string, ...args: unknown[]): void { const evt = new CustomEvent(event, { detail: args, bubbles: true }) this.dispatchEvent(evt) } // Obsidian adds detach() on Node.prototype — equivalent to remove() if (!(Node.prototype as any).detach) { ;(Node.prototype as any).detach = function detach(): void { if (this.parentNode) this.parentNode.removeChild(this) } } } declare global { interface Window { __REAMS_PLUGIN_API__: Record __REAMS_CM6_MODULES__?: Record> } } /** * Install the plugin API on the global scope. * Must be called once before external plugins are loaded. */ export function installPluginApiGlobal(): void { // Install moment on window for plugins that use window.moment directly ;(window as any).moment = momentLib // Obsidian exposes its active document as a global. Some desktop plugins // use this identifier directly rather than `document` or `window.document`. ;(window as any).activeDocument = document window.__REAMS_PLUGIN_API__ = pluginApi // Install CM6 modules for require('@codemirror/state'), etc. // Obsidian bundles CM6 and plugins can require these modules directly. if (!window.__REAMS_CM6_MODULES__) { // NOTE: @lezer/highlight is NOT part of Obsidian's public API and is not exposed. window.__REAMS_CM6_MODULES__ = { '@codemirror/state': codemirrorStateModule, '@codemirror/view': codemirrorViewModule, '@codemirror/language': codemirrorLanguageModule, '@codemirror/commands': codemirrorCommandsModule, '@codemirror/autocomplete': codemirrorAutocompleteModule, '@lezer/common': lezerCommonModule, ...browserNodeModules, } } // Obsidian adds String.isString — plugins like QuickAdd use it // to validate settings values. Standard JS has no such method. if (!(String as any).isString) { ;(String as any).isString = function isString(val: unknown): val is string { return typeof val === 'string' } } // Obsidian historically exposes the legacy CodeMirror 5 global to plugins // for backward compat. Many plugins (e.g. cm-typewriter-scroll-obsidian) // touch `CodeMirror.commands` / `CodeMirror.defineOption` at module-load // time, even when their actual editor functionality runs through CM6 via // `registerEditorExtension()`. Without this stub the module-level access // throws ReferenceError before the plugin's real CM6 code can register. // // This is intentionally NO-OP: we do NOT execute any CM5 commands, option, // or mode callbacks. The stub exists only to allow module evaluation to // succeed so the plugin's CM6 path runs. const legacyCodeMirror = ((window as any).CodeMirror ??= {}) as { commands?: Record defineOption?: (name: string, defaultVal: unknown, onChange: (...args: unknown[]) => void) => void defineMode?: (name: string, factory: (...args: unknown[]) => unknown) => void Init?: symbol } legacyCodeMirror.commands ??= {} legacyCodeMirror.defineOption ??= (_name: string, _defaultVal: unknown, _onChange: (...args: unknown[]) => void) => { // No-op: we don't run CM5 callbacks. CM6 path handles real behavior. } legacyCodeMirror.defineMode ??= (_name: string, _factory: (...args: unknown[]) => unknown) => { // No-op: Dataview only registers this legacy mode during startup. } legacyCodeMirror.Init ??= Symbol('CodeMirror.Init') // Install HTMLElement extensions (createEl, createDiv, etc.) installHtmlElementExtensions() // Install global shortcut functions (create detached elements, not appended to any parent) if (typeof window !== 'undefined') { ;(window as any).createFragment = function createFragment( callback?: (el: DocumentFragment) => void ): DocumentFragment { const frag = document.createDocumentFragment() if (callback) callback(frag) return frag } ;(window as any).createDiv = function createDiv( o?: DomElementInfo | string, callback?: (el: HTMLDivElement) => void ): HTMLDivElement { const el = document.createElement('div') as HTMLDivElement applyDomElementInfo(el, o) if (typeof o === 'object' && o?.parent) o.parent.appendChild(el) if (callback) callback(el) return el } ;(window as any).createSpan = function createSpan( o?: DomElementInfo | string, callback?: (el: HTMLSpanElement) => void ): HTMLSpanElement { const el = document.createElement('span') as HTMLSpanElement applyDomElementInfo(el, o) if (typeof o === 'object' && o?.parent) o.parent.appendChild(el) if (callback) callback(el) return el } } }