diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2fcbee9..63ba809 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,86 +1,33 @@ -name: Release plugin +name: Release Obsidian plugin on: push: - # Sequence of patterns matched against refs/tags tags: - - "*" # Push events to matching any tag format, i.e. 1.0, 20.15.10 - -env: - PLUGIN_NAME: obisidian-3d-graph + - "*" jobs: build: runs-on: ubuntu-latest steps: - - uses: jetli/wasm-pack-action@v0.3.0 + - uses: actions/checkout@v3 + + - uses: oven-sh/setup-bun@v1 with: - # Optional version of wasm-pack to install(eg. 'v0.9.1', 'latest') - version: "latest" - - uses: actions/checkout@v2 - - name: Use Node.js - uses: actions/setup-node@v1 - with: - node-version: "14.x" # You might need to adjust this value to your own version - - name: Build - id: build + bun-version: latest + + - name: Build plugin run: | - npm install - npm run build - mkdir ${{ env.PLUGIN_NAME }} - cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }} - zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} - ls - echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)" - - name: Create Release - id: create_release - uses: actions/create-release@v1 + bun install + bun run build + + - name: Create release env: - GITHUB_TOKEN: ${{ secrets.OBSIDIAN_3D_GRAPH_TOKEN }} - VERSION: ${{ github.ref }} - with: - tag_name: ${{ github.ref }} - release_name: ${{ github.ref }} - draft: false - prerelease: false - - name: Upload zip file - id: upload-zip - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.OBSIDIAN_3D_GRAPH_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./${{ env.PLUGIN_NAME }}.zip - asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip - asset_content_type: application/zip - - name: Upload main.js - id: upload-main - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.OBSIDIAN_3D_GRAPH_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./main.js - asset_name: main.js - asset_content_type: text/javascript - - name: Upload manifest.json - id: upload-manifest - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.OBSIDIAN_3D_GRAPH_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./manifest.json - asset_name: manifest.json - asset_content_type: application/json - - name: Upload main.js - id: upload-css - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.OBSIDIAN_3D_GRAPH_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./styles.css - asset_name: styles.css - asset_content_type: text/css + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + tag="${GITHUB_REF#refs/tags/}" + + gh release create "$tag" \ + --title="$tag" \ + --draft \ + main.js manifest.json styles.css diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..eb6f8ed --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +bun run typecheck +bun run build diff --git a/LICENSE b/LICENSE index 5f406ea..692d20c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Alexander Weichart +Copyright (c) 2023 Hananoshika Yomuru Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 4feb993..7caf7f2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -## Obsidian 3D Graph +## Obsidian 3D Graph (Yomaru) + +> This is a fork from the original https://github.com/AlexW00/obsidian-3d-graph A 3D Graph for Obsidian! @@ -8,9 +10,10 @@ https://user-images.githubusercontent.com/55558407/190087315-8386feee-b861-4520- ### ⬇️ Installation -3D-Graph is an official community plugin. You can download by: -- clicking [here](https://obsidian.md/plugins?id=3d-graph) -- searching for "3D Graph" in the Obsidian plugins tab +3D-Graph is an official community plugin. You can download by: + +- clicking [here](https://obsidian.md/plugins?id=3d-graph) +- searching for "3D Graph" in the Obsidian plugins tab ### 👨‍💻 Development diff --git a/bun-fix.d.ts b/bun-fix.d.ts new file mode 100644 index 0000000..7d17aa0 --- /dev/null +++ b/bun-fix.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..d99a36d Binary files /dev/null and b/bun.lockb differ diff --git a/esbuild.config.mjs b/esbuild.config.mjs index b33ec53..e4197e9 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -1,42 +1,48 @@ import esbuild from "esbuild"; import process from "process"; -import builtins from 'builtin-modules' +import builtins from "builtin-modules"; -const banner = -`/* +const banner = `/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ `; -const prod = (process.argv[2] === 'production'); +const prod = process.argv[2] === "production"; -esbuild.build({ +const context = await esbuild.context({ banner: { js: banner, }, - entryPoints: ['src/main.ts'], + entryPoints: ["src/main.ts"], bundle: true, external: [ - 'obsidian', - 'electron', - '@codemirror/autocomplete', - '@codemirror/collab', - '@codemirror/commands', - '@codemirror/language', - '@codemirror/lint', - '@codemirror/search', - '@codemirror/state', - '@codemirror/view', - '@lezer/common', - '@lezer/highlight', - '@lezer/lr', - ...builtins], - format: 'cjs', - watch: !prod, - target: 'es2018', + "obsidian", + "electron", + "@codemirror/autocomplete", + "@codemirror/collab", + "@codemirror/commands", + "@codemirror/language", + "@codemirror/lint", + "@codemirror/search", + "@codemirror/state", + "@codemirror/view", + "@lezer/common", + "@lezer/highlight", + "@lezer/lr", + ...builtins, + ], + format: "cjs", + target: "es2018", logLevel: "info", - sourcemap: prod ? false : 'inline', + sourcemap: prod ? false : "inline", treeShaking: true, - outfile: 'main.js', -}).catch(() => process.exit(1)); + outfile: "main.js", +}); + +if (prod) { + await context.rebuild(); + process.exit(0); +} else { + await context.watch(); +} diff --git a/package.json b/package.json index 38fcb8f..248314e 100644 --- a/package.json +++ b/package.json @@ -4,27 +4,39 @@ "description": "A 3D graph for Obsidian", "main": "main.js", "scripts": { - "dev": "node esbuild.config.mjs", - "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", + "dev": "bun esbuild.config.mjs", + "build": "bun esbuild.config.mjs production", + "version": "bun version-bump.mjs && git add manifest.json versions.json", "build:copy": "npm run build && ./copy_to_vault.sh ~/Desktop/YouTube", - "version": "node version-bump.mjs && git add manifest.json versions.json" + "prepare": "husky install", + "typecheck": " tsc -noEmit -skipLibCheck" }, - "keywords": [], - "author": "", + "keywords": [ + "obsidian", + "plugin", + "3D", + "D3", + "3D graph", + "graph" + ], + "author": "Hananoshika Yomaru", "license": "MIT", "devDependencies": { + "@total-typescript/ts-reset": "^0.5.1", "@types/d3": "^7.4.0", "@types/node": "^16.11.6", "@typescript-eslint/eslint-plugin": "5.29.0", "@typescript-eslint/parser": "5.29.0", "builtin-modules": "3.3.0", - "esbuild": "0.14.47", + "bun-types": "^1.0.5", + "esbuild": "0.17.3", + "husky": "^8.0.3", "obsidian": "latest", "tslib": "2.4.0", - "typescript": "4.7.4" + "typescript": "^5.0.5" }, "dependencies": { - "3d-force-graph": "^1.70.12", + "3d-force-graph": "^1.72.3", "d3": "^7.6.1", "observable-slim": "^0.1.6" } diff --git a/src/graph/Graph.ts b/src/graph/Graph.ts index 86c5bbc..46fb0e3 100644 --- a/src/graph/Graph.ts +++ b/src/graph/Graph.ts @@ -26,6 +26,7 @@ export default class Graph { public getNodeById(id: string): Node | null { const index = this.nodeIndex.get(id); if (index !== undefined) { + // @ts-ignore return this.nodes[index]; } return null; @@ -40,6 +41,7 @@ export default class Graph { if (sourceLinkMap) { const index = sourceLinkMap.get(targetNodeId); if (index !== undefined) { + // @ts-ignore return this.links[index]; } } @@ -50,6 +52,7 @@ export default class Graph { public getLinksFromNode(sourceNodeId: string): Link[] { const sourceLinkMap = this.linkIndex.get(sourceNodeId); if (sourceLinkMap) { + // @ts-ignore return Array.from(sourceLinkMap.values()).map( (index) => this.links[index] ); diff --git a/src/graph/Link.ts b/src/graph/Link.ts index d83ef1c..7319f41 100644 --- a/src/graph/Link.ts +++ b/src/graph/Link.ts @@ -32,6 +32,7 @@ export default class Link { ): [Link[], Map>] { const links = Object.keys(cache) .map((node1Id) => { + // @ts-ignore return Object.keys(cache[node1Id]) .map((node2Id) => { const [node1Index, node2Index] = [ @@ -42,7 +43,9 @@ export default class Link { node1Index !== undefined && node2Index !== undefined ) { + // @ts-ignore return nodes[node1Index].addNeighbor( + // @ts-ignore nodes[node2Index] ); } diff --git a/src/main.ts b/src/main.ts index 2ada708..e9122fd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,7 +6,9 @@ import Graph from "./graph/Graph"; import ObsidianTheme from "./util/ObsidianTheme"; import EventBus from "./util/EventBus"; import { ResolvedLinkCache } from "./graph/Link"; -import shallowCompare from "./util/ShallowCompare"; +import compare from "./util/ShallowCompare"; +import "@total-typescript/ts-reset"; +import "@total-typescript/ts-reset/dom"; export default class Graph3dPlugin extends Plugin { _resolvedCache: ResolvedLinkCache; @@ -122,10 +124,7 @@ export default class Graph3dPlugin extends Plugin { // with the resolve event if ( this.cacheIsReady.value && - !shallowCompare( - this._resolvedCache, - this.app.metadataCache.resolvedLinks - ) + !compare(this._resolvedCache, this.app.metadataCache.resolvedLinks) ) { this._resolvedCache = structuredClone( this.app.metadataCache.resolvedLinks @@ -136,7 +135,7 @@ export default class Graph3dPlugin extends Plugin { "changed but ", this.cacheIsReady.value, " and ", - shallowCompare( + compare( this._resolvedCache, this.app.metadataCache.resolvedLinks ) diff --git a/src/typings/obsidian-ex.d.ts b/src/typings/obsidian-ex.d.ts new file mode 100644 index 0000000..50ddc26 --- /dev/null +++ b/src/typings/obsidian-ex.d.ts @@ -0,0 +1,3651 @@ +import { + App, + CachedMetadata, + Command, + Constructor, + DataWriteOptions, + EditorPosition, + EditorRange, + EditorSuggest, + EventRef, + Events, + FileView, + KeymapEventHandler, + KeymapEventListener, + KeymapInfo, + Loc, + Modifier, + ObsidianProtocolHandler, + OpenViewState, + PaneType, + Plugin, + Reference, + SplitDirection, + TAbstractFile, + TFile, + TFolder, + View, + ViewState, + WorkspaceLeaf, + WorkspaceMobileDrawer, + WorkspaceSidedock, + WorkspaceSplit, + WorkspaceTabs, + WorkspaceWindow, + WorkspaceWindowInitData, +} from "obsidian"; +import { EditorView } from "@codemirror/view"; +import { EditorState, Extension } from "@codemirror/state"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +interface Account { + /** + * The company associated with the activated commercial license + */ + company: string; + /** + * The email address associated with the account + */ + email: string; + /** + * + */ + expiry: number; + /** + * + */ + key: string | undefined; + /** + * + */ + keyValidation: string; + /** + * The license available to the account + */ + license: "" | "insider"; + /** + * Profile name + */ + name: string; + /** + * + */ + seats: number; + /** + * + */ + token: string; + + // TODO: Add Sync and Publish API functions here +} + +// interface AppMenuBarManager { +// /** +// * Reference to App +// */ +// app: App; +// +// /** +// * +// */ +// requestRender: () => void; +// +// /** +// * +// */ +// requestUpdateViewState: () => void; +// } + +interface Commands { + /** + * Reference to App + */ + app: App; + + /** + * Commands *without* editor callback, will always be available in the command palette + * @example `app:open-vault` or `app:reload` + */ + commands: Record; + /** + * Commands *with* editor callback, will only be available when editor is active and callback returns true + * @example `editor:fold-all` or `command-palette:open` + */ + editorCommands: Record; + /** + * Add a command to the command registry + * @param command Command to add + */ + addCommand: (command: Command) => void; + /** + * Execute a command by reference + * @param command Command to execute + */ + executeCommand: (command: Command) => boolean; + /** + * Execute a command by ID + * @param commandId ID of command to execute + */ + executeCommandById: (commandId: string) => boolean; + /** + * Find a command by ID + * @param commandId + */ + findCommand: (commandId: string) => Command | undefined; + /** + * Lists **all** commands, both with and without editor callback + */ + listCommands: () => Command[]; + /** + * Remove a command from the command registry + * @param commandId Command to remove + */ + removeCommand: (commandId: string) => void; +} + +// interface ThemeRecord { +// /** +// * Author of the theme +// */ +// author: string; +// /** +// * @internal +// */ +// authorEl?: HTMLElement; +// /** +// * Amount of downloads the theme +// */ +// downloads: number; +// /** +// * @internal +// */ +// el?: HTMLElement; +// matches: null; +// modes: ['light', 'dark'] | ['light']; +// name: string; +// nameEl?: HTMLElement; +// repo: string; +// score: number; +// screenshot: string; +// updateIconEl?: HTMLElement; +// /** +// * Whether the theme was updated +// */ +// updated: number; +// } + +interface ThemeManifest { + /** + * Name of the author of the theme + */ + author: string; + /** + * URL to the author's website + */ + authorUrl?: string; + /** + * Storage location of the theme relative to the vault root + */ + dir: string; + /** + * URL for funding the author + */ + fundingUrl?: string; + /** + * Minimum Obsidian version compatible with the theme + */ + minAppVersion: string; + /** + * Name of the theme + */ + name: string; + /** + * Version of the theme + * @remark Defaults to "0.0.0" if no theme manifest was provided in the repository + */ + version: "0.0.0" | string; +} + +interface CustomCSS extends Component { + /** + * Reference to App + */ + app: App; + /** + * @internal + */ + boundRaw: () => void; + /** + * @internal Cache of CSS snippet filepath (relative to vault root) to CSS snippet contents + */ + csscache: Map; + /** + * Set of enabled snippet, given by filenames + */ + enabledSnippets: Set; + /** + * @internal + * Contains references to Style elements containing custom CSS snippets + */ + extraStyleEls: HTMLStyleElement[]; + /** + * List of theme names not fully updated to post v1.0.0 theme guidelines + */ + oldThemes: string[]; + /** + * @internal + */ + queue: WeakMap; + /** + * @internal + */ + requestLoadSnippets: () => void; + /** + * @internal + */ + requestLoadTheme: () => void; + /** + * @internal + */ + requestReadThemes: () => void; + /** + * List of snippets detected by Obsidian, given by their filenames + */ + snippets: string[]; + /** + * Currently active theme, given by its name + * @remark "" is the default Obsidian theme + */ + theme: "" | string; + /** + * Mapping of theme names to their manifest + */ + themes: Record; + /** + * @internal + */ + updates: Record; + + /** + * Check whether a specific theme can be updated + * @param themeName - Name of the theme to check + */ + checkForUpdate: (themeName: string) => void; + /** + * Check all themes for updates + */ + checkForUpdates: () => void; + /** + * Disable translucency of application background + */ + disableTranslucency: () => void; + /** + * Fetch legacy theme CSS using the pre-v1.0.0 theme download pipeline + * @returns string obsidian.css contents + */ + downloadLegacyTheme: ({ repo: string }) => Promise; + /** + * Enable translucency of application background + */ + enableTranslucency: () => void; + /** + * Fetch a theme's manifest using repository URL + * @remark Do **not** include github prefix, only `username/repo` + */ + getManifest: (repoUrl: string) => Promise; + /** + * Convert snippet name to its corresponding filepath (relative to vault root) + * @returns string `.obsidian/snippets/${snippetName}.css` + */ + getSnippetPath: (snippetName: string) => string; + /** + * Returns the folder path where snippets are stored (relative to vault root) + */ + getSnippetsFolder: () => string; + /** + * Returns the folder path where themes are stored (relative to vault root) + */ + getThemesFolder: () => string; + /** + * Convert theme name to its corresponding filepath (relative to vault root) + * @returns string `.obsidian/themes/${themeName}/theme.css` + */ + getThemePath: (themeName: string) => string; + /** + * Returns whether there are themes that can be updated + */ + hasUpdates: () => boolean; + /** + * Install a legacy theme using the pre-v1.0.0 theme download pipeline
+ * Will create a corresponding dummy manifest for the theme + * @remark Name will be used as the folder name for the theme + */ + installLegacyTheme: ({ + name: string, + repo: string, + author: string, + }) => Promise; + /** + * Install a theme using the regular theme download pipeline + */ + installTheme: ( + { name: string, repo: string, author: string }, + version: string + ) => Promise; + /** + * Check whether a specific theme is installed by theme name + */ + isThemeInstalled: (themeName: string) => boolean; + /** + * @internal + */ + onRaw: (e: any) => void; + /** + * @internal + */ + onload: () => void; + /** + * @todo + * @internal + */ + readSnippets: () => void; + /** + * @todo + * @internal + */ + readThemes: () => void; + /** + * Remove a theme by theme name + */ + removeTheme: (themeName: string) => Promise; + /** + * Set the activation status of a snippet by snippet name + */ + setCssEnabledStatus: (snippetName: string, enabled: boolean) => void; + /** + * Set the active theme by theme name + */ + setTheme: (themeName: string) => void; + /** + * Set the translucency of application background + */ + setTranslucency: (translucency: boolean) => void; +} + +interface ObsidianDOM { + /** + * Root element of the application + */ + appContainerEl: HTMLElement; + /** + * Child of `appContainerEl` containing the main content of the application + */ + horizontalMainContainerEl: HTMLElement; + /** + * Status bar element containing word count among other things + */ + statusBarEl: HTMLElement; + /** + * Child of `horizontalMainContainerEl` containing the workspace DOM + */ + workspaceEl: HTMLElement; +} + +// interface EmbedRegistry { +// embedByExtension: Map any>; +// } + +interface PositionedReference extends Reference { + /** + * Position of the reference in the file + */ + position: { + start: Loc; + end: Loc; + }; +} + +interface LinkUpdate { + /** + * Reference to App + */ + app: App; + /** + * Link position in the file + */ + reference: PositionedReference; + /** + * File that was resolved + */ + resolvedFile: TFile; + /** + * Paths the file could have been resolved to + */ + resolvedPaths: string[]; + /** + * File that contains the link + */ + sourceFile: TFile; +} + +interface HotkeyManager { + /** + * Reference to App + */ + app: App; + /** + * @internal Whether hotkeys have been baked (checks completed) + */ + baked: boolean; + /** + * Assigned hotkeys + */ + bakedHotkeys: KeymapInfo[]; + /** + * Array of hotkey index to command ID + */ + bakedIds: string[]; + /** + * Custom (non-Obsidian default) hotkeys, one to many mapping of command ID to assigned hotkey + */ + customKeys: Record; + /** + * Default hotkeys, one to many mapping of command ID to assigned hotkey + */ + defaultKeys: Record; + + /** + * Add a hotkey to the default hotkeys + * @param command - Command ID to add hotkey to + * @param keys - Hotkeys to add + */ + addDefaultHotkeys: (command: string, keys: KeymapInfo[]) => void; + /** + * Get hotkey associated with command ID + * @param command - Command ID to get hotkey for + */ + getDefaultHotkeys: (command: string) => KeymapInfo[]; + /** + * Remove a hotkey from the default hotkeys + * @param command - Command ID to remove hotkey from + */ + removeDefaultHotkeys: (command: string) => void; + /** + * Add a hotkey to the custom hotkeys (overrides default hotkeys) + * @param command - Command ID to add hotkey to + * @param keys - Hotkeys to add + */ + setHotkeys: (command: string, keys: KeymapInfo[]) => void; + /** + * Get hotkey associated with command ID + * @param command - Command ID to get hotkey for + */ + getHotkeys: (command: string) => KeymapInfo[]; + /** + * Remove a hotkey from the custom hotkeys + * @param command - Command ID to remove hotkey from + */ + removeHotkeys: (command: string) => void; + /** + * Pretty-print hotkey of a command + * @param command + */ + printHotkeyForCommand: (command: string) => string; + /** + * Trigger a command by keyboard event + * @param event - Keyboard event to trigger command with + * @param keypress - Pressed key information + */ + onTrigger: (event: KeyboardEvent, keypress: KeymapInfo) => boolean; + /** + * @internal Bake hotkeys (create mapping of pressed key to command ID) + */ + bake: () => void; + /** + * @internal Load hotkeys from storage + */ + load: () => void; + /** + * @internal Save custom hotkeys to storage + */ + save: () => void; +} + +type InternalPlugin = + | "audio-recorder" + | "backlink" + | "bookmarks" + | "canvas" + | "command-palette" + | "daily-notes" + | "editor-status" + | "file-explorer" + | "file-recovery" + | "global-search" + | "graph" + | "markdown-importer" + | "note-composer" + | "outgoing-link" + | "outline" + | "page-preview" + | "properties" + | "publish" + | "random-note" + | "slash-command" + | "slides" + | "starred" + | "switcher" + | "sync" + | "tag-pane" + | "templates" + | "word-count" + | "workspaces" + | "zk-prefixer"; + +interface InternalPlugins extends Events { + /** + * Reference to App + */ + app: App; + /** + * Mapping of whether an internal plugin is enabled + */ + config: Record; + /** + * @internal + */ + migration: boolean; + /** + * Plugin configs for internal plugins + */ + plugins: Record; + /** + * @internal Request save of plugin configs + */ + requestSaveConfig: () => void; + + /** + * Get an enabled internal plugin by ID + * @param id - ID of the plugin to get + */ + getEnabledPluginById: (id: InternalPlugin) => Plugin | null; + /** + * Get all enabled internal plugins + */ + getEnabledPlugins: () => Plugin[]; + /** + * Get an internal plugin by ID + * @param id - ID of the plugin to get + */ + getPluginById: (id: InternalPlugin) => Plugin; + + /** + * @internal - Load plugin configs and enable plugins + */ + enable: () => Promise; + /** + * @internal + */ + loadPlugin: ({ id: string, name: string }) => string; + /** + * @internal + */ + on: (inp: any, cb: () => void, arg: string) => void; + /** + * @internal + */ + onRaw: (cb1: any, cb2: any) => void; + /** + * @internal - Save current plugin configs + */ + saveConfig: () => Promise; + /** + * @internal + */ + trigger: (arg: string) => void; +} + +interface KeyScope { + /** + * Callback of function to execute when key is pressed + */ + func: () => void; + /** + * Key to match + */ + key: string | null; + /** + * Modifiers to match + */ + modifiers: string | null; + /** + * Scope where the key interceptor is registered + */ + scope: EScope; +} + +// interface KeymapManager { +// /** +// * Modifiers pressed within keyscope +// */ +// modifiers: string; +// /** +// * @internal +// */ +// prevScopes: EScope[]; +// /** +// * @internal - Root scope of the application +// */ +// rootScope: EScope; +// +// /** +// * Get the root scope of the application +// */ +// getRootScope: () => EScope; +// /** +// * Check whether a specific modifier was part of the keypress +// */ +// hasModifier: (modifier: Modifier) => boolean; +// /** +// * Check whether event has same modifiers as the current keypress +// */ +// matchModifiers: (event: KeyboardEvent) => boolean; +// /** +// * @internal - On focus keyscope +// */ +// onFocusIn: (event: FocusEvent) => void; +// /** +// * @internal - On keypress find matching keyscope and execute callback +// */ +// onKeyEvent: (event: KeyboardEvent) => void; +// /** +// * @internal - Pop a scope from the prevScopes stack +// */ +// popScope: (scope: EScope) => void; +// /** +// * @internal - Push a scope to the prevScopes stack and set it as the current scope +// */ +// pushScope: (scope: EScope) => void; +// /** +// * @internal - Update last pressed modifiers +// */ +// updateModifiers: (event: KeyboardEvent) => void; +// } + +// interface LoadProgress { +// /** +// * Application's document +// */ +// doc: Document; +// /** +// * @internal Loading bar element +// */ +// el: HTMLElement; +// /** +// * @internal First part of the line +// */ +// line1El: HTMLElement; +// /** +// * @internal Second part of the line +// */ +// line2El: HTMLElement; +// /** +// * @internal Main line element +// */ +// lineEl: HTMLElement; +// /** +// * @internal Message element for the loading bar +// */ +// messageEl: HTMLElement; +// /** +// * @internal Timeout for the loading bar +// */ +// showTimeout: number; +// +// /** +// * @internal Delay showing the loading bar +// */ +// delayedShow: () => LoadProgress; +// /** +// * @internal Hide and remove the loading bar +// */ +// hide: () => LoadProgress; +// /** +// * @internal Update the loading bar message +// * @param message - Message to update to +// */ +// setMessage: (message: string) => LoadProgress; +// /** +// * @internal Update the loading bar progress +// * @param current - Current progress +// * @param max - Maximum progress +// */ +// setProgress: (current: number, max: number) => LoadProgress; +// /** +// * @internal Set the loading bar to unknown progress +// */ +// setUnknownProgress: () => LoadProgress; +// } + +interface BlockCache { + /** + * Reference to App + */ + app: App; + + /** + * @internal + */ + cache: any; +} + +interface FileCacheEntry { + /** + * Hash of file contents + */ + hash: string; + /** + * Last modified time of file + */ + mtime: number; + /** + * Size of file in bytes + */ + size: number; +} + +interface CustomArrayDict { + data: Record; + + add: (key: T, value: Q) => void; + remove: (key: T, value: Q) => void; + removeKey: (key: T) => void; + get: (key: T) => Q; + keys: () => T[]; + clear: (key: T) => void; + clearAll: () => void; + contains: (key: T) => boolean; + count: () => number; +} + +interface PropertyInfo { + /** + * Name of property + */ + name: string; + /** + * Type of property + */ + type: string; + /** + * Usage count of property + */ + count: number; +} + +type PropertyWidgetType = + | "aliases" + | "checkbox" + | "date" + | "datetime" + | "multitext" + | "number" + | "tags" + | "text"; + +interface PropertyWidget { + /** + * @internal + */ + default: () => void; + /** + * Lucide-dev icon associated with the widget + */ + icon: string; + /** + * @internal Name proxy + */ + name: any; + /** + * @internal Render function for the widget + */ + render: ( + element: HTMLElement, + metadataField: any, + property: PropertyInfo + ) => void; + /** + * @internal Reserved keys for the widget + */ + reservedKeys: string[]; + /** + * Widget type + */ + type: string; + /** + * @internal Validate correctness of property input with respects to the widget + */ + validate: (value: any) => boolean; +} + +interface MetadataTypeManager extends Events { + /** + * Reference to App + */ + app: App; + /** + * Registered properties of the vault + */ + properties: Record; + /** + * @internal Registered type widgets + */ + registeredTypeWidgets: Record; + /** + * Associated widget types for each property + */ + types: Record; + + /** + * Get all registered properties of the vault + */ + getAllProperties: () => Record; + /** + * Get assigned widget type for property + */ + getAssignedType: (property: string) => PropertyWidgetType | null; + /** + * Get info for property + */ + getPropertyInfo: (property: string) => PropertyInfo; + /** + * @internal Get expected widget type for property and the one inferred from the property value + */ + getTypeInfo: ({ key: string, type: string, value: any }) => { + inferred: PropertyWidget; + expected: PropertyWidget; + }; + /** + * Get all properties with an assigned widget type + */ + getTypes: () => string[]; + /** + * @internal Load property types from config + */ + loadData: () => Promise; + /** + * @internal + */ + on: (args: any) => void; + /** + * @internal Save property types to config + */ + save: () => Promise; + /** + * @internal Get all properties from metadata cache + */ + savePropertyInfo: () => void; + /** + * @internal Set widget type for property + */ + setType: (property: string, type: PropertyWidgetType) => void; + /** + * @internal + */ + trigger: (e: any) => void; + /** + * @internal Unset widget type for property + */ + unsetType: (property: string) => void; +} +// +// interface MobileNavbar { +// /** +// * Reference to App +// */ +// app: App; +// /** +// * @internal Back button element +// */ +// backButtonEl: HTMLElement; +// /** +// * @internal Container element +// */ +// containerEl: HTMLElement; +// /** +// * @internal Forward button element +// */ +// forwardButtonEl: HTMLElement; +// /** +// * Whether the mobile navbar is currently visible +// */ +// isVisible: boolean; +// /** +// * @internal On ribbon click +// */ +// onRibbonClick: () => void; +// /** +// * @internal Ribbon menu flair element +// */ +// ribbonMenuFlairEl: HTMLElement; +// /** +// * @internal Ribbon menu item element +// */ +// ribbonMenuItemEl: HTMLElement; +// /** +// * @internal Tab button element +// */ +// tabButtonEl: HTMLElement; +// +// /** +// * @internal Hide mobile navbar +// */ +// hide: () => void; +// /** +// * @internal Show mobile navbar +// */ +// show: () => void; +// /** +// * @internal Show ribbon menu +// */ +// showRibbonMenu: () => void; +// /** +// * @internal Update navigation buttons +// */ +// updateNavButtons: () => void; +// /** +// * @internal Update ribbon menu item +// */ +// updateRibbonMenuItem: () => void; +// } +// +// interface MobileToolbar { +// /** +// * Reference to App +// */ +// app: App; +// /** +// * @internal Container element +// */ +// containerEl: HTMLElement; +// /** +// * @internal Last selected command ID +// */ +// lastCommandIds: string; +// /** +// * @internal Options container element +// */ +// optionsContainerEl: HTMLElement; +// +// /** +// * @internal Compile all actions for the toolbar +// */ +// compileToolbar: () => void; +// /** +// * @internal Hide mobile toolbar +// */ +// hide: () => void; +// /** +// * @internal Show mobile toolbar +// */ +// show: () => void; +// } + +interface PluginManifest { + /** + * Name of the author of the plugin + */ + author: string; + /** + * URL to the author's website + */ + authorUrl?: string; + /** + * Description of the plugin's functionality + */ + description: string; + /** + * Storage location of the plugin relative to the vault root + */ + dir: string; + /** + * URL for funding the author + */ + fundingUrl?: string; + /** + * Unique identifier of the plugin + */ + id: string; + /** + * Whether the plugin is designed for desktop use only + */ + isDesktopOnly: boolean; + /** + * Minimum Obsidian version compatible with the plugin + */ + minAppVersion: string; + /** + * Name of the plugin + */ + name: string; + /** + * Version of the plugin + */ + version: string; +} + +interface PluginUpdateManifest { + /** + * Manifest of the plugin + */ + manifest: PluginManifest; + /** + * Repository of the plugin + */ + repo: string; + /** + * New version of the plugin + */ + version: string; +} + +interface Plugins { + /** + * Reference to App + */ + app: App; + /** + * Set of enabled plugin IDs + */ + enabledPlugins: Set; + /** + * @internal Plugin ID that is currently being enabled + */ + loadingPluginId: string | null; + /** + * Manifests of all the plugins + */ + manifests: Record; + /** + * Mapping of plugin ID to plugin instance + */ + plugins: Record; + /** + * Mapping of plugin ID to available updates + */ + updates: Map; + + /** + * @internal Check online list for deprecated plugins to automatically disable + */ + checkForDeprecations: () => Promise; + /** + * Check for plugin updates + */ + checkForUpdates: () => Promise; + /** + * Unload a plugin by ID + */ + disablePlugin: (id: string) => Promise; + /** + * Unload a plugin by ID and save config for persistence + */ + disablePluginAndSave: (id: string) => Promise; + /** + * Enable a plugin by ID + */ + enablePlugin: (id: string) => Promise; + /** + * Enable a plugin by ID and save config for persistence + */ + enablePluginAndSave: (id: string) => Promise; + /** + * Get a plugin by ID + */ + getPlugin: (id: string) => Plugin | null; + /** + * Get the folder where plugins are stored + */ + getPluginFolder: () => string; + /** + * @internal Load plugin manifests and enable plugins from config + */ + initialize: () => Promise; + /** + * Install a plugin from a given URL + */ + installPlugin: ( + repo: string, + manifest: PluginManifest, + version: string + ) => Promise; + /** + * Check whether a plugin is deprecated + */ + isDeprecated: (id: string) => boolean; + /** + * Check whether community plugins are enabled + */ + isEnabled: () => boolean; + /** + * Load a specific plugin's manifest by its ID + */ + loadManifest: (id: string) => Promise; + /** + * @internal Load all plugin manifests from plugin folder + */ + loadManifests: () => Promise; + /** + *Load a plugin by its ID + */ + loadPlugin: (id: string) => Promise; + /** + * @internal + */ + onRaw: (e: any) => void; + /** + * @internal - Save current plugin configs + */ + saveConfig: () => Promise; + /** + * @internal Toggle whether community plugins are enabled + */ + setEnable: (enabled: boolean) => Promise; + /** + * Uninstall a plugin by ID + */ + uninstallPlugin: (id: string) => Promise; + /** + * Unload a plugin by ID + */ + unloadPlugin: (id: string) => Promise; +} + +interface WindowSelection { + focusEl: HTMLElement; + range: Range; + win: Window; +} + +type ConfigItem = + | "accentColor" + | "alwaysUpdateLinks" + | "attachmentFolderPath" + | "autoConvertHtml" + | "autoPairBrackets" + | "autoPairMarkdown" + | "baseFontSize" + | "baseFontSizeAction" + | "cssTheme" + | "defaultViewMode" + | "emacsyKeys" + | "enabledCssSnippets" + | "fileSortOrder" + | "focusNewTab" + | "foldHeading" + | "foldIndent" + | "hotkeys" + | "interfaceFontFamily" + | "legacyEditor" + | "livePreview" + | "mobilePullAction" + | "mobileQuickRibbonItem" + | "mobileToolbarCommands" + | "monospaceFontFamily" + | "nativeMenus" + | "newFileFolderPath" + | "newFileLocation" + | "newLinkFormat" + | "pdfExportSettings" + | "promptDelete" + | "propertiesInDocument" + | "readableLineLength" + | "rightToLeft" + | "showIndentGuide" + | "showInlineTitle" + | "showLineNumber" + | "showUnsupportedFiles" + | "showViewHeader" + | "smartIndentList" + | "spellcheck" + | "spellcheckLanguages" + | "strictLineBreaks" + | "tabSize" + | "textFontFamily" + | "theme" + | "translucency" + | "trashOption" + | "types" + | "useMarkdownLinks" + | "useTab" + | "userIgnoreFilters" + | "vimMode"; + +interface AppVaultConfig { + /** + * Appearance > Accent color + */ + accentColor: "" | string; + /** + * Files & Links > Automatically update internal links + */ + alwaysUpdateLinks?: false | boolean; + /** + * Files & Links > Attachment folder path + */ + attachmentFolderPath?: "/" | string; + /** + * Editor > Auto convert HTML + */ + autoConvertHtml?: true | boolean; + /** + * Editor > Auto pair brackets + */ + autoPairBrackets?: true | boolean; + /** + * Editor > Auto pair Markdown syntax + */ + autoPairMarkdown?: true | boolean; + /** + * Appearance > Font size + */ + baseFontSize?: 16 | number; + /** + * Appearance > Quick font size adjustment + */ + baseFontSizeAction?: true | boolean; + /** + * Community Plugins > Browse > Sort order + */ + communityPluginSortOrder: + | "download" + | "update" + | "release" + | "alphabetical"; + /** + * Themes > Browse > Sort order + */ + communityThemeSortOrder: "download" | "update" | "release" | "alphabetical"; + /** + * Appearance > Theme + * @remark "" is the default Obsidian theme + */ + cssTheme?: "" | string; + /** + * Editor > Default view for new tabs + */ + defaultViewMode?: "source" | "preview"; + /** + * + */ + emacsyKeys?: true | boolean; + /** + * Appearance > CSS snippets + */ + enabledCssSnippets?: string[]; + /** + * + */ + fileSortOrder?: "alphabetical"; + /** + * Editor > Always focus new tabs + */ + focusNewTab?: true | boolean; + /** + * Editor > Fold heading + */ + foldHeading?: true | boolean; + /** + * Editor > Fold indent + */ + foldIndent?: true | boolean; + /** + * Hotkeys + * @deprecated Likely not used anymore + */ + hotkeys?: Record; + /** + * Appearance > Interface font + */ + interfaceFontFamily?: "" | string; + /** + * Editor > Use legacy editor + */ + legacyEditor?: false | boolean; + /** + * + */ + livePreview?: true | boolean; + /** + * Mobile > Configure mobile Quick Action + */ + mobilePullAction?: "command-palette:open" | string; + /** + * + */ + mobileQuickRibbonItem?: "" | string; + /** + * Mobile > Manage toolbar options + */ + mobileToolbarCommands?: string[]; + /** + * + */ + monospaceFontFamily?: "" | string; + /** + * Appearance > Native menus + */ + nativeMenus?: null | boolean; + /** + * Files & Links > Default location for new notes | 'folder' > Folder to create new notes in + */ + newFileFolderPath?: "/" | string; + /** + * Files & Links > Default location for new notes + */ + newFileLocation?: "root" | "current" | "folder"; + /** + * Files & Links > New link format + */ + newLinkFormat?: "shortest" | "relative" | "absolute"; + /** + * Saved on executing 'Export to PDF' command + */ + pdfExportSettings?: { + pageSize: "letter" | string; + landscape: false | boolean; + margin: "0" | string; + downscalePercent: 100 | number; + }; + /** + * Files & Links > Confirm line deletion + */ + promptDelete?: true | boolean; + /** + * Editor > Properties in document + */ + propertiesInDocument?: "visible" | "hidden" | "source"; + /** + * Editor > Readable line length + */ + readableLineLength?: true | boolean; + /** + * Editor > Right-to-left (RTL) + */ + rightToLeft?: false | boolean; + /** + * @deprecated Removed as of version 1.4.3 + */ + showFrontmatter?: false | boolean; + /** + * Editor > Show indentation guides + */ + showIndentGuide?: true | boolean; + /** + * Editor > Show inline title + */ + showInlineTitle?: true | boolean; + /** + * Editor > Show line numbers + */ + showLineNumber?: false | boolean; + /** + * Files & Links > Detect all file extensions + */ + showUnsupportedFiles?: false | boolean; + /** + * Appearance > Show tab title bar + */ + showViewHeader?: false | boolean; + /** + * Editor > Smart indent lists + */ + smartIndentList?: true | boolean; + /** + * Editor > Spellcheck + */ + spellcheck?: false | boolean; + /** + * @deprecated + */ + spellcheckDictionary?: [] | string[]; + /** + * Editor > Spellcheck languages + */ + spellcheckLanguages?: null | string[]; + /** + * Editor > Strict line breaks + */ + strictLineBreaks?: false | boolean; + /** + * Editor > Tab indent size + */ + tabSize?: 4 | number; + /** + * Appearance > Text font + */ + textFontFamily?: "" | string; + /** + * Appearance > Base color scheme + * @remark Not be confused with cssTheme, this setting is for the light/dark mode + * @remark "moonstone" is light theme, "obsidian" is dark theme + */ + theme?: "moonstone" | "obsidian"; + /** + * Appearance > Translucent window + */ + translucency?: false | boolean; + /** + * Files & Links > Deleted files + */ + trashOption?: "system" | "local" | "none"; + /** + * @deprecated Probably left-over code from old properties type storage + */ + types: object; + /** + * Files & Links > Use [[Wikilinks]] + */ + useMarkdownLinks?: false | boolean; + /** + * Editor > Indent using tabs + */ + useTab?: true | boolean; + /** + * Files & Links > Excluded files + */ + userIgnoreFilters?: null | string[]; + /** + * Editor > Vim key bindings + */ + vimMode?: false | boolean; +} + +interface FileEntry { + /** + * Creation time (if file) + */ + ctime?: number; + /** + * Modification time (if file) + */ + mtime?: number; + /** + * Full path to file or folder + * @remark Might be used for resolving symlinks + */ + realpath: string; + /** + * Size in bytes (if file) + */ + size?: number; + /** + * Type of entry + */ + type: "file" | "folder"; +} + +interface ViewRegistry extends Events { + /** + * Mapping of file extensions to view type + */ + typeByExtension: Record; + /** + * Mapping of view type to view constructor + */ + viewByType: Record View>; + + /** + * Get the view type associated with a file extension + * @param extension File extension + */ + getTypeByExtension: (extension: string) => string; + /** + * Get the view constructor associated with a view type + */ + getViewCreatorByType: (type: string) => (leaf: WorkspaceLeaf) => View; + /** + * Check whether a view type is registered + */ + isExtensionRegistered: (extension: string) => boolean; + /** + * @internal + */ + on: (args: any[]) => EventRef; + /** + * Register a view type for a file extension + * @param extension File extension + * @param type View type + * @remark Prefer registering the extension via the Plugin class + */ + registerExtensions: (extension: string[], type: string) => void; + /** + * Register a view constructor for a view type + */ + registerView: ( + type: string, + viewCreator: (leaf: WorkspaceLeaf) => View + ) => void; + /** + * Register a view and its associated file extensions + */ + registerViewWithExtensions: ( + extensions: string[], + type: string, + viewCreator: (leaf: WorkspaceLeaf) => View + ) => void; + /** + * @internal + */ + trigger: (type: string) => void; + /** + * Unregister extensions for a view type + */ + unregisterExtensions: (extension: string[]) => void; + /** + * Unregister a view type + */ + unregisterView: (type: string) => void; +} + +interface HoverLinkSource { + display: string; + defaultMod: boolean; +} + +interface RecentFileTracker { + /** + * List of last opened file paths, limited to 50 + */ + lastOpenFiles: string[]; + /** + * Reference to Vault + */ + vault: EVault; + /** + * Reference to Workspace + */ + workspace: EWorkspace; + + /** + * @internal + */ + collect: (file: TFile) => void; + /** + * Returns the last 10 opened files + */ + getLastOpenFiles: () => string[]; + /** + * Get last n files of type (defaults to 10) + */ + getRecentFiles: ({ + showMarkdown: boolean, + showCanvas: boolean, + showNonImageAttachments: boolean, + showImages: boolean, + maxCount: number, + }?) => string[]; + /** + * Set the last opened files + */ + load: (savedFiles: string[]) => void; + /** + * @internal On file create, save file to last opened files + */ + onFileCreated: (file: TFile) => void; + /** + * @internal On file open, save file to last opened files + */ + onFileOpen: (prevFile: TFile, file: TFile) => void; + /** + * @internal On file rename, update file path in last opened files + */ + onRename: (file: TFile, oldPath: string) => void; + /** + * @internal Get last opened files + */ + serialize: () => string[]; +} + +interface StateHistory { + /** + * Ephemeral cursor state within Editor of leaf + */ + eState: { + cursor: EditorRange; + scroll: number; + }; + /** + * Icon of the leaf + */ + icon?: string; + /** + * History of previous and future states of leaf + */ + leafHistory?: { + backHistory: StateHistory[]; + forwardHistory: StateHistory[]; + }; + /** + * Id of parent to which the leaf belonged + */ + parentId?: string; + /** + * Id of root to which the leaf belonged + */ + rootId?: string; + /** + * Last state of the leaf + */ + state: ViewState; + /** + * Title of the leaf + */ + title?: string; +} + +interface LeafEntry { + children?: LeafEntry[]; + direction?: SplitDirection; + id: string; + state?: ViewState; + type: string; + width?: number; +} + +interface SerializedWorkspace { + /** + * Last active leaf + */ + active: string; + /** + * Last opened files + */ + lastOpenFiles: string[]; + /** + * Left opened leaf + */ + left: LeafEntry; + /** + * Left ribbon + */ + leftRibbon: { hiddenItems: Record }; + /** + * Main (center) workspace leaf + */ + main: LeafEntry; + /** + * Right opened leaf + */ + right: LeafEntry; +} + +interface ImportedAttachments { + data: Promise; + extension: string; + filename: string; + name: string; +} + +declare module "obsidian" { + interface App { + /** + * The account signed in to Obsidian + */ + account: Account; + /** + * ID that uniquely identifies the vault + * @tutorial Used for implementing device *and* vault-specific + * data storage using LocalStorage or IndexedDB + */ + appId: string; + // /** + // * @internal + // */ + // appMenuBarManager: AppMenuBarManager; + /** + * Contains all registered commands + * @tutorial Can be used to manually invoke the functionality of a specific command + */ + commands: Commands; + /** + * Custom CSS (snippets/themes) applied to the application + * @tutorial Can be used to view which snippets are enabled or available, + * or inspect loaded-in theme manifests + */ + customCss: CustomCSS; + /** References to important DOM elements of the application */ + dom: ObsidianDOM; + // /** + // * @internal + // */ + // dragManager: any; + // /** + // * @internal + // */ + // embedRegistry: EmbedRegistry; + /** + * Manage the creation, deletion and renaming of files from the UI. + * @remark Prefer using the `vault` API for programmatic file management + */ + fileManager: EFileManager; + // /** + // * @internal + // */ + // foldManager: any; + /** + * Manages global hotkeys + * @tutorial Can be used for manually invoking a command, or finding which hotkey is assigned to a specific key input + * @remark This should not be used for adding hotkeys to your custom commands, this can easily be done via the official API + */ + hotkeyManager: HotkeyManager; + /** + * Manager of internal 'core' plugins + * @tutorial Can be used to check whether a specific internal plugin is enabled, or grab specific parts + * from its config for simplifying your own plugin's settings + */ + internalPlugins: InternalPlugins; + /** + * Whether the application is currently running on mobile + * @remark Prefer usage of `Platform.isMobile` + * @remark Will be true if `app.emulateMobile()` was enabled + */ + isMobile: boolean; + // /** + // * @internal + // */ + // keymap: KeymapManager; + // /** + // * @internal + // */ + // loadProgress: LoadProgress; + /** + * Manages the gathering and updating of metadata for all files in the vault + * @tutorial Use for finding tags and backlinks for specific files, grabbing frontmatter properties, ... + */ + metadataCache: EMetadataCache; + /** + * Manages the frontmatter properties of the vault and the rendering of the properties + * @tutorial Fetching properties used in all frontmatter fields, may potentially be used for adding custom frontmatter widgets + */ + metadataTypeManager: MetadataTypeManager; + + // /** + // * @internal + // */ + // mobileNavbar?: MobileNavbar; + // /** + // * @internal + // */ + // mobileToolbar?: MobileToolbar; + + // /** + // * @internal Events to execute on the next frame + // */ + // nextFrameEvents: any[]; + // /** + // * @internal Timer for the next frame + // */ + // nextFrameTimer: number; + + /** + * Manages loading and enabling of community (non-core) plugins + * @tutorial Can be used to communicate with other plugins, custom plugin management, ... + * @remark Be careful when overriding loading logic, as this may result in other plugins not loading + */ + plugins: Plugins; + /** + * @internal Root keyscope of the application + */ + scope: EScope; + /** + * Manages the settings modal and its tabs + * @tutorial Can be used to open the settings modal to a specific tab, extend the settings modal functionality, ... + * @remark Do not use this to get settings values from other plugins, it is better to do this via `app.plugins.getPlugin(ID)?.settings` (check how the plugin stores its settings) + */ + setting: Setting; + // /** + // * @internal + // */ + // shareReceiver: { app: App } + // /** + // * @internal Status bar of the application + // */ + // statusBar: { app: App , containerEl: HTMLElement } + /** + * Name of the vault with version suffix + * @remark Formatted as 'NAME - Obsidian vX.Y.Z' + */ + title: string; + /** + * Manages all file operations for the vault, contains hooks for file changes, and an adapter + * for low-level file system operations + * @tutorial Used for creating your own files and folders, renaming, ... + * @tutorial Use `app.vault.adapter` for accessing files outside the vault (desktop-only) + * @remark Prefer using the regular `vault` whenever possible + */ + vault: Vault; + /** + * Manages the construction of appropriate views when opening a file of a certain type + * @remark Prefer usage of view registration via the Plugin class + */ + viewRegistry: ViewRegistry; + /** + * Manages the workspace layout, construction, rendering and manipulation of leaves + * @tutorial Used for accessing the active editor leaf, grabbing references to your views, ... + */ + workspace: Workspace; + + /** + * Sets the accent color of the application to the OS preference + */ + adaptToSystemTheme: () => void; + /** + * Sets the accent color of the application (light/dark mode) + */ + changeTheme: (theme: "moonstone" | "obsidian") => void; + /** + * Copies Obsidian URI of given file to clipboard + * @param file File to generate URI for + */ + copyObsidianUrl: (file: TFile) => void; + /** + * Disables all CSS transitions in the vault (until manually re-enabled) + */ + disableCssTransition: () => void; + /** + * Restarts Obsidian and renders workspace in mobile mode + * @tutorial Very useful for testing the rendering of your plugin on mobile devices + */ + emulateMobile: (emulate: boolean) => void; + /** + * Enables all CSS transitions in the vault + */ + enableCssTransition: () => void; + /** + * Manually fix all file links pointing towards image/audio/video resources in element + * @param element Element to fix links in + */ + fixFileLinks: (element: HTMLElement) => void; + /** + * Applies an obfuscation font to all text characters in the vault + * @tutorial Useful for hiding sensitive information or sharing pretty screenshots of your vault + * @remark Uses the `Flow Circular` font + * @remark You will have to restart the app to get normal text back + */ + garbleText: () => void; + /** + * Get the accent color of the application + * @remark Often a better alternative than `app.vault.getConfig('accentColor')` as it returns an empty string if no accent color was set + */ + getAccentColor: () => string; + /** + * Get the current title of the application + * @remark The title is based on the currently active leaf + */ + getAppTitle: () => string; + /** + * Get the URI for opening specified file in Obsidian + */ + getObsidianUrl: (file: TFile) => string; + /** + * Get currently active spellcheck languages + * @deprecated Originally spellcheck languages were stored in app settings, + * languages are now stored in `localStorage.getItem(spellcheck-languages)` + */ + getSpellcheckLanguages: () => string[]; + /** + * Get the current color scheme of the application + * @remark Identical to `app.vault.getConfig('theme')` + */ + getTheme: () => "moonstone" | "obsidian"; + /** + * Import attachments into specified folder + */ + importAttachments: ( + imports: ImportedAttachments[], + folder: TFolder + ) => Promise; + /** + * @internal Initialize the entire application using the provided FS adapter + */ + initializeWithAdapter: (adapter: EDataAdapter) => Promise; + /** + * Load a value from the localstorage given key + * @param key Key of value to load + * @remark This method is device *and* vault specific + * @tutorial Use load/saveLocalStorage for saving configuration data that needs to be unique to the current vault + */ + loadLocalStorage: (key: string) => string; + /** + * @internal Add callback to execute on next frame + */ + nextFrame: (callback: () => void) => void; + /** + * @internal Add callback to execute on next frame, and remove after execution + */ + nextFrameOnceCallback: (callback: () => void) => void; + /** + * @internal Add callback to execute on next frame with promise + */ + nextFramePromise: (callback: () => Promise) => Promise; + /** + * @internal + */ + on: (args: any[]) => EventRef; + /** + * @internal + */ + onMouseEvent: (evt: MouseEvent) => void; + /** + * @internal Execute all logged callback (called when next frame is loaded) + */ + onNextFrame: (callback: () => void) => void; + /** + * Open the help vault (or site if mobile) + */ + openHelp: () => void; + /** + * Open the vault picker + */ + openVaultChooser: () => void; + /** + * Open the file with OS defined default file browser application + */ + openWithDefaultApp: (path: string) => void; + /** + * @internal Register all basic application commands + */ + registerCommands: () => void; + /** + * @internal Register a hook for saving workspace data before unload + */ + registerQuitHook: () => void; + /** + * @internal Save attachment at default attachments location + */ + saveAttachment: ( + path: string, + extension: string, + data: ArrayBuffer + ) => Promise; + /** + * Save a value to the localstorage given key + * @param key Key of value to save + * @param value Value to save + * @remark This method is device *and* vault specific + * @tutorial Use load/saveLocalStorage for saving configuration data that needs to be unique to the current vault + */ + saveLocalStorage: (key: string, value: any) => void; + /** + * Set the accent color of the application + * @remark Also updates the CSS `--accent` variables + */ + setAccentColor: (color: string) => void; + /** + * Set the path where attachments should be stored + */ + setAttachmentFolder: (path: string) => void; + /** + * Set the spellcheck languages + */ + setSpellcheckLanguages: (languages: string[]) => void; + /** + * Set the current color scheme of the application and reload the CSS + */ + setTheme: (theme: "moonstone" | "obsidian") => void; + /** + * Open the OS file picker at path location + */ + showInFolder: (path: string) => void; + /** + * Show the release notes for provided version as a new leaf + * @param version Version to show release notes for (defaults to current version) + */ + showReleaseNotes: (version?: string) => void; + /** + * Updates the accent color and reloads the CSS + */ + updateAccentColor: () => void; + /** + * Update the font family of the application and reloads the CSS + */ + updateFontFamily: () => void; + /** + * Update the font size of the application and reloads the CSS + */ + updateFontSize: () => void; + /** + * Update the inline title rendering in notes + */ + updateInlineTitleDisplay: () => void; + /** + * Update the color scheme of the application and reloads the CSS + */ + updateTheme: () => void; + /** + * Update the view header display in notes + */ + updateViewHeaderDisplay: () => void; + } + + interface Scope { + /** + * Overridden keys that exist in this scope + */ + keys: KeyScope[]; + + /** + * @internal Scope that this scope is a child of + */ + parent: EScope | undefined; + /** + * @internal - Callback to execute when scope is matched + */ + cb: (() => boolean) | undefined; + /** + * @internal + */ + tabFocusContainer: HTMLElement | null; + /** + * @internal Execute keypress within this scope + * @param event - Keyboard event + * @param keypress - Pressed key information + */ + handleKey: (event: KeyboardEvent, keypress: KeymapInfo) => any; + /** + * @internal + * @deprecated - Executes same functionality as `Scope.register` + */ + registerKey: ( + modifiers: Modifier[], + key: string | null, + func: KeymapEventListener + ) => KeymapEventHandler; + /** + * @internal + */ + setTabFocusContainer: (container: HTMLElement) => void; + } + + class MetadataCache { + /** + * Reference to App + */ + app: App; + /** + * @internal + */ + blockCache: BlockCache; + /** + * @internal IndexedDB database + */ + db: IDBDatabase; + /** + * @internal File contents cache + */ + fileCache: Record; + /** + * @internal Amount of tasks currently in progress + */ + inProgressTaskCount: number; + /** + * @internal Whether the cache is fully loaded + */ + initialized: boolean; + /** + * @internal + */ + linkResolverQueue: any; + /** + * @internal File hash to metadata cache entry mapping + */ + metadataCache: Record; + /** + * @internal Callbacks to execute on cache clean + */ + onCleanCacheCallbacks: any[]; + /** + * @internal Mapping of filename to collection of files that share the same name + */ + uniqueFileLookup: CustomArrayDict; + /** + * @internal + */ + userIgnoreFilterCache: any; + /** + * @internal + */ + userIgnoreFilters: any; + /** + * @internal + */ + userIgnoreFiltersString: string; + /** + * Reference to Vault + */ + vault: Vault; + /** + * @internal + */ + workQueue: any; + /** + * @internal + */ + worker: Worker; + /** + * @internal + */ + workerResolve: any; + + /** + * Get all property infos of the vault + */ + getAllPropertyInfos: () => Record; + /** + * Get all backlink information for a file + */ + getBacklinksForFile: ( + file?: TFile + ) => CustomArrayDict; + /** + * Get paths of all files cached in the vault + */ + getCachedFiles: () => string[]; + /** + * Get an entry from the file cache + */ + getFileInfo: (path: string) => FileCacheEntry; + /** + * Get property values for frontmatter property key + */ + getFrontmatterPropertyValuesForKey: (key: string) => string[]; + /** + * Get all links (resolved or unresolved) in the vault + */ + getLinkSuggestions: () => { file: TFile | null; path: string }[]; + /** + * Get destination of link path + */ + getLinkpathDest: (origin: string = "", path: string) => TFile[]; + /** + * Get all links within the vault per file + */ + getLinks: () => Record; + /** + * Get all tags within the vault and their usage count + */ + getTags: () => Record; + + /** + * @internal Clear all caches to null values + */ + cleanupDeletedCache: () => void; + /** + * @internal + */ + clear: () => any; + /** + * @internal + */ + computeMetadataAsync: (e: any) => Promise; + /** + * @internal Remove all entries that contain deleted path + */ + deletePath: (path: string) => void; + /** + * @internal Initialize Database connection and load up caches + */ + initialize: () => Promise; + /** + * @internal Check whether there are no cache tasks in progress + */ + isCacheClean: () => boolean; + /** + * @internal Check whether file can support metadata (by checking extension support) + */ + isSupportedFile: (file: TFile) => boolean; + /** + * @internal Check whether string is part of the user ignore filters + */ + isUserIgnored: (filter: any) => boolean; + /** + * Iterate over all link references in the vault with callback + */ + iterateReferences: (callback: (path: string) => void) => void; + /** + * @internal + */ + linkResolver: () => void; + /** + * @internal Execute onCleanCache callbacks if cache is clean + */ + onCleanCache: () => void; + /** + * @internal On creation of the cache: update metadata cache + */ + onCreate: (file: TFile) => void; + /** + * @internal On creation or modification of the cache: update metadata cache + */ + onCreateOrModify: (file: TFile) => void; + /** + * @internal On deletion of the cache: update metadata cache + */ + onDelete: (file: TFile) => void; + /** + * @internal + */ + onReceiveMessageFromWorker: (e: any) => void; + /** + * @internal On rename of the cache: update metadata cache + */ + onRename: (file: TFile, oldPath: string) => void; + /** + * @internal Check editor for unresolved links and mark these as unresolved + */ + resolveLinks: (editor: Element) => void; + /** + * @internal Update file cache entry and sync to indexedDB + */ + saveFileCache: (path: string, entry: FileCacheEntry) => void; + /** + * @internal Update metadata cache entry and sync to indexedDB + */ + saveMetaCache: (path: string, entry: CachedMetadata) => void; + /** + * @internal Show a notice that the cache is being rebuilt + */ + showIndexingNotice: () => void; + /** + * @internal + */ + trigger: (e: any) => void; + /** + * @internal Re-resolve all links for changed path + */ + updateRelatedLinks: (path: string) => void; + /** + * @internal Update user ignore filters from settings + */ + updateUserIgnoreFilters: () => void; + /** + * @internal Bind actions to listeners on vault + */ + watchVaultChanges: () => void; + /** + * @internal Send message to worker to update metadata cache + */ + work: (cacheEntry: any) => void; + } + + interface SettingTab { + /** + * Unique ID of the tab + */ + id: string; + /** + * Sidebar name of the tab + */ + name: string; + /** + * Sidebar navigation element of the tab + */ + navEl: HTMLElement; + /** + * Reference to the settings modal + */ + setting: Setting; + /** + * Reference to the plugin that initialised the tab + * @if Tab is a plugin tab + */ + plugin?: Plugin; + /** + * Reference to installed plugins element + * @if Tab is the community plugins tab + */ + installedPluginsEl?: HTMLElement; + + // TODO: Editor, Files & Links, Appearance and About all have private properties too + } + + interface FileManager { + /** + * Reference to App + */ + app: App; + /** + * Creates a new Markdown file in specified location and opens it in a new view + * @param path - Path to the file to create (missing folders will be created) + * @param manner - Where to open the view containing the new file + */ + createAndOpenMarkdownFile: ( + path: string, + location: PaneType + ) => Promise; + /** + * Create a new file in the vault at specified location + * @param location - Location to create the file in, defaults to root + * @param filename - Name of the file to create, defaults to "Untitled" (incremented if file already exists) + * @param extension - Extension of the file to create, defaults to "md" + * @param contents - Contents of the file to create, defaults to empty string + */ + createNewFile: ( + location: TFolder = null, + filename: string = null, + extension: string = "md", + contents: string = "" + ) => Promise; + /** + * Creates a new untitled folder in the vault at specified location + * @param location - Location to create the folder in, defaults to root + */ + createNewFolder: (location: TFolder = null) => Promise; + /** + * Creates a new Markdown file in the vault at specified location + */ + createNewMarkdownFile: ( + location: TFolder = null, + filename: string = null, + contents: string = "" + ) => Promise; + /** + * Creates a new Markdown file based on linktext and path + * @param filename - Name of the file to create + * @param path - Path to where the file should be created + */ + createNewMarkdownFileFromLinktext: ( + filename: string, + path: string + ) => Promise; + /** + * @internal + */ + getAllLinkResolutions: () => []; + /** + * Gets the folder that new markdown files should be saved to, based on the current settings + * @param path - The path of the current opened/focused file, used when the user + * wants new files to be created in the same folder as the current file + */ + getMarkdownNewFileParent: (path: string) => TFolder; + /** + * Insert text into a file + * @param file - File to insert text into + * @param primary_text - Text to insert (will not be inserted if secondary_text exists) + * @param basename - ??? + * @param secondary_text - Text to insert (always inserted) + * @param atStart - Whether to insert text at the start or end of the file + */ + insertTextIntoFile: ( + file: TFile, + primary_text: string, + basename: string, + secondary_text: string, + atStart: boolean = true + ) => Promise; + /** + * Iterate over all links in the vault with callback + * @param callback - Callback to execute for each link + */ + iterateAllRefs: ( + callback: (path: string, link: PositionedReference) => void + ) => void; + /** + * Merge two files + * @param file - File to merge to + * @param otherFile - File to merge from + * @param override - If not empty, will override the contents of the file with this string + * @param atStart - Whether to insert text at the start or end of the file + */ + mergeFile: ( + file: TFile, + otherFile: TFile, + override: string, + atStart: boolean + ) => Promise; + /** + * Prompt the user to delete a file + */ + promptForDeletion: (file: TFile) => Promise; + /** + * Prompt the user to rename a file + */ + promptForFileRename: (file: TFile) => Promise; + /** + * @internal + * Register an extension to be the parent for a specific file type + */ + registerFileParentCreator: ( + extension: string, + location: TFolder + ) => void; + /** + * @internal + * @param callback - Callback to execute for each link + */ + runAsyncLinkUpdate: (callback: (link: LinkUpdate) => any) => void; + /** + * @internal + * @param path + * @param data + */ + storeTextFileBackup: (path: string, data: string) => void; + /** + * Remove a file and put it in the trash (no confirmation modal) + */ + trashFile: (file: TFile) => Promise; + /** + * @internal: Unregister extension as root input directory for file type + */ + unregisterFileCreator: (extension: string) => void; + /** + * @internal + */ + updateAllLinks: (links: any[]) => Promise; + /** + * @internal + */ + updateInternalLinks: (data: any) => any; + + /** + * @internal + */ + fileParentCreatorByType: Map any>; + /** + * @internal + */ + inProgressUpdates: null | any[]; + /** + * @internal + */ + linkUpdaters: Map any>; + /** + * @internal + */ + updateQueue: Map any>; + /** + * Reference to Vault + */ + vault: Vault; + } + + interface Modal { + /** + * @internal Background applied to application to dim it + */ + bgEl: HTMLElement; + /** + * @internal Opacity percentage of the background + */ + bgOpacity: number; + /** + * @internal Whether the background is being dimmed + */ + dimBackground: boolean; + /** + * @internal Modal container element + */ + modalEl: HTMLElement; + /** + * @internal Selection logic handler + */ + selection: WindowSelection; + /** + * Reference to the global Window object + */ + win: Window; + + /** + * @internal On escape key press close modal + */ + onEscapeKey: () => void; + /** + * @internal On closing of the modal + */ + onWindowClose: () => void; + /** + * @internal Set the background opacity of the dimmed background + * @param opacity Opacity percentage + */ + setBackgroundOpacity: (opacity: string) => this; + /** + * @internal Set the content of the modal + * @param content Content to set + */ + setContent: (content: HTMLElement | string) => this; + /** + * @internal Set whether the background should be dimmed + * @param dim Whether the background should be dimmed + */ + setDimBackground: (dim: boolean) => this; + /** + * @internal Set the title of the modal + * @param title Title to set + */ + setTitle: (title: string) => this; + } + + interface Setting extends Modal { + /** + * Current active tab of the settings modal + */ + activateTab: ESettingTab; + /** + * @internal Container element containing the community plugins + */ + communityPluginTabContainer: HTMLElement; + /** + * @internal Container element containing the community plugins header + */ + communityPluginTabHeaderGroup: HTMLElement; + /** + * Previously opened tab ID + */ + lastTabId: string; + /** + * List of all plugin tabs (core and community, ordered by precedence) + */ + pluginTabs: ESettingTab[]; + /** + * List of all core settings tabs (editor, files & links, ...) + */ + settingTabs: ESettingTab[]; + /** + * @internal Container element containing the core settings + */ + tabContainer: HTMLElement; + /** + * @internal Container for currently active settings tab + */ + tabContentContainer: HTMLElement; + /** + * @internal Container for all settings tabs + */ + tabHeadersEl: HTMLElement; + + /** + * Open a specific tab by ID + * @param id ID of the tab to open + */ + openTabById: (id: string) => void; + /** + * @internal Add a new plugin tab to the settings modal + * @param tab Tab to add + */ + addSettingTab: (tab: ESettingTab) => void; + /** + * @internal Closes the currently active tab + */ + closeActiveTab: () => void; + /** + * @internal Check whether tab is a plugin tab + * @param tab Tab to check + */ + isPluginSettingTab: (tab: ESettingTab) => boolean; + /** + * @internal Open a specific tab by tab reference + * @param tab Tab to open + */ + openTab: (tab: ESettingTab) => void; + /** + * @internal Remove a plugin tab from the settings modal + * @param tab Tab to remove + */ + removeSettingTab: (tab: ESettingTab) => void; + /** + * @internal Update the title of the modal + * @param tab Tab to update the title to + */ + updateModalTitle: (tab: ESettingTab) => void; + /** + * @internal Update a tab section + */ + updatePluginSection: () => void; + } + + interface DataAdapter { + /** + * Base OS path for the vault (e.g. /home/user/vault, or C:\Users\user\documents\vault) + */ + basePath: string; + /** + * @internal + */ + btime: { btime: (path: string, btime: number) => void }; + /** + * Mapping of file/folder path to vault entry, includes non-MD files + */ + files: Record; + /** + * Reference to node fs module + */ + fs?: fs; + /** + * Reference to node fs:promises module + */ + fsPromises?: fsPromises; + /** + * @internal + */ + insensitive: boolean; + /** + * Reference to electron ipcRenderer module + */ + ipcRenderer?: IpcRenderer; + /** + * Reference to node path module + */ + path: path; + /** + * @internal + */ + promise: Promise; + /** + * Reference to node URL module + */ + url: URL; + /** + * @internal + */ + watcher: any; + /** + * @internal + */ + watchers: Record; + + /** + * @internal Apply data write options to file + * @param normalizedPath Path to file + * @param options Data write options + */ + applyWriteOptions: ( + normalizedPath: string, + options: DataWriteOptions + ) => Promise; + /** + * Get base path of vault (OS path) + */ + getBasePath: () => string; + /** + * Get full path of file (OS path) + * @param normalizedPath Path to file + * @return URL path to file + */ + getFilePath: (normalizedPath: string) => URL; + /** + * Get full path of file (OS path) + * @param normalizedPath Path to file + * @return string full path to file + */ + getFullPath: (normalizedPath: string) => string; + /** + * Get full path of file (OS path) + * @param normalizedPath Path to file + * @return string full path to file + */ + getFullRealPath: (normalizedPath: string) => string; + /** + * @internal Get resource path of file (URL path) + * @param normalizedPath Path to file + * @return string URL of form: app://FILEHASH/path/to/file + */ + getResourcePath: (normalizedPath: string) => string; + /** + * @internal Handles vault events + */ + handler: () => void; + /** + * @internal Kill file system action due to timeout + */ + kill: () => void; + /** + * @internal + */ + killLastAction: () => void; + /** + * @internal Generates `this.files` from the file system + */ + listAll: () => Promise; + /** + * @internal Generates `this.files` for specific directory of the vault + */ + listRecursive: (normalizedPath: string) => Promise; + /** + * @internal Helper function for `listRecursive` reads children of directory + * @param normalizedPath Path to directory + */ + listRecursiveChild: (normalizedPath: string) => Promise; + /** + * @internal + */ + onFileChange: (normalizedPath: string) => void; + /** + * @internal + */ + queue: (cb: any) => Promise; + + /** + * @internal + */ + reconcileDeletion: ( + normalizedPath: string, + normalizedNewPath: string, + option: boolean + ) => void; + /** + * @internal + */ + reconcileFile: ( + normalizedPath: string, + normalizedNewPath: string, + option: boolean + ) => void; + /** + * @internal + */ + reconcileFileCreation: ( + normalizedPath: string, + normalizedNewPath: string, + option: boolean + ) => void; + /** + * @internal + */ + reconcileFileInternal: ( + normalizedPath: string, + normalizedNewPath: string + ) => void; + /** + * @internal + */ + reconcileFolderCreation: ( + normalizedPath: string, + normalizedNewPath: string + ) => void; + /** + * @internal + */ + reconcileInternalFile: (normalizedPath: string) => void; + /** + * @internal + */ + reconcileSymbolicLinkCreation: ( + normalizedPath: string, + normalizedNewPath: string + ) => void; + /** + * @internal Remove file from files listing and trigger deletion event + */ + removeFile: (normalizedPath: string) => void; + /** + * @internal + */ + startWatchpath: (normalizedPath: string) => Promise; + /** + * @internal Remove all listeners + */ + stopWatch: () => void; + /** + * @internal Remove listener on specific path + */ + stopWatchPath: (normalizedPath: string) => void; + /** + * @internal Set whether OS is insensitive to case + */ + testInsensitive: () => void; + /** + * @internal + */ + thingsHappening: () => void; + /** + * @internal Trigger an event on handler + */ + trigger: (any) => void; + /** + * @internal + */ + update: (normalizedPath: string) => any; + /** + * @internal Add change watcher to path + */ + watch: (normalizedPath: string) => Promise; + /** + * @internal Watch recursively for changes + */ + watchHiddenRecursive: (normalizedPath: string) => Promise; + } + + interface Workspace { + /** + * Currently active tab group + */ + activeTabGroup: WorkspaceTabs; + /** + * Reference to App + */ + app: App; + /** + * @internal + */ + backlinkInDocument?: any; + /** + * Registered CodeMirror editor extensions, to be applied to all CM instances + */ + editorExtensions: Extension[]; + /** + * @internal + */ + editorSuggest: { + currentSuggest?: EditorSuggest; + suggests: EditorSuggest[]; + }; + /** + * @internal + */ + floatingSplit: WorkspaceSplit; + /** + * @internal + */ + hoverLinkSources: Record; + /** + * Last opened file in the vault + */ + lastActiveFile: TFile; + /** + * @internal + */ + lastTabGroupStacked: boolean; + /** + * @internal + */ + layoutItemQueue: any[]; + /** + * Workspace has finished loading + */ + layoutReady: boolean; + /** + * @internal + */ + leftSidebarToggleButtonEl: HTMLElement; + /** + * @internal Array of renderCallbacks + */ + mobileFileInfos: any[]; + /** + * @internal + */ + onLayoutReadyCallbacks?: any; + /** + * Protocol handlers registered on the workspace + */ + protocolHandlers: Map; + /** + * Tracks last opened files in the vault + */ + recentFileTracker: RecentFileTracker; + /** + * @internal + */ + rightSidebarToggleButtonEl: HTMLElement; + /** + * @internal Keyscope registered to the vault + */ + scope: EScope; + /** + * List of states that were closed and may be reopened + */ + undoHistory: StateHistory[]; + + /** + * @internal Change active leaf and trigger leaf change event + */ + activeLeafEvents: () => void; + /** + * @internal Add file to mobile file info + */ + addMobileFileInfo: (file: any) => void; + /** + * @internal Clear layout of workspace and destruct all leaves + */ + clearLayout: () => Promise; + /** + * @internal Create a leaf in the selected tab group or last used tab group + * @param tabs Tab group to create leaf in + */ + createLeafInTabGroup: (tabs?: WorkspaceTabs) => WorkspaceLeaf; + /** + * @internal Deserialize workspace entries into actual Leaf objects + * @param leaf Leaf entry to deserialize + * @param ribbon Whether the leaf belongs to the left or right ribbon + */ + deserializeLayout: ( + leaf: LeafEntry, + ribbon?: "left" | "right" + ) => Promise; + /** + * @internal Reveal leaf in side ribbon with specified view type and state + * @param type View type of leaf + * @param ribbon Side ribbon to reveal leaf in + * @param viewstate Open state of leaf + */ + ensureSideLeaf: ( + type: string, + ribbon: "left" | "right", + viewstate: OpenViewState + ) => void; + /** + * Get active file view if exists + */ + getActiveFileView: () => FileView | null; + /** + * @deprecated Use `getActiveViewOfType` instead + */ + getActiveLeafOfViewType(type: Constructor): T | null; + /** + * Get adjacent leaf in specified direction + * @remark Potentially does not work + */ + getAdjacentLeafInDirection: ( + leaf: WorkspaceLeaf, + direction: "top" | "bottom" | "left" | "right" + ) => WorkspaceLeaf | null; + /** + * @internal Get the direction where the leaf should be dropped on dragevent + */ + getDropDirection: ( + e: DragEvent, + rect: DOMRect, + directions: ["left", "right"], + leaf: WorkspaceLeaf + ) => "left" | "right" | "top" | "bottom" | "center"; + /** + * @internal Get the leaf where the leaf should be dropped on dragevent + * @param e Drag event + */ + getDropLocation: (e: DragEvent) => WorkspaceLeaf | null; + /** + * Get the workspace split for the currently focused container + */ + getFocusedContainer: () => WorkspaceSplit; + /** + * Get n last opened files of type (defaults to 10) + */ + getRecentFiles: ({ + showMarkdown: boolean, + showCanvas: boolean, + showNonImageAttachments: boolean, + showImages: boolean, + maxCount: number, + }?) => string[]; + /** + * Get leaf in the side ribbon/dock and split if necessary + * @param sideRibbon Side ribbon to get leaf from + * @param split Whether to split the leaf if it does not exist + */ + getSideLeaf: ( + sideRibbon: WorkspaceSidedock | WorkspaceMobileDrawer, + split: boolean + ) => WorkspaceLeaf; + /** + * @internal + */ + handleExternalLinkContextMenu: (menu: Menu, linkText: string) => void; + /** + * @internal + */ + handleLinkContextMenu: ( + menu: Menu, + linkText: string, + sourcePath: string + ) => void; + /** + * @internal Check if leaf has been attached to the workspace + */ + isAttached: (leaf?: WorkspaceLeaf) => boolean; + /** + * Iterate the leaves of a split + */ + iterateLeaves: ( + split: WorkspaceSplit, + callback: (leaf: WorkspaceLeaf) => any + ) => void; + /** + * Iterate the tabs of a split till meeting a condition + */ + iterateTabs: ( + tabs: WorkspaceSplit | WorkspaceSplit[], + cb: (leaf) => boolean + ) => boolean; + /** + * @internal Load workspace from disk and initialize + */ + loadLayout: () => Promise; + /** + * @internal + */ + on: (args: any[]) => EventRef; + /** + * @internal Handles drag event on leaf + */ + onDragLeaf: (e: DragEvent, leaf: WorkspaceLeaf) => void; + /** + * @internal Handles layout change and saves layout to disk + */ + onLayoutChange: (leaf?: WorkspaceLeaf) => void; + /** + * @internal + */ + onLinkContextMenu: (args: any[]) => void; + /** + * @internal + */ + onQuickPreview: (args: any[]) => void; + /** + * @internal + */ + onResize: () => void; + /** + * @internal + */ + onStartLink: (leaf: WorkspaceLeaf) => void; + /** + * Open a leaf in a popup window + * @remark Prefer usage of `app.workspace.openPopoutLeaf` + */ + openPopout: (data?: WorkspaceWindowInitData) => WorkspaceWindow; + /** + * @internal Push leaf change to history + */ + pushUndoHistory: ( + leaf: WorkspaceLeaf, + parentID: string, + rootID: string + ) => void; + /** + * @internal Get drag event target location + */ + recursiveGetTarget: ( + e: DragEvent, + leaf: WorkspaceLeaf + ) => WorkspaceTabs | null; + /** + * @internal Register a CodeMirror editor extension + * @remark Prefer registering the extension via the Plugin class + */ + registerEditorExtension: (extension: Extension) => void; + /** + * @internal Registers hover link source + */ + registerHoverLinkSource: (key: string, source: HoverLinkSource) => void; + /** + * @internal Registers Obsidian protocol handler + */ + registerObsidianProtocolHandler: ( + protocol: string, + handler: ObsidianProtocolHandler + ) => void; + /** + * @internal Constructs hook for receiving URI actions + */ + registerUriHook: () => void; + /** + * @internal Request execution of activeLeaf change events + */ + requestActiveLeafEvents: () => void; + /** + * @internal Request execution of resize event + */ + requestResize: () => void; + /** + * @internal Request execution of layout save event + */ + requestSaveLayout: () => void; + /** + * @internal Request execution of layout update event + */ + requestUpdateLayout: () => void; + /** + * Save workspace layout to disk + */ + saveLayout: () => Promise; + /** + * @internal Use deserialized layout data to reconstruct the workspace + */ + setLayout: (data: SerializedWorkspace) => Promise; + /** + * @internal Split leaves in specified direction + */ + splitLeaf: ( + leaf: WorkspaceLeaf, + newleaf: WorkspaceLeaf, + direction?: SplitDirection, + before?: boolean + ) => void; + /** + * Split provided leaf, or active leaf if none provided + */ + splitLeafOrActive: ( + leaf?: WorkspaceLeaf, + direction?: SplitDirection + ) => void; + /** + * @internal + */ + trigger: (e: any) => void; + /** + * @internal Unregister a CodeMirror editor extension + */ + unregisterEditorExtension: (extension: Extension) => void; + /** + * @internal Unregister hover link source + */ + unregisterHoverLinkSource: (key: string) => void; + /** + * @internal Unregister Obsidian protocol handler + */ + unregisterObsidianProtocolHandler: (protocol: string) => void; + /** + * @internal + */ + updateFrameless: () => void; + /** + * @internal Invoke workspace layout update, redraw and save + */ + updateLayout: () => void; + /** + * @internal Update visibility of tab group + */ + updateMobileVisibleTabGroup: () => void; + /** + * Update the internal title of the application + * @remark This title is shown as the application title in the OS taskbar + */ + updateTitle: () => void; + } + + interface Vault { + /** + * Low-level file system adapter for read and write operations + * @tutorial Can be used to read binaries, or files not located directly within the vault + */ + adapter: DataAdapter; + /** + * @internal Max size of the cache in bytes + */ + cacheLimit: number; + /** + * Object containing all config settings for the vault (editor, appearance, ... settings) + * @remark Prefer usage of `app.vault.getConfig(key)` to get settings, config does not contain + * settings that were not changed from their default value + */ + config: AppVaultConfig; + /** + * @internal Timestamp of the last config change + */ + configTs: number; + /** + * @internal Mapping of path to Obsidian folder or file structure + */ + fileMap: Record; + + on(name: "config-changed", callback: () => void, ctx?: any): EventRef; + + /** + * @internal Add file as child/parent to respective folders + */ + addChild: (file: TAbstractFile) => void; + /** + * @internal Check whether new file path is available + */ + checkForDuplicate: (file: TAbstractFile, newPath: string) => boolean; + /** + * @internal Check whether path has valid formatting (no dots/spaces at end, ...) + */ + checkPath: (path: string) => boolean; + /** + * @internal Remove a vault config file + */ + deleteConfigJson: (configFile: string) => Promise; + /** + * Check whether a file exists in the vault + */ + exists: (file: TAbstractFile, senstive?: boolean) => Promise; + /** + * @internal + */ + generateFiles: (any) => Promise; + /** + * Get an abstract file by path, insensitive to case + */ + getAbstractFileByPathInsensitive: ( + path: string + ) => TAbstractFile | null; + /** + * @internal Get path for file that does not conflict with other existing files + */ + getAvailablePath: (path: string, extension: string) => string; + /** + * @internal Get path for attachment that does not conflict with other existing files + */ + getAvailablePathForAttachments: ( + filename: string, + file: TAbstractFile, + extension: string + ) => string; + /** + * Get value from config by key + * @remark Default value will be selected if config value was not manually changed + * @param key Key of config value + */ + getConfig: (string: ConfigItem) => any; + /** + * Get path to config file (relative to vault root) + */ + getConfigFile: (configFile: string) => string; + /** + * Get direct parent of file + * @param file File to get parent of + */ + getDirectParent: (file: TAbstractFile) => TFolder | null; + /** + * @internal Check whether files map cache is empty + */ + isEmpty: () => boolean; + /** + * @internal Iterate over the files and read them + */ + iterateFiles: (files: TFile[], cachedRead: boolean) => void; + /** + * @internal Load vault adapter + */ + load: () => Promise; + /** + * @internal Listener for all events on the vault + */ + onChange: (eventType: string, path: string, x: any, y: any) => void; + /** + * Read a config file from the vault and parse it as JSON + * @param config Name of config file + */ + readConfigJson: (config: string) => Promise; + /** + * Read a config file (full path) from the vault and parse it as JSON + * @param path Full path to config file + */ + readJson: (path: string) => Promise; + /** + * Read a plugin config file (full path relative to vault root) from the vault and parse it as JSON + * @param path Full path to plugin config file + */ + readPluginData: (path: string) => Promise; + /** + * Read a file from the vault as a string + * @param path Path to file + */ + readRaw: (path: string) => Promise; + /** + * @internal Reload all config files + */ + reloadConfig: () => void; + /** + * @internal Remove file as child/parent from respective folders + * @param file File to remove + */ + removeChild: (file: TAbstractFile) => void; + /** + * @internal Get the file by absolute path + * @param path Path to file + */ + resolveFilePath: (path: string) => TAbstractFile | null; + /** + * @internal Get the file by Obsidian URL + */ + resolveFileUrl: (url: string) => TAbstractFile | null; + /** + * @internal Debounced function for saving config + */ + requestSaveConfig: () => void; + /** + * @internal Save app and appearance configs to disk + */ + saveConfig: () => Promise; + /** + * Set value of config by key + * @param key Key of config value + * @param value Value to set + */ + setConfig: (key: ConfigItem, value: any) => void; + /** + * Set where the config files are stored (relative to vault root) + * @param configDir Path to config files + */ + setConfigDir: (configDir: string) => void; + /** + * @internal Set file cache limit + */ + setFileCacheLimit: (limit: number) => void; + /** + * @internal Load all config files into memory + */ + setupConfig: () => Promise; + /** + * @internal Trigger an event on handler + */ + trigger: (type: string) => void; + /** + * Write a config file to disk + * @param config Name of config file + * @param data Data to write + */ + writeConfigJson: (config: string, data: object) => Promise; + /** + * Write a config file (full path) to disk + * @param path Full path to config file + * @param data Data to write + * @param pretty Whether to insert tabs or spaces + */ + writeJson: ( + path: string, + data: object, + pretty?: boolean + ) => Promise; + /** + * Write a plugin config file (path relative to vault root) to disk + */ + writePluginData: (path: string, data: object) => Promise; + } + + // TODO: Add missing elements to other Obsidian interfaces and classes + + interface Editor { + /** + * CodeMirror editor instance + */ + cm: EditorViewI; + /** + * HTML instance the CM editor is attached to + */ + containerEl: HTMLElement; + + /** + * Make ranges of text highlighted within the editor given specified class (style) + */ + addHighlights: ( + ranges: { from: EditorPosition; to: EditorPosition }[], + style: "is-flashing" | "obsidian-search-match-highlight", + remove_previous: boolean, + x: boolean + ) => void; + /** + * Convert editor position to screen position + * @param pos Editor position + * @param mode Relative to the editor or the application window + */ + coordsAtPos: ( + pos: EditorPosition, + relative_to_editor = false + ) => { left: number; top: number; bottom: number; right: number }; + /** + * Unfolds all folded lines one level up + * @remark If level 1 and 2 headings are folded, level 2 headings will be unfolded + */ + foldLess: () => void; + /** + * Folds all the blocks that are of the lowest unfolded level + * @remark If there is a document with level 1 and 2 headings, level 2 headings will be folded + */ + foldMore: () => void; + /** + * Get all ranges that can be folded away in the editor + */ + getAllFoldableLines: () => { from: number; to: number }[]; + /** + * Get a clickable link - if it exists - at specified position + */ + getClickableTokenAt: ( + pos: EditorPosition + ) => { + start: EditorPosition; + end: EditorPosition; + text: string; + type: string; + } | null; + /** + * Get all blocks that were folded by their starting character position + */ + getFoldOffsets: () => Set; + /** + * Checks whether the editor has a highlight of specified class + * @remark If no style is specified, checks whether the editor has any highlights + */ + hasHighlight: (style?: string) => boolean; + /** + * Wraps current line around specified characters + * @remark Was added in a recent Obsidian update (1.4.0 update cycle) + **/ + insertBlock: (start: string, end: string) => void; + /** + * Get the closest character position to the specified coordinates + */ + posAtCoords: (coords: { left: number; top: number }) => EditorPosition; + /** + * Removes all highlights of specified class + */ + removeHighlights: (style: string) => void; + /** + * Adds a search cursor to the editor + */ + searchCursor: (searchString: string) => { + current: () => { from: EditorPosition; to: EditorPosition }; + findAll: () => { from: EditorPosition; to: EditorPosition }[]; + findNext: () => { from: EditorPosition; to: EditorPosition }; + findPrevious: () => { from: EditorPosition; to: EditorPosition }; + replace: (replacement?: string, origin: string) => void; + replaceAll: (replacement?: string, origin: string) => void; + }; + /** + * Applies specified markdown syntax to selected text or word under cursor + */ + toggleMarkdownFormatting: ( + syntax: + | "bold" + | "italic" + | "strikethrough" + | "highlight" + | "code" + | "math" + | "comment" + ) => void; + + /** + * Clean-up function executed after indenting lists + */ + afterIndent: () => void; + /** + * Expand text + * @internal + */ + expandText: () => void; + /** + * Indents a list by one level + */ + indentList: () => void; + /** + * Insert a template callout at the current cursor position + */ + insertCallout: () => void; + /** + * Insert a template code block at the current cursor position + */ + insertCodeblock: () => void; + /** + * Insert a markdown link at the current cursor position + */ + insertLink: () => void; + /** + * Insert a mathjax equation block at the current cursor position + */ + insertMathJax: () => void; + /** + * Insert specified text at the current cursor position + * @remark Might be broken, inserts at the end of the document + */ + insertText: (text: string) => void; + /** + * Inserts a new line and continues a markdown bullet point list at the same level + */ + newlineAndIndentContinueMarkdownList: () => void; + /** + * Inserts a new line at the same indent level + */ + newlineAndIndentOnly: () => void; + /** + * Get the character position at a mouse event + */ + posAtMouse: (e: MouseEvent) => EditorPosition; + /** + * Toggles blockquote syntax on paragraph under cursor + */ + toggleBlockquote: () => void; + /** + * Toggle bullet point list syntax on paragraph under cursor + */ + toggleBulletList: () => void; + /** + * Toggle checkbox syntax on paragraph under cursor + */ + toggleCheckList: () => void; + /** + * Toggle numbered list syntax on paragraph under cursor + */ + toggleNumberList: () => void; + /** + * Convert word under cursor into a wikilink + * @param embed Whether to embed the link or not + */ + triggerWikiLink: (embed: boolean) => void; + /** + * Unindents a list by one level + */ + unindentList: () => void; + } + + interface View { + headerEl: HTMLElement; + titleEl: HTMLElement; + } + + interface WorkspaceLeaf { + id?: string; + + tabHeaderEl: HTMLElement; + tabHeaderInnerIconEl: HTMLElement; + tabHeaderInnerTitleEl: HTMLElement; + } + + interface Menu { + dom: HTMLElement; + items: MenuItem[]; + onMouseOver: (evt: MouseEvent) => void; + hide: () => void; + } + + interface MenuItem { + callback: () => void; + dom: HTMLElement; + setSubmenu: () => Menu; + onClick: (evt: MouseEvent) => void; + disabled: boolean; + } + + interface MarkdownPreviewView { + renderer: ReadViewRenderer; + } + + interface EventRef { + /** + * Context applied to the event callback + */ + ctx?: any; + + /** + * Events object the event was registered on + */ + e: Events; + + /** + * Function to be called on event trigger on the events object + */ + fn: (any) => void; + + /** + * Event name the event was registered on + */ + name: string; + } +} + +interface RendererSection { + el: HTMLElement; + html: string; + rendered: boolean; +} + +interface ReadViewRenderer { + addBottomPadding: boolean; + lastRender: number; + lastScroll: number; + lastText: string; + previewEl: HTMLElement; + pusherEl: HTMLElement; + clear: () => void; + queueRender: () => void; + parseSync: () => void; + parseAsync: () => void; + set: (text: string) => void; + text: string; + sections: RendererSection[]; + asyncSections: any[]; + recycledSections: any[]; + rendered: any[]; +} + +interface CMState extends EditorState { + vim: { + inputState: { + changeQueue: null; + keyBuffer: []; + motion: null; + motionArgs: null; + motionRepeat: []; + operator: null; + operatorArgs: null; + prefixRepeat: []; + registerName: null; + }; + insertMode: false; + insertModeRepeat: undefined; + lastEditActionCommand: undefined; + lastEditInputState: undefined; + lastHPos: number; + lastHSPos: number; + lastMotion: { + name?: string; + }; + lastPastedText: null; + lastSelection: null; + }; + vimPlugin: { + lastKeydown: string; + }; +} + +interface CMView extends EditorView { + state: CMState; +} + +interface EditorViewI extends EditorView { + cm?: CMView; +} diff --git a/src/util/ShallowCompare.ts b/src/util/ShallowCompare.ts index dae2e3e..283def4 100644 --- a/src/util/ShallowCompare.ts +++ b/src/util/ShallowCompare.ts @@ -1,17 +1,16 @@ // Shallow compare for nested objects /* eslint-disable @typescript-eslint/no-explicit-any */ -const shallowCompare = (obj1: any, obj2: any): boolean => { +const compare = (obj1: any, obj2: any): boolean => { if (!obj1 || !obj2) return obj1 == obj2; else if (obj1 instanceof Object && obj2 instanceof Object) { return ( Object.keys(obj1).length === Object.keys(obj2).length && Object.keys(obj1).every( (key) => - obj2.hasOwnProperty(key) && - shallowCompare(obj1[key], obj2[key]) + obj2.hasOwnProperty(key) && compare(obj1[key], obj2[key]) ) ); } else return obj1 == obj2; }; -export default shallowCompare; +export default compare; diff --git a/src/views/graph/ForceGraph.ts b/src/views/graph/ForceGraph.ts index 5f670d4..00e7ff7 100644 --- a/src/views/graph/ForceGraph.ts +++ b/src/views/graph/ForceGraph.ts @@ -15,7 +15,13 @@ export class ForceGraph { private instance: ForceGraph3DInstance; private readonly rootHtmlElement: HTMLElement; + /** + * the node connected to the hover node + */ private readonly highlightedNodes: Set = new Set(); + /** + * the links connected to the hover node + */ private readonly highlightedLinks: Set = new Set(); hoveredNode: Node | null; @@ -116,6 +122,14 @@ export class ForceGraph { .nodeColor((node: Node) => this.getNodeColor(node)) .nodeVisibility(this.doShowNode) .onNodeHover(this.onNodeHover); + // @ts-ignore + // .nodeThreeObject((node) => { + // const sprite = new SpriteText(node.id); + // sprite.material.depthWrite = false; // make sprite background transparent + // sprite.color = node.color; + // sprite.textHeight = 8; + // return sprite; + // }); }; private getNodeColor = (node: Node): string => { diff --git a/tsconfig.json b/tsconfig.json index 40f4807..b80b0a0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,25 +1,25 @@ { - "compilerOptions": { - "baseUrl": ".", - "inlineSourceMap": true, - "inlineSources": true, - "module": "ESNext", - "target": "ES6", - "allowJs": true, - "noImplicitAny": true, - "moduleResolution": "node", - "importHelpers": true, - "isolatedModules": true, - "strictNullChecks": true, - "lib": [ - "DOM", - "ES5", - "ES6", - "ES7" - ], - "allowSyntheticDefaultImports": true - }, - "include": [ - "**/*.ts" - ] + "compilerOptions": { + "baseUrl": ".", + "inlineSourceMap": true, + "inlineSources": true, + "module": "ESNext", + "target": "ES6", + "allowJs": true, + "noImplicitAny": true, + "moduleResolution": "node", + "importHelpers": true, + "isolatedModules": true, + "strictNullChecks": true, + "allowSyntheticDefaultImports": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "lib": ["DOM", "ES5", "ES6", "ES7", "ESNext"], + "types": ["bun-types"], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["**/*.ts"], + "exclude": ["node_modules"] }