From ca78428970895d1a3a803182b1745df7ee3c57a6 Mon Sep 17 00:00:00 2001 From: Hananoshika Yomaru Date: Thu, 12 Oct 2023 00:39:24 -0700 Subject: [PATCH] update to bun --- .github/workflows/release.yml | 93 +- .husky/pre-push | 5 + LICENSE | 2 +- README.md | 11 +- bun-fix.d.ts | 2 + bun.lockb | Bin 0 -> 68468 bytes esbuild.config.mjs | 58 +- package.json | 28 +- src/graph/Graph.ts | 3 + src/graph/Link.ts | 3 + src/main.ts | 11 +- src/typings/obsidian-ex.d.ts | 3651 +++++++++++++++++++++++++++++++++ src/util/ShallowCompare.ts | 7 +- src/views/graph/ForceGraph.ts | 14 + tsconfig.json | 46 +- 15 files changed, 3789 insertions(+), 145 deletions(-) create mode 100755 .husky/pre-push create mode 100644 bun-fix.d.ts create mode 100755 bun.lockb create mode 100644 src/typings/obsidian-ex.d.ts 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 0000000000000000000000000000000000000000..d99a36d84e8aac11520716fb8d4cb582f60244d4 GIT binary patch literal 68468 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%0D1F)OBJ0_&* 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 Dgl(EH literal 0 HcmV?d00001 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"] }