Files
logseq/libs/src/LSPlugin.ts
Charlie 18b7a7864b Feat/plugin marketplace (#2766)
* Squashed commit of the following:

commit ea9af272e4
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Fri Aug 27 00:23:24 2021 +0800

    feat: type c to git commit

    also, fixed an issue that backspace can delete selected blocks
    when there's a dialog.

commit 78e24f7479
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Fri Aug 27 00:01:21 2021 +0800

    fix: add patch parser worker to yarn watch

commit 7f6e777bcd
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Thu Aug 26 23:57:46 2021 +0800

    fix: add several shortcuts to the Others category

commit 509697b276
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Thu Aug 26 23:55:40 2021 +0800

    fix: git username and email configuration

* Squashed commit of the following:

commit 401d85be5f
Author: Peng Xiao <pengxiao@outlook.com>
Date:   Fri Aug 27 11:10:32 2021 +0800

    feat: add protobuf mode

commit dc1e9fdfc9
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Fri Aug 27 13:33:47 2021 +0800

    chore: replace : with comma for git path

    ':' is a reserved character on Windows

commit ea9af272e4
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Fri Aug 27 00:23:24 2021 +0800

    feat: type c to git commit

    also, fixed an issue that backspace can delete selected blocks
    when there's a dialog.

commit 78e24f7479
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Fri Aug 27 00:01:21 2021 +0800

    fix: add patch parser worker to yarn watch

commit 7f6e777bcd
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Thu Aug 26 23:57:46 2021 +0800

    fix: add several shortcuts to the Others category

commit 509697b276
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Thu Aug 26 23:55:40 2021 +0800

    fix: git username and email configuration

* feat(plugin): ui of marketplace plugins list

* improve(plugin): support reload

* improve(plugin): installation from marketplace

* fix conflicts

* improve(plugin): installation from github public repo

* chore: remove unwanted dependency

* chore: remove console

* improve(plugin): add shortcuts

* ui(plugin): polish LOADING indicator

* improve(plugin): support up-to-date of marketplace plugin

* fix: remove debug option

* improve(plugin): better interaction of themes picker

* improve(plugin): better experience when installing theme from marketplace

* fix(plugin): downloads label of marketplace plugin

* improve(plugin): update package name field

* improve(plugin): change marketplace packages repo to `logseq/marketplace`

* fix(plugin): plugin title when updating notification

* fix: conflicts

* enhance(plugin): i18n related marketplace & lifecycle of plugin installation

* improve(plugin): handle offline situation

* ui(plugin): header plugin icons container

* fix(ui): add class identity for journal page with date page name

* improve(plugin): remote readme for marketplace plugin

* enhance(plugin): polish plugin card

* chore(plugin): build libs core

* Squashed commit of the following:

commit 751db4828c
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Tue Sep 7 16:58:25 2021 +0800

    enhance: log git errors

commit c2dbbc77bf
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Tue Sep 7 16:27:00 2021 +0800

    enhance: display refresh status

commit f734b6db37
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Tue Sep 7 16:03:57 2021 +0800

    fix: .git doesn't work well with third-party cloud services

commit 7e44d81f1d
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Tue Sep 7 13:52:38 2021 +0800

    fix: git init into the current graph folder

    instead of a separate git directory because .gitdir might has
    different paths on multiple devices, another reason is that the graph
    might have different histories considering the .git directory is not
    synced.

commit b86a801514
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 23:23:27 2021 +0800

    enhance: don't show diff if there's only blank changes

commit 0b55d119aa
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 22:39:06 2021 +0800

    fix: save the previous content in Logseq first and commit it

    to avoid overwritten when syncing with iCloud/Dropbox/syncthing.

commit e0baf4b05c
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 22:18:45 2021 +0800

    fix: close file watcher when exit the app

commit 10e7a9fbd6
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 21:05:38 2021 +0800

    fix: disable cut selections in the query result block

commit 90c2bd7cc2
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 18:42:17 2021 +0800

    fix: terminate parser and persist dbs when reloading the app (electron)

commit 571c81af30
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 17:57:33 2021 +0800

    enhance: add sync from local files (the old refresh way)

commit a16e5c98ba
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 17:23:57 2021 +0800

    fix: Dragging blocks to update notes does not synchronize updates to
    the notes file in real time.

    close #2744

commit 6897a22a3f
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 16:43:30 2021 +0800

    fix: disable page/block auto-complete once the cursor went outside

commit feb4404874
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 13:28:47 2021 +0800

    fix: wrong page metadata saved for another graph

commit b96332122f
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 13:08:28 2021 +0800

    enhance: catch errors when app quits

commit 1ee0c240c3
Author: Jiang Hailong <gombiuda@gmail.com>
Date:   Wed Sep 1 20:54:13 2021 +0800

    FIX: Linked reference is not refresh after file altering #2694

commit 0550c8a876
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 12:25:39 2021 +0800

    fix: display logbook for scheduled tasks

commit 2a5f0cee7c
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 12:21:19 2021 +0800

    fix: spent hours for logbook

commit 1f2c9e4d3f
Author: leizhe <lzhes43@gmail.com>
Date:   Sat Sep 4 12:59:09 2021 +0900

    fix(timestamp): remove old SCHEDULED/DEADLINE timestamp

    When using `date-picker` to update the SCHEDULED/DEADLINE timestamp by
    clicking an existing one, logseq will add a new timestamp instead of
    updating the old one. This patch fixs this issue.

commit cb23b967e4
Author: leizhe <lzhes43@gmail.com>
Date:   Sat Sep 4 11:36:34 2021 +0900

    fix(repeat): more consistent with orgmode style

    Ref: https://orgmode.org/manual/Tracking-your-habits.html

commit 229c7f2594
Author: leizhe <lzhes43@gmail.com>
Date:   Sat Sep 4 15:11:07 2021 +0900

    enhance(property): remove empty properties drawer

commit a76df9ce97
Author: leizhe <lzhes43@gmail.com>
Date:   Sat Sep 4 10:27:30 2021 +0900

    fix(clock): duplicate clock-in log twice

    1. `set-marker` will not log time anymore. The time logging is moved
    to `with-timetracking`
    2. Concat `logbook` only if `new-clocks` is nil, which fixs the
    duplication of clock-in log.

commit c79e9f9e3e
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 12:04:16 2021 +0800

    code: cleanup

commit 9ec85db09a
Author: DarshanSudhakar <$K3Ug1i&>
Date:   Mon Sep 6 08:32:07 2021 +0530

    Fixing typo  for the tooltip 'Block reference'

commit eec677873b
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 11:49:58 2021 +0800

    enhance: page history support reverting back

commit 7273112a00
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 11:23:31 2021 +0800

    git: revert back

commit cd853b5864
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 10:39:08 2021 +0800

    git: should compare ignored-files with disk content

commit a84dfb5eff
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 10:31:16 2021 +0800

    git: add ignore-files to avoid repeated notifications

commit 86577e7ebf
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 10:07:39 2021 +0800

    fix: ignore permission denied error when git add all

commit 8dc0ca9ff5
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 09:54:03 2021 +0800

    fix: run git config core.safecrlf false on windows

commit 9edaae559d
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 09:20:06 2021 +0800

    refactor: extract file ops

commit f12f58d3fa
Author: tiagodevezas <tiagodevezas@gmail.com>
Date:   Fri Sep 3 23:25:07 2021 +0100

    fix typos

commit 9e82f0117c
Author: tiagodevezas <tiagodevezas@gmail.com>
Date:   Fri Sep 3 21:32:03 2021 +0100

    Translate shortcuts to Portuguese (pt-PT)

commit 9a2c17bb05
Author: tiagodevezas <tiagodevezas@gmail.com>
Date:   Fri Sep 3 20:58:33 2021 +0100

    Translate new settings to pt-PT

commit c05034cc34
Author: Sebastian Bensusan <sbensu@gmail.com>
Date:   Sat Sep 4 07:57:07 2021 -0700

    feat(calc): Understand percentages

commit bfe6a5d6cb
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 6 08:46:30 2021 +0800

    enhance: commit the content in logseq when detecting any disk changes

* improve(plugin): add install plugin api

* fix(plugin): protected plugin installation api

* improve(plugin): tweak readme display for local

* fix: conflicts

* fix(plugin): get block option with include children

* improve(plugin): copy more marketplace manifest fields to plugin

* fix: conflicts

* improve(plugin): shortcut for copying plugin id

* Squashed commit of the following:

commit e51ea54a75
Author: Tienson Qin <tiensonqin@gmail.com>
Date:   Mon Sep 13 10:40:25 2021 +0800

    fix: change ack timeout

* fix(plugin): non blank icon string

* fix: conflicts

* fix: e name

* fix: remove debug from state

* chore(plugin): bump libs minor version

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
2021-09-13 19:49:51 +08:00

540 lines
13 KiB
TypeScript

import EventEmitter from 'eventemitter3'
import * as CSS from 'csstype'
import { LSPluginCaller } from './LSPlugin.caller'
import { LSPluginFileStorage } from './modules/LSPlugin.Storage'
export type PluginLocalIdentity = string
export type ThemeOptions = {
name: string
url: string
description?: string
mode?: 'dark' | 'light'
[key: string]: any
}
export type StyleString = string;
export type StyleOptions = {
key?: string
style: StyleString
}
export type UIFrameAttrs = {
draggable: boolean
resizable: boolean
[key: string]: any
}
export type UIBaseOptions = {
key?: string
replace?: boolean
template: string
}
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 = UIPathOptions | UISlotOptions
export interface LSPluginPkgConfig {
id: PluginLocalIdentity
title: string
mode: 'shadow' | 'iframe'
themes: Array<ThemeOptions>
icon: string
}
export interface LSPluginBaseInfo {
id: string // should be unique
mode: 'shadow' | 'iframe'
settings: {
disabled: boolean
[key: string]: any
},
[key: string]: any
}
export type IHookEvent = {
[key: string]: any
}
export type IUserOffHook = () => void
export type IUserHook<E = any, R = IUserOffHook> = (callback: (e: IHookEvent & E) => void) => IUserOffHook
export type IUserSlotHook<E = any> = (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 }
export type IBatchBlock = { content: string, properties?: Record<string, any>, children?: Array<IBatchBlock> }
export interface AppUserInfo {
[key: string]: any
}
/**
* User's app configurations
*/
export interface AppUserConfigs {
preferredThemeMode: 'dark' | 'light'
preferredFormat: 'markdown' | 'org'
preferredDateFormat: string
preferredLanguage: string
preferredWorkflow: string
[key: string]: any
}
/**
* In Logseq, a graph represents a repository of connected pages and blocks
*/
export interface AppGraphInfo {
name: string
url: string
path: string
[key: string]: any
}
/**
* Block - Logseq's fundamental data structure.
*/
export interface BlockEntity {
id: EntityID // db id
uuid: BlockUUID
left: IEntityID
format: 'markdown' | 'org'
parent: IEntityID
unordered: boolean
content: string
page: IEntityID
// 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 }
title?: Array<any>
[key: string]: any
}
/**
* Page is just a block with some specific properties.
*/
export interface PageEntity {
id: EntityID
uuid: BlockUUID
name: string
originalName: string
'journal?': boolean
file?: IEntityID
namespace?: IEntityID
format?: 'markdown' | 'org'
journalDay?: number
}
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 BlockCommandCallback = (e: IHookEvent & { uuid: BlockUUID }) => Promise<void>
export type BlockCursorPosition = { left: number, top: number, height: number, pos: number, rect: DOMRect }
/**
* App level APIs
*/
export interface IAppProxy {
getUserInfo: () => Promise<AppUserInfo | null>
getUserConfigs: () => Promise<AppUserConfigs>
// native
relaunch: () => Promise<void>
quit: () => Promise<void>
openExternalLink: (url: string) => Promise<void>
// graph
getCurrentGraph: () => Promise<AppGraphInfo | 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
// ui
showMsg: (content: string, status?: 'success' | 'warning' | string) => void
setZoomFactor: (factor: number) => 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
onThemeModeChanged: IUserHook<{ mode: 'dark' | 'light' }>
onBlockRendererSlotted: IUserSlotHook<{ uuid: BlockUUID }>
onMacroRendererSlotted: IUserSlotHook<{ payload: { arguments: Array<string>, uuid: string, [key: string]: any } }>
onPageHeadActionsSlotted: IUserSlotHook
onRouteChanged: IUserHook<{ path: string, template: string }>
onSidebarVisibleChanged: IUserHook<{ visible: boolean }>
}
/**
* 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
* ```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 tag - displayed name of command
* @param action - can be a single callback function to run when the command is called
*/
registerBlockContextMenuItem: (
tag: string,
action: BlockCommandCallback
) => unknown
// block related APIs
checkEditing: () => Promise<BlockUUID | boolean>
/**
* insert a string at the current cursor
*/
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>
getCurrentBlock: () => Promise<BlockEntity | null>
/**
* 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>>
insertBlock: (
srcBlock: BlockIdentity,
content: string,
opts?: Partial<{ before: boolean; sibling: boolean; properties: {} }>
) => Promise<BlockEntity | null>
insertBatchBlock: (
srcBlock: BlockIdentity,
batch: IBatchBlock | Array<IBatchBlock>,
opts?: Partial<{ before: boolean, sibling: boolean }>
) => Promise<Array<BlockEntity> | null>
updateBlock: (
srcBlock: BlockIdentity,
content: string,
opts?: Partial<{ properties: {} }>
) => Promise<void>
removeBlock: (
srcBlock: BlockIdentity
) => Promise<void>
getBlock: (
srcBlock: BlockIdentity | EntityID,
opts?: Partial<{ includeChildren: boolean }>
) => Promise<BlockEntity | null>
getPage: (
srcPage: PageIdentity | EntityID,
opts?: Partial<{ includeChildren: boolean }>
) => Promise<PageEntity | null>
createPage: (
pageName: BlockPageName,
properties?: {},
opts?: Partial<{ redirect: boolean, createFirstBlock: boolean, format: BlockEntity['format'] }>
) => Promise<PageEntity | null>
deletePage: (
pageName: BlockPageName
) => Promise<void>
renamePage: (oldName: string, newName: string) => Promise<void>
getAllPages: (repo?: string) => Promise<any>
getPreviousSiblingBlock: (
srcBlock: BlockIdentity
) => Promise<BlockEntity | null>
getNextSiblingBlock: (srcBlock: BlockIdentity) => Promise<BlockEntity | null>
moveBlock: (
srcBlock: BlockIdentity,
targetBlock: BlockIdentity,
opts?: Partial<{ before: boolean; children: boolean }>
) => Promise<void>
editBlock: (srcBlock: BlockIdentity, opts?: { pos: number }) => Promise<void>
upsertBlockProperty: (
block: BlockIdentity,
key: string,
value: any
) => Promise<void>
removeBlockProperty: (block: BlockIdentity, key: string) => Promise<void>
getBlockProperty: (block: BlockIdentity, key: string) => Promise<any>
getBlockProperties: (block: BlockIdentity) => Promise<any>
scrollToBlockInPage: (
pageName: BlockPageName,
blockId: BlockIdentity
) => void
}
/**
* Datascript related APIs
*/
export interface IDBProxy {
/**
* Run a DSL query
* @link https://logseq.github.io/#/page/queries
* @param dsl
*/
q: <T = any>(dsl: string) => Promise<Array<T> | null>
/**
* Run a datascript query
*/
datascriptQuery: <T = any>(query: string) => Promise<T>
}
export interface ILSPluginThemeManager extends EventEmitter {
themes: Map<PluginLocalIdentity, Array<ThemeOptions>>
registerTheme (id: PluginLocalIdentity, opt: ThemeOptions): Promise<void>
unregisterTheme (id: PluginLocalIdentity): Promise<void>
selectTheme (opt?: ThemeOptions): 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: ThemeOptions): this
/**
* Inject custom css for the main Logseq app
*
* @example
* ```ts
* logseq.provideStyle(`
* @import url("https://at.alicdn.com/t/font_2409735_r7em724douf.css");
* )
* ```
*
* @example
* ```ts
*
* ```
*/
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
* ```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
updateSettings (attrs: Record<string, any>): void
setMainUIAttrs (attrs: Record<string, any>): void
/**
* Set the style for the plugin's UI
*
* @example
* ```ts
* logseq.setMainUIInlineStyle({
* position: 'fixed',
* zIndex: 11,
* })
* ```
*/
setMainUIInlineStyle (style: CSS.Properties): void
/**
* show the plugin's UI
*/
showMainUI (): void
/**
* hide the plugin's UI
*/
hideMainUI (opts?: { restoreEditingCursor: boolean }): void
/**
* toggle the plugin's UI
*/
toggleMainUI (): void
isMainUIVisible: boolean
resolveResourceFullUrl (filePath: string): string
App: IAppProxy & Record<string, any>
Editor: IEditorProxy & Record<string, any>
DB: IDBProxy
FileStorage: LSPluginFileStorage
}