import * as CSS from 'csstype' import EventEmitter from 'eventemitter3' import { LSPluginCaller } from './LSPlugin.caller' import { LSPluginExperiments } from './modules/LSPlugin.Experiments' import { IAsyncStorage, LSPluginFileStorage } from './modules/LSPlugin.Storage' import { LSPluginRequest } from './modules/LSPlugin.Request' export type WithOptional = Omit & Partial>; export type PluginLocalIdentity = string export type ThemeMode = 'light' | 'dark' export interface LegacyTheme { name: string url: string description?: string mode?: ThemeMode pid: PluginLocalIdentity } export interface Theme extends LegacyTheme { mode: ThemeMode } export type StyleString = string export type StyleOptions = { key?: string style: StyleString } export type UIContainerAttrs = { draggable: boolean resizable: boolean [key: string]: any } export type UIBaseOptions = { key?: string replace?: boolean template: string | null style?: CSS.Properties attrs?: Record close?: 'outside' | string reset?: boolean // reset slot content or not } export type UIPathIdentity = { /** * DOM selector */ path: string } export type UISlotIdentity = { /** * Slot key */ slot: string } export type UISlotOptions = UIBaseOptions & UISlotIdentity export type UIPathOptions = UIBaseOptions & UIPathIdentity export type UIOptions = UIBaseOptions | UIPathOptions | UISlotOptions export interface LSPluginPkgConfig { id: PluginLocalIdentity main: string entry: string // alias of main title: string mode: 'shadow' | 'iframe' themes: Theme[] icon: string [key: string]: any } export interface LSPluginBaseInfo { id: string // should be unique mode: 'shadow' | 'iframe' settings: { disabled: boolean [key: string]: any } [key: string]: any } export type IHookEvent = { [key: string]: any } export type IUserOffHook = () => void export type IUserHook = ( callback: (e: IHookEvent & E) => void ) => IUserOffHook export type IUserSlotHook = ( callback: (e: IHookEvent & UISlotIdentity & E) => void ) => void export type EntityID = number export type BlockUUID = string export type BlockUUIDTuple = ['uuid', BlockUUID] export type IEntityID = { id: EntityID; [key: string]: any } export type IBatchBlock = { content: string properties?: Record children?: Array } export type IDatom = [e: number, a: string, v: any, t: number, added: boolean] export type IGitResult = { stdout: string; stderr: string; exitCode: number } export interface AppUserInfo { [key: string]: any } export interface AppInfo { version: string [key: string]: any } /** * User's app configurations */ export interface AppUserConfigs { preferredThemeMode: ThemeMode preferredFormat: 'markdown' | 'org' preferredDateFormat: string preferredStartOfWeek: string preferredLanguage: string preferredWorkflow: string currentGraph: string showBracket: boolean enabledFlashcards: boolean enabledJournals: boolean [key: string]: any } /** * In Logseq, a graph represents a repository of connected pages and blocks */ export interface AppGraphInfo { name: string url: string path: string [key: string]: any } /** * Block - Logseq's fundamental data structure. */ export interface BlockEntity { id: EntityID // db id uuid: BlockUUID left: IEntityID format: 'markdown' | 'org' parent: IEntityID unordered: boolean content: string page: IEntityID properties?: Record // optional fields in dummy page anchor?: string body?: any children?: Array container?: string file?: IEntityID level?: number meta?: { timestamps: any; properties: any; startPos: number; endPos: number } title?: Array [key: string]: any } /** * Page is just a block with some specific properties. */ export interface PageEntity { id: EntityID uuid: BlockUUID name: string originalName: string 'journal?': boolean file?: IEntityID namespace?: IEntityID children?: Array properties?: Record format?: 'markdown' | 'org' journalDay?: number updatedAt?: number [key: string]: any } export type BlockIdentity = BlockUUID | Pick export type BlockPageName = string export type PageIdentity = BlockPageName | BlockIdentity export type SlashCommandActionCmd = | 'editor/input' | 'editor/hook' | 'editor/clear-current-slash' | 'editor/restore-saved-cursor' export type SlashCommandAction = [cmd: SlashCommandActionCmd, ...args: any] export type SimpleCommandCallback = (e: IHookEvent) => void export type BlockCommandCallback = ( e: IHookEvent & { uuid: BlockUUID } ) => Promise export type BlockCursorPosition = { left: number top: number height: number pos: number rect: DOMRect } export type SimpleCommandKeybinding = { mode?: 'global' | 'non-editing' | 'editing' binding: string mac?: string // special for Mac OS } export type SettingSchemaDesc = { key: string type: 'string' | 'number' | 'boolean' | 'enum' | 'object' | 'heading' default: string | number | boolean | Array | object | null title: string description: string // support markdown inputAs?: 'color' | 'date' | 'datetime-local' | 'range' | 'textarea' enumChoices?: Array enumPicker?: 'select' | 'radio' | 'checkbox' // default: select } export type ExternalCommandType = | 'logseq.command/run' | 'logseq.editor/cycle-todo' | 'logseq.editor/down' | 'logseq.editor/up' | 'logseq.editor/expand-block-children' | 'logseq.editor/collapse-block-children' | 'logseq.editor/open-file-in-default-app' | 'logseq.editor/open-file-in-directory' | 'logseq.editor/select-all-blocks' | 'logseq.editor/toggle-open-blocks' | 'logseq.editor/zoom-in' | 'logseq.editor/zoom-out' | 'logseq.editor/indent' | 'logseq.editor/outdent' | 'logseq.editor/copy' | 'logseq.editor/cut' | 'logseq.go/home' | 'logseq.go/journals' | 'logseq.go/keyboard-shortcuts' | 'logseq.go/next-journal' | 'logseq.go/prev-journal' | 'logseq.go/search' | 'logseq.go/search-in-page' | 'logseq.go/tomorrow' | 'logseq.go/backward' | 'logseq.go/forward' | 'logseq.search/re-index' | 'logseq.sidebar/clear' | 'logseq.sidebar/open-today-page' | 'logseq.ui/goto-plugins' | 'logseq.ui/select-theme-color' | 'logseq.ui/toggle-brackets' | 'logseq.ui/toggle-cards' | 'logseq.ui/toggle-contents' | 'logseq.ui/toggle-document-mode' | 'logseq.ui/toggle-help' | 'logseq.ui/toggle-left-sidebar' | 'logseq.ui/toggle-right-sidebar' | 'logseq.ui/toggle-settings' | 'logseq.ui/toggle-theme' | 'logseq.ui/toggle-wide-mode' | 'logseq.command-palette/toggle' export type UserProxyTags = 'app' | 'editor' | 'db' | 'git' | 'ui' | 'assets' /** * App level APIs */ export interface IAppProxy { /** * @added 0.0.4 * @param key */ getInfo: (key?: keyof AppInfo) => Promise getUserInfo: () => Promise getUserConfigs: () => Promise // commands registerCommand: ( type: string, opts: { key: string label: string desc?: string palette?: boolean keybinding?: SimpleCommandKeybinding }, action: SimpleCommandCallback ) => void registerCommandPalette: ( opts: { key: string label: string keybinding?: SimpleCommandKeybinding }, action: SimpleCommandCallback ) => void /** * Supported key names * @link https://gist.github.com/xyhp915/d1a6d151a99f31647a95e59cdfbf4ddc * @param keybinding * @param action */ registerCommandShortcut: ( keybinding: SimpleCommandKeybinding, action: SimpleCommandCallback ) => void invokeExternalCommand: ( type: ExternalCommandType, ...args: Array ) => Promise /** * Get state from app store * valid state is here * https://github.com/logseq/logseq/blob/master/src/main/frontend/state.cljs#L27 * * @example * ```ts * const isDocMode = await logseq.App.getStateFromStore('document/mode?') * ``` * @param path */ getStateFromStore: (path: string | Array) => Promise // native relaunch: () => Promise quit: () => Promise openExternalLink: (url: string) => Promise /** * @deprecated Using `logseq.Git.execCommand` * @link https://github.com/desktop/dugite/blob/master/docs/api/exec.md * @param args */ execGitCommand: (args: string[]) => Promise // graph getCurrentGraph: () => Promise // router pushState: ( k: string, params?: Record, query?: Record ) => void replaceState: ( k: string, params?: Record, query?: Record ) => void // ui queryElementById: (id: string) => Promise /** * @added 0.0.5 * @param selector */ queryElementRect: (selector: string) => Promise /** * @deprecated Use `logseq.UI.showMsg` instead * @param content * @param status */ showMsg: ( content: string, status?: 'success' | 'warning' | 'error' | string ) => void setZoomFactor: (factor: number) => void setFullScreen: (flag: boolean | 'toggle') => void setLeftSidebarVisible: (flag: boolean | 'toggle') => void setRightSidebarVisible: (flag: boolean | 'toggle') => void registerUIItem: ( type: 'toolbar' | 'pagebar', opts: { key: string; template: string } ) => void registerPageMenuItem: ( tag: string, action: (e: IHookEvent & { page: string }) => void ) => void // hook events onCurrentGraphChanged: IUserHook onGraphAfterIndexed: IUserHook<{ repo: string }> onThemeModeChanged: IUserHook<{ mode: 'dark' | 'light' }> onThemeChanged: IUserHook> onBlockRendererSlotted: IUserSlotHook<{ uuid: BlockUUID }> /** * provide ui slot to block `renderer` macro for `{{renderer arg1, arg2}}` * * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-pomodoro-timer * @example * ```ts * // e.g. {{renderer :h1, hello world, green}} * * logseq.App.onMacroRendererSlotted(({ slot, payload: { arguments } }) => { * let [type, text, color] = arguments * if (type !== ':h1') return * logseq.provideUI({ * key: 'h1-playground', * slot, template: ` *

${text}

* `, * }) * }) * ``` */ onMacroRendererSlotted: IUserSlotHook<{ payload: { arguments: Array; uuid: string; [key: string]: any } }> onPageHeadActionsSlotted: IUserSlotHook onRouteChanged: IUserHook<{ path: string; template: string }> onSidebarVisibleChanged: IUserHook<{ visible: boolean }> // internal _installPluginHook: (pid: string, hook: string) => void _uninstallPluginHook: (pid: string, hookOrAll: string | boolean) => void } /** * Editor related APIs */ export interface IEditorProxy extends Record { /** * register a custom command which will be added to the Logseq slash command list * @param tag - displayed name of command * @param action - can be a single callback function to run when the command is called, or an array of fixed commands with arguments * * * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-slash-commands * * @example * ```ts * logseq.Editor.registerSlashCommand("Say Hi", () => { * console.log('Hi!') * }) * ``` * * @example * ```ts * logseq.Editor.registerSlashCommand("💥 Big Bang", [ * ["editor/hook", "customCallback"], * ["editor/clear-current-slash"], * ]); * ``` */ registerSlashCommand: ( tag: string, action: BlockCommandCallback | Array ) => unknown /** * register a custom command in the block context menu (triggered by right clicking the block dot) * @param tag - displayed name of command * @param action - can be a single callback function to run when the command is called */ registerBlockContextMenuItem: ( tag: string, action: BlockCommandCallback ) => unknown // block related APIs checkEditing: () => Promise insertAtEditingCursor: (content: string) => Promise restoreEditingCursor: () => Promise exitEditingMode: (selectBlock?: boolean) => Promise getEditingCursorPosition: () => Promise getEditingBlockContent: () => Promise getCurrentPage: () => Promise getCurrentBlock: () => Promise getSelectedBlocks: () => Promise | null> /** * get all blocks of the current page as a tree structure * * @example * ```ts * const blocks = await logseq.Editor.getCurrentPageBlocksTree() * initMindMap(blocks) * ``` */ getCurrentPageBlocksTree: () => Promise> /** * get all blocks for the specified page * * @param srcPage - the page name or uuid */ getPageBlocksTree: (srcPage: PageIdentity) => Promise> /** * get all page/block linked references * @param srcPage */ getPageLinkedReferences: ( srcPage: PageIdentity ) => Promise]> | null> /** * get flatten pages from top namespace * @param namespace */ getPagesFromNamespace: ( namespace: BlockPageName ) => Promise | null> /** * construct pages tree from namespace pages * @param namespace */ getPagesTreeFromNamespace: ( namespace: BlockPageName ) => Promise | null> /** * Create a unique UUID string which can then be assigned to a block. * @added 0.0.8 */ newBlockUUID: () => Promise /** * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-reddit-hot-news * * @param srcBlock * @param content * @param opts */ insertBlock: ( srcBlock: BlockIdentity, content: string, opts?: Partial<{ before: boolean sibling: boolean isPageBlock: boolean customUUID: string properties: {} }> ) => Promise insertBatchBlock: ( srcBlock: BlockIdentity, batch: IBatchBlock | Array, opts?: Partial<{ before: boolean; sibling: boolean }> ) => Promise | null> updateBlock: ( srcBlock: BlockIdentity, content: string, opts?: Partial<{ properties: {} }> ) => Promise removeBlock: (srcBlock: BlockIdentity) => Promise getBlock: ( srcBlock: BlockIdentity | EntityID, opts?: Partial<{ includeChildren: boolean }> ) => Promise /** * @example * * ```ts * logseq.Editor.setBlockCollapsed('uuid', true) * logseq.Editor.setBlockCollapsed('uuid', 'toggle') * ``` * @param uuid * @param opts */ setBlockCollapsed: ( uuid: BlockUUID, opts: { flag: boolean | 'toggle' } | boolean | 'toggle' ) => Promise getPage: ( srcPage: PageIdentity | EntityID, opts?: Partial<{ includeChildren: boolean }> ) => Promise createPage: ( pageName: BlockPageName, properties?: {}, opts?: Partial<{ redirect: boolean createFirstBlock: boolean format: BlockEntity['format'] journal: boolean }> ) => Promise deletePage: (pageName: BlockPageName) => Promise renamePage: (oldName: string, newName: string) => Promise getAllPages: (repo?: string) => Promise prependBlockInPage: ( page: PageIdentity, content: string, opts?: Partial<{ properties: {} }> ) => Promise appendBlockInPage: ( page: PageIdentity, content: string, opts?: Partial<{ properties: {} }> ) => Promise getPreviousSiblingBlock: ( srcBlock: BlockIdentity ) => Promise getNextSiblingBlock: (srcBlock: BlockIdentity) => Promise moveBlock: ( srcBlock: BlockIdentity, targetBlock: BlockIdentity, opts?: Partial<{ before: boolean; children: boolean }> ) => Promise editBlock: (srcBlock: BlockIdentity, opts?: { pos: number }) => Promise selectBlock: (srcBlock: BlockIdentity) => Promise upsertBlockProperty: ( block: BlockIdentity, key: string, value: any ) => Promise removeBlockProperty: (block: BlockIdentity, key: string) => Promise getBlockProperty: (block: BlockIdentity, key: string) => Promise getBlockProperties: (block: BlockIdentity) => Promise scrollToBlockInPage: ( pageName: BlockPageName, blockId: BlockIdentity, opts?: { replaceState: boolean } ) => void openInRightSidebar: (uuid: BlockUUID) => void /** * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-a-translator */ onInputSelectionEnd: IUserHook<{ caret: any point: { x: number; y: number } start: number end: number text: string }> } /** * Datascript related APIs */ export interface IDBProxy { /** * Run a DSL query * @link https://docs.logseq.com/#/page/queries * @param dsl */ q: (dsl: string) => Promise | null> /** * Run a datascript query */ datascriptQuery: (query: string, ...inputs: Array) => Promise /** * Hook all transaction data of DB * * @added 0.0.2 */ onChanged: IUserHook<{ blocks: Array txData: Array txMeta?: { outlinerOp: string; [key: string]: any } }> /** * Subscribe a specific block changed event * * @added 0.0.2 */ onBlockChanged( uuid: BlockUUID, callback: ( block: BlockEntity, txData: Array, txMeta?: { outlinerOp: string; [key: string]: any } ) => void ): IUserOffHook } /** * Git related APIS */ export interface IGitProxy { /** * @added 0.0.2 * @link https://github.com/desktop/dugite/blob/master/docs/api/exec.md * @param args */ execCommand: (args: string[]) => Promise loadIgnoreFile: () => Promise saveIgnoreFile: (content: string) => Promise } /** * UI related APIs */ export type UIMsgOptions = { key: string timeout: number // milliseconds. `0` indicate that keep showing } export type UIMsgKey = UIMsgOptions['key'] export interface IUIProxy { /** * @added 0.0.2 * * @param content * @param status * @param opts */ showMsg: ( content: string, status?: 'success' | 'warning' | 'error' | string, opts?: Partial ) => Promise closeMsg: (key: UIMsgKey) => void } /** * Assets related APIs */ export interface IAssetsProxy { /** * @added 0.0.2 * @param exts */ listFilesOfCurrentGraph(exts?: string | string[]): Promise<{ path: string size: number accessTime: number modifiedTime: number changeTime: number birthTime: number }> /** * @example https://github.com/logseq/logseq/pull/6488 * @added 0.0.10 */ makeSandboxStorage(): IAsyncStorage } export interface ILSPluginThemeManager { get themes(): Map registerTheme(id: PluginLocalIdentity, opt: Theme): Promise unregisterTheme(id: PluginLocalIdentity, effect?: boolean): Promise selectTheme( opt: Theme | LegacyTheme, options: { effect?: boolean; emit?: boolean } ): Promise } export type LSPluginUserEvents = 'ui:visible:changed' | 'settings:changed' export interface ILSPluginUser extends EventEmitter { /** * Connection status with the main app */ connected: boolean /** * Duplex message caller */ caller: LSPluginCaller /** * The plugin configurations from package.json */ baseInfo: LSPluginBaseInfo /** * The plugin user settings */ settings?: LSPluginBaseInfo['settings'] /** * The main Logseq app is ready to run the plugin * * @param model - same as the model in `provideModel` */ ready(model?: Record): Promise /** * @param callback - a function to run when the main Logseq app is ready */ ready(callback?: (e: any) => void | {}): Promise ready( model?: Record, callback?: (e: any) => void | {} ): Promise beforeunload: (callback: () => Promise) => void /** * Create a object to hold the methods referenced in `provideUI` * * @example * ```ts * logseq.provideModel({ * openCalendar () { * console.log('Open the calendar!') * } * }) * ``` */ provideModel(model: Record): this /** * Set the theme for the main Logseq app */ provideTheme(theme: Theme): this /** * Inject custom css for the main Logseq app * * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts * @example * ```ts * logseq.provideStyle(` * @import url("https://at.alicdn.com/t/font_2409735_r7em724douf.css"); * ) * ``` */ provideStyle(style: StyleString | StyleOptions): this /** * Inject custom UI at specific DOM node. * Event handlers can not be passed by string, so you need to create them in `provideModel` * * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-a-translator * @example * ```ts * logseq.provideUI({ * key: 'open-calendar', * path: '#search', * template: ` * * * * ` * }) * ``` */ provideUI(ui: UIOptions): this /** * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts * * @param schemas */ useSettingsSchema(schemas: Array): this /** * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts * * @param attrs */ updateSettings(attrs: Record): void onSettingsChanged(cb: (a: T, b: T) => void): IUserOffHook showSettingsUI(): void hideSettingsUI(): void setMainUIAttrs(attrs: Record): void /** * Set the style for the plugin's UI * * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts * @example * ```ts * logseq.setMainUIInlineStyle({ * position: 'fixed', * zIndex: 11, * }) * ``` */ setMainUIInlineStyle(style: CSS.Properties): void /** * show the plugin's UI */ showMainUI(opts?: { autoFocus: boolean }): void /** * hide the plugin's UI */ hideMainUI(opts?: { restoreEditingCursor: boolean }): void /** * toggle the plugin's UI */ toggleMainUI(): void isMainUIVisible: boolean resolveResourceFullUrl(filePath: string): string App: IAppProxy & Record Editor: IEditorProxy & Record DB: IDBProxy Git: IGitProxy UI: IUIProxy Assets: IAssetsProxy Request: LSPluginRequest FileStorage: LSPluginFileStorage Experiments: LSPluginExperiments }