Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181// 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<string, unknown> = { 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<string, unknown> = { 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<string, unknown> = { 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<string, unknown> = { 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<string, unknown> = { 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<string, unknown> = { // 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<string, Record<string, unknown>> = { 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<T extends AnyConstructor>(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<string, unknown> = { // 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<T>` 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<string, string> 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<HTMLElement, Map<string, Array<{ selector: string | null handler: (...args: unknown[]) => 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<string, unknown> __REAMS_CM6_MODULES__?: Record<string, Record<string, unknown>> }}
/** * 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<string, unknown> 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 } }}