LSPlugin.ts 27 KB

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