LSPlugin.ts 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164
  1. import * as CSS from 'csstype'
  2. import EventEmitter from 'eventemitter3'
  3. import { LSPluginCaller } from './LSPlugin.caller'
  4. import { LSPluginExperiments } from './modules/LSPlugin.Experiments'
  5. import { IAsyncStorage, LSPluginFileStorage } from './modules/LSPlugin.Storage'
  6. import { LSPluginRequest } from './modules/LSPlugin.Request'
  7. export type WithOptional<T, K extends keyof T> = Omit<T, K> &
  8. Partial<Pick<T, K>>
  9. export type PluginLocalIdentity = string
  10. export type ThemeMode = 'light' | 'dark'
  11. export interface LegacyTheme {
  12. name: string
  13. url: string
  14. description?: string
  15. mode?: ThemeMode
  16. pid: PluginLocalIdentity
  17. }
  18. export interface Theme extends LegacyTheme {
  19. mode: ThemeMode
  20. }
  21. export type StyleString = string
  22. export type StyleOptions = {
  23. key?: string
  24. style: StyleString
  25. }
  26. export type UIContainerAttrs = {
  27. draggable: boolean
  28. resizable: boolean
  29. }
  30. export type UIBaseOptions = {
  31. key?: string
  32. replace?: boolean
  33. template: string | null
  34. style?: CSS.Properties
  35. attrs?: Record<string, string>
  36. close?: 'outside' | string
  37. reset?: boolean // reset slot content or not
  38. }
  39. export type UIPathIdentity = {
  40. /**
  41. * DOM selector
  42. */
  43. path: string
  44. }
  45. export type UISlotIdentity = {
  46. /**
  47. * Slot key
  48. */
  49. slot: string
  50. }
  51. export type UISlotOptions = UIBaseOptions & UISlotIdentity
  52. export type UIPathOptions = UIBaseOptions & UIPathIdentity
  53. export type UIOptions = UIBaseOptions | UIPathOptions | UISlotOptions
  54. export interface LSPluginPkgConfig {
  55. id: PluginLocalIdentity
  56. main: string
  57. entry: string // alias of main
  58. title: string
  59. mode: 'shadow' | 'iframe'
  60. themes: Theme[]
  61. icon: string
  62. /**
  63. * Alternative entrypoint for development.
  64. */
  65. devEntry: string
  66. /**
  67. * For legacy themes, do not use.
  68. */
  69. theme: unknown
  70. }
  71. export interface LSPluginBaseInfo {
  72. /**
  73. * Must be unique.
  74. */
  75. id: string
  76. mode: 'shadow' | 'iframe'
  77. settings: {
  78. disabled: boolean
  79. } & Record<string, unknown>
  80. effect: boolean
  81. /**
  82. * For internal use only. Indicates if plugin is installed in dot root.
  83. */
  84. iir: boolean
  85. /**
  86. * For internal use only.
  87. */
  88. lsr: string
  89. }
  90. export type IHookEvent = {
  91. [key: string]: any
  92. }
  93. export type IUserOffHook = () => void
  94. export type IUserHook<E = any, R = IUserOffHook> = (
  95. callback: (e: IHookEvent & E) => void
  96. ) => IUserOffHook
  97. export type IUserSlotHook<E = any> = (
  98. callback: (e: IHookEvent & UISlotIdentity & E) => void
  99. ) => void
  100. export type IUserConditionSlotHook<C = any, E = any> = (
  101. condition: C,
  102. callback: (e: IHookEvent & UISlotIdentity & E) => void
  103. ) => void
  104. export type EntityID = number
  105. export type BlockUUID = string
  106. export type BlockUUIDTuple = ['uuid', BlockUUID]
  107. export type IEntityID = { id: EntityID; [key: string]: any }
  108. export type IBatchBlock = {
  109. content: string
  110. /**
  111. * @NOTE: not supported for DB graph
  112. */
  113. properties?: Record<string, any>
  114. children?: Array<IBatchBlock>
  115. }
  116. export type IDatom = [e: number, a: string, v: any, t: number, added: boolean]
  117. export type IGitResult = { stdout: string; stderr: string; exitCode: number }
  118. export interface AppUserInfo {
  119. [key: string]: any
  120. }
  121. export interface AppInfo {
  122. version: string
  123. supportDb: boolean
  124. [key: string]: unknown
  125. }
  126. /**
  127. * User's app configurations
  128. */
  129. export interface AppUserConfigs {
  130. preferredThemeMode: ThemeMode
  131. preferredFormat: 'markdown' | 'org'
  132. preferredDateFormat: string
  133. preferredStartOfWeek: string
  134. preferredLanguage: string
  135. preferredWorkflow: string
  136. currentGraph: string
  137. showBracket: boolean
  138. enabledFlashcards: boolean
  139. enabledJournals: boolean
  140. [key: string]: unknown
  141. }
  142. /**
  143. * In Logseq, a graph represents a repository of connected pages and blocks
  144. */
  145. export interface AppGraphInfo {
  146. name: string
  147. url: string
  148. path: string
  149. [key: string]: unknown
  150. }
  151. /**
  152. * Block - Logseq's fundamental data structure.
  153. */
  154. export interface BlockEntity {
  155. id: EntityID // db id
  156. uuid: BlockUUID
  157. order: string
  158. format: 'markdown' | 'org'
  159. parent: IEntityID
  160. title: string
  161. fullTitle: string // replace block reference uuid with title text
  162. content?: string // @deprecated. use :title instead!
  163. page: IEntityID // owner page
  164. createdAt: number
  165. updatedAt: number
  166. ident?: string // ident for property block
  167. properties?: Record<string, any>
  168. 'collapsed?': boolean
  169. // optional fields in dummy page
  170. anchor?: string
  171. body?: any
  172. children?: Array<BlockEntity | BlockUUIDTuple>
  173. container?: string
  174. file?: IEntityID
  175. level?: number
  176. meta?: { timestamps: any; properties: any; startPos: number; endPos: number }
  177. marker?: string
  178. [key: string]: unknown
  179. }
  180. /**
  181. * Page is just a block with some specific properties.
  182. */
  183. export interface PageEntity {
  184. id: EntityID
  185. uuid: BlockUUID
  186. name: string
  187. format: 'markdown' | 'org'
  188. type: 'page' | 'journal' | 'whiteboard' | 'class' | 'property' | 'hidden'
  189. updatedAt: number
  190. createdAt: number
  191. 'journal?': boolean
  192. title?: string
  193. file?: IEntityID
  194. originalName?: string
  195. namespace?: IEntityID
  196. children?: Array<PageEntity>
  197. properties?: Record<string, any>
  198. journalDay?: number
  199. ident?: string
  200. [key: string]: unknown
  201. }
  202. export type BlockIdentity = BlockUUID | Pick<BlockEntity, 'uuid'>
  203. export type BlockPageName = string
  204. export type PageIdentity = BlockPageName | BlockIdentity
  205. export type SlashCommandActionCmd =
  206. | 'editor/input'
  207. | 'editor/hook'
  208. | 'editor/clear-current-slash'
  209. | 'editor/restore-saved-cursor'
  210. export type SlashCommandAction = [cmd: SlashCommandActionCmd, ...args: any]
  211. export type SimpleCommandCallback<E = any> = (e: IHookEvent & E) => void
  212. export type BlockCommandCallback = (
  213. e: IHookEvent & { uuid: BlockUUID }
  214. ) => Promise<void>
  215. export type BlockCursorPosition = {
  216. left: number
  217. top: number
  218. height: number
  219. pos: number
  220. rect: DOMRect
  221. }
  222. export type Keybinding = string | Array<string>
  223. export type SimpleCommandKeybinding = {
  224. mode?: 'global' | 'non-editing' | 'editing'
  225. binding: Keybinding
  226. mac?: string // special for Mac OS
  227. }
  228. export type SettingSchemaDesc = {
  229. key: string
  230. type: 'string' | 'number' | 'boolean' | 'enum' | 'object' | 'heading'
  231. default: string | number | boolean | Array<any> | object | null
  232. title: string
  233. description: string // support markdown
  234. inputAs?: 'color' | 'date' | 'datetime-local' | 'range' | 'textarea'
  235. enumChoices?: Array<string>
  236. enumPicker?: 'select' | 'radio' | 'checkbox' // default: select
  237. }
  238. export type ExternalCommandType =
  239. | 'logseq.command/run'
  240. | 'logseq.editor/cycle-todo'
  241. | 'logseq.editor/down'
  242. | 'logseq.editor/up'
  243. | 'logseq.editor/expand-block-children'
  244. | 'logseq.editor/collapse-block-children'
  245. | 'logseq.editor/open-file-in-default-app'
  246. | 'logseq.editor/open-file-in-directory'
  247. | 'logseq.editor/select-all-blocks'
  248. | 'logseq.editor/toggle-open-blocks'
  249. | 'logseq.editor/zoom-in'
  250. | 'logseq.editor/zoom-out'
  251. | 'logseq.editor/indent'
  252. | 'logseq.editor/outdent'
  253. | 'logseq.editor/copy'
  254. | 'logseq.editor/cut'
  255. | 'logseq.go/home'
  256. | 'logseq.go/journals'
  257. | 'logseq.go/keyboard-shortcuts'
  258. | 'logseq.go/next-journal'
  259. | 'logseq.go/prev-journal'
  260. | 'logseq.go/search'
  261. | 'logseq.go/tomorrow'
  262. | 'logseq.go/backward'
  263. | 'logseq.go/forward'
  264. | 'logseq.search/re-index'
  265. | 'logseq.sidebar/clear'
  266. | 'logseq.sidebar/open-today-page'
  267. | 'logseq.ui/goto-plugins'
  268. | 'logseq.ui/select-theme-color'
  269. | 'logseq.ui/toggle-brackets'
  270. | 'logseq.ui/toggle-contents'
  271. | 'logseq.ui/toggle-document-mode'
  272. | 'logseq.ui/toggle-help'
  273. | 'logseq.ui/toggle-left-sidebar'
  274. | 'logseq.ui/toggle-right-sidebar'
  275. | 'logseq.ui/toggle-settings'
  276. | 'logseq.ui/toggle-theme'
  277. | 'logseq.ui/toggle-wide-mode'
  278. export type UserProxyNSTags = 'app' | 'editor' | 'db' | 'git' | 'ui' | 'assets' | 'utils'
  279. export type SearchIndiceInitStatus = boolean
  280. export type SearchBlockItem = {
  281. id: EntityID
  282. uuid: BlockIdentity
  283. content: string
  284. page: EntityID
  285. }
  286. export type SearchPageItem = string
  287. export type SearchFileItem = string
  288. export interface IPluginSearchServiceHooks {
  289. name: string
  290. options?: Record<string, any>
  291. onQuery: (
  292. graph: string,
  293. key: string,
  294. opts: Partial<{ limit: number }>
  295. ) => Promise<{
  296. graph: string
  297. key: string
  298. blocks?: Array<Partial<SearchBlockItem>>
  299. pages?: Array<SearchPageItem>
  300. files?: Array<SearchFileItem>
  301. }>
  302. onIndiceInit: (graph: string) => Promise<SearchIndiceInitStatus>
  303. onIndiceReset: (graph: string) => Promise<void>
  304. onBlocksChanged: (
  305. graph: string,
  306. changes: {
  307. added: Array<SearchBlockItem>
  308. removed: Array<EntityID>
  309. }
  310. ) => Promise<void>
  311. onGraphRemoved: (graph: string, opts?: {}) => Promise<any>
  312. }
  313. /**
  314. * App level APIs
  315. */
  316. export interface IAppProxy {
  317. /**
  318. * @added 0.0.4
  319. * @param key
  320. */
  321. getInfo: (key?: keyof AppInfo) => Promise<AppInfo | any>
  322. getUserInfo: () => Promise<AppUserInfo | null>
  323. getUserConfigs: () => Promise<AppUserConfigs>
  324. // services
  325. registerSearchService<T extends IPluginSearchServiceHooks>(s: T): void
  326. // commands
  327. registerCommand: (
  328. type: string,
  329. opts: {
  330. key: string
  331. label: string
  332. desc?: string
  333. palette?: boolean
  334. keybinding?: SimpleCommandKeybinding
  335. },
  336. action: SimpleCommandCallback
  337. ) => void
  338. registerCommandPalette: (
  339. opts: {
  340. key: string
  341. label: string
  342. keybinding?: SimpleCommandKeybinding
  343. },
  344. action: SimpleCommandCallback
  345. ) => void
  346. /**
  347. * Supported key names
  348. * @link https://gist.github.com/xyhp915/d1a6d151a99f31647a95e59cdfbf4ddc
  349. * @param keybinding
  350. * @param action
  351. */
  352. registerCommandShortcut: (
  353. keybinding: SimpleCommandKeybinding | string,
  354. action: SimpleCommandCallback,
  355. opts?: Partial<{
  356. key: string
  357. label: string
  358. desc: string
  359. extras: Record<string, any>
  360. }>
  361. ) => void
  362. /**
  363. * Supported all registered palette commands
  364. * @param type
  365. * @param args
  366. */
  367. invokeExternalCommand: (
  368. type: ExternalCommandType,
  369. ...args: Array<any>
  370. ) => Promise<void>
  371. /**
  372. * Call external plugin command provided by models or registered commands
  373. * @added 0.0.13
  374. * @param type `xx-plugin-id.commands.xx-key`, `xx-plugin-id.models.xx-key`
  375. * @param args
  376. */
  377. invokeExternalPlugin: (type: string, ...args: Array<any>) => Promise<unknown>
  378. /**
  379. * @added 0.0.13
  380. * @param pid
  381. */
  382. getExternalPlugin: (pid: string) => Promise<{} | null>
  383. /**
  384. * Get state from app store
  385. * valid state is here
  386. * https://github.com/logseq/logseq/blob/master/src/main/frontend/state.cljs#L27
  387. *
  388. * @example
  389. * ```ts
  390. * const isDocMode = await logseq.App.getStateFromStore('document/mode?')
  391. * ```
  392. * @param path
  393. */
  394. getStateFromStore: <T = any>(path: string | Array<string>) => Promise<T>
  395. setStateFromStore: (path: string | Array<string>, value: any) => Promise<void>
  396. // native
  397. relaunch: () => Promise<void>
  398. quit: () => Promise<void>
  399. openExternalLink: (url: string) => Promise<void>
  400. /**
  401. * @deprecated Using `logseq.Git.execCommand`
  402. * @link https://github.com/desktop/dugite/blob/master/docs/api/exec.md
  403. * @param args
  404. */
  405. execGitCommand: (args: string[]) => Promise<string>
  406. // graph
  407. getCurrentGraph: () => Promise<AppGraphInfo | null>
  408. checkCurrentIsDbGraph: () => Promise<Boolean>
  409. getCurrentGraphConfigs: (...keys: string[]) => Promise<any>
  410. setCurrentGraphConfigs: (configs: {}) => Promise<void>
  411. getCurrentGraphFavorites: () => Promise<Array<string | PageEntity> | null>
  412. getCurrentGraphRecent: () => Promise<Array<string | PageEntity> | null>
  413. getCurrentGraphTemplates: () => Promise<Record<string, BlockEntity> | null>
  414. // router
  415. pushState: (
  416. k: string,
  417. params?: Record<string, any>,
  418. query?: Record<string, any>
  419. ) => void
  420. replaceState: (
  421. k: string,
  422. params?: Record<string, any>,
  423. query?: Record<string, any>
  424. ) => void
  425. // templates
  426. getTemplate: (name: string) => Promise<BlockEntity | null>
  427. existTemplate: (name: string) => Promise<Boolean>
  428. createTemplate: (
  429. target: BlockUUID,
  430. name: string,
  431. opts?: { overwrite: boolean }
  432. ) => Promise<any>
  433. removeTemplate: (name: string) => Promise<any>
  434. insertTemplate: (target: BlockUUID, name: string) => Promise<any>
  435. setZoomFactor: (factor: number) => void
  436. setFullScreen: (flag: boolean | 'toggle') => void
  437. setLeftSidebarVisible: (flag: boolean | 'toggle') => void
  438. setRightSidebarVisible: (flag: boolean | 'toggle') => void
  439. clearRightSidebarBlocks: (opts?: { close: boolean }) => void
  440. registerUIItem: (
  441. type: 'toolbar' | 'pagebar',
  442. opts: { key: string; template: string }
  443. ) => void
  444. registerPageMenuItem: (
  445. tag: string,
  446. action: (e: IHookEvent & { page: string }) => void
  447. ) => void
  448. // hook events
  449. onCurrentGraphChanged: IUserHook
  450. onGraphAfterIndexed: IUserHook<{ repo: string }>
  451. onThemeModeChanged: IUserHook<{ mode: 'dark' | 'light' }>
  452. onThemeChanged: IUserHook<
  453. Partial<{ name: string; mode: string; pid: string; url: string }>>
  454. onTodayJournalCreated: IUserHook<{ title: string }>
  455. onBeforeCommandInvoked: (condition: ExternalCommandType | string, callback: (e: IHookEvent) => void) => IUserOffHook
  456. onAfterCommandInvoked: (condition: ExternalCommandType | string, callback: (e: IHookEvent) => void) => IUserOffHook
  457. /**
  458. * provide ui slot to specific block with UUID
  459. *
  460. * @added 0.0.13
  461. */
  462. onBlockRendererSlotted: IUserConditionSlotHook<
  463. BlockUUID,
  464. Omit<BlockEntity, 'children' | 'page'>
  465. >
  466. /**
  467. * provide ui slot to block `renderer` macro for `{{renderer arg1, arg2}}`
  468. *
  469. * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-pomodoro-timer
  470. * @example
  471. * ```ts
  472. * // e.g. {{renderer :h1, hello world, green}}
  473. *
  474. * logseq.App.onMacroRendererSlotted(({ slot, payload: { arguments } }) => {
  475. * let [type, text, color] = arguments
  476. * if (type !== ':h1') return
  477. * logseq.provideUI({
  478. * key: 'h1-playground',
  479. * slot, template: `
  480. * <h2 style="color: ${color || 'red'}">${text}</h2>
  481. * `,
  482. * })
  483. * })
  484. * ```
  485. */
  486. onMacroRendererSlotted: IUserSlotHook<{
  487. payload: { arguments: Array<string>; uuid: string; [key: string]: any }
  488. }>
  489. onPageHeadActionsSlotted: IUserSlotHook
  490. onRouteChanged: IUserHook<{ path: string; template: string }>
  491. onSidebarVisibleChanged: IUserHook<{ visible: boolean }>
  492. // internal
  493. _installPluginHook: (pid: string, hook: string, opts?: any) => void
  494. _uninstallPluginHook: (pid: string, hookOrAll: string | boolean) => void
  495. }
  496. /**
  497. * Editor related APIs
  498. */
  499. export interface IEditorProxy extends Record<string, any> {
  500. /**
  501. * register a custom command which will be added to the Logseq slash command list
  502. * @param tag - displayed name of command
  503. * @param action - can be a single callback function to run when the command is called, or an array of fixed commands with arguments
  504. *
  505. *
  506. * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-slash-commands
  507. *
  508. * @example
  509. * ```ts
  510. * logseq.Editor.registerSlashCommand("Say Hi", () => {
  511. * console.log('Hi!')
  512. * })
  513. * ```
  514. *
  515. * @example
  516. * ```ts
  517. * logseq.Editor.registerSlashCommand("💥 Big Bang", [
  518. * ["editor/hook", "customCallback"],
  519. * ["editor/clear-current-slash"],
  520. * ]);
  521. * ```
  522. */
  523. registerSlashCommand: (
  524. tag: string,
  525. action: BlockCommandCallback | Array<SlashCommandAction>
  526. ) => unknown
  527. /**
  528. * register a custom command in the block context menu (triggered by right-clicking the block dot)
  529. * @param label - displayed name of command
  530. * @param action - can be a single callback function to run when the command is called
  531. */
  532. registerBlockContextMenuItem: (
  533. label: string,
  534. action: BlockCommandCallback
  535. ) => unknown
  536. /**
  537. * Current it's only available for pdf viewer
  538. * @param label - displayed name of command
  539. * @param action - callback for the clickable item
  540. * @param opts - clearSelection: clear highlight selection when callback invoked
  541. */
  542. registerHighlightContextMenuItem: (
  543. label: string,
  544. action: SimpleCommandCallback,
  545. opts?: {
  546. clearSelection: boolean
  547. }
  548. ) => unknown
  549. // block related APIs
  550. checkEditing: () => Promise<BlockUUID | boolean>
  551. insertAtEditingCursor: (content: string) => Promise<void>
  552. restoreEditingCursor: () => Promise<void>
  553. exitEditingMode: (selectBlock?: boolean) => Promise<void>
  554. getEditingCursorPosition: () => Promise<BlockCursorPosition | null>
  555. getEditingBlockContent: () => Promise<string>
  556. getCurrentPage: () => Promise<PageEntity | BlockEntity | null>
  557. getCurrentBlock: () => Promise<BlockEntity | null>
  558. getSelectedBlocks: () => Promise<Array<BlockEntity> | null>
  559. clearSelectedBlocks: () => Promise<void>
  560. /**
  561. * get all blocks of the current page as a tree structure
  562. *
  563. * @example
  564. * ```ts
  565. * const blocks = await logseq.Editor.getCurrentPageBlocksTree()
  566. * initMindMap(blocks)
  567. * ```
  568. */
  569. getCurrentPageBlocksTree: () => Promise<Array<BlockEntity>>
  570. /**
  571. * get all blocks for the specified page
  572. *
  573. * @param srcPage - the page name or uuid
  574. */
  575. getPageBlocksTree: (srcPage: PageIdentity) => Promise<Array<BlockEntity> | null>
  576. /**
  577. * get all page/block linked references
  578. * @param srcPage
  579. */
  580. getPageLinkedReferences: (
  581. srcPage: PageIdentity
  582. ) => Promise<Array<[page: PageEntity, blocks: Array<BlockEntity>]> | null>
  583. /**
  584. * get flatten pages from top namespace
  585. * @param namespace
  586. */
  587. getPagesFromNamespace: (
  588. namespace: BlockPageName
  589. ) => Promise<Array<PageEntity> | null>
  590. /**
  591. * construct pages tree from namespace pages
  592. * @param namespace
  593. */
  594. getPagesTreeFromNamespace: (
  595. namespace: BlockPageName
  596. ) => Promise<Array<PageEntity> | null>
  597. /**
  598. * Create a unique UUID string which can then be assigned to a block.
  599. * @added 0.0.8
  600. */
  601. newBlockUUID: () => Promise<string>
  602. isPageBlock: (block: BlockEntity | PageEntity) => Boolean
  603. /**
  604. * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-reddit-hot-news
  605. *
  606. * @param srcBlock
  607. * @param content
  608. * @param opts
  609. */
  610. insertBlock: (
  611. srcBlock: BlockIdentity | EntityID,
  612. content: string,
  613. opts?: Partial<{
  614. before: boolean
  615. sibling: boolean
  616. start: boolean
  617. end: boolean
  618. customUUID: string
  619. properties: {}
  620. }>
  621. ) => Promise<BlockEntity | null>
  622. /**
  623. * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-reddit-hot-news
  624. *
  625. * `keepUUID` will allow you to set a custom UUID for blocks by setting their properties.id
  626. */
  627. insertBatchBlock: (
  628. srcBlock: BlockIdentity,
  629. batch: IBatchBlock | Array<IBatchBlock>,
  630. opts?: Partial<{ before: boolean; sibling: boolean; keepUUID: boolean }>
  631. ) => Promise<Array<BlockEntity> | null>
  632. updateBlock: (
  633. srcBlock: BlockIdentity | EntityID,
  634. content: string,
  635. opts?: Partial<{ properties: {} }>
  636. ) => Promise<void>
  637. removeBlock: (srcBlock: BlockIdentity | EntityID) => Promise<void>
  638. getBlock: (
  639. srcBlock: BlockIdentity | EntityID,
  640. opts?: Partial<{ includeChildren: boolean }>
  641. ) => Promise<BlockEntity | null>
  642. setBlockCollapsed: (
  643. srcBlock: BlockIdentity | EntityID,
  644. opts: { flag: boolean | 'toggle' } | boolean | 'toggle'
  645. ) => Promise<void>
  646. getPage: (
  647. srcPage: PageIdentity | EntityID,
  648. opts?: Partial<{ includeChildren: boolean }>
  649. ) => Promise<PageEntity | null>
  650. createPage: (
  651. pageName: BlockPageName,
  652. properties?: {},
  653. opts?: Partial<{
  654. redirect: boolean
  655. createFirstBlock: boolean
  656. customUUID: string
  657. format: BlockEntity['format']
  658. journal: boolean
  659. }>
  660. ) => Promise<PageEntity | null>
  661. createJournalPage: (
  662. date: string | Date
  663. ) => Promise<PageEntity | null>
  664. deletePage: (pageName: BlockPageName) => Promise<void>
  665. renamePage: (oldName: string, newName: string) => Promise<void>
  666. getAllPages: (repo?: string) => Promise<PageEntity[] | null>
  667. getAllTags: () => Promise<PageEntity[] | null>
  668. getAllProperties: () => Promise<PageEntity[] | null>
  669. getTagObjects: (nameOrIdent: string) => Promise<BlockEntity[] | null>
  670. createTag: (tagName: string, opts?: Partial<{ uuid: string }>) => Promise<PageEntity | null>
  671. getTag: (nameOrIdent: string | EntityID) => Promise<PageEntity | null>
  672. getTagsByName: (tagName: string) => Promise<Array<PageEntity> | null>
  673. addTagProperty: (tagId: BlockIdentity, propertyIdOrName: BlockIdentity) => Promise<void>
  674. removeTagProperty: (tagId: BlockIdentity, propertyIdOrName: BlockIdentity) => Promise<void>
  675. addTagExtends: (tagId: BlockIdentity, parentTagIdOrName: BlockIdentity) => Promise<void>
  676. removeTagExtends: (tagId: BlockIdentity, parentTagIdOrName: BlockIdentity) => Promise<void>
  677. addBlockTag: (blockId: BlockIdentity, tagId: BlockIdentity) => Promise<void>
  678. removeBlockTag: (blockId: BlockIdentity, tagId: BlockIdentity) => Promise<void>
  679. /**
  680. * @note Emoji icon name from https://learn.missiveapp.com/open/emoji-mart
  681. * */
  682. setBlockIcon: (blockId: BlockIdentity, iconType: 'tabler-icon' | 'emoji', iconName: string) => Promise<void>
  683. removeBlockIcon: (blockId: BlockIdentity) => Promise<void>
  684. prependBlockInPage: (
  685. page: PageIdentity,
  686. content: string,
  687. opts?: Partial<{ properties: {} }>
  688. ) => Promise<BlockEntity | null>
  689. appendBlockInPage: (
  690. page: PageIdentity,
  691. content: string,
  692. opts?: Partial<{ properties: {} }>
  693. ) => Promise<BlockEntity | null>
  694. getPreviousSiblingBlock: (
  695. srcBlock: BlockIdentity | EntityID
  696. ) => Promise<BlockEntity | null>
  697. getNextSiblingBlock: (
  698. srcBlock: BlockIdentity | EntityID
  699. ) => Promise<BlockEntity | null>
  700. moveBlock: (
  701. srcBlock: BlockIdentity,
  702. targetBlock: BlockIdentity,
  703. opts?: Partial<{ before: boolean; children: boolean }>
  704. ) => Promise<void>
  705. editBlock: (srcBlock: BlockIdentity, opts?: { pos: number }) => Promise<void>
  706. selectBlock: (srcBlock: BlockIdentity) => Promise<void>
  707. saveFocusedCodeEditorContent: () => Promise<void>
  708. // property entity related APIs (DB only)
  709. getProperty: (key: string) => Promise<BlockEntity | null>
  710. // insert or update property entity
  711. upsertProperty: (
  712. key: string,
  713. schema?: Partial<{
  714. type: 'default' | 'number' | 'node' | 'date' | 'checkbox' | 'url' | string,
  715. cardinality: 'many' | 'one',
  716. hide: boolean
  717. public: boolean
  718. }>,
  719. opts?: { name?: string }) => Promise<IEntityID>
  720. // remove property entity
  721. removeProperty: (key: string) => Promise<void>
  722. // block property related APIs
  723. upsertBlockProperty: (
  724. block: BlockIdentity | EntityID,
  725. key: string,
  726. value: any
  727. ) => Promise<void>
  728. removeBlockProperty: (block: BlockIdentity | EntityID, key: string) => Promise<void>
  729. getBlockProperty: (block: BlockIdentity | EntityID, key: string) => Promise<BlockEntity | null>
  730. getBlockProperties: (block: BlockIdentity | EntityID) => Promise<Record<string, any> | null>
  731. getPageProperties: (page: PageIdentity | EntityID) => Promise<Record<string, any> | null>
  732. scrollToBlockInPage: (
  733. pageName: BlockPageName,
  734. blockId: BlockIdentity,
  735. opts?: { replaceState: boolean }
  736. ) => void
  737. openInRightSidebar: (id: BlockUUID | EntityID) => void
  738. openPDFViewer: (assetBlockIdOrFileUrl: string | EntityID) => Promise<void>
  739. /**
  740. * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-a-translator
  741. */
  742. onInputSelectionEnd: IUserHook<{
  743. caret: any
  744. point: { x: number; y: number }
  745. start: number
  746. end: number
  747. text: string
  748. }>
  749. }
  750. /**
  751. * Datascript related APIs
  752. */
  753. export interface IDBProxy {
  754. /**
  755. * Run a DSL query
  756. * @link https://docs.logseq.com/#/page/queries
  757. * @param dsl
  758. */
  759. q: <T = any>(dsl: string) => Promise<Array<T> | null>
  760. /**
  761. * Run a datascript query
  762. */
  763. datascriptQuery: <T = any>(query: string, ...inputs: Array<any>) => Promise<T>
  764. /**
  765. * Hook all transaction data of DB
  766. *
  767. * @added 0.0.2
  768. */
  769. onChanged: IUserHook<{
  770. blocks: Array<BlockEntity>
  771. txData: Array<IDatom>
  772. txMeta?: { outlinerOp: string; [key: string]: any }
  773. }>
  774. /**
  775. * Subscribe a specific block changed event
  776. *
  777. * @added 0.0.2
  778. */
  779. onBlockChanged(
  780. uuid: BlockUUID,
  781. callback: (
  782. block: BlockEntity,
  783. txData: Array<IDatom>,
  784. txMeta?: { outlinerOp: string; [key: string]: any }
  785. ) => void
  786. ): IUserOffHook
  787. }
  788. /**
  789. * Git related APIS
  790. */
  791. export interface IGitProxy {
  792. /**
  793. * @added 0.0.2
  794. * @link https://github.com/desktop/dugite/blob/master/docs/api/exec.md
  795. * @param args
  796. */
  797. execCommand: (args: string[]) => Promise<IGitResult>
  798. loadIgnoreFile: () => Promise<string>
  799. saveIgnoreFile: (content: string) => Promise<void>
  800. }
  801. /**
  802. * UI related APIs
  803. */
  804. export type UIMsgOptions = {
  805. key: string
  806. timeout: number // milliseconds. `0` indicate that keep showing
  807. }
  808. export type UIMsgKey = UIMsgOptions['key']
  809. export interface IUIProxy {
  810. showMsg: (
  811. content: string,
  812. status?: 'success' | 'warning' | 'error' | string,
  813. opts?: Partial<UIMsgOptions>
  814. ) => Promise<UIMsgKey>
  815. closeMsg: (key: UIMsgKey) => void
  816. queryElementRect: (selector: string) => Promise<DOMRectReadOnly | null>
  817. queryElementById: (id: string) => Promise<string | boolean>
  818. checkSlotValid: (slot: UISlotIdentity['slot']) => Promise<boolean>
  819. resolveThemeCssPropsVals: (props: string | Array<string>) => Promise<Record<string, string | undefined> | null>
  820. }
  821. export interface IUtilsProxy {
  822. toJs: <R = unknown>(obj: {}) => Promise<R>
  823. }
  824. /**
  825. * Assets related APIs
  826. */
  827. export interface IAssetsProxy {
  828. /**
  829. * @added 0.0.2
  830. * @param exts
  831. */
  832. listFilesOfCurrentGraph(exts?: string | string[]): Promise<
  833. Array<{
  834. path: string
  835. size: number
  836. accessTime: number
  837. modifiedTime: number
  838. changeTime: number
  839. birthTime: number
  840. }>
  841. >
  842. /**
  843. * @example https://github.com/logseq/logseq/pull/6488
  844. * @added 0.0.10
  845. */
  846. makeSandboxStorage(): IAsyncStorage
  847. /**
  848. * make assets scheme url based on current graph
  849. * @added 0.0.15
  850. * @param path
  851. */
  852. makeUrl(path: string): Promise<string>
  853. /**
  854. * try to open asset type file in Logseq app
  855. * @added 0.0.16
  856. * @param path
  857. */
  858. builtInOpen(path: string): Promise<boolean | undefined>
  859. }
  860. export interface ILSPluginThemeManager {
  861. get themes(): Map<PluginLocalIdentity, Theme[]>
  862. registerTheme(id: PluginLocalIdentity, opt: Theme): Promise<void>
  863. unregisterTheme(id: PluginLocalIdentity, effect?: boolean): Promise<void>
  864. selectTheme(
  865. opt: Theme | LegacyTheme,
  866. options: { effect?: boolean; emit?: boolean }
  867. ): Promise<void>
  868. }
  869. export type LSPluginUserEvents = 'ui:visible:changed' | 'settings:changed'
  870. export interface ILSPluginUser extends EventEmitter<LSPluginUserEvents> {
  871. /**
  872. * Connection status with the main app
  873. */
  874. connected: boolean
  875. /**
  876. * Duplex message caller
  877. */
  878. caller: LSPluginCaller
  879. /**
  880. * The plugin configurations from package.json
  881. */
  882. baseInfo: LSPluginBaseInfo
  883. /**
  884. * The plugin user settings
  885. */
  886. settings?: LSPluginBaseInfo['settings']
  887. /**
  888. * The main Logseq app is ready to run the plugin
  889. *
  890. * @param model - same as the model in `provideModel`
  891. */
  892. ready(model?: Record<string, any>): Promise<any>
  893. /**
  894. * @param callback - a function to run when the main Logseq app is ready
  895. */
  896. ready(callback?: (e: any) => void | {}): Promise<any>
  897. ready(
  898. model?: Record<string, any>,
  899. callback?: (e: any) => void | {}
  900. ): Promise<any>
  901. beforeunload: (callback: () => Promise<void>) => void
  902. /**
  903. * Create a object to hold the methods referenced in `provideUI`
  904. *
  905. * @example
  906. * ```ts
  907. * logseq.provideModel({
  908. * openCalendar () {
  909. * console.log('Open the calendar!')
  910. * }
  911. * })
  912. * ```
  913. */
  914. provideModel(model: Record<string, any>): this
  915. /**
  916. * Set the theme for the main Logseq app
  917. */
  918. provideTheme(theme: Theme): this
  919. /**
  920. * Inject custom css for the main Logseq app
  921. *
  922. * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts
  923. * @example
  924. * ```ts
  925. * logseq.provideStyle(`
  926. * @import url("https://at.alicdn.com/t/font_2409735_r7em724douf.css");
  927. * )
  928. * ```
  929. */
  930. provideStyle(style: StyleString | StyleOptions): this
  931. /**
  932. * Inject custom UI at specific DOM node.
  933. * Event handlers can not be passed by string, so you need to create them in `provideModel`
  934. *
  935. * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-a-translator
  936. * @example
  937. * ```ts
  938. * logseq.provideUI({
  939. * key: 'open-calendar',
  940. * path: '#search',
  941. * template: `
  942. * <a data-on-click="openCalendar" onclick="alert('abc')' style="opacity: .6; display: inline-flex; padding-left: 3px;'>
  943. * <i class="iconfont icon-Calendaralt2"></i>
  944. * </a>
  945. * `
  946. * })
  947. * ```
  948. */
  949. provideUI(ui: UIOptions): this
  950. /**
  951. * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts
  952. *
  953. * @param schemas
  954. */
  955. useSettingsSchema(schemas: Array<SettingSchemaDesc>): this
  956. /**
  957. * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts
  958. *
  959. * @param attrs
  960. */
  961. updateSettings(attrs: Record<string, any>): void
  962. onSettingsChanged<T = any>(cb: (a: T, b: T) => void): IUserOffHook
  963. showSettingsUI(): void
  964. hideSettingsUI(): void
  965. setMainUIAttrs(attrs: Record<string, any>): void
  966. /**
  967. * Set the style for the plugin's UI
  968. *
  969. * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts
  970. * @example
  971. * ```ts
  972. * logseq.setMainUIInlineStyle({
  973. * position: 'fixed',
  974. * zIndex: 11,
  975. * })
  976. * ```
  977. */
  978. setMainUIInlineStyle(style: CSS.Properties): void
  979. /**
  980. * show the plugin's UI
  981. */
  982. showMainUI(opts?: { autoFocus: boolean }): void
  983. /**
  984. * hide the plugin's UI
  985. */
  986. hideMainUI(opts?: { restoreEditingCursor: boolean }): void
  987. /**
  988. * toggle the plugin's UI
  989. */
  990. toggleMainUI(): void
  991. isMainUIVisible: boolean
  992. resolveResourceFullUrl(filePath: string): string
  993. App: IAppProxy
  994. Editor: IEditorProxy
  995. DB: IDBProxy
  996. Git: IGitProxy
  997. UI: IUIProxy
  998. Assets: IAssetsProxy
  999. Request: LSPluginRequest
  1000. FileStorage: LSPluginFileStorage
  1001. Experiments: LSPluginExperiments
  1002. }