Enhance: plugin APIs (#10399)

* enhance(plugin): call apis with the sdk ns

* enhance(plugin): types

* enhance(api): get value from the computed style

* enhance(api): types

* enhance(plugin): types

* enhance(plugin): types

* fix: lint

* fix(apis): incorrect shortcut command registion for block editing mode #10392

* fix(api): types

* enhance(apis): support register shortcuts with multi binding vals

* fix(plugins): normalize command key to make the internal keyword legal

* chore(plugin): build libs core

* chore(plugin): bump version

* enhance(apis): normalize apis cljs data

* chore(plugin): update libs user sdk

* chore(plugin): CHANGELOG.md

* fix: typo

* fix(ux): support querying plugins with right space chars
This commit is contained in:
Charlie
2023-12-13 15:42:21 +08:00
committed by GitHub
parent eb22435280
commit 036df25a17
19 changed files with 222 additions and 160 deletions

View File

@@ -4,8 +4,20 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
## [0.0.15]
## [0.0.16]
### Added
- Support api of `logseq.UI.queryElementRect: (selector: string) => Promise<DOMRectReadOnly | null>`
- Support api of `logseq.UI.queryElementById: (id: string) => Promise<string | boolean>`
- Support api of `logseq.UI.checkSlotValid: (slot: UISlotIdentity['slot']) => Promise<boolean>`
- Support api of `logseq.UI.resolveThemeCssPropsVals: (props: string | Array<string>) => Promise<any>`
- Support api of `logseq.Assets.builtInOpen(path: string): Promise<boolean | undefined>`
### Fixed
- fix Plugin can't register command shortcut with editing mode [#10392](https://github.com/logseq/logseq/issues/10392)
- fix [Plugin API] [Keymap] Command without keybinding can't be present in Keymap [#10466](https://github.com/logseq/logseq/issues/10466)
- fix [Possible DATA LOSS] [Plugin API] [Keymap] Any plugin could break the global config.edn [#10465](https://github.com/logseq/logseq/issues/10465)
## [0.0.15]
### Added
- Support a plug-in flag for the plugin slash commands item
- Support api of `logseq.App.setCurrentGraphConfigs: (configs: {}) => Promise<void>`

View File

@@ -1,6 +1,6 @@
{
"name": "@logseq/libs",
"version": "0.0.15",
"version": "0.0.16",
"description": "Logseq SDK libraries",
"main": "dist/lsplugin.user.js",
"typings": "index.d.ts",

View File

@@ -54,6 +54,7 @@ const DIR_PLUGINS = 'plugins'
declare global {
interface Window {
LSPluginCore: LSPluginCore
DOMPurify: typeof DOMPurify
}
}
@@ -308,23 +309,25 @@ function initProviderHandlers(pluginLocal: PluginLocal) {
// provider:ui
pluginLocal.on(_('ui'), (ui: UIOptions) => {
pluginLocal._onHostMounted(() => {
pluginLocal._dispose(
setupInjectedUI.call(
pluginLocal,
ui,
Object.assign(
{
'data-ref': pluginLocal.id,
},
ui.attrs || {}
),
({ el, float }) => {
if (!float) return
const identity = el.dataset.identity
pluginLocal.layoutCore.move_container_to_top(identity)
}
)
const ret = setupInjectedUI.call(
pluginLocal,
ui,
Object.assign(
{
'data-ref': pluginLocal.id,
},
ui.attrs || {}
),
({ el, float }) => {
if (!float) return
const identity = el.dataset.identity
pluginLocal.layoutCore.move_container_to_top(identity)
}
)
if (typeof ret === 'function') {
pluginLocal._dispose(ret)
}
})
})
}
@@ -346,8 +349,6 @@ function initApiProxyHandlers(pluginLocal: PluginLocal) {
}
}
const { _sync } = payload
if (pluginLocal.shadow) {
if (payload.actor) {
payload.actor.resolve(ret)
@@ -355,6 +356,8 @@ function initApiProxyHandlers(pluginLocal: PluginLocal) {
return
}
const { _sync } = payload
if (_sync != null) {
const reply = (result: any) => {
pluginLocal.caller?.callUserModel(LSPMSG_SYNC, {
@@ -430,7 +433,7 @@ class PluginLocal extends EventEmitter<
async _setupUserSettings(reload?: boolean) {
const { _options } = this
const logger = (this._logger = new PluginLogger('Loader'))
const logger = (this._logger = new PluginLogger(`Loader:${this.debugTag}`))
if (_options.settings && !reload) {
return
@@ -532,7 +535,7 @@ class PluginLocal extends EventEmitter<
const localRoot = (this._localRoot = safetyPathNormalize(url))
const logseq: Partial<LSPluginPkgConfig> = pkg.logseq || {}
// Pick legal attrs
// Pick legal attrs
;[
'name',
'author',
@@ -642,10 +645,10 @@ class PluginLocal extends EventEmitter<
<meta charset="UTF-8">
<title>logseq plugin entry</title>
${
IS_DEV
? `<script src="${sdkPathRoot}/lsplugin.user.js?v=${tag}"></script>`
: `<script src="https://cdn.jsdelivr.net/npm/@logseq/libs/dist/lsplugin.user.min.js?v=${tag}"></script>`
}
IS_DEV
? `<script src="${sdkPathRoot}/lsplugin.user.js?v=${tag}"></script>`
: `<script src="https://cdn.jsdelivr.net/npm/@logseq/libs/dist/lsplugin.user.min.js?v=${tag}"></script>`
}
</head>
<body>
@@ -866,7 +869,7 @@ class PluginLocal extends EventEmitter<
this._dispose(cleanInjectedScripts.bind(this))
} catch (e) {
this.logger?.error('[Load Plugin]', e, true)
this.logger.error('load', e, true)
this.dispose().catch(null)
this._status = PluginLocalLoadStatus.ERROR
@@ -924,7 +927,7 @@ class PluginLocal extends EventEmitter<
)
this.emit('beforeunload', eventBeforeUnload)
} catch (e) {
this.logger.error('[beforeunload]', e)
this.logger.error('beforeunload', e)
}
await this.dispose()
@@ -932,7 +935,7 @@ class PluginLocal extends EventEmitter<
this.emit('unloaded')
} catch (e) {
this.logger.error('[unload Error]', e)
this.logger.error('unload', e)
} finally {
this._status = PluginLocalLoadStatus.UNLOADED
}
@@ -1038,7 +1041,7 @@ class PluginLocal extends EventEmitter<
get debugTag() {
const name = this._options?.name
return `#${this._id} ${name ?? ''}`
return `#${this._id} - ${name ?? ''}`
}
get localRoot(): string {
@@ -1103,8 +1106,7 @@ class LSPluginCore
| 'beforereload'
| 'reloaded'
>
implements ILSPluginThemeManager
{
implements ILSPluginThemeManager {
private _isRegistering = false
private _readyIndicator?: DeferredActor
private readonly _hostMountedActor: DeferredActor = deferred()
@@ -1566,12 +1568,12 @@ class LSPluginCore
await this.saveUserPreferences(
theme.mode
? {
themes: {
...this._userPreferences.themes,
mode: theme.mode,
[theme.mode]: theme,
},
}
themes: {
...this._userPreferences.themes,
mode: theme.mode,
[theme.mode]: theme,
},
}
: { theme: theme }
)
}

View File

@@ -192,7 +192,7 @@ export interface BlockEntity {
level?: number
meta?: { timestamps: any; properties: any; startPos: number; endPos: number }
title?: Array<any>
marker?: string
marker?: string
}
/**
@@ -235,9 +235,10 @@ export type BlockCursorPosition = {
rect: DOMRect
}
export type Keybinding = string | Array<string>
export type SimpleCommandKeybinding = {
mode?: 'global' | 'non-editing' | 'editing'
binding: string
binding: Keybinding
mac?: string // special for Mac OS
}
@@ -469,25 +470,6 @@ export interface IAppProxy {
removeTemplate: (name: string) => Promise<any>
insertTemplate: (target: BlockUUID, name: string) => Promise<any>
// ui
queryElementById: (id: string) => Promise<string | boolean>
/**
* @added 0.0.5
* @param selector
*/
queryElementRect: (selector: string) => Promise<DOMRectReadOnly | null>
/**
* @deprecated Use `logseq.UI.showMsg` instead
* @param content
* @param status
*/
showMsg: (
content: string,
status?: 'success' | 'warning' | 'error' | string
) => void
setZoomFactor: (factor: number) => void
setFullScreen: (flag: boolean | 'toggle') => void
setLeftSidebarVisible: (flag: boolean | 'toggle') => void
@@ -891,20 +873,16 @@ export type UIMsgOptions = {
export type UIMsgKey = UIMsgOptions['key']
export interface IUIProxy {
/**
* @added 0.0.2
*
* @param content
* @param status
* @param opts
*/
showMsg: (
content: string,
status?: 'success' | 'warning' | 'error' | string,
opts?: Partial<UIMsgOptions>
) => Promise<UIMsgKey>
closeMsg: (key: UIMsgKey) => void
queryElementRect: (selector: string) => Promise<DOMRectReadOnly | null>
queryElementById: (id: string) => Promise<string | boolean>
checkSlotValid: (slot: UISlotIdentity['slot']) => Promise<boolean>
resolveThemeCssPropsVals: (props: string | Array<string>) => Promise<Record<string, string | undefined> | null>
}
/**
@@ -938,6 +916,13 @@ export interface IAssetsProxy {
* @param path
*/
makeUrl(path: string): Promise<string>
/**
* try to open asset type file in Logseq app
* @added 0.0.16
* @param path
*/
builtInOpen(path: string): Promise<boolean | undefined>
}
export interface ILSPluginThemeManager {

View File

@@ -4,7 +4,7 @@ import {
mergeSettingsWithSchema,
PluginLogger,
safeSnakeCase,
safetyPathJoin,
safetyPathJoin, normalizeKeyStr,
} from './helpers'
import { LSPluginCaller } from './LSPlugin.caller'
import * as callableAPIs from './callable.apis'
@@ -77,12 +77,21 @@ function registerSimpleCommand (
},
action: SimpleCommandCallback
) {
const { key, label, desc, palette, keybinding, extras } = opts
if (typeof action !== 'function') {
this.logger.error(`${key || label}: command action should be function.`)
return false
}
const { key, label, desc, palette, keybinding, extras } = opts
const eventKey = `SimpleCommandHook${key}${++registeredCmdUid}`
const normalizedKey = normalizeKeyStr(key)
if (!normalizedKey) {
this.logger.error(`${label}: command key is required.`)
return false
}
const eventKey = `SimpleCommandHook${normalizedKey}${++registeredCmdUid}`
this.Editor['on' + eventKey](action)
@@ -92,7 +101,7 @@ function registerSimpleCommand (
this.baseInfo.id,
// [cmd, action]
[
{ key, label, type, desc, keybinding, extras },
{ key: normalizedKey, label, type, desc, keybinding, extras },
['editor/hook', eventKey],
],
palette,
@@ -171,7 +180,7 @@ const app: Partial<IAppProxy> = {
const { binding } = keybinding
const group = '$shortcut$'
const key = group + safeSnakeCase(binding)
const key = opts.key || (group + safeSnakeCase(binding?.toString()))
return registerSimpleCommand.call(
this,

View File

@@ -2,7 +2,7 @@ import { SettingSchemaDesc, StyleString, UIOptions } from './LSPlugin'
import { PluginLocal } from './LSPlugin.core'
import * as nodePath from 'path'
import DOMPurify from 'dompurify'
import merge from 'deepmerge';
import merge from 'deepmerge'
import { snakeCase } from 'snake-case'
import * as callables from './callable.apis'
import EventEmitter from 'eventemitter3'
@@ -211,12 +211,23 @@ export function deferred<T = any>(timeout?: number, tag?: string) {
export function invokeHostExportedApi(method: string, ...args: Array<any>) {
method = method?.startsWith('_call') ? method : method?.replace(/^[_$]+/, '')
const method1 = safeSnakeCase(method)
let method1 = safeSnakeCase(method)
// @ts-ignore
const nsSDK = window.logseq?.sdk
const supportedNS = nsSDK && Object.keys(nsSDK)
let nsTarget = {}
const ns0 = method1?.split('_')?.[0]
if (ns0 && supportedNS.includes(ns0)) {
method1 = method1.replace(new RegExp(`^${ns0}_`), '')
nsTarget = nsSDK?.[ns0]
}
const logseqHostExportedApi = Object.assign(
// @ts-ignore
window.logseq?.api || {},
callables
{}, window.logseq?.api,
nsTarget, callables
)
const fn =
@@ -266,9 +277,9 @@ export function setupInjectedStyle(
el.textContent = style
attrs &&
Object.entries(attrs).forEach(([k, v]) => {
el.setAttribute(k, v)
})
Object.entries(attrs).forEach(([k, v]) => {
el.setAttribute(k, v)
})
document.head.append(el)
@@ -313,7 +324,7 @@ export function setupInjectedUI(
console.error(
`${this.debugTag} can not resolve selector target ${selector}`
)
return
return false
}
if (ui.template) {
@@ -344,22 +355,22 @@ export function setupInjectedUI(
// update attributes
attrs &&
Object.entries(attrs).forEach(([k, v]) => {
el.setAttribute(k, v)
})
Object.entries(attrs).forEach(([k, v]) => {
el.setAttribute(k, v)
})
let positionDirty = el.dataset.dx != null
ui.style &&
Object.entries(ui.style).forEach(([k, v]) => {
if (
positionDirty &&
['left', 'top', 'bottom', 'right', 'width', 'height'].includes(k)
) {
return
}
Object.entries(ui.style).forEach(([k, v]) => {
if (
positionDirty &&
['left', 'top', 'bottom', 'right', 'width', 'height'].includes(k)
) {
return
}
el.style[k] = v
})
el.style[k] = v
})
return
}
@@ -379,14 +390,14 @@ export function setupInjectedUI(
content.innerHTML = ui.template
attrs &&
Object.entries(attrs).forEach(([k, v]) => {
el.setAttribute(k, v)
})
Object.entries(attrs).forEach(([k, v]) => {
el.setAttribute(k, v)
})
ui.style &&
Object.entries(ui.style).forEach(([k, v]) => {
el.style[k] = v
})
Object.entries(ui.style).forEach(([k, v]) => {
el.style[k] = v
})
let teardownUI: () => void
let disposeFloat: () => void
@@ -399,11 +410,11 @@ export function setupInjectedUI(
el.classList.add('lsp-ui-float-container', 'visible')
disposeFloat =
(pl._setupResizableContainer(el, key),
pl._setupDraggableContainer(el, {
key,
close: () => teardownUI(),
title: attrs?.title,
}))
pl._setupDraggableContainer(el, {
key,
close: () => teardownUI(),
title: attrs?.title,
}))
}
if (!!slot && ui.reset) {
@@ -542,3 +553,8 @@ export function mergeSettingsWithSchema(
// shadow copy
return Object.assign(defaults, settings)
}
export function normalizeKeyStr(s: string) {
if (typeof s !== 'string') return
return s.trim().replace(/\s/g, '_').toLowerCase()
}