mirror of
https://github.com/logseq/logseq.git
synced 2026-05-17 17:32:35 +00:00
This pull request refactors the plugin library infrastructure and adds new experimental features for hosted/sidebar renderers. The main changes include: Purpose: Refactor plugin communication library (Postmate) to support MessageChannel for improved performance, add support for hosted/sidebar renderers in plugins, add new debug APIs, and consolidate helper functions. Changes: Added MessageChannel support to Postmate for optimized plugin-host communication with backward compatibility Introduced hosted renderer and sidebar renderer APIs for plugins to register custom UI components Added new app APIs: get_current_route, export_debug_log_db, reset_debug_log_db Refactored helper functions from helpers.ts to common.ts and updated all import paths Extended block property APIs to include class properties with default values Added comprehensive documentation for experiments APIs and plugin development Added E2E test for plugin marketplace installation Version bump from 0.2.12 to 0.3.1
1198 lines
31 KiB
TypeScript
1198 lines
31 KiB
TypeScript
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<T, K extends keyof T> = Omit<T, K> &
|
|
Partial<Pick<T, K>>
|
|
|
|
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
|
|
}
|
|
|
|
export type UIBaseOptions = {
|
|
key?: string
|
|
replace?: boolean
|
|
template: string | null
|
|
style?: CSS.Properties
|
|
attrs?: Record<string, string>
|
|
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
|
|
/**
|
|
* Alternative entrypoint for development.
|
|
*/
|
|
devEntry: string
|
|
/**
|
|
* For legacy themes, do not use.
|
|
*/
|
|
theme: unknown
|
|
}
|
|
|
|
export interface LSPluginBaseInfo {
|
|
/**
|
|
* Must be unique.
|
|
*/
|
|
id: string
|
|
mode: 'shadow' | 'iframe'
|
|
settings: {
|
|
disabled: boolean
|
|
} & Record<string, unknown>
|
|
effect: boolean
|
|
/**
|
|
* For internal use only. Indicates if plugin is installed in dot root.
|
|
*/
|
|
iir: boolean
|
|
/**
|
|
* For internal use only.
|
|
*/
|
|
lsr: string
|
|
}
|
|
|
|
export type IHookEvent = {
|
|
[key: string]: any
|
|
}
|
|
|
|
export type IUserOffHook = () => void
|
|
export type IUserHook<E = any, R = IUserOffHook> = (
|
|
callback: (e: IHookEvent & E) => void
|
|
) => IUserOffHook
|
|
export type IUserSlotHook<E = any> = (
|
|
callback: (e: IHookEvent & UISlotIdentity & E) => void
|
|
) => void
|
|
export type IUserConditionSlotHook<C = any, E = any> = (
|
|
condition: C,
|
|
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
|
|
|
|
/**
|
|
* @NOTE: not supported for DB graph
|
|
*/
|
|
properties?: Record<string, any>
|
|
|
|
children?: Array<IBatchBlock>
|
|
}
|
|
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
|
|
supportDb: boolean
|
|
|
|
[key: string]: unknown
|
|
}
|
|
|
|
/**
|
|
* 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]: unknown
|
|
}
|
|
|
|
/**
|
|
* In Logseq, a graph represents a repository of connected pages and blocks
|
|
*/
|
|
export interface AppGraphInfo {
|
|
name: string
|
|
url: string
|
|
path: string
|
|
|
|
[key: string]: unknown
|
|
}
|
|
|
|
/**
|
|
* Block - Logseq's fundamental data structure.
|
|
*/
|
|
export interface BlockEntity {
|
|
id: EntityID // db id
|
|
uuid: BlockUUID
|
|
order: string
|
|
format: 'markdown' | 'org'
|
|
parent: IEntityID
|
|
title: string
|
|
fullTitle: string // replace block reference uuid with title text
|
|
content?: string // @deprecated. use :title instead!
|
|
page: IEntityID // owner page
|
|
createdAt: number
|
|
updatedAt: number
|
|
ident?: string // ident for property block
|
|
properties?: Record<string, any>
|
|
'collapsed?': boolean
|
|
|
|
// optional fields in dummy page
|
|
anchor?: string
|
|
body?: any
|
|
children?: Array<BlockEntity | BlockUUIDTuple>
|
|
container?: string
|
|
file?: IEntityID
|
|
level?: number
|
|
meta?: { timestamps: any; properties: any; startPos: number; endPos: number }
|
|
marker?: string
|
|
|
|
[key: string]: unknown
|
|
}
|
|
|
|
/**
|
|
* Page is just a block with some specific properties.
|
|
*/
|
|
export interface PageEntity {
|
|
id: EntityID
|
|
uuid: BlockUUID
|
|
name: string
|
|
format: 'markdown' | 'org'
|
|
type: 'page' | 'journal' | 'whiteboard' | 'class' | 'property' | 'hidden'
|
|
updatedAt: number
|
|
createdAt: number
|
|
'journal?': boolean
|
|
|
|
title?: string
|
|
file?: IEntityID
|
|
originalName?: string
|
|
namespace?: IEntityID
|
|
children?: Array<PageEntity>
|
|
properties?: Record<string, any>
|
|
journalDay?: number
|
|
ident?: string
|
|
|
|
[key: string]: unknown
|
|
}
|
|
|
|
export type BlockIdentity = BlockUUID | Pick<BlockEntity, 'uuid'>
|
|
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 = any> = (e: IHookEvent & E) => void
|
|
export type BlockCommandCallback = (
|
|
e: IHookEvent & { uuid: BlockUUID }
|
|
) => Promise<void>
|
|
export type BlockCursorPosition = {
|
|
left: number
|
|
top: number
|
|
height: number
|
|
pos: number
|
|
rect: DOMRect
|
|
}
|
|
|
|
export type Keybinding = string | Array<string>
|
|
export type SimpleCommandKeybinding = {
|
|
mode?: 'global' | 'non-editing' | 'editing'
|
|
binding: Keybinding
|
|
mac?: string // special for Mac OS
|
|
}
|
|
|
|
export type SettingSchemaDesc = {
|
|
key: string
|
|
type: 'string' | 'number' | 'boolean' | 'enum' | 'object' | 'heading'
|
|
default: string | number | boolean | Array<any> | object | null
|
|
title: string
|
|
description: string // support markdown
|
|
inputAs?: 'color' | 'date' | 'datetime-local' | 'range' | 'textarea'
|
|
enumChoices?: Array<string>
|
|
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/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/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-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'
|
|
|
|
export type UserProxyNSTags = 'app' | 'editor' | 'db' | 'git' | 'ui' | 'assets' | 'utils'
|
|
|
|
export type SearchIndiceInitStatus = boolean
|
|
export type SearchBlockItem = {
|
|
id: EntityID
|
|
uuid: BlockIdentity
|
|
content: string
|
|
page: EntityID
|
|
}
|
|
export type SearchPageItem = string
|
|
export type SearchFileItem = string
|
|
|
|
export type PropertySchema = {
|
|
type: 'default' | 'number' | 'node' | 'date' | 'checkbox' | 'url' | string,
|
|
cardinality: 'many' | 'one',
|
|
hide: boolean
|
|
public: boolean
|
|
}
|
|
|
|
export interface IPluginSearchServiceHooks {
|
|
name: string
|
|
options?: Record<string, any>
|
|
|
|
onQuery: (
|
|
graph: string,
|
|
key: string,
|
|
opts: Partial<{ limit: number }>
|
|
) => Promise<{
|
|
graph: string
|
|
key: string
|
|
blocks?: Array<Partial<SearchBlockItem>>
|
|
pages?: Array<SearchPageItem>
|
|
files?: Array<SearchFileItem>
|
|
}>
|
|
|
|
onIndiceInit: (graph: string) => Promise<SearchIndiceInitStatus>
|
|
onIndiceReset: (graph: string) => Promise<void>
|
|
onBlocksChanged: (
|
|
graph: string,
|
|
changes: {
|
|
added: Array<SearchBlockItem>
|
|
removed: Array<EntityID>
|
|
}
|
|
) => Promise<void>
|
|
onGraphRemoved: (graph: string, opts?: {}) => Promise<any>
|
|
}
|
|
|
|
/**
|
|
* App level APIs
|
|
*/
|
|
export interface IAppProxy {
|
|
/**
|
|
* @added 0.0.4
|
|
* @param key
|
|
*/
|
|
getInfo: (key?: keyof AppInfo) => Promise<AppInfo | any>
|
|
|
|
getUserInfo: () => Promise<AppUserInfo | null>
|
|
getUserConfigs: () => Promise<AppUserConfigs>
|
|
|
|
// services
|
|
registerSearchService<T extends IPluginSearchServiceHooks>(s: T): void
|
|
|
|
// 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 | string,
|
|
action: SimpleCommandCallback,
|
|
opts?: Partial<{
|
|
key: string
|
|
label: string
|
|
desc: string
|
|
extras: Record<string, any>
|
|
}>
|
|
) => void
|
|
|
|
/**
|
|
* Supported all registered palette commands
|
|
* @param type
|
|
* @param args
|
|
*/
|
|
invokeExternalCommand: (
|
|
type: ExternalCommandType,
|
|
...args: Array<any>
|
|
) => Promise<void>
|
|
|
|
/**
|
|
* Call external plugin command provided by models or registered commands
|
|
* @added 0.0.13
|
|
* @param type `xx-plugin-id.commands.xx-key`, `xx-plugin-id.models.xx-key`
|
|
* @param args
|
|
*/
|
|
invokeExternalPlugin: (type: string, ...args: Array<any>) => Promise<unknown>
|
|
|
|
/**
|
|
* @added 0.0.13
|
|
* @param pid
|
|
*/
|
|
getExternalPlugin: (pid: string) => Promise<{} | null>
|
|
|
|
/**
|
|
* 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: <T = any>(path: string | Array<string>) => Promise<T>
|
|
setStateFromStore: (path: string | Array<string>, value: any) => Promise<void>
|
|
|
|
// native
|
|
relaunch: () => Promise<void>
|
|
quit: () => Promise<void>
|
|
openExternalLink: (url: string) => Promise<void>
|
|
|
|
/**
|
|
* @deprecated Using `logseq.Git.execCommand`
|
|
* @link https://github.com/desktop/dugite/blob/master/docs/api/exec.md
|
|
* @param args
|
|
*/
|
|
execGitCommand: (args: string[]) => Promise<string>
|
|
|
|
// graph
|
|
getCurrentGraph: () => Promise<AppGraphInfo | null>
|
|
checkCurrentIsDbGraph: () => Promise<Boolean>
|
|
getCurrentGraphConfigs: (...keys: string[]) => Promise<any>
|
|
setCurrentGraphConfigs: (configs: {}) => Promise<void>
|
|
getCurrentGraphFavorites: () => Promise<Array<string | PageEntity> | null>
|
|
getCurrentGraphRecent: () => Promise<Array<string | PageEntity> | null>
|
|
getCurrentGraphTemplates: () => Promise<Record<string, BlockEntity> | null>
|
|
|
|
// router
|
|
pushState: (
|
|
k: string,
|
|
params?: Record<string, any>,
|
|
query?: Record<string, any>
|
|
) => void
|
|
replaceState: (
|
|
k: string,
|
|
params?: Record<string, any>,
|
|
query?: Record<string, any>
|
|
) => void
|
|
getCurrentRoute: () => Promise<{
|
|
path: string
|
|
parameters: Record<string, any>
|
|
template: string
|
|
}>
|
|
|
|
// templates
|
|
getTemplate: (name: string) => Promise<BlockEntity | null>
|
|
existTemplate: (name: string) => Promise<Boolean>
|
|
createTemplate: (
|
|
target: BlockUUID,
|
|
name: string,
|
|
opts?: { overwrite: boolean }
|
|
) => Promise<any>
|
|
removeTemplate: (name: string) => Promise<any>
|
|
insertTemplate: (target: BlockUUID, name: string) => Promise<any>
|
|
|
|
setZoomFactor: (factor: number) => void
|
|
setFullScreen: (flag: boolean | 'toggle') => void
|
|
setLeftSidebarVisible: (flag: boolean | 'toggle') => void
|
|
setRightSidebarVisible: (flag: boolean | 'toggle') => void
|
|
clearRightSidebarBlocks: (opts?: { close: boolean }) => 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<
|
|
Partial<{ name: string; mode: string; pid: string; url: string }>>
|
|
onTodayJournalCreated: IUserHook<{ title: string }>
|
|
onBeforeCommandInvoked: (condition: ExternalCommandType | string, callback: (e: IHookEvent) => void) => IUserOffHook
|
|
onAfterCommandInvoked: (condition: ExternalCommandType | string, callback: (e: IHookEvent) => void) => IUserOffHook
|
|
|
|
/**
|
|
* provide ui slot to specific block with UUID
|
|
*
|
|
* @added 0.0.13
|
|
*/
|
|
onBlockRendererSlotted: IUserConditionSlotHook<
|
|
BlockUUID,
|
|
Omit<BlockEntity, 'children' | 'page'>
|
|
>
|
|
|
|
/**
|
|
* 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: `
|
|
* <h2 style="color: ${color || 'red'}">${text}</h2>
|
|
* `,
|
|
* })
|
|
* })
|
|
* ```
|
|
*/
|
|
onMacroRendererSlotted: IUserSlotHook<{
|
|
payload: { arguments: Array<string>; uuid: string; [key: string]: any }
|
|
}>
|
|
|
|
onPageHeadActionsSlotted: IUserSlotHook
|
|
onRouteChanged: IUserHook<{ path: string; template: string }>
|
|
onSidebarVisibleChanged: IUserHook<{ visible: boolean }>
|
|
|
|
// internal
|
|
_installPluginHook: (pid: string, hook: string, opts?: any) => void
|
|
_uninstallPluginHook: (pid: string, hookOrAll: string | boolean) => void
|
|
}
|
|
|
|
/**
|
|
* Editor related APIs
|
|
*/
|
|
export interface IEditorProxy extends Record<string, any> {
|
|
/**
|
|
* 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<SlashCommandAction>
|
|
) => unknown
|
|
|
|
/**
|
|
* register a custom command in the block context menu (triggered by right-clicking the block dot)
|
|
* @param label - displayed name of command
|
|
* @param action - can be a single callback function to run when the command is called
|
|
*/
|
|
registerBlockContextMenuItem: (
|
|
label: string,
|
|
action: BlockCommandCallback
|
|
) => unknown
|
|
|
|
/**
|
|
* Current it's only available for pdf viewer
|
|
* @param label - displayed name of command
|
|
* @param action - callback for the clickable item
|
|
* @param opts - clearSelection: clear highlight selection when callback invoked
|
|
*/
|
|
registerHighlightContextMenuItem: (
|
|
label: string,
|
|
action: SimpleCommandCallback,
|
|
opts?: {
|
|
clearSelection: boolean
|
|
}
|
|
) => unknown
|
|
|
|
// block related APIs
|
|
|
|
checkEditing: () => Promise<BlockUUID | boolean>
|
|
|
|
insertAtEditingCursor: (content: string) => Promise<void>
|
|
|
|
restoreEditingCursor: () => Promise<void>
|
|
|
|
exitEditingMode: (selectBlock?: boolean) => Promise<void>
|
|
|
|
getEditingCursorPosition: () => Promise<BlockCursorPosition | null>
|
|
|
|
getEditingBlockContent: () => Promise<string>
|
|
|
|
getCurrentPage: () => Promise<PageEntity | BlockEntity | null>
|
|
getTodayPage: () => Promise<PageEntity | null>
|
|
|
|
getCurrentBlock: () => Promise<BlockEntity | null>
|
|
|
|
getSelectedBlocks: () => Promise<Array<BlockEntity> | null>
|
|
|
|
clearSelectedBlocks: () => Promise<void>
|
|
|
|
/**
|
|
* get all blocks of the current page as a tree structure
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const blocks = await logseq.Editor.getCurrentPageBlocksTree()
|
|
* initMindMap(blocks)
|
|
* ```
|
|
*/
|
|
getCurrentPageBlocksTree: () => Promise<Array<BlockEntity>>
|
|
|
|
/**
|
|
* get all blocks for the specified page
|
|
*
|
|
* @param srcPage - the page name or uuid
|
|
*/
|
|
getPageBlocksTree: (srcPage: PageIdentity) => Promise<Array<BlockEntity> | null>
|
|
|
|
/**
|
|
* get all page/block linked references
|
|
* @param srcPage
|
|
*/
|
|
getPageLinkedReferences: (
|
|
srcPage: PageIdentity
|
|
) => Promise<Array<[page: PageEntity, blocks: Array<BlockEntity>]> | null>
|
|
|
|
/**
|
|
* get flatten pages from top namespace
|
|
* @param namespace
|
|
*/
|
|
getPagesFromNamespace: (
|
|
namespace: BlockPageName
|
|
) => Promise<Array<PageEntity> | null>
|
|
|
|
/**
|
|
* construct pages tree from namespace pages
|
|
* @param namespace
|
|
*/
|
|
getPagesTreeFromNamespace: (
|
|
namespace: BlockPageName
|
|
) => Promise<Array<PageEntity> | null>
|
|
|
|
/**
|
|
* Create a unique UUID string which can then be assigned to a block.
|
|
* @added 0.0.8
|
|
*/
|
|
newBlockUUID: () => Promise<string>
|
|
|
|
isPageBlock: (block: BlockEntity | PageEntity) => Boolean
|
|
|
|
/**
|
|
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-reddit-hot-news
|
|
*
|
|
* @param srcBlock
|
|
* @param content
|
|
* @param opts
|
|
*/
|
|
insertBlock: (
|
|
srcBlock: BlockIdentity | EntityID,
|
|
content: string,
|
|
opts?: Partial<{
|
|
before: boolean
|
|
sibling: boolean
|
|
start: boolean
|
|
end: boolean
|
|
customUUID: string
|
|
properties: {}
|
|
}>
|
|
) => Promise<BlockEntity | null>
|
|
|
|
/**
|
|
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-reddit-hot-news
|
|
*
|
|
* `keepUUID` will allow you to set a custom UUID for blocks by setting their properties.id
|
|
*/
|
|
insertBatchBlock: (
|
|
srcBlock: BlockIdentity,
|
|
batch: IBatchBlock | Array<IBatchBlock>,
|
|
opts?: Partial<{ before: boolean; sibling: boolean; keepUUID: boolean }>
|
|
) => Promise<Array<BlockEntity> | null>
|
|
|
|
updateBlock: (
|
|
srcBlock: BlockIdentity | EntityID,
|
|
content: string,
|
|
opts?: Partial<{ properties: {} }>
|
|
) => Promise<void>
|
|
|
|
removeBlock: (srcBlock: BlockIdentity | EntityID) => Promise<void>
|
|
|
|
getBlock: (
|
|
srcBlock: BlockIdentity | EntityID,
|
|
opts?: Partial<{ includeChildren: boolean }>
|
|
) => Promise<BlockEntity | null>
|
|
|
|
setBlockCollapsed: (
|
|
srcBlock: BlockIdentity | EntityID,
|
|
opts: { flag: boolean | 'toggle' } | boolean | 'toggle'
|
|
) => Promise<void>
|
|
|
|
getPage: (
|
|
srcPage: PageIdentity | EntityID,
|
|
opts?: Partial<{ includeChildren: boolean }>
|
|
) => Promise<PageEntity | null>
|
|
|
|
createPage: (
|
|
pageName: BlockPageName,
|
|
properties?: {},
|
|
opts?: Partial<{
|
|
redirect: boolean
|
|
createFirstBlock: boolean
|
|
customUUID: string
|
|
format: BlockEntity['format']
|
|
journal: boolean
|
|
}>
|
|
) => Promise<PageEntity | null>
|
|
|
|
createJournalPage: (
|
|
date: string | Date
|
|
) => Promise<PageEntity | null>
|
|
|
|
deletePage: (pageName: BlockPageName) => Promise<void>
|
|
|
|
renamePage: (oldName: string, newName: string) => Promise<void>
|
|
|
|
getAllPages: (repo?: string) => Promise<PageEntity[] | null>
|
|
getAllTags: () => Promise<PageEntity[] | null>
|
|
getAllProperties: () => Promise<PageEntity[] | null>
|
|
getTagObjects: (nameOrIdent: string) => Promise<BlockEntity[] | null>
|
|
createTag: (
|
|
tagName: string,
|
|
opts?: Partial<{
|
|
uuid: string, // custom uuid
|
|
tagProperties: Array<{ name: string, schema?: Partial<PropertySchema>, properties?: {} }>
|
|
}>) => Promise<PageEntity | null>
|
|
getTag: (nameOrIdent: string | EntityID) => Promise<PageEntity | null>
|
|
getTagsByName: (tagName: string) => Promise<Array<PageEntity> | null>
|
|
addTagProperty: (tagId: BlockIdentity, propertyIdOrName: BlockIdentity) => Promise<void>
|
|
removeTagProperty: (tagId: BlockIdentity, propertyIdOrName: BlockIdentity) => Promise<void>
|
|
addTagExtends: (tagId: BlockIdentity, parentTagIdOrName: BlockIdentity) => Promise<void>
|
|
removeTagExtends: (tagId: BlockIdentity, parentTagIdOrName: BlockIdentity) => Promise<void>
|
|
addBlockTag: (blockId: BlockIdentity, tagId: BlockIdentity) => Promise<void>
|
|
removeBlockTag: (blockId: BlockIdentity, tagId: BlockIdentity) => Promise<void>
|
|
/**
|
|
* @note Emoji icon name from https://learn.missiveapp.com/open/emoji-mart
|
|
* */
|
|
setBlockIcon: (blockId: BlockIdentity, iconType: 'tabler-icon' | 'emoji', iconName: string) => Promise<void>
|
|
removeBlockIcon: (blockId: BlockIdentity) => Promise<void>
|
|
addPropertyValueChoices: (propertyId: BlockIdentity, choices: Array<BlockIdentity>) => Promise<void>
|
|
setPropertyNodeTags: (propertyId: BlockIdentity, tagIds: Array<EntityID>) => Promise<void>
|
|
|
|
prependBlockInPage: (
|
|
page: PageIdentity,
|
|
content: string,
|
|
opts?: Partial<{ properties: {} }>
|
|
) => Promise<BlockEntity | null>
|
|
|
|
appendBlockInPage: (
|
|
page: PageIdentity,
|
|
content: string,
|
|
opts?: Partial<{ properties: {} }>
|
|
) => Promise<BlockEntity | null>
|
|
|
|
getPreviousSiblingBlock: (
|
|
srcBlock: BlockIdentity | EntityID
|
|
) => Promise<BlockEntity | null>
|
|
|
|
getNextSiblingBlock: (
|
|
srcBlock: BlockIdentity | EntityID
|
|
) => Promise<BlockEntity | null>
|
|
|
|
moveBlock: (
|
|
srcBlock: BlockIdentity,
|
|
targetBlock: BlockIdentity,
|
|
opts?: Partial<{ before: boolean; children: boolean }>
|
|
) => Promise<void>
|
|
|
|
editBlock: (srcBlock: BlockIdentity, opts?: { pos: number }) => Promise<void>
|
|
selectBlock: (srcBlock: BlockIdentity) => Promise<void>
|
|
|
|
saveFocusedCodeEditorContent: () => Promise<void>
|
|
|
|
// property entity related APIs (DB only)
|
|
getProperty: (key: string) => Promise<BlockEntity | null>
|
|
|
|
// insert or update property entity
|
|
upsertProperty: (
|
|
key: string,
|
|
schema?: Partial<PropertySchema>,
|
|
opts?: { name?: string }) => Promise<IEntityID>
|
|
|
|
// remove property entity
|
|
removeProperty: (key: string) => Promise<void>
|
|
|
|
// block property related APIs
|
|
upsertBlockProperty: (
|
|
block: BlockIdentity | EntityID,
|
|
key: string,
|
|
value: any,
|
|
options?: Partial<{
|
|
reset: boolean
|
|
}>
|
|
) => Promise<void>
|
|
|
|
removeBlockProperty: (block: BlockIdentity | EntityID, key: string) => Promise<void>
|
|
getBlockProperty: (block: BlockIdentity | EntityID, key: string) => Promise<BlockEntity | null>
|
|
getBlockProperties: (block: BlockIdentity | EntityID) => Promise<Record<string, any> | null>
|
|
getPageProperties: (page: PageIdentity | EntityID) => Promise<Record<string, any> | null>
|
|
|
|
scrollToBlockInPage: (
|
|
pageName: BlockPageName,
|
|
blockId: BlockIdentity,
|
|
opts?: { replaceState: boolean }
|
|
) => void
|
|
|
|
openInRightSidebar: (id: BlockUUID | EntityID) => void
|
|
openPDFViewer: (assetBlockIdOrFileUrl: string | EntityID) => Promise<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. https://docs.logseq.com/#/page/queries
|
|
*/
|
|
q: <T = any>(dsl: string) => Promise<T>
|
|
|
|
/**
|
|
* Executes a datalog query through query-react,
|
|
* given either a regular datalog query or a simple query.
|
|
*/
|
|
customQuery: <T = any>(query: string) => Promise<T>
|
|
|
|
/**
|
|
* Run a datascript query with parameters.
|
|
*/
|
|
datascriptQuery: <T = any>(query: string, ...inputs: Array<any>) => Promise<T>
|
|
|
|
/**
|
|
* Hook all transaction data of DB.
|
|
*
|
|
* @added 0.0.2
|
|
*/
|
|
onChanged: IUserHook<{
|
|
blocks: Array<BlockEntity>
|
|
txData: Array<IDatom>
|
|
txMeta?: { outlinerOp: string; [key: string]: any }
|
|
}>
|
|
|
|
/**
|
|
* Subscribe a specific block changed event
|
|
*
|
|
* @added 0.0.2
|
|
*/
|
|
onBlockChanged(
|
|
uuid: BlockUUID,
|
|
callback: (
|
|
block: BlockEntity,
|
|
txData: Array<IDatom>,
|
|
txMeta?: { outlinerOp: string; [key: string]: any }
|
|
) => void
|
|
): IUserOffHook
|
|
|
|
/**
|
|
* For built-in files path `logseq/custom.js`, `logseq/custom.css`, `logseq/publish.js`, `logseq/publish.css` etc.
|
|
* @param path
|
|
* @param content
|
|
*/
|
|
setFileContent: (path: string, content: string) => Promise<void>
|
|
getFileContent: (path: string) => Promise<string | null>
|
|
}
|
|
|
|
/**
|
|
* 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<IGitResult>
|
|
|
|
loadIgnoreFile: () => Promise<string>
|
|
saveIgnoreFile: (content: string) => Promise<void>
|
|
}
|
|
|
|
/**
|
|
* UI related APIs
|
|
*/
|
|
export type UIMsgOptions = {
|
|
key: string
|
|
timeout: number // milliseconds. `0` indicate that keep showing
|
|
}
|
|
|
|
export type UIMsgKey = UIMsgOptions['key']
|
|
|
|
export interface IUIProxy {
|
|
showMsg: (
|
|
content: string,
|
|
status?: 'success' | 'warning' | 'error' | string,
|
|
opts?: Partial<UIMsgOptions>
|
|
) => Promise<UIMsgKey>
|
|
closeMsg: (key: UIMsgKey) => void
|
|
queryElementRect: (selector: string) => Promise<DOMRectReadOnly | null>
|
|
queryElementById: (id: string) => Promise<string | boolean>
|
|
checkSlotValid: (slot: UISlotIdentity['slot']) => Promise<boolean>
|
|
resolveThemeCssPropsVals: (props: string | Array<string>) => Promise<Record<string, string | undefined> | null>
|
|
}
|
|
|
|
export interface IUtilsProxy {
|
|
toJs: <R = unknown>(obj: {}) => Promise<R>
|
|
}
|
|
|
|
/**
|
|
* Assets related APIs
|
|
*/
|
|
export interface IAssetsProxy {
|
|
/**
|
|
* @added 0.0.2
|
|
* @param exts
|
|
*/
|
|
listFilesOfCurrentGraph(exts?: string | string[]): Promise<
|
|
Array<{
|
|
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
|
|
|
|
/**
|
|
* make assets scheme url based on current graph
|
|
* @added 0.0.15
|
|
* @param path
|
|
*/
|
|
makeUrl(path: string): Promise<string>
|
|
|
|
/**
|
|
* try to open asset type file in Logseq app
|
|
* @added 0.0.16
|
|
* @param path
|
|
*/
|
|
builtInOpen(path: string): Promise<boolean | undefined>
|
|
}
|
|
|
|
export interface ILSPluginThemeManager {
|
|
get themes(): Map<PluginLocalIdentity, Theme[]>
|
|
|
|
registerTheme(id: PluginLocalIdentity, opt: Theme): Promise<void>
|
|
|
|
unregisterTheme(id: PluginLocalIdentity, effect?: boolean): Promise<void>
|
|
|
|
selectTheme(
|
|
opt: Theme | LegacyTheme,
|
|
options: { effect?: boolean; emit?: boolean }
|
|
): Promise<void>
|
|
}
|
|
|
|
export type LSPluginUserEvents = 'ui:visible:changed' | 'settings:changed'
|
|
|
|
export interface ILSPluginUser extends EventEmitter<LSPluginUserEvents> {
|
|
/**
|
|
* 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<string, any>): Promise<any>
|
|
|
|
/**
|
|
* @param callback - a function to run when the main Logseq app is ready
|
|
*/
|
|
ready(callback?: (e: any) => void | {}): Promise<any>
|
|
|
|
ready(
|
|
model?: Record<string, any>,
|
|
callback?: (e: any) => void | {}
|
|
): Promise<any>
|
|
|
|
beforeunload: (callback: () => Promise<void>) => void
|
|
|
|
/**
|
|
* Create a object to hold the methods referenced in `provideUI`
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* logseq.provideModel({
|
|
* openCalendar () {
|
|
* console.log('Open the calendar!')
|
|
* }
|
|
* })
|
|
* ```
|
|
*/
|
|
provideModel(model: Record<string, any>): 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: `
|
|
* <a data-on-click="openCalendar" onclick="alert('abc')' style="opacity: .6; display: inline-flex; padding-left: 3px;'>
|
|
* <i class="iconfont icon-Calendaralt2"></i>
|
|
* </a>
|
|
* `
|
|
* })
|
|
* ```
|
|
*/
|
|
provideUI(ui: UIOptions): this
|
|
|
|
/**
|
|
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts
|
|
*
|
|
* @param schemas
|
|
*/
|
|
useSettingsSchema(schemas: Array<SettingSchemaDesc>): this
|
|
|
|
/**
|
|
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts
|
|
*
|
|
* Patch the current plugin settings with the provided attributes.
|
|
*
|
|
* @param attrs
|
|
*/
|
|
updateSettings(attrs: Record<string, any>): void
|
|
|
|
/**
|
|
* Called with full settings snapshots: `(nextSettings, previousSettings)`.
|
|
*/
|
|
onSettingsChanged<T = any>(cb: (a: T, b: T) => void): IUserOffHook
|
|
|
|
showSettingsUI(): void
|
|
|
|
hideSettingsUI(): void
|
|
|
|
setMainUIAttrs(attrs: Record<string, any>): 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
|
|
Editor: IEditorProxy
|
|
DB: IDBProxy
|
|
Git: IGitProxy
|
|
UI: IUIProxy
|
|
Assets: IAssetsProxy
|
|
|
|
Request: LSPluginRequest
|
|
FileStorage: LSPluginFileStorage
|
|
Experiments: LSPluginExperiments
|
|
}
|