mirror of
https://github.com/logseq/logseq.git
synced 2026-04-25 06:35:02 +00:00
Enhance/more ns plugin api (#4828)
* improve(plugin): WIP add settings schema * improve(plugin): add identity for settings modal * improve(plugin): WIP add settings input * fix(ui): scrollbar overlay of modal panel content * improve(plugin): WIP add more render types of setting item * improve(plugin): WIP polish settings items * improve(plugin): WIP settings list of plugins * improve(plugin): more settings types & polish releated ui * fix(plugin): sometimes disable plugin not work * improve(plugin): polish ui of plugin settings * fix(dev): warning of lint * improve(plugin): add api of settings changed * chore: build libs core * fix(ui): width of settings panel wrap * improve(plugin): separate layouts data from settings aio file * imporve(plugin): container size of single plugin settings * fix: add missing state * improve(plugin): add Git ns * improve(plugin): git related api * improve(api): type of git result * chore: build libs core * fix(dev): kondo lint * fix(plugin): use cdn sdk when js entry * chore: build libs core * fix(plugin): env condition * improve(plugin): add UI ns * fix(api): arguments of datascript query * enhance(api): manageable message instance of UI tools * enhance(api): WIP add experiments api * enhance(api): WIP add resources state of plugin * improve(plugin): add status of loading script resources * improve(plugin): more opts for script loader * improve(plugin): WIP add fenced code renderer hook * improve(plugin): fenced code renderer hook * fix(plugin): resource root path of plugin fs location * imporve(plugin): support local files for loading scripts * improve(plugin): types of expirements api * fix: typo of class * enhance(api): add namespace related apis * enhance(api): add linked refrences related apis * enhance(plugin): add sample links to related api comments * improve(plugin): add db changed hook & optimize strategy of caller for hooks * improve(plugin): compatible commands registration for old sdk * improve(plugin): collect user sdk version for plugin local * improve(plugin): add internal callable apis for user sdk * chore(plugin): missing files & bump libs version * improve(plugin): compatiable for old sdk about hook messaging optimization * improve(plugin): db hook optimization for old sdk * enhance(ux): auto focus searchbar when open plugins list * improve(plugin): api of a hook from specific block changed event * improve(plugin): api of db block change hook * improve(plugin): add show bracket user config of api * improve(plugin): api of db block change hook * fix(api): toggle collapsed of block * improve(api): try to init grpah with git before exec git commands * improve(plugin): attributes of sandbox container * improve(dev): support register command with keybinding * improve(plugin): add api of register shortcut command * fix(plugin): reubild slash commands when new command registration * fix(dev): lint * improve(dev): lint script of libs codebase * chore(dev): remove useless codes * improve(plugin):sanitize path string of plugin repo value * fix(plugin): rebuild commands list when unregister a plugin * fix(ui): overflow width of query result table * chore: rebuild libs core * improve(plugin): add assets related apis * chore: rebuild libs core * improve(plugin): support replace state of into block in page api * improve(plugin): prepend/append child block in page * improve(plugin): polished exceptions message of plugin update/install * fix(plugin): update settings within gui * improve(ux): debounce change event of input for plugin settings gui * chore: rebuild libs core * enhance(plugin): catch exception of hook plugin
This commit is contained in:
4
libs/.prettierrc.js
Normal file
4
libs/.prettierrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
...require('prettier-config-standard'),
|
||||
trailingComma: 'es5'
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@logseq/libs",
|
||||
"version": "0.0.1-alpha.35",
|
||||
"version": "0.0.2",
|
||||
"description": "Logseq SDK libraries",
|
||||
"main": "dist/lsplugin.user.js",
|
||||
"typings": "index.d.ts",
|
||||
@@ -10,7 +10,9 @@
|
||||
"dev:user": "npm run build:user -- --mode development --watch",
|
||||
"build:core": "webpack --config webpack.config.core.js --mode production",
|
||||
"dev:core": "npm run build:core -- --mode development --watch",
|
||||
"build": "tsc && rm dist/*.js && npm run build:user"
|
||||
"build": "tsc && rm dist/*.js && npm run build:user",
|
||||
"lint": "prettier --check \"src/**/*.{ts, js}\"",
|
||||
"fix": "prettier --write \"src/**/*.{ts, js}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"csstype": "3.0.8",
|
||||
@@ -26,6 +28,8 @@
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/dompurify": "^2.2.1",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-config-standard": "^5.0.0",
|
||||
"ts-loader": "^8.0.17",
|
||||
"typescript": "^4.2.2",
|
||||
"webpack": "^5.24.3",
|
||||
|
||||
@@ -33,14 +33,16 @@ class LSPluginCaller extends EventEmitter {
|
||||
private _status?: 'pending' | 'timeout'
|
||||
private _userModel: any = {}
|
||||
|
||||
private _call?: (type: string, payload: any, actor?: DeferredActor) => Promise<any>
|
||||
private _call?: (
|
||||
type: string,
|
||||
payload: any,
|
||||
actor?: DeferredActor
|
||||
) => Promise<any>
|
||||
private _callUserModel?: (type: string, payload: any) => Promise<any>
|
||||
|
||||
private _debugTag = ''
|
||||
|
||||
constructor (
|
||||
private _pluginLocal: PluginLocal | null
|
||||
) {
|
||||
constructor(private _pluginLocal: PluginLocal | null) {
|
||||
super()
|
||||
|
||||
if (_pluginLocal) {
|
||||
@@ -75,8 +77,14 @@ class LSPluginCaller extends EventEmitter {
|
||||
const model: any = this._extendUserModel({
|
||||
[LSPMSG_READY]: async (baseInfo) => {
|
||||
// dynamically setup common msg handler
|
||||
model[LSPMSGFn(baseInfo?.pid)] = ({ type, payload }: { type: string, payload: any }) => {
|
||||
debug(`[call from host (_call)] ${this._debugTag}`, type, payload)
|
||||
model[LSPMSGFn(baseInfo?.pid)] = ({
|
||||
type,
|
||||
payload,
|
||||
}: {
|
||||
type: string
|
||||
payload: any
|
||||
}) => {
|
||||
debug(`[host (_call) -> *user] ${this._debugTag}`, type, payload)
|
||||
// host._call without async
|
||||
caller.emit(type, payload)
|
||||
}
|
||||
@@ -95,7 +103,10 @@ class LSPluginCaller extends EventEmitter {
|
||||
},
|
||||
|
||||
[LSPMSG]: async ({ ns, type, payload }: any) => {
|
||||
debug(`[call from host (async)] ${this._debugTag}`, ns, type, payload)
|
||||
debug(
|
||||
`[host (async) -> *user] ${this._debugTag} ns=${ns} type=${type}`,
|
||||
payload
|
||||
)
|
||||
|
||||
if (ns && ns.startsWith('hook')) {
|
||||
caller.emit(`${ns}:${type}`, payload)
|
||||
@@ -106,7 +117,7 @@ class LSPluginCaller extends EventEmitter {
|
||||
},
|
||||
|
||||
[LSPMSG_SYNC]: ({ _sync, result }: any) => {
|
||||
debug(`[sync reply] #${_sync}`, result)
|
||||
debug(`[sync host -> *user] #${_sync}`, result)
|
||||
|
||||
if (syncActors.has(_sync)) {
|
||||
const actor = syncActors.get(_sync)
|
||||
@@ -123,7 +134,7 @@ class LSPluginCaller extends EventEmitter {
|
||||
}
|
||||
},
|
||||
|
||||
...userModel
|
||||
...userModel,
|
||||
})
|
||||
|
||||
if (isShadowMode) {
|
||||
@@ -136,7 +147,8 @@ class LSPluginCaller extends EventEmitter {
|
||||
|
||||
this._status = 'pending'
|
||||
|
||||
await handshake.then((refParent: ChildAPI) => {
|
||||
await handshake
|
||||
.then((refParent: ChildAPI) => {
|
||||
this._child = refParent
|
||||
this._connected = true
|
||||
|
||||
@@ -173,7 +185,8 @@ class LSPluginCaller extends EventEmitter {
|
||||
}
|
||||
}
|
||||
}, 1000 * 60 * 30)
|
||||
}).finally(() => {
|
||||
})
|
||||
.finally(() => {
|
||||
this._status = undefined
|
||||
})
|
||||
|
||||
@@ -186,6 +199,7 @@ class LSPluginCaller extends EventEmitter {
|
||||
return this._call?.call(this, type, payload)
|
||||
}
|
||||
|
||||
// only for callable apis for sdk user
|
||||
async callAsync(type: any, payload: any = {}) {
|
||||
const actor = deferred(1000 * 10)
|
||||
return this._call?.call(this, type, payload, actor)
|
||||
@@ -199,18 +213,22 @@ class LSPluginCaller extends EventEmitter {
|
||||
async _setupIframeSandbox() {
|
||||
const pl = this._pluginLocal!
|
||||
const id = pl.id
|
||||
const domId = `${id}_lsp_main`
|
||||
const url = new URL(pl.options.entry!)
|
||||
|
||||
url.searchParams
|
||||
.set(`__v__`, IS_DEV ? Date.now().toString() : pl.options.version)
|
||||
url.searchParams.set(
|
||||
`__v__`,
|
||||
IS_DEV ? Date.now().toString() : pl.options.version
|
||||
)
|
||||
|
||||
// clear zombie sandbox
|
||||
const zb = document.querySelector(`#${id}`)
|
||||
const zb = document.querySelector(`#${domId}`)
|
||||
if (zb) zb.parentElement.removeChild(zb)
|
||||
|
||||
const cnt = document.createElement('div')
|
||||
cnt.classList.add('lsp-iframe-sandbox-container')
|
||||
cnt.id = id
|
||||
cnt.id = domId
|
||||
cnt.dataset.pid = id
|
||||
|
||||
// TODO: apply any container layout data
|
||||
try {
|
||||
@@ -219,20 +237,24 @@ class LSPluginCaller extends EventEmitter {
|
||||
cnt.dataset.inited_layout = 'true'
|
||||
const { width, height, left, top } = mainLayoutInfo
|
||||
Object.assign(cnt.style, {
|
||||
width: width + 'px', height: height + 'px',
|
||||
left: left + 'px', top: top + 'px'
|
||||
width: width + 'px',
|
||||
height: height + 'px',
|
||||
left: left + 'px',
|
||||
top: top + 'px',
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[Restore Layout Error]", e)
|
||||
console.error('[Restore Layout Error]', e)
|
||||
}
|
||||
|
||||
document.body.appendChild(cnt)
|
||||
|
||||
const pt = new Postmate({
|
||||
id: id + '_iframe', container: cnt, url: url.href,
|
||||
id: id + '_iframe',
|
||||
container: cnt,
|
||||
url: url.href,
|
||||
classListArray: ['lsp-iframe-sandbox'],
|
||||
model: { baseInfo: JSON.parse(JSON.stringify(pl.toJSON())) }
|
||||
model: { baseInfo: JSON.parse(JSON.stringify(pl.toJSON())) },
|
||||
})
|
||||
|
||||
let handshake = pt.sendHandshake()
|
||||
@@ -246,13 +268,14 @@ class LSPluginCaller extends EventEmitter {
|
||||
reject(new Error(`handshake Timeout`))
|
||||
}, 3 * 1000) // 3secs
|
||||
|
||||
handshake.then((refChild: ParentAPI) => {
|
||||
handshake
|
||||
.then((refChild: ParentAPI) => {
|
||||
this._parent = refChild
|
||||
this._connected = true
|
||||
this.emit('connected')
|
||||
|
||||
refChild.on(LSPMSGFn(pl.id), ({ type, payload }: any) => {
|
||||
debug(`[call from plugin] `, type, payload)
|
||||
debug(`[user -> *host] `, type, payload)
|
||||
|
||||
this._pluginLocal?.emit(type, payload || {})
|
||||
})
|
||||
@@ -260,9 +283,10 @@ class LSPluginCaller extends EventEmitter {
|
||||
this._call = async (...args: any) => {
|
||||
// parent all will get message before handshaked
|
||||
await refChild.call(LSPMSGFn(pl.id), {
|
||||
type: args[0], payload: Object.assign(args[1] || {}, {
|
||||
$$pid: pl.id
|
||||
})
|
||||
type: args[0],
|
||||
payload: Object.assign(args[1] || {}, {
|
||||
$$pid: pl.id,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -276,22 +300,26 @@ class LSPluginCaller extends EventEmitter {
|
||||
}
|
||||
|
||||
resolve(null)
|
||||
}).catch(e => {
|
||||
})
|
||||
.catch((e) => {
|
||||
reject(e)
|
||||
}).finally(() => {
|
||||
})
|
||||
.finally(() => {
|
||||
clearTimeout(timer)
|
||||
})
|
||||
}).catch(e => {
|
||||
})
|
||||
.catch((e) => {
|
||||
debug('[iframe sandbox] error', e)
|
||||
throw e
|
||||
}).finally(() => {
|
||||
})
|
||||
.finally(() => {
|
||||
this._status = undefined
|
||||
})
|
||||
}
|
||||
|
||||
async _setupShadowSandbox() {
|
||||
const pl = this._pluginLocal!
|
||||
const shadow = this._shadow = new LSPluginShadowFrame(pl)
|
||||
const shadow = (this._shadow = new LSPluginShadowFrame(pl))
|
||||
|
||||
try {
|
||||
this._status = 'pending'
|
||||
@@ -305,9 +333,12 @@ class LSPluginCaller extends EventEmitter {
|
||||
actor && (payload.actor = actor)
|
||||
|
||||
// @ts-ignore Call in same thread
|
||||
this._pluginLocal?.emit(type, Object.assign(payload, {
|
||||
$$pid: pl.id
|
||||
}))
|
||||
this._pluginLocal?.emit(
|
||||
type,
|
||||
Object.assign(payload, {
|
||||
$$pid: pl.id,
|
||||
})
|
||||
)
|
||||
|
||||
return actor?.promise
|
||||
}
|
||||
@@ -374,6 +405,4 @@ class LSPluginCaller extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
LSPluginCaller
|
||||
}
|
||||
export { LSPluginCaller }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ function userFetch (url, opts) {
|
||||
resolve({
|
||||
text() {
|
||||
return content
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
@@ -32,9 +32,7 @@ class LSPluginShadowFrame extends EventEmitter<'mounted' | 'unmounted'> {
|
||||
private _loaded = false
|
||||
private _unmountFns: Array<() => Promise<void>> = []
|
||||
|
||||
constructor (
|
||||
private _pluginLocal: PluginLocal
|
||||
) {
|
||||
constructor(private _pluginLocal: PluginLocal) {
|
||||
super()
|
||||
|
||||
_pluginLocal._dispose(() => {
|
||||
@@ -47,15 +45,15 @@ class LSPluginShadowFrame extends EventEmitter<'mounted' | 'unmounted'> {
|
||||
|
||||
if (this.loaded || !entry) return
|
||||
|
||||
const { template, execScripts } = await importHTML(entry, { fetch: userFetch })
|
||||
const { template, execScripts } = await importHTML(entry, {
|
||||
fetch: userFetch,
|
||||
})
|
||||
|
||||
this._mount(template, document.body)
|
||||
|
||||
const sandbox = createSandboxContainer(
|
||||
name, {
|
||||
const sandbox = createSandboxContainer(name, {
|
||||
elementGetter: () => this._root?.firstChild,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
const global = sandbox.instance.proxy as any
|
||||
|
||||
@@ -76,7 +74,7 @@ class LSPluginShadowFrame extends EventEmitter<'mounted' | 'unmounted'> {
|
||||
}
|
||||
|
||||
_mount(content: string, container: HTMLElement) {
|
||||
const frame = this._frame = document.createElement('div')
|
||||
const frame = (this._frame = document.createElement('div'))
|
||||
frame.classList.add('lsp-shadow-sandbox')
|
||||
frame.id = this._pluginLocal.id
|
||||
|
||||
@@ -111,6 +109,4 @@ class LSPluginShadowFrame extends EventEmitter<'mounted' | 'unmounted'> {
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
LSPluginShadowFrame
|
||||
}
|
||||
export { LSPluginShadowFrame }
|
||||
|
||||
@@ -2,6 +2,7 @@ import EventEmitter from 'eventemitter3'
|
||||
import * as CSS from 'csstype'
|
||||
import { LSPluginCaller } from './LSPlugin.caller'
|
||||
import { LSPluginFileStorage } from './modules/LSPlugin.Storage'
|
||||
import { LSPluginExperiments } from './modules/LSPlugin.Experiments'
|
||||
|
||||
export type PluginLocalIdentity = string
|
||||
|
||||
@@ -14,7 +15,7 @@ export type ThemeOptions = {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export type StyleString = string;
|
||||
export type StyleString = string
|
||||
export type StyleOptions = {
|
||||
key?: string
|
||||
style: StyleString
|
||||
@@ -76,7 +77,7 @@ export interface LSPluginBaseInfo {
|
||||
settings: {
|
||||
disabled: boolean
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
[key: string]: any
|
||||
}
|
||||
@@ -86,15 +87,26 @@ export type IHookEvent = {
|
||||
}
|
||||
|
||||
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 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 type IEntityID = { id: EntityID; [key: string]: any }
|
||||
export type IBatchBlock = {
|
||||
content: string
|
||||
properties?: Record<string, any>
|
||||
children?: Array<IBatchBlock>
|
||||
}
|
||||
export type IDatom = [e: number, a: string, v: any, t: number, added: boolean]
|
||||
|
||||
export type IGitResult = { stdout: string; stderr: string; exitCode: number }
|
||||
|
||||
export interface AppUserInfo {
|
||||
[key: string]: any
|
||||
@@ -111,6 +123,9 @@ export interface AppUserConfigs {
|
||||
preferredLanguage: string
|
||||
preferredWorkflow: string
|
||||
|
||||
currentGraph: string
|
||||
showBracket: boolean
|
||||
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
@@ -145,7 +160,7 @@ export interface BlockEntity {
|
||||
container?: string
|
||||
file?: IEntityID
|
||||
level?: number
|
||||
meta?: { timestamps: any, properties: any, startPos: number, endPos: number }
|
||||
meta?: { timestamps: any; properties: any; startPos: number; endPos: number }
|
||||
title?: Array<any>
|
||||
|
||||
[key: string]: any
|
||||
@@ -163,6 +178,7 @@ export interface PageEntity {
|
||||
|
||||
file?: IEntityID
|
||||
namespace?: IEntityID
|
||||
children?: Array<PageEntity>
|
||||
format?: 'markdown' | 'org'
|
||||
journalDay?: number
|
||||
}
|
||||
@@ -171,18 +187,26 @@ export type BlockIdentity = BlockUUID | Pick<BlockEntity, 'uuid'>
|
||||
export type BlockPageName = string
|
||||
export type PageIdentity = BlockPageName | BlockIdentity
|
||||
export type SlashCommandActionCmd =
|
||||
'editor/input'
|
||||
| 'editor/input'
|
||||
| 'editor/hook'
|
||||
| 'editor/clear-current-slash'
|
||||
| 'editor/restore-saved-cursor'
|
||||
export type SlashCommandAction = [cmd: SlashCommandActionCmd, ...args: any]
|
||||
export type SimpleCommandCallback = (e: IHookEvent) => void
|
||||
export type BlockCommandCallback = (e: IHookEvent & { uuid: BlockUUID }) => Promise<void>
|
||||
export type BlockCursorPosition = { left: number, top: number, height: number, pos: number, rect: DOMRect }
|
||||
export type BlockCommandCallback = (
|
||||
e: IHookEvent & { uuid: BlockUUID }
|
||||
) => Promise<void>
|
||||
export type BlockCursorPosition = {
|
||||
left: number
|
||||
top: number
|
||||
height: number
|
||||
pos: number
|
||||
rect: DOMRect
|
||||
}
|
||||
|
||||
export type SimpleCommandKeybinding = {
|
||||
mode?: 'global' | 'non-editing' | 'editing',
|
||||
binding: string,
|
||||
mode?: 'global' | 'non-editing' | 'editing'
|
||||
binding: string
|
||||
mac?: string // special for Mac OS
|
||||
}
|
||||
|
||||
@@ -198,48 +222,50 @@ export type SettingSchemaDesc = {
|
||||
}
|
||||
|
||||
export type ExternalCommandType =
|
||||
'logseq.command/run' |
|
||||
'logseq.editor/cycle-todo' |
|
||||
'logseq.editor/down' |
|
||||
'logseq.editor/up' |
|
||||
'logseq.editor/expand-block-children' |
|
||||
'logseq.editor/collapse-block-children' |
|
||||
'logseq.editor/open-file-in-default-app' |
|
||||
'logseq.editor/open-file-in-directory' |
|
||||
'logseq.editor/select-all-blocks' |
|
||||
'logseq.editor/toggle-open-blocks' |
|
||||
'logseq.editor/zoom-in' |
|
||||
'logseq.editor/zoom-out' |
|
||||
'logseq.editor/indent' |
|
||||
'logseq.editor/outdent' |
|
||||
'logseq.editor/copy' |
|
||||
'logseq.editor/cut' |
|
||||
'logseq.go/home' |
|
||||
'logseq.go/journals' |
|
||||
'logseq.go/keyboard-shortcuts' |
|
||||
'logseq.go/next-journal' |
|
||||
'logseq.go/prev-journal' |
|
||||
'logseq.go/search' |
|
||||
'logseq.go/search-in-page' |
|
||||
'logseq.go/tomorrow' |
|
||||
'logseq.go/backward' |
|
||||
'logseq.go/forward' |
|
||||
'logseq.search/re-index' |
|
||||
'logseq.sidebar/clear' |
|
||||
'logseq.sidebar/open-today-page' |
|
||||
'logseq.ui/goto-plugins' |
|
||||
'logseq.ui/select-theme-color' |
|
||||
'logseq.ui/toggle-brackets' |
|
||||
'logseq.ui/toggle-cards' |
|
||||
'logseq.ui/toggle-contents' |
|
||||
'logseq.ui/toggle-document-mode' |
|
||||
'logseq.ui/toggle-help' |
|
||||
'logseq.ui/toggle-left-sidebar' |
|
||||
'logseq.ui/toggle-right-sidebar' |
|
||||
'logseq.ui/toggle-settings' |
|
||||
'logseq.ui/toggle-theme' |
|
||||
'logseq.ui/toggle-wide-mode' |
|
||||
'logseq.command-palette/toggle'
|
||||
| 'logseq.command/run'
|
||||
| 'logseq.editor/cycle-todo'
|
||||
| 'logseq.editor/down'
|
||||
| 'logseq.editor/up'
|
||||
| 'logseq.editor/expand-block-children'
|
||||
| 'logseq.editor/collapse-block-children'
|
||||
| 'logseq.editor/open-file-in-default-app'
|
||||
| 'logseq.editor/open-file-in-directory'
|
||||
| 'logseq.editor/select-all-blocks'
|
||||
| 'logseq.editor/toggle-open-blocks'
|
||||
| 'logseq.editor/zoom-in'
|
||||
| 'logseq.editor/zoom-out'
|
||||
| 'logseq.editor/indent'
|
||||
| 'logseq.editor/outdent'
|
||||
| 'logseq.editor/copy'
|
||||
| 'logseq.editor/cut'
|
||||
| 'logseq.go/home'
|
||||
| 'logseq.go/journals'
|
||||
| 'logseq.go/keyboard-shortcuts'
|
||||
| 'logseq.go/next-journal'
|
||||
| 'logseq.go/prev-journal'
|
||||
| 'logseq.go/search'
|
||||
| 'logseq.go/search-in-page'
|
||||
| 'logseq.go/tomorrow'
|
||||
| 'logseq.go/backward'
|
||||
| 'logseq.go/forward'
|
||||
| 'logseq.search/re-index'
|
||||
| 'logseq.sidebar/clear'
|
||||
| 'logseq.sidebar/open-today-page'
|
||||
| 'logseq.ui/goto-plugins'
|
||||
| 'logseq.ui/select-theme-color'
|
||||
| 'logseq.ui/toggle-brackets'
|
||||
| 'logseq.ui/toggle-cards'
|
||||
| 'logseq.ui/toggle-contents'
|
||||
| 'logseq.ui/toggle-document-mode'
|
||||
| 'logseq.ui/toggle-help'
|
||||
| 'logseq.ui/toggle-left-sidebar'
|
||||
| 'logseq.ui/toggle-right-sidebar'
|
||||
| 'logseq.ui/toggle-settings'
|
||||
| 'logseq.ui/toggle-theme'
|
||||
| 'logseq.ui/toggle-wide-mode'
|
||||
| 'logseq.command-palette/toggle'
|
||||
|
||||
export type UserProxyTags = 'app' | 'editor' | 'db' | 'git' | 'ui' | 'assets'
|
||||
|
||||
/**
|
||||
* App level APIs
|
||||
@@ -253,25 +279,33 @@ export interface IAppProxy {
|
||||
registerCommand: (
|
||||
type: string,
|
||||
opts: {
|
||||
key: string,
|
||||
label: string,
|
||||
desc?: string,
|
||||
palette?: boolean,
|
||||
key: string
|
||||
label: string
|
||||
desc?: string
|
||||
palette?: boolean
|
||||
keybinding?: SimpleCommandKeybinding
|
||||
},
|
||||
action: SimpleCommandCallback) => void
|
||||
action: SimpleCommandCallback
|
||||
) => void
|
||||
|
||||
registerCommandPalette: (
|
||||
opts: {
|
||||
key: string,
|
||||
label: string,
|
||||
key: string
|
||||
label: string
|
||||
keybinding?: SimpleCommandKeybinding
|
||||
},
|
||||
action: SimpleCommandCallback) => void
|
||||
action: SimpleCommandCallback
|
||||
) => void
|
||||
|
||||
registerCommandShortcut: (
|
||||
keybinding: SimpleCommandKeybinding,
|
||||
action: SimpleCommandCallback
|
||||
) => void
|
||||
|
||||
invokeExternalCommand: (
|
||||
type: ExternalCommandType,
|
||||
...args: Array<any>) => Promise<void>
|
||||
...args: Array<any>
|
||||
) => Promise<void>
|
||||
|
||||
/**
|
||||
* Get state from app store
|
||||
@@ -284,8 +318,7 @@ export interface IAppProxy {
|
||||
* ```
|
||||
* @param path
|
||||
*/
|
||||
getStateFromStore:
|
||||
<T = any>(path: string | Array<string>) => Promise<T>
|
||||
getStateFromStore: <T = any>(path: string | Array<string>) => Promise<T>
|
||||
|
||||
// native
|
||||
relaunch: () => Promise<void>
|
||||
@@ -293,6 +326,7 @@ export interface IAppProxy {
|
||||
openExternalLink: (url: string) => Promise<void>
|
||||
|
||||
/**
|
||||
* @deprecated Using `logseq.Git.execCommand`
|
||||
* @link https://github.com/desktop/dugite/blob/master/docs/api/exec.md
|
||||
* @param args
|
||||
*/
|
||||
@@ -302,12 +336,23 @@ export interface IAppProxy {
|
||||
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
|
||||
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
|
||||
queryElementById: (id: string) => Promise<string | boolean>
|
||||
showMsg: (content: string, status?: 'success' | 'warning' | 'error' | string) => void
|
||||
showMsg: (
|
||||
content: string,
|
||||
status?: 'success' | 'warning' | 'error' | string
|
||||
) => void
|
||||
setZoomFactor: (factor: number) => void
|
||||
setFullScreen: (flag: boolean | 'toggle') => void
|
||||
setLeftSidebarVisible: (flag: boolean | 'toggle') => void
|
||||
@@ -315,7 +360,7 @@ export interface IAppProxy {
|
||||
|
||||
registerUIItem: (
|
||||
type: 'toolbar' | 'pagebar',
|
||||
opts: { key: string, template: string }
|
||||
opts: { key: string; template: string }
|
||||
) => void
|
||||
|
||||
registerPageMenuItem: (
|
||||
@@ -331,6 +376,7 @@ export interface IAppProxy {
|
||||
/**
|
||||
* provide ui slot to block `renderer` macro for `{{renderer arg1, arg2}}`
|
||||
*
|
||||
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-pomodoro-timer
|
||||
* @example
|
||||
* ```ts
|
||||
* // e.g. {{renderer :h1, hello world, green}}
|
||||
@@ -347,11 +393,17 @@ export interface IAppProxy {
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
onMacroRendererSlotted: IUserSlotHook<{ payload: { arguments: Array<string>, uuid: string, [key: string]: any } }>
|
||||
onMacroRendererSlotted: IUserSlotHook<{
|
||||
payload: { arguments: Array<string>; uuid: string; [key: string]: any }
|
||||
}>
|
||||
|
||||
onPageHeadActionsSlotted: IUserSlotHook
|
||||
onRouteChanged: IUserHook<{ path: string, template: string }>
|
||||
onRouteChanged: IUserHook<{ path: string; template: string }>
|
||||
onSidebarVisibleChanged: IUserHook<{ visible: boolean }>
|
||||
|
||||
// internal
|
||||
_installPluginHook: (pid: string, hook: string) => void
|
||||
_uninstallPluginHook: (pid: string, hookOrAll: string | boolean) => void
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -360,10 +412,12 @@ export interface IAppProxy {
|
||||
export interface IEditorProxy extends Record<string, any> {
|
||||
/**
|
||||
* register a custom command which will be added to the Logseq slash command list
|
||||
*
|
||||
* @param tag - displayed name of command
|
||||
* @param action - can be a single callback function to run when the command is called, or an array of fixed commands with arguments
|
||||
*
|
||||
*
|
||||
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-slash-commands
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* logseq.Editor.registerSlashCommand("Say Hi", () => {
|
||||
@@ -398,9 +452,6 @@ export interface IEditorProxy extends Record<string, any> {
|
||||
|
||||
checkEditing: () => Promise<BlockUUID | boolean>
|
||||
|
||||
/**
|
||||
* insert a string at the current cursor
|
||||
*/
|
||||
insertAtEditingCursor: (content: string) => Promise<void>
|
||||
|
||||
restoreEditingCursor: () => Promise<void>
|
||||
@@ -435,16 +486,52 @@ export interface IEditorProxy extends Record<string, any> {
|
||||
*/
|
||||
getPageBlocksTree: (srcPage: PageIdentity) => Promise<Array<BlockEntity>>
|
||||
|
||||
/**
|
||||
* get all page/block linked references
|
||||
* @param srcPage
|
||||
*/
|
||||
getPageLinkedReferences: (
|
||||
srcPage: PageIdentity
|
||||
) => Promise<Array<[page: PageEntity, blocks: Array<BlockEntity>]> | null>
|
||||
|
||||
/**
|
||||
* get flatten pages from top namespace
|
||||
* @param namespace
|
||||
*/
|
||||
getPagesFromNamespace: (
|
||||
namespace: BlockPageName
|
||||
) => Promise<Array<PageEntity> | null>
|
||||
|
||||
/**
|
||||
* construct pages tree from namespace pages
|
||||
* @param namespace
|
||||
*/
|
||||
getPagesTreeFromNamespace: (
|
||||
namespace: BlockPageName
|
||||
) => Promise<Array<PageEntity> | null>
|
||||
|
||||
/**
|
||||
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-reddit-hot-news
|
||||
*
|
||||
* @param srcBlock
|
||||
* @param content
|
||||
* @param opts
|
||||
*/
|
||||
insertBlock: (
|
||||
srcBlock: BlockIdentity,
|
||||
content: string,
|
||||
opts?: Partial<{ before: boolean; sibling: boolean; isPageBlock: boolean; properties: {} }>
|
||||
opts?: Partial<{
|
||||
before: boolean
|
||||
sibling: boolean
|
||||
isPageBlock: boolean
|
||||
properties: {}
|
||||
}>
|
||||
) => Promise<BlockEntity | null>
|
||||
|
||||
insertBatchBlock: (
|
||||
srcBlock: BlockIdentity,
|
||||
batch: IBatchBlock | Array<IBatchBlock>,
|
||||
opts?: Partial<{ before: boolean, sibling: boolean }>
|
||||
opts?: Partial<{ before: boolean; sibling: boolean }>
|
||||
) => Promise<Array<BlockEntity> | null>
|
||||
|
||||
updateBlock: (
|
||||
@@ -453,9 +540,7 @@ export interface IEditorProxy extends Record<string, any> {
|
||||
opts?: Partial<{ properties: {} }>
|
||||
) => Promise<void>
|
||||
|
||||
removeBlock: (
|
||||
srcBlock: BlockIdentity
|
||||
) => Promise<void>
|
||||
removeBlock: (srcBlock: BlockIdentity) => Promise<void>
|
||||
|
||||
getBlock: (
|
||||
srcBlock: BlockIdentity | EntityID,
|
||||
@@ -475,17 +560,24 @@ export interface IEditorProxy extends Record<string, any> {
|
||||
createPage: (
|
||||
pageName: BlockPageName,
|
||||
properties?: {},
|
||||
opts?: Partial<{ redirect: boolean, createFirstBlock: boolean, format: BlockEntity['format'], journal: boolean }>
|
||||
opts?: Partial<{
|
||||
redirect: boolean
|
||||
createFirstBlock: boolean
|
||||
format: BlockEntity['format']
|
||||
journal: boolean
|
||||
}>
|
||||
) => Promise<PageEntity | null>
|
||||
|
||||
deletePage: (
|
||||
pageName: BlockPageName
|
||||
) => Promise<void>
|
||||
deletePage: (pageName: BlockPageName) => Promise<void>
|
||||
|
||||
renamePage: (oldName: string, newName: string) => Promise<void>
|
||||
|
||||
getAllPages: (repo?: string) => Promise<any>
|
||||
|
||||
prependBlockInPage: (page: PageIdentity, content: string, opts?: Partial<{ properties: {} }>) => Promise<BlockEntity | null>
|
||||
|
||||
appendBlockInPage: (page: PageIdentity, content: string, opts?: Partial<{ properties: {} }>) => Promise<BlockEntity | null>
|
||||
|
||||
getPreviousSiblingBlock: (
|
||||
srcBlock: BlockIdentity
|
||||
) => Promise<BlockEntity | null>
|
||||
@@ -514,13 +606,22 @@ export interface IEditorProxy extends Record<string, any> {
|
||||
|
||||
scrollToBlockInPage: (
|
||||
pageName: BlockPageName,
|
||||
blockId: BlockIdentity
|
||||
blockId: BlockIdentity,
|
||||
opts?: { replaceState: boolean }
|
||||
) => void
|
||||
|
||||
openInRightSidebar: (uuid: BlockUUID) => void
|
||||
|
||||
// events
|
||||
onInputSelectionEnd: IUserHook<{ caret: any, point: { x: number, y: number }, start: number, end: number, text: string }>
|
||||
/**
|
||||
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-a-translator
|
||||
*/
|
||||
onInputSelectionEnd: IUserHook<{
|
||||
caret: any
|
||||
point: { x: number; y: number }
|
||||
start: number
|
||||
end: number
|
||||
text: string
|
||||
}>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -537,7 +638,78 @@ export interface IDBProxy {
|
||||
/**
|
||||
* Run a datascript query
|
||||
*/
|
||||
datascriptQuery: <T = any>(query: string) => Promise<T>
|
||||
datascriptQuery: <T = any>(query: string, ...inputs: Array<any>) => Promise<T>
|
||||
|
||||
/**
|
||||
* Hook all transaction data of DB
|
||||
*/
|
||||
onChanged: IUserHook<{
|
||||
blocks: Array<BlockEntity>
|
||||
txData: Array<IDatom>
|
||||
txMeta?: { outlinerOp: string; [key: string]: any }
|
||||
}>
|
||||
|
||||
/**
|
||||
* Subscribe a specific block changed event
|
||||
*/
|
||||
onBlockChanged(
|
||||
uuid: BlockUUID,
|
||||
callback: (
|
||||
block: BlockEntity,
|
||||
txData: Array<IDatom>,
|
||||
txMeta?: { outlinerOp: string; [key: string]: any }
|
||||
) => void
|
||||
): IUserOffHook
|
||||
}
|
||||
|
||||
/**
|
||||
* Git related APIS
|
||||
*/
|
||||
export interface IGitProxy {
|
||||
/**
|
||||
* @link https://github.com/desktop/dugite/blob/master/docs/api/exec.md
|
||||
* @param args
|
||||
*/
|
||||
execCommand: (args: string[]) => Promise<IGitResult>
|
||||
|
||||
loadIgnoreFile: () => Promise<string>
|
||||
saveIgnoreFile: (content: string) => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* UI related APIs
|
||||
*/
|
||||
export type UIMsgOptions = {
|
||||
key: string
|
||||
timeout: number // milliseconds. `0` indicate that keep showing
|
||||
}
|
||||
|
||||
export type UIMsgKey = UIMsgOptions['key']
|
||||
|
||||
export interface IUIProxy {
|
||||
showMsg: (
|
||||
content: string,
|
||||
status?: 'success' | 'warning' | 'error' | string,
|
||||
opts?: Partial<UIMsgOptions>
|
||||
) => Promise<UIMsgKey>
|
||||
|
||||
closeMsg: (key: UIMsgKey) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Assets related APIs
|
||||
*/
|
||||
export interface IAssetsProxy {
|
||||
listFilesOfCurrentGraph(
|
||||
exts: string | string[]
|
||||
): Promise<{
|
||||
path: string
|
||||
size: number
|
||||
accessTime: number
|
||||
modifiedTime: number
|
||||
changeTime: number
|
||||
birthTime: number
|
||||
}>
|
||||
}
|
||||
|
||||
export interface ILSPluginThemeManager extends EventEmitter {
|
||||
@@ -614,17 +786,13 @@ export interface ILSPluginUser extends EventEmitter<LSPluginUserEvents> {
|
||||
/**
|
||||
* Inject custom css for the main Logseq app
|
||||
*
|
||||
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts
|
||||
* @example
|
||||
* ```ts
|
||||
* logseq.provideStyle(`
|
||||
* @import url("https://at.alicdn.com/t/font_2409735_r7em724douf.css");
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
provideStyle(style: StyleString | StyleOptions): this
|
||||
|
||||
@@ -632,6 +800,7 @@ export interface ILSPluginUser extends EventEmitter<LSPluginUserEvents> {
|
||||
* Inject custom UI at specific DOM node.
|
||||
* Event handlers can not be passed by string, so you need to create them in `provideModel`
|
||||
*
|
||||
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-a-translator
|
||||
* @example
|
||||
* ```ts
|
||||
* logseq.provideUI({
|
||||
@@ -647,8 +816,18 @@ export interface ILSPluginUser extends EventEmitter<LSPluginUserEvents> {
|
||||
*/
|
||||
provideUI(ui: UIOptions): this
|
||||
|
||||
/**
|
||||
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts
|
||||
*
|
||||
* @param schemas
|
||||
*/
|
||||
useSettingsSchema(schemas: Array<SettingSchemaDesc>): this
|
||||
|
||||
/**
|
||||
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts
|
||||
*
|
||||
* @param attrs
|
||||
*/
|
||||
updateSettings(attrs: Record<string, any>): void
|
||||
|
||||
onSettingsChanged<T = any>(cb: (a: T, b: T) => void): IUserOffHook
|
||||
@@ -662,6 +841,7 @@ export interface ILSPluginUser extends EventEmitter<LSPluginUserEvents> {
|
||||
/**
|
||||
* Set the style for the plugin's UI
|
||||
*
|
||||
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-awesome-fonts
|
||||
* @example
|
||||
* ```ts
|
||||
* logseq.setMainUIInlineStyle({
|
||||
@@ -694,6 +874,9 @@ export interface ILSPluginUser extends EventEmitter<LSPluginUserEvents> {
|
||||
App: IAppProxy & Record<string, any>
|
||||
Editor: IEditorProxy & Record<string, any>
|
||||
DB: IDBProxy
|
||||
Git: IGitProxy
|
||||
UI: IUIProxy
|
||||
|
||||
FileStorage: LSPluginFileStorage
|
||||
Experiments: LSPluginExperiments
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { deepMerge, mergeSettingsWithSchema, safetyPathJoin } from './helpers'
|
||||
import {
|
||||
deepMerge,
|
||||
mergeSettingsWithSchema,
|
||||
safeSnakeCase,
|
||||
safetyPathJoin,
|
||||
} from './helpers'
|
||||
import { LSPluginCaller } from './LSPlugin.caller'
|
||||
import {
|
||||
IAppProxy, IDBProxy,
|
||||
IAppProxy,
|
||||
IDBProxy,
|
||||
IEditorProxy,
|
||||
ILSPluginUser,
|
||||
LSPluginBaseInfo,
|
||||
@@ -10,19 +16,33 @@ import {
|
||||
BlockCommandCallback,
|
||||
StyleString,
|
||||
ThemeOptions,
|
||||
UIOptions, IHookEvent, BlockIdentity,
|
||||
UIOptions,
|
||||
IHookEvent,
|
||||
BlockIdentity,
|
||||
BlockPageName,
|
||||
UIContainerAttrs, SimpleCommandCallback, SimpleCommandKeybinding, SettingSchemaDesc, IUserOffHook
|
||||
UIContainerAttrs,
|
||||
SimpleCommandCallback,
|
||||
SimpleCommandKeybinding,
|
||||
SettingSchemaDesc,
|
||||
IUserOffHook,
|
||||
IGitProxy,
|
||||
IUIProxy,
|
||||
UserProxyTags,
|
||||
BlockUUID,
|
||||
BlockEntity,
|
||||
IDatom,
|
||||
IAssetsProxy,
|
||||
} from './LSPlugin'
|
||||
import Debug from 'debug'
|
||||
import * as CSS from 'csstype'
|
||||
import { snakeCase } from 'snake-case'
|
||||
import EventEmitter from 'eventemitter3'
|
||||
import { LSPluginFileStorage } from './modules/LSPlugin.Storage'
|
||||
import { LSPluginExperiments } from './modules/LSPlugin.Experiments'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__LSP__HOST__: boolean
|
||||
logseq: LSPluginUser
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +58,10 @@ function registerSimpleCommand (
|
||||
this: LSPluginUser,
|
||||
type: string,
|
||||
opts: {
|
||||
key: string,
|
||||
label: string,
|
||||
desc?: string,
|
||||
palette?: boolean,
|
||||
key: string
|
||||
label: string
|
||||
desc?: string
|
||||
palette?: boolean
|
||||
keybinding?: SimpleCommandKeybinding
|
||||
},
|
||||
action: SimpleCommandCallback
|
||||
@@ -57,7 +77,11 @@ function registerSimpleCommand (
|
||||
|
||||
this.caller?.call(`api:call`, {
|
||||
method: 'register-plugin-simple-command',
|
||||
args: [this.baseInfo.id, [{ key, label, type, desc, keybinding }, ['editor/hook', eventKey]], palette]
|
||||
args: [
|
||||
this.baseInfo.id,
|
||||
[{ key, label, type, desc, keybinding }, ['editor/hook', eventKey]],
|
||||
palette,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -65,28 +89,46 @@ const app: Partial<IAppProxy> = {
|
||||
registerCommand: registerSimpleCommand,
|
||||
|
||||
registerCommandPalette(
|
||||
opts: { key: string; label: string, keybinding?: SimpleCommandKeybinding },
|
||||
action: SimpleCommandCallback) {
|
||||
|
||||
opts: { key: string; label: string; keybinding?: SimpleCommandKeybinding },
|
||||
action: SimpleCommandCallback
|
||||
) {
|
||||
const { key, label, keybinding } = opts
|
||||
const group = 'global-palette-command'
|
||||
const group = '$palette$'
|
||||
|
||||
return registerSimpleCommand.call(
|
||||
this, group,
|
||||
this,
|
||||
group,
|
||||
{ key, label, palette: true, keybinding },
|
||||
action)
|
||||
action
|
||||
)
|
||||
},
|
||||
|
||||
registerCommandShortcut(
|
||||
keybinding: SimpleCommandKeybinding,
|
||||
action: SimpleCommandCallback
|
||||
) {
|
||||
const { binding } = keybinding
|
||||
const group = '$shortcut$'
|
||||
const key = group + safeSnakeCase(binding)
|
||||
|
||||
return registerSimpleCommand.call(
|
||||
this,
|
||||
group,
|
||||
{ key, palette: false, keybinding },
|
||||
action
|
||||
)
|
||||
},
|
||||
|
||||
registerUIItem(
|
||||
type: 'toolbar' | 'pagebar',
|
||||
opts: { key: string, template: string }
|
||||
opts: { key: string; template: string }
|
||||
) {
|
||||
const pid = this.baseInfo.id
|
||||
// opts.key = `${pid}_${opts.key}`
|
||||
|
||||
this.caller?.call(`api:call`, {
|
||||
method: 'register-plugin-ui-item',
|
||||
args: [pid, type, opts]
|
||||
args: [pid, type, opts],
|
||||
})
|
||||
},
|
||||
|
||||
@@ -103,23 +145,28 @@ const app: Partial<IAppProxy> = {
|
||||
const label = tag
|
||||
const type = 'page-menu-item'
|
||||
|
||||
registerSimpleCommand.call(this,
|
||||
type, {
|
||||
key, label
|
||||
}, action)
|
||||
registerSimpleCommand.call(
|
||||
this,
|
||||
type,
|
||||
{
|
||||
key,
|
||||
label,
|
||||
},
|
||||
action
|
||||
)
|
||||
},
|
||||
|
||||
setFullScreen(flag) {
|
||||
const sf = (...args) => this._callWin('setFullScreen', ...args)
|
||||
|
||||
if (flag === 'toggle') {
|
||||
this._callWin('isFullScreen').then(r => {
|
||||
this._callWin('isFullScreen').then((r) => {
|
||||
r ? sf() : sf(true)
|
||||
})
|
||||
} else {
|
||||
flag ? sf(true) : sf()
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
let registeredCmdUid = 0
|
||||
@@ -136,7 +183,7 @@ const editor: Partial<IEditorProxy> = {
|
||||
actions = [
|
||||
['editor/clear-current-slash', false],
|
||||
['editor/restore-saved-cursor'],
|
||||
['editor/hook', actions]
|
||||
['editor/hook', actions],
|
||||
]
|
||||
}
|
||||
|
||||
@@ -169,7 +216,7 @@ const editor: Partial<IEditorProxy> = {
|
||||
|
||||
this.caller?.call(`api:call`, {
|
||||
method: 'register-plugin-slash-command',
|
||||
args: [this.baseInfo.id, [tag, actions]]
|
||||
args: [this.baseInfo.id, [tag, actions]],
|
||||
})
|
||||
},
|
||||
|
||||
@@ -186,30 +233,68 @@ const editor: Partial<IEditorProxy> = {
|
||||
const label = tag
|
||||
const type = 'block-context-menu-item'
|
||||
|
||||
registerSimpleCommand.call(this,
|
||||
type, {
|
||||
key, label
|
||||
}, action)
|
||||
registerSimpleCommand.call(
|
||||
this,
|
||||
type,
|
||||
{
|
||||
key,
|
||||
label,
|
||||
},
|
||||
action
|
||||
)
|
||||
},
|
||||
|
||||
scrollToBlockInPage(
|
||||
this: LSPluginUser,
|
||||
pageName: BlockPageName,
|
||||
blockId: BlockIdentity
|
||||
blockId: BlockIdentity,
|
||||
opts?: { replaceState: boolean }
|
||||
) {
|
||||
const anchor = `block-content-` + blockId
|
||||
this.App.pushState(
|
||||
'page',
|
||||
{ name: pageName },
|
||||
{ anchor }
|
||||
)
|
||||
if (opts?.replaceState) {
|
||||
this.App.replaceState('page', { name: pageName }, { anchor })
|
||||
} else {
|
||||
this.App.pushState('page', { name: pageName }, { anchor })
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const db: Partial<IDBProxy> = {}
|
||||
const db: Partial<IDBProxy> = {
|
||||
onBlockChanged(
|
||||
this: LSPluginUser,
|
||||
uuid: BlockUUID,
|
||||
callback: (
|
||||
block: BlockEntity,
|
||||
txData: Array<IDatom>,
|
||||
txMeta?: { outlinerOp: string; [p: string]: any }
|
||||
) => void
|
||||
): IUserOffHook {
|
||||
const pid = this.baseInfo.id
|
||||
const hook = `hook:db:${safeSnakeCase(`block:${uuid}`)}`
|
||||
const aBlockChange = ({ block, txData, txMeta }) => {
|
||||
if (block.uuid !== uuid) {
|
||||
return
|
||||
}
|
||||
|
||||
callback(block, txData, txMeta)
|
||||
}
|
||||
|
||||
this.caller.on(hook, aBlockChange)
|
||||
this.App._installPluginHook(pid, hook)
|
||||
|
||||
return () => {
|
||||
this.caller.off(hook, aBlockChange)
|
||||
this.App._uninstallPluginHook(pid, hook)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const git: Partial<IGitProxy> = {}
|
||||
const ui: Partial<IUIProxy> = {}
|
||||
const assets: Partial<IAssetsProxy> = {}
|
||||
|
||||
type uiState = {
|
||||
key?: number,
|
||||
key?: number
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
@@ -219,7 +304,12 @@ const KEY_MAIN_UI = 0
|
||||
* User plugin instance
|
||||
* @public
|
||||
*/
|
||||
export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements ILSPluginUser {
|
||||
export class LSPluginUser
|
||||
extends EventEmitter<LSPluginUserEvents>
|
||||
implements ILSPluginUser {
|
||||
// @ts-ignore
|
||||
private _version: string = LIB_VERSION
|
||||
private _debugTag: string = ''
|
||||
private _settingsSchema?: Array<SettingSchemaDesc>
|
||||
private _connected: boolean = false
|
||||
|
||||
@@ -229,7 +319,8 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
|
||||
*/
|
||||
private _ui = new Map<number, uiState>()
|
||||
|
||||
private readonly _fileStorage: LSPluginFileStorage
|
||||
private _mFileStorage: LSPluginFileStorage
|
||||
private _mExperiments: LSPluginExperiments
|
||||
|
||||
/**
|
||||
* handler of before unload plugin
|
||||
@@ -264,22 +355,16 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
|
||||
const cb = this._beforeunloadCallback
|
||||
|
||||
try {
|
||||
cb && await cb(rest)
|
||||
cb && (await cb(rest))
|
||||
actor?.resolve(null)
|
||||
} catch (e) {
|
||||
console.debug(`${_caller.debugTag} [beforeunload] `, e)
|
||||
actor?.reject(e)
|
||||
}
|
||||
})
|
||||
|
||||
// modules
|
||||
this._fileStorage = new LSPluginFileStorage(this)
|
||||
}
|
||||
|
||||
async ready (
|
||||
model?: any,
|
||||
callback?: any
|
||||
) {
|
||||
async ready(model?: any, callback?: any) {
|
||||
if (this._connected) return
|
||||
|
||||
try {
|
||||
@@ -296,7 +381,8 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
|
||||
|
||||
if (this._settingsSchema) {
|
||||
baseInfo.settings = mergeSettingsWithSchema(
|
||||
baseInfo.settings, this._settingsSchema
|
||||
baseInfo.settings,
|
||||
this._settingsSchema
|
||||
)
|
||||
|
||||
// TODO: sync host settings schema
|
||||
@@ -304,12 +390,17 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
|
||||
}
|
||||
|
||||
if (baseInfo?.id) {
|
||||
this._debugTag =
|
||||
this._caller.debugTag = `#${baseInfo.id} [${baseInfo.name}]`
|
||||
}
|
||||
|
||||
await this._execCallableAPIAsync('setSDKMetadata', {
|
||||
version: this._version,
|
||||
})
|
||||
|
||||
callback && callback.call(this, baseInfo)
|
||||
} catch (e) {
|
||||
console.error('[LSPlugin Ready Error]', e)
|
||||
console.error(`${this._debugTag} [Ready Error]`, e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +438,8 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
|
||||
useSettingsSchema(schema: Array<SettingSchemaDesc>) {
|
||||
if (this.connected) {
|
||||
this.caller.call('settings:schema', {
|
||||
schema, isSync: true
|
||||
schema,
|
||||
isSync: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -383,14 +475,22 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
|
||||
}
|
||||
|
||||
hideMainUI(opts?: { restoreEditingCursor: boolean }): void {
|
||||
const payload = { key: KEY_MAIN_UI, visible: false, cursor: opts?.restoreEditingCursor }
|
||||
const payload = {
|
||||
key: KEY_MAIN_UI,
|
||||
visible: false,
|
||||
cursor: opts?.restoreEditingCursor,
|
||||
}
|
||||
this.caller.call('main-ui:visible', payload)
|
||||
this.emit('ui:visible:changed', payload)
|
||||
this._ui.set(payload.key, payload)
|
||||
}
|
||||
|
||||
showMainUI(opts?: { autoFocus: boolean }): void {
|
||||
const payload = { key: KEY_MAIN_UI, visible: true, autoFocus: opts?.autoFocus }
|
||||
const payload = {
|
||||
key: KEY_MAIN_UI,
|
||||
visible: true,
|
||||
autoFocus: opts?.autoFocus,
|
||||
}
|
||||
this.caller.call('main-ui:visible', payload)
|
||||
this.emit('ui:visible:changed', payload)
|
||||
this._ui.set(payload.key, payload)
|
||||
@@ -406,6 +506,10 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
|
||||
}
|
||||
}
|
||||
|
||||
get version(): string {
|
||||
return this._version
|
||||
}
|
||||
|
||||
get isMainUIVisible(): boolean {
|
||||
const state = this._ui.get(KEY_MAIN_UI)
|
||||
return Boolean(state && state.visible)
|
||||
@@ -437,10 +541,7 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_makeUserProxy (
|
||||
target: any,
|
||||
tag?: 'app' | 'editor' | 'db'
|
||||
) {
|
||||
_makeUserProxy(target: any, tag?: UserProxyTags) {
|
||||
const that = this
|
||||
const caller = this.caller
|
||||
|
||||
@@ -450,7 +551,7 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
|
||||
|
||||
return function (this: any, ...args: any) {
|
||||
if (origMethod) {
|
||||
const ret = origMethod.apply(that, args)
|
||||
const ret = origMethod.apply(that, args.concat(tag))
|
||||
if (ret !== PROXY_CONTINUE) return
|
||||
}
|
||||
|
||||
@@ -462,33 +563,59 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
|
||||
const f = hookMatcher[0].toLowerCase()
|
||||
const s = hookMatcher.input!
|
||||
const e = s.slice(f.length)
|
||||
const isOff = f === 'off'
|
||||
const pid = that.baseInfo.id
|
||||
|
||||
const type = `hook:${tag}:${snakeCase(e)}`
|
||||
const type = `hook:${tag}:${safeSnakeCase(e)}`
|
||||
const handler = args[0]
|
||||
caller[f](type, handler)
|
||||
return f !== 'off' ? () => (caller.off(type, handler)) : void 0
|
||||
|
||||
if (isOff) {
|
||||
return () => {
|
||||
caller.off(type, handler)
|
||||
that.App._uninstallPluginHook(pid, type)
|
||||
}
|
||||
} else {
|
||||
return that.App._installPluginHook(pid, type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let method = propKey as string
|
||||
|
||||
if ((['git', 'ui', 'assets'] as UserProxyTags[]).includes(tag)) {
|
||||
method = tag + '_' + method
|
||||
}
|
||||
|
||||
// Call host
|
||||
return caller.callAsync(`api:call`, {
|
||||
tag, method: propKey, args: args
|
||||
tag,
|
||||
method,
|
||||
args: args,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param args
|
||||
*/
|
||||
_callWin (...args) {
|
||||
_execCallableAPIAsync(method, ...args) {
|
||||
return this._caller.callAsync(`api:call`, {
|
||||
method: '_callMainWin',
|
||||
args: args
|
||||
method,
|
||||
args,
|
||||
})
|
||||
}
|
||||
|
||||
_execCallableAPI(method, ...args) {
|
||||
this._caller.call(`api:call`, {
|
||||
method,
|
||||
args,
|
||||
})
|
||||
}
|
||||
|
||||
_callWin(...args) {
|
||||
return this._execCallableAPIAsync(`_callMainWin`, ...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface methods of {@link IAppProxy}
|
||||
*/
|
||||
@@ -501,11 +628,31 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
|
||||
}
|
||||
|
||||
get DB(): IDBProxy {
|
||||
return this._makeUserProxy(db)
|
||||
return this._makeUserProxy(db, 'db')
|
||||
}
|
||||
|
||||
get Git(): IGitProxy {
|
||||
return this._makeUserProxy(git, 'git')
|
||||
}
|
||||
|
||||
get UI(): IUIProxy {
|
||||
return this._makeUserProxy(ui, 'ui')
|
||||
}
|
||||
|
||||
get Assets(): IAssetsProxy {
|
||||
return this._makeUserProxy(assets, 'assets')
|
||||
}
|
||||
|
||||
get FileStorage(): LSPluginFileStorage {
|
||||
return this._fileStorage
|
||||
let m = this._mFileStorage
|
||||
if (!m) m = this._mFileStorage = new LSPluginFileStorage(this)
|
||||
return m
|
||||
}
|
||||
|
||||
get Experiments(): LSPluginExperiments {
|
||||
let m = this._mExperiments
|
||||
if (!m) m = this._mExperiments = new LSPluginExperiments(this)
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,8 +668,8 @@ export function setupPluginUserInstance (
|
||||
return new LSPluginUser(pluginBaseInfo, pluginCaller)
|
||||
}
|
||||
|
||||
if (window.__LSP__HOST__ == null) { // Entry of iframe mode
|
||||
// entry of iframe mode
|
||||
if (window.__LSP__HOST__ == null) {
|
||||
const caller = new LSPluginCaller(null)
|
||||
// @ts-ignore
|
||||
window.logseq = setupPluginUserInstance({} as any, caller)
|
||||
}
|
||||
|
||||
7
libs/src/callable.apis.ts
Normal file
7
libs/src/callable.apis.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { PluginLocal } from './LSPlugin.core'
|
||||
|
||||
export function setSDKMetadata(this: PluginLocal, data: any) {
|
||||
if (this?.sdk && data) {
|
||||
this.sdk = Object.assign({}, this.sdk, data)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
import { SettingSchemaDesc, StyleString, UIOptions } from './LSPlugin'
|
||||
import { PluginLocal } from './LSPlugin.core'
|
||||
import { snakeCase } from 'snake-case'
|
||||
import * as nodePath from 'path'
|
||||
import DOMPurify from 'dompurify'
|
||||
import { merge } from 'lodash-es'
|
||||
|
||||
interface IObject {
|
||||
[key: string]: any;
|
||||
}
|
||||
import { snakeCase } from 'snake-case'
|
||||
import * as callables from './callable.apis'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -16,7 +13,8 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export const path = navigator.platform.toLowerCase() === 'win32' ? nodePath.win32 : nodePath.posix
|
||||
export const path =
|
||||
navigator.platform.toLowerCase() === 'win32' ? nodePath.win32 : nodePath.posix
|
||||
export const IS_DEV = process.env.NODE_ENV === 'development'
|
||||
export const PROTOCOL_FILE = 'file://'
|
||||
export const PROTOCOL_LSP = 'lsp://'
|
||||
@@ -24,14 +22,18 @@ export const URL_LSP = PROTOCOL_LSP + 'logseq.io/'
|
||||
|
||||
let _appPathRoot
|
||||
|
||||
// TODO: snakeCase of lodash is incompatible with `snake-case`
|
||||
export const safeSnakeCase = snakeCase
|
||||
|
||||
export async function getAppPathRoot(): Promise<string> {
|
||||
if (_appPathRoot) {
|
||||
return _appPathRoot
|
||||
}
|
||||
|
||||
return (_appPathRoot =
|
||||
await invokeHostExportedApi('_callApplication', 'getAppPath')
|
||||
)
|
||||
return (_appPathRoot = await invokeHostExportedApi(
|
||||
'_callApplication',
|
||||
'getAppPath'
|
||||
))
|
||||
}
|
||||
|
||||
export async function getSDKPathRoot(): Promise<string> {
|
||||
@@ -46,7 +48,7 @@ export async function getSDKPathRoot (): Promise<string> {
|
||||
}
|
||||
|
||||
export function isObject(item: any) {
|
||||
return (item === Object(item) && !Array.isArray(item))
|
||||
return item === Object(item) && !Array.isArray(item)
|
||||
}
|
||||
|
||||
export const deepMerge = merge
|
||||
@@ -112,38 +114,45 @@ export function deferred<T = any> (timeout?: number, tag?: string) {
|
||||
|
||||
if (timeout) {
|
||||
// @ts-ignore
|
||||
timeout = setTimeout(() => reject(new Error(`[deferred timeout] ${tag}`)), timeout)
|
||||
timeout = setTimeout(
|
||||
() => reject(new Error(`[deferred timeout] ${tag}`)),
|
||||
timeout
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
created: Date.now(),
|
||||
setTag: (t: string) => tag = t,
|
||||
resolve, reject, promise,
|
||||
setTag: (t: string) => (tag = t),
|
||||
resolve,
|
||||
reject,
|
||||
promise,
|
||||
get settled() {
|
||||
return settled
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function invokeHostExportedApi (
|
||||
method: string,
|
||||
...args: Array<any>
|
||||
) {
|
||||
method = method?.startsWith('_call') ? method :
|
||||
method?.replace(/^[_$]+/, '')
|
||||
const method1 = snakeCase(method)
|
||||
export function invokeHostExportedApi(method: string, ...args: Array<any>) {
|
||||
method = method?.startsWith('_call') ? method : method?.replace(/^[_$]+/, '')
|
||||
const method1 = safeSnakeCase(method)
|
||||
|
||||
const logseqHostExportedApi = Object.assign(
|
||||
// @ts-ignore
|
||||
const logseqHostExportedApi = window.logseq?.api || {}
|
||||
window.logseq?.api || {},
|
||||
callables
|
||||
)
|
||||
|
||||
const fn = logseqHostExportedApi[method1] || window.apis[method1] ||
|
||||
logseqHostExportedApi[method] || window.apis[method]
|
||||
const fn =
|
||||
logseqHostExportedApi[method1] ||
|
||||
window.apis[method1] ||
|
||||
logseqHostExportedApi[method] ||
|
||||
window.apis[method]
|
||||
|
||||
if (!fn) {
|
||||
throw new Error(`Not existed method #${method}`)
|
||||
}
|
||||
return typeof fn !== 'function' ? fn : fn.apply(null, args)
|
||||
return typeof fn !== 'function' ? fn : fn.apply(this, args)
|
||||
}
|
||||
|
||||
export function setupIframeSandbox(
|
||||
@@ -180,7 +189,8 @@ export function setupInjectedStyle (
|
||||
el = document.createElement('style')
|
||||
el.textContent = style
|
||||
|
||||
attrs && Object.entries(attrs).forEach(([k, v]) => {
|
||||
attrs &&
|
||||
Object.entries(attrs).forEach(([k, v]) => {
|
||||
el.setAttribute(k, v)
|
||||
})
|
||||
|
||||
@@ -197,7 +207,7 @@ export function setupInjectedUI (
|
||||
this: PluginLocal,
|
||||
ui: UIOptions,
|
||||
attrs: Record<string, string>,
|
||||
initialCallback?: (e: { el: HTMLElement, float: boolean }) => void
|
||||
initialCallback?: (e: { el: HTMLElement; float: boolean }) => void
|
||||
) {
|
||||
let slot: string = ''
|
||||
let selector: string
|
||||
@@ -217,21 +227,32 @@ export function setupInjectedUI (
|
||||
const id = `${ui.key}-${slot}-${pl.id}`
|
||||
const key = `${ui.key}--${pl.id}`
|
||||
|
||||
const target = float ? document.body : (selector && document.querySelector(selector))
|
||||
const target = float
|
||||
? document.body
|
||||
: selector && document.querySelector(selector)
|
||||
if (!target) {
|
||||
console.error(`${this.debugTag} can not resolve selector target ${selector}`)
|
||||
console.error(
|
||||
`${this.debugTag} can not resolve selector target ${selector}`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (ui.template) {
|
||||
// safe template
|
||||
ui.template = DOMPurify.sanitize(
|
||||
ui.template, {
|
||||
ui.template = DOMPurify.sanitize(ui.template, {
|
||||
ADD_TAGS: ['iframe'],
|
||||
ALLOW_UNKNOWN_PROTOCOLS: true,
|
||||
ADD_ATTR: ['allow', 'src', 'allowfullscreen', 'frameborder', 'scrolling', 'target']
|
||||
ADD_ATTR: [
|
||||
'allow',
|
||||
'src',
|
||||
'allowfullscreen',
|
||||
'frameborder',
|
||||
'scrolling',
|
||||
'target',
|
||||
],
|
||||
})
|
||||
} else { // remove ui
|
||||
} else {
|
||||
// remove ui
|
||||
injectedUIEffects.get(id)?.call(null)
|
||||
return
|
||||
}
|
||||
@@ -243,14 +264,17 @@ export function setupInjectedUI (
|
||||
content.innerHTML = ui.template
|
||||
|
||||
// update attributes
|
||||
attrs && Object.entries(attrs).forEach(([k, v]) => {
|
||||
attrs &&
|
||||
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)
|
||||
ui.style &&
|
||||
Object.entries(ui.style).forEach(([k, v]) => {
|
||||
if (
|
||||
positionDirty &&
|
||||
['left', 'top', 'bottom', 'right', 'width', 'height'].includes(k)
|
||||
) {
|
||||
return
|
||||
}
|
||||
@@ -275,11 +299,13 @@ export function setupInjectedUI (
|
||||
// TODO: enhance template
|
||||
content.innerHTML = ui.template
|
||||
|
||||
attrs && Object.entries(attrs).forEach(([k, v]) => {
|
||||
attrs &&
|
||||
Object.entries(attrs).forEach(([k, v]) => {
|
||||
el.setAttribute(k, v)
|
||||
})
|
||||
|
||||
ui.style && Object.entries(ui.style).forEach(([k, v]) => {
|
||||
ui.style &&
|
||||
Object.entries(ui.style).forEach(([k, v]) => {
|
||||
el.style[k] = v
|
||||
})
|
||||
|
||||
@@ -291,33 +317,54 @@ export function setupInjectedUI (
|
||||
el.setAttribute('resizable', 'true')
|
||||
ui.close && (el.dataset.close = ui.close)
|
||||
el.classList.add('lsp-ui-float-container', 'visible')
|
||||
disposeFloat = (
|
||||
pl._setupResizableContainer(el, key),
|
||||
pl._setupDraggableContainer(el, { key, close: () => teardownUI(), title: attrs?.title }))
|
||||
disposeFloat =
|
||||
(pl._setupResizableContainer(el, key),
|
||||
pl._setupDraggableContainer(el, {
|
||||
key,
|
||||
close: () => teardownUI(),
|
||||
title: attrs?.title,
|
||||
}))
|
||||
}
|
||||
|
||||
if (!!slot && ui.reset) {
|
||||
const exists = Array.from(target.querySelectorAll('[data-injected-ui]'))
|
||||
.map((it: HTMLElement) => it.id)
|
||||
const exists = Array.from(
|
||||
target.querySelectorAll('[data-injected-ui]')
|
||||
).map((it: HTMLElement) => it.id)
|
||||
|
||||
exists?.forEach((exist: string) => {
|
||||
injectedUIEffects.get(exist)?.call(null)
|
||||
})
|
||||
}
|
||||
|
||||
target.appendChild(el);
|
||||
target.appendChild(el)
|
||||
|
||||
// TODO: How handle events
|
||||
['click', 'focus', 'focusin', 'focusout', 'blur', 'dblclick',
|
||||
'keyup', 'keypress', 'keydown', 'change', 'input'].forEach((type) => {
|
||||
el.addEventListener(type, (e) => {
|
||||
;[
|
||||
'click',
|
||||
'focus',
|
||||
'focusin',
|
||||
'focusout',
|
||||
'blur',
|
||||
'dblclick',
|
||||
'keyup',
|
||||
'keypress',
|
||||
'keydown',
|
||||
'change',
|
||||
'input',
|
||||
].forEach((type) => {
|
||||
el.addEventListener(
|
||||
type,
|
||||
(e) => {
|
||||
const target = e.target! as HTMLElement
|
||||
const trigger = target.closest(`[data-on-${type}]`) as HTMLElement
|
||||
if (!trigger) return
|
||||
|
||||
const msgType = trigger.dataset[`on${ucFirst(type)}`]
|
||||
msgType && pl.caller?.callUserModel(msgType, transformableEvent(trigger, e))
|
||||
}, false)
|
||||
msgType &&
|
||||
pl.caller?.callUserModel(msgType, transformableEvent(trigger, e))
|
||||
},
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
// callback
|
||||
@@ -333,6 +380,12 @@ export function setupInjectedUI (
|
||||
return teardownUI
|
||||
}
|
||||
|
||||
export function cleanInjectedScripts(this: PluginLocal) {
|
||||
const scripts = document.head.querySelectorAll(`script[data-ref=${this.id}]`)
|
||||
|
||||
scripts?.forEach((it) => it.remove())
|
||||
}
|
||||
|
||||
export function transformableEvent(target: HTMLElement, e: Event) {
|
||||
const obj: any = {}
|
||||
|
||||
@@ -340,9 +393,7 @@ export function transformableEvent (target: HTMLElement, e: Event) {
|
||||
const ds = target.dataset
|
||||
const FLAG_RECT = 'rect'
|
||||
|
||||
;['value', 'id', 'className',
|
||||
'dataset', FLAG_RECT
|
||||
].forEach((k) => {
|
||||
;['value', 'id', 'className', 'dataset', FLAG_RECT].forEach((k) => {
|
||||
let v: any
|
||||
|
||||
switch (k) {
|
||||
@@ -389,7 +440,8 @@ export function setupInjectedTheme (url?: string) {
|
||||
|
||||
export function mergeSettingsWithSchema(
|
||||
settings: Record<string, any>,
|
||||
schema: Array<SettingSchemaDesc>) {
|
||||
schema: Array<SettingSchemaDesc>
|
||||
) {
|
||||
const defaults = (schema || []).reduce((a, b) => {
|
||||
if ('default' in b) {
|
||||
a[b.key] = b.default
|
||||
|
||||
67
libs/src/modules/LSPlugin.Experiments.ts
Normal file
67
libs/src/modules/LSPlugin.Experiments.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { LSPluginUser } from '../LSPlugin.user'
|
||||
import { PluginLocal } from '../LSPlugin.core'
|
||||
import { safeSnakeCase } from '../helpers'
|
||||
|
||||
/**
|
||||
* Some experiment features
|
||||
*/
|
||||
export class LSPluginExperiments {
|
||||
constructor(private ctx: LSPluginUser) {}
|
||||
|
||||
get React(): unknown {
|
||||
return this.ensureHostScope().React
|
||||
}
|
||||
|
||||
get ReactDOM(): unknown {
|
||||
return this.ensureHostScope().ReactDOM
|
||||
}
|
||||
|
||||
get pluginLocal(): PluginLocal {
|
||||
return this.ensureHostScope().LSPluginCore.ensurePlugin(
|
||||
this.ctx.baseInfo.id
|
||||
)
|
||||
}
|
||||
|
||||
private invokeExperMethod(type: string, ...args: Array<any>) {
|
||||
const host = this.ensureHostScope()
|
||||
type = safeSnakeCase(type)?.toLowerCase()
|
||||
return host.logseq.api['exper_' + type]?.apply(host, args)
|
||||
}
|
||||
|
||||
async loadScripts(...scripts: Array<string>) {
|
||||
scripts = scripts.map((it) => {
|
||||
if (!it?.startsWith('http')) {
|
||||
return this.ctx.resolveResourceFullUrl(it)
|
||||
}
|
||||
|
||||
return it
|
||||
})
|
||||
|
||||
scripts.unshift(this.ctx.baseInfo.id)
|
||||
await this.invokeExperMethod('loadScripts', ...scripts)
|
||||
}
|
||||
|
||||
registerFencedCodeRenderer(
|
||||
type: string,
|
||||
opts: {
|
||||
edit?: boolean
|
||||
before?: () => Promise<void>
|
||||
subs?: Array<string>
|
||||
render: (props: { content: string }) => any
|
||||
}
|
||||
) {
|
||||
return this.ensureHostScope().logseq.api.exper_register_fenced_code_renderer(
|
||||
this.ctx.baseInfo.id,
|
||||
type,
|
||||
opts
|
||||
)
|
||||
}
|
||||
|
||||
ensureHostScope(): any {
|
||||
if (window === top) {
|
||||
throw new Error('Can not access host scope!')
|
||||
}
|
||||
|
||||
return top
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,7 @@ class LSPluginFileStorage implements IAsyncStorage {
|
||||
/**
|
||||
* @param ctx
|
||||
*/
|
||||
constructor (
|
||||
private ctx: LSPluginUser
|
||||
) {}
|
||||
constructor(private ctx: LSPluginUser) {}
|
||||
|
||||
/**
|
||||
* plugin id
|
||||
@@ -37,7 +35,7 @@ class LSPluginFileStorage implements IAsyncStorage {
|
||||
setItem(key: string, value: string): Promise<void> {
|
||||
return this.ctx.caller.callAsync(`api:call`, {
|
||||
method: 'write-plugin-storage-file',
|
||||
args: [this.ctxId, key, value]
|
||||
args: [this.ctxId, key, value],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -47,7 +45,7 @@ class LSPluginFileStorage implements IAsyncStorage {
|
||||
getItem(key: string): Promise<string | undefined> {
|
||||
return this.ctx.caller.callAsync(`api:call`, {
|
||||
method: 'read-plugin-storage-file',
|
||||
args: [this.ctxId, key]
|
||||
args: [this.ctxId, key],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -57,7 +55,7 @@ class LSPluginFileStorage implements IAsyncStorage {
|
||||
removeItem(key: string): Promise<void> {
|
||||
return this.ctx.caller.call(`api:call`, {
|
||||
method: 'unlink-plugin-storage-file',
|
||||
args: [this.ctxId, key]
|
||||
args: [this.ctxId, key],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -67,7 +65,7 @@ class LSPluginFileStorage implements IAsyncStorage {
|
||||
clear(): Promise<void> {
|
||||
return this.ctx.caller.call(`api:call`, {
|
||||
method: 'clear-plugin-storage-files',
|
||||
args: [this.ctxId]
|
||||
args: [this.ctxId],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -77,11 +75,9 @@ class LSPluginFileStorage implements IAsyncStorage {
|
||||
hasItem(key: string): Promise<boolean> {
|
||||
return this.ctx.caller.callAsync(`api:call`, {
|
||||
method: 'exist-plugin-storage-file',
|
||||
args: [this.ctxId, key]
|
||||
args: [this.ctxId, key],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
LSPluginFileStorage
|
||||
}
|
||||
export { LSPluginFileStorage }
|
||||
|
||||
@@ -27,7 +27,7 @@ export const generateNewMessageId = () => ++_messageId
|
||||
/**
|
||||
* Postmate logging function that enables/disables via config
|
||||
*/
|
||||
export const log = (...args) => Postmate.debug ? console.log(...args) : null
|
||||
export const log = (...args) => (Postmate.debug ? console.log(...args) : null)
|
||||
|
||||
/**
|
||||
* Takes a URL and returns the origin
|
||||
@@ -38,7 +38,11 @@ export const resolveOrigin = (url) => {
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
const protocol = a.protocol.length > 4 ? a.protocol : window.location.protocol
|
||||
const host = a.host.length ? ((a.port === '80' || a.port === '443') ? a.hostname : a.host) : window.location.host
|
||||
const host = a.host.length
|
||||
? a.port === '80' || a.port === '443'
|
||||
? a.hostname
|
||||
: a.host
|
||||
: window.location.host
|
||||
return a.origin || `${protocol}//${host}`
|
||||
}
|
||||
|
||||
@@ -58,15 +62,11 @@ const messageTypes = {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export const sanitize = (message, allowedOrigin) => {
|
||||
if (
|
||||
typeof allowedOrigin === 'string' &&
|
||||
message.origin !== allowedOrigin
|
||||
) return false
|
||||
if (typeof allowedOrigin === 'string' && message.origin !== allowedOrigin)
|
||||
return false
|
||||
if (!message.data) return false
|
||||
if (
|
||||
typeof message.data === 'object' &&
|
||||
!('postmate' in message.data)
|
||||
) return false
|
||||
if (typeof message.data === 'object' && !('postmate' in message.data))
|
||||
return false
|
||||
if (message.data.type !== messageType) return false
|
||||
if (!messageTypes[message.data.postmate]) return false
|
||||
return true
|
||||
@@ -80,8 +80,8 @@ export const sanitize = (message, allowedOrigin) => {
|
||||
* @return {Promise}
|
||||
*/
|
||||
export const resolveValue = (model, property) => {
|
||||
const unwrappedContext = typeof model[property] === 'function'
|
||||
? model[property]() : model[property]
|
||||
const unwrappedContext =
|
||||
typeof model[property] === 'function' ? model[property]() : model[property]
|
||||
return Promise.resolve(unwrappedContext)
|
||||
}
|
||||
|
||||
@@ -114,14 +114,14 @@ export class ParentAPI {
|
||||
/**
|
||||
* the assignments below ensures that e, data, and value are all defined
|
||||
*/
|
||||
const { data, name } = (((e || {}).data || {}).value || {})
|
||||
const { data, name } = ((e || {}).data || {}).value || {}
|
||||
|
||||
if (e.data.postmate === 'emit') {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
log(`Parent: Received event emission: ${name}`)
|
||||
}
|
||||
if (name in this.events) {
|
||||
this.events[name].forEach(callback => {
|
||||
this.events[name].forEach((callback) => {
|
||||
callback.call(this, data)
|
||||
})
|
||||
}
|
||||
@@ -149,23 +149,29 @@ export class ParentAPI {
|
||||
this.parent.addEventListener('message', transact, false)
|
||||
|
||||
// Then ask child for information
|
||||
this.child.postMessage({
|
||||
this.child.postMessage(
|
||||
{
|
||||
postmate: 'request',
|
||||
type: messageType,
|
||||
property,
|
||||
uid,
|
||||
}, this.childOrigin)
|
||||
},
|
||||
this.childOrigin
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
call(property, data) {
|
||||
// Send information to the child
|
||||
this.child.postMessage({
|
||||
this.child.postMessage(
|
||||
{
|
||||
postmate: 'call',
|
||||
type: messageType,
|
||||
property,
|
||||
data,
|
||||
}, this.childOrigin)
|
||||
},
|
||||
this.childOrigin
|
||||
)
|
||||
}
|
||||
|
||||
on(eventName, callback) {
|
||||
@@ -215,22 +221,27 @@ export class ChildAPI {
|
||||
const { property, uid, data } = e.data
|
||||
|
||||
if (e.data.postmate === 'call') {
|
||||
if (property in this.model && typeof this.model[property] === 'function') {
|
||||
if (
|
||||
property in this.model &&
|
||||
typeof this.model[property] === 'function'
|
||||
) {
|
||||
this.model[property](data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Reply to Parent
|
||||
resolveValue(this.model, property)
|
||||
.then(value => {
|
||||
(e.source as WindowProxy).postMessage({
|
||||
resolveValue(this.model, property).then((value) => {
|
||||
;(e.source as WindowProxy).postMessage(
|
||||
{
|
||||
property,
|
||||
postmate: 'reply',
|
||||
type: messageType,
|
||||
uid,
|
||||
value,
|
||||
}, e.origin)
|
||||
},
|
||||
e.origin
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -239,14 +250,17 @@ export class ChildAPI {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
log(`Child: Emitting Event "${name}"`, data)
|
||||
}
|
||||
this.parent.postMessage({
|
||||
this.parent.postMessage(
|
||||
{
|
||||
postmate: 'emit',
|
||||
type: messageType,
|
||||
value: {
|
||||
name,
|
||||
data,
|
||||
},
|
||||
}, this.parentOrigin)
|
||||
},
|
||||
this.parentOrigin
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +297,10 @@ export class Postmate {
|
||||
this.frame = document.createElement('iframe')
|
||||
if (opts.id) this.frame.id = opts.id
|
||||
if (opts.name) this.frame.name = opts.name
|
||||
this.frame.classList.add.apply(this.frame.classList, opts.classListArray || [])
|
||||
this.frame.classList.add.apply(
|
||||
this.frame.classList,
|
||||
opts.classListArray || []
|
||||
)
|
||||
this.container.appendChild(this.frame)
|
||||
this.child = this.frame.contentWindow
|
||||
this.model = opts.model || {}
|
||||
@@ -330,11 +347,14 @@ export class Postmate {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
log(`Parent: Sending handshake attempt ${attempt}`, { childOrigin })
|
||||
}
|
||||
this.child.postMessage({
|
||||
this.child.postMessage(
|
||||
{
|
||||
postmate: 'handshake',
|
||||
type: messageType,
|
||||
model: this.model,
|
||||
}, childOrigin)
|
||||
},
|
||||
childOrigin
|
||||
)
|
||||
|
||||
if (attempt === maxHandshakeRequests) {
|
||||
clearInterval(responseInterval)
|
||||
@@ -394,16 +414,19 @@ export class Model {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
log('Child: Sending handshake reply to Parent')
|
||||
}
|
||||
(e.source as WindowProxy).postMessage({
|
||||
;(e.source as WindowProxy).postMessage(
|
||||
{
|
||||
postmate: 'handshake-reply',
|
||||
type: messageType,
|
||||
}, e.origin)
|
||||
},
|
||||
e.origin
|
||||
)
|
||||
this.parentOrigin = e.origin
|
||||
|
||||
// Extend model with the one provided by the parent
|
||||
const defaults = e.data.model
|
||||
if (defaults) {
|
||||
Object.keys(defaults).forEach(key => {
|
||||
Object.keys(defaults).forEach((key) => {
|
||||
this.model[key] = defaults[key]
|
||||
})
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const pkg = require('./package.json')
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
||||
@@ -20,6 +21,9 @@ module.exports = {
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
LIB_VERSION: JSON.stringify(pkg.version)
|
||||
})
|
||||
// new BundleAnalyzerPlugin()
|
||||
],
|
||||
output: {
|
||||
|
||||
@@ -857,6 +857,16 @@ pkg-dir@^4.2.0:
|
||||
dependencies:
|
||||
find-up "^4.0.0"
|
||||
|
||||
prettier-config-standard@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/prettier-config-standard/-/prettier-config-standard-5.0.0.tgz#c99dbef099412eda0876f75fdc1732ffef2ab0e0"
|
||||
integrity sha512-QK252QwCxlsak8Zx+rPKZU31UdbRcu9iUk9X1ONYtLSO221OgvV9TlKoTf6iPDZtvF3vE2mkgzFIEgSUcGELSQ==
|
||||
|
||||
prettier@^2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032"
|
||||
integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -33,6 +33,13 @@
|
||||
(log-error error))
|
||||
(p/rejected error)))))))
|
||||
|
||||
(defn run-git2!
|
||||
[commands]
|
||||
(when-let [path (state/get-graph-path)]
|
||||
(when (fs/existsSync path)
|
||||
(p/let [^js result (.exec GitProcess commands path)]
|
||||
result))))
|
||||
|
||||
(defn git-dir-exists?
|
||||
[]
|
||||
(try
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
["path" :as path]
|
||||
["os" :as os]
|
||||
["diff-match-patch" :as google-diff]
|
||||
["/electron/utils" :as js-utils]
|
||||
[electron.fs-watcher :as watcher]
|
||||
[electron.configs :as cfgs]
|
||||
[promesa.core :as p]
|
||||
@@ -336,6 +337,11 @@
|
||||
(defmethod handle :getAppBaseInfo [^js win [_ _opts]]
|
||||
{:isFullScreen (.isFullScreen win)})
|
||||
|
||||
(defmethod handle :getAssetsFiles [^js win [_ {:keys [exts]}]]
|
||||
(when-let [graph-path (state/get-window-graph-path win)]
|
||||
(p/let [^js files (js-utils/getAllFiles (.join path graph-path "assets") (clj->js exts))]
|
||||
files)))
|
||||
|
||||
(defn close-watcher-when-orphaned!
|
||||
"When it's the last window for the directory, close the watcher."
|
||||
[window graph-path]
|
||||
@@ -358,6 +364,11 @@
|
||||
(when (seq args)
|
||||
(git/raw! args)))
|
||||
|
||||
(defmethod handle :runGitWithinCurrentGraph [_ [_ args]]
|
||||
(when (seq args)
|
||||
(git/init!)
|
||||
(git/run-git2! (clj->js args))))
|
||||
|
||||
(defmethod handle :gitCommitAll [_ [_ message]]
|
||||
(git/add-all-and-commit! message))
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
(defn fetch-latest-release-asset
|
||||
[{:keys [repo theme]}]
|
||||
(p/catch
|
||||
(p/let [api #(str "https://api.github.com/repos/" repo "/" %)
|
||||
(p/let [repo (some-> repo (string/trim) (string/replace #"^/+(.+?)/+$" "$1"))
|
||||
api #(str "https://api.github.com/repos/" repo "/" %)
|
||||
endpoint (api "releases/latest")
|
||||
^js res (fetch endpoint)
|
||||
res (.json res)
|
||||
@@ -47,14 +48,15 @@
|
||||
(:body res)])
|
||||
|
||||
(fn [^js e]
|
||||
(emit :lsp-installed {:status :error :payload e})
|
||||
(throw (js/Error. :release-network-issue)))))
|
||||
(debug e)
|
||||
(throw (js/Error. [:release-channel-issue (.-message e)])))))
|
||||
|
||||
(defn download-asset-zip
|
||||
[{:keys [id repo title author description effect sponsors]} dl-url dl-version dot-extract-to]
|
||||
(p/catch
|
||||
(p/let [^js res (fetch dl-url {:timeout 30000})
|
||||
_ (when-not (.-ok res) (throw (js/Error. :download-network-issue)))
|
||||
_ (when-not (.-ok res)
|
||||
(throw (js/Error. [:download-channel-issue (.-statusText res)])))
|
||||
frm-zip (p/create
|
||||
(fn [resolve1 reject1]
|
||||
(let [body (.-body res)
|
||||
@@ -155,7 +157,7 @@
|
||||
|
||||
_ (when-not dl-url
|
||||
(debug "[Download URL Error]" asset)
|
||||
(throw (js/Error. :release-asset-not-found)))
|
||||
(throw (js/Error. [:release-asset-not-found (js/JSON.stringify asset)])))
|
||||
|
||||
dest (.join path cfgs/dot-root "plugins" (:id item))
|
||||
_ (when-not only-check (download-asset-zip item dl-url latest-version dest))
|
||||
@@ -175,7 +177,8 @@
|
||||
(emit :lsp-installed
|
||||
{:status :error
|
||||
:only-check only-check
|
||||
:payload (assoc item :error-code (.-message e))}))
|
||||
:payload (assoc item :error-code (.-message e))})
|
||||
(debug e))
|
||||
(resolve nil)))))
|
||||
|
||||
(p/finally
|
||||
|
||||
@@ -1,18 +1,56 @@
|
||||
const path = require('path')
|
||||
const { readdir, lstat } = require('fs').promises
|
||||
|
||||
// workaround from https://github.com/electron/electron/issues/426#issuecomment-658901422
|
||||
// We set an intercept on incoming requests to disable x-frame-options
|
||||
// headers.
|
||||
|
||||
export const disableXFrameOptions = (win) => {
|
||||
win.webContents.session.webRequest.onHeadersReceived({ urls: [ "*://*/*" ] },
|
||||
win.webContents.session.webRequest.onHeadersReceived({ urls: ['*://*/*'] },
|
||||
(d, c) => {
|
||||
if (d.responseHeaders['X-Frame-Options']) {
|
||||
delete d.responseHeaders['X-Frame-Options'];
|
||||
delete d.responseHeaders['X-Frame-Options']
|
||||
} else if (d.responseHeaders['x-frame-options']) {
|
||||
delete d.responseHeaders['x-frame-options'];
|
||||
delete d.responseHeaders['x-frame-options']
|
||||
}
|
||||
|
||||
c({cancel: false, responseHeaders: d.responseHeaders});
|
||||
c({ cancel: false, responseHeaders: d.responseHeaders })
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
export async function getAllFiles (dir, exts) {
|
||||
const dirents = await readdir(dir, { withFileTypes: true })
|
||||
|
||||
if (exts) {
|
||||
!Array.isArray(exts) && (exts = [exts])
|
||||
|
||||
exts = exts.map(it => {
|
||||
if (it && !it.startsWith('.')) {
|
||||
it = '.' + it
|
||||
}
|
||||
|
||||
return it?.toLowerCase()
|
||||
})
|
||||
}
|
||||
|
||||
const files = await Promise.all(dirents.map(async (dirent) => {
|
||||
if (exts && !exts.includes(path.extname(dirent.name))) {
|
||||
return null
|
||||
}
|
||||
|
||||
const filePath = path.resolve(dir, dirent.name)
|
||||
const fileStats = await lstat(filePath)
|
||||
const stats = {
|
||||
size: fileStats.size,
|
||||
accessTime: fileStats.atimeMs,
|
||||
modifiedTime: fileStats.mtimeMs,
|
||||
changeTime: fileStats.ctimeMs,
|
||||
birthTime: fileStats.birthtimeMs
|
||||
}
|
||||
return dirent.isDirectory() ? getAllFiles(filePath) : {
|
||||
path: filePath, ...stats
|
||||
}
|
||||
}))
|
||||
return files.flat().filter(it => it != null)
|
||||
}
|
||||
|
||||
@@ -2880,7 +2880,10 @@
|
||||
[:sup.fn (str name "↩︎")]])]])
|
||||
|
||||
["Src" options]
|
||||
(src-cp config options html-export?)
|
||||
[:div.cp__fenced-code-block
|
||||
(if-let [opts (plugin-handler/hook-fenced-code-by-type (util/safe-lower-case (:language options)))]
|
||||
(plugins/hook-ui-fenced-code (string/join "" (:lines options)) opts)
|
||||
(src-cp config options html-export?))]
|
||||
|
||||
:else
|
||||
"")
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
.block-content-wrapper {
|
||||
/* 38px is the width of block-control */
|
||||
width: calc(100% - 22px);
|
||||
|
||||
@screen sm {
|
||||
width: calc(100% - 33px);
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +185,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
html.is-mobile,
|
||||
html.is-native-iphone,
|
||||
html.is-native-android {
|
||||
.references .block-control {
|
||||
margin-left: -20px;
|
||||
}
|
||||
}
|
||||
|
||||
.block-ref {
|
||||
border-bottom: 0.5px solid;
|
||||
border-bottom-color: var(--ls-block-ref-link-text-color);
|
||||
@@ -357,22 +367,27 @@
|
||||
.document-mode .editor-inner .h1 {
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
.document-mode .ls-block h2,
|
||||
.document-mode .editor-inner .h2 {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
|
||||
.document-mode .ls-block h3,
|
||||
.document-mode .editor-inner .h3 {
|
||||
margin: 0.83em 0;
|
||||
}
|
||||
|
||||
.document-mode .ls-block h4,
|
||||
.document-mode .editor-inner .h4 {
|
||||
margin: 1.12em 0;
|
||||
}
|
||||
|
||||
.document-mode .ls-block h5,
|
||||
.document-mode .editor-inner .h5 {
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
.document-mode .ls-block h6,
|
||||
.document-mode .editor-inner .h6 {
|
||||
margin: 1.67em 0;
|
||||
@@ -558,6 +573,12 @@ a.cloze-revealed {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.cp__fenced-code-block {
|
||||
.not-edit {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
html.is-native-ios {
|
||||
audio {
|
||||
width: 300px;
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
[page]
|
||||
(let [namespaces (get-relation page)]
|
||||
(when (seq namespaces)
|
||||
[:div.page-hierachy.mt-6
|
||||
[:div.page-hierarchy.mt-6
|
||||
(ui/foldable
|
||||
[:h2.font-bold.opacity-30 "Hierarchy"]
|
||||
[:ul.namespaces {:style {:margin "12px 24px"}}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
[frontend.components.plugins-settings :as plugins-settings]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.handler.plugin :as plugin-handler]
|
||||
[frontend.handler.page :as page-handler]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(rum/defcs installed-themes
|
||||
@@ -234,8 +233,7 @@
|
||||
|
||||
(ui/toggle (not disabled?)
|
||||
(fn []
|
||||
(js-invoke js/LSPluginCore (if disabled? "enable" "disable") id)
|
||||
(page-handler/init-commands!))
|
||||
(js-invoke js/LSPluginCore (if disabled? "enable" "disable") id))
|
||||
true)]])
|
||||
|
||||
(rum/defc plugin-item-card < rum/static
|
||||
@@ -316,10 +314,12 @@
|
||||
[:input.form-input.is-small
|
||||
{:placeholder "Search plugins"
|
||||
:ref *search-ref
|
||||
:auto-focus true
|
||||
:on-key-down (fn [^js e]
|
||||
(when (= 27 (.-keyCode e))
|
||||
(when-not (string/blank? search-key)
|
||||
(util/stop e)
|
||||
(if (string/blank? search-key)
|
||||
(some-> (js/document.querySelector ".cp__plugins-page") (.focus))
|
||||
(reset! *search-key nil))))
|
||||
:on-change #(let [^js target (.-target %)]
|
||||
(reset! *search-key (util/trim-safe (.-value target))))
|
||||
@@ -726,10 +726,7 @@
|
||||
[:span])))
|
||||
|
||||
(rum/defcs hook-ui-items < rum/reactive
|
||||
"type
|
||||
- :toolbar
|
||||
- :pagebar
|
||||
"
|
||||
"type: :toolbar, :pagebar"
|
||||
[_state type]
|
||||
(when (state/sub [:plugin/installed-ui-items])
|
||||
(let [items (state/get-plugins-ui-items-with-type type)]
|
||||
@@ -739,6 +736,15 @@
|
||||
(for [[_ {:keys [key] :as opts} pid] items]
|
||||
(rum/with-key (ui-item-renderer pid type opts) key))]))))
|
||||
|
||||
(rum/defcs hook-ui-fenced-code < rum/reactive
|
||||
[_state content {:keys [render edit] :as _opts}]
|
||||
|
||||
[:div
|
||||
{:on-mouse-down (fn [e] (when (false? edit) (util/stop e)))
|
||||
:class (util/classnames [{:not-edit (false? edit)}])}
|
||||
(when (fn? render)
|
||||
(js/React.createElement render #js {:content content}))])
|
||||
|
||||
(rum/defc plugins-page
|
||||
[]
|
||||
|
||||
@@ -746,11 +752,6 @@
|
||||
market? (= active :marketplace)
|
||||
*el-ref (rum/create-ref)]
|
||||
|
||||
(rum/use-effect!
|
||||
#(let [^js el (rum/deref *el-ref)]
|
||||
(js/setTimeout (fn [] (.focus el)) 100))
|
||||
[])
|
||||
|
||||
[:div.cp__plugins-page
|
||||
{:ref *el-ref
|
||||
:tab-index "-1"}
|
||||
@@ -775,10 +776,13 @@
|
||||
|
||||
(rum/defcs focused-settings-content
|
||||
< rum/reactive
|
||||
(rum/local (state/sub :plugin/focused-settings) ::cache)
|
||||
[_state title]
|
||||
(let [focused (state/sub :plugin/focused-settings)
|
||||
(let [*cache (::cache _state)
|
||||
focused (state/sub :plugin/focused-settings)
|
||||
nav? (state/sub :plugin/navs-settings?)
|
||||
_ (state/sub :plugin/installed-plugins)]
|
||||
_ (state/sub :plugin/installed-plugins)
|
||||
_ (js/setTimeout #(reset! *cache focused) 100)]
|
||||
|
||||
[:div.cp__plugins-settings.cp__settings-main
|
||||
[:header
|
||||
@@ -802,7 +806,8 @@
|
||||
|
||||
[:article
|
||||
[:div.panel-wrap
|
||||
(when-let [^js pl (and focused (plugin-handler/get-plugin-inst focused))]
|
||||
(when-let [^js pl (and focused (= @*cache focused)
|
||||
(plugin-handler/get-plugin-inst focused))]
|
||||
(ui/catch-error
|
||||
[:p.warning.text-lg.mt-5 "Settings schema Error!"]
|
||||
(plugins-settings/settings-container
|
||||
|
||||
@@ -720,7 +720,6 @@
|
||||
|
||||
&.is-dragging {
|
||||
/*height: var(--ls-draggable-handle-height) !important;*/
|
||||
overflow: hidden;
|
||||
opacity: .7;
|
||||
|
||||
> .draggable-handle {
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
[frontend.util :as util]
|
||||
[frontend.ui :as ui]
|
||||
[frontend.handler.plugin :as plugin-handler]
|
||||
[cljs-bean.core :as bean]))
|
||||
[cljs-bean.core :as bean]
|
||||
[goog.functions :refer [debounce]]))
|
||||
|
||||
(rum/defc edit-settings-file
|
||||
[pid {:keys [class]}]
|
||||
@@ -26,8 +27,8 @@
|
||||
[:input
|
||||
{:class (util/classnames [{:form-input (not (contains? #{:color :range} input-as))}])
|
||||
:type (name input-as)
|
||||
:value (or val default)
|
||||
:on-change #(update-setting! key (util/evalue %))}])]])
|
||||
:defaultValue (or val default)
|
||||
:on-change (debounce #(update-setting! key (util/evalue %)) 1000)}])]])
|
||||
|
||||
(rum/defc render-item-toggle
|
||||
[val {:keys [key title description default]} update-setting!]
|
||||
@@ -77,12 +78,11 @@
|
||||
[schema ^js pl]
|
||||
(let [^js _settings (.-settings pl)
|
||||
pid (.-id pl)
|
||||
[settings, set-settings] (rum/use-state nil)
|
||||
[settings, set-settings] (rum/use-state (bean/->clj (.toJSON _settings)))
|
||||
update-setting! (fn [k v] (.set _settings (name k) (bean/->js v)))]
|
||||
|
||||
(rum/use-effect!
|
||||
(fn []
|
||||
(set-settings (bean/->clj (.toJSON _settings)))
|
||||
(let [on-change (fn [^js s]
|
||||
(when-let [s (bean/->clj s)]
|
||||
(set-settings s)))]
|
||||
|
||||
@@ -69,9 +69,15 @@
|
||||
flex: 1;
|
||||
padding: 0 12px 12px;
|
||||
max-height: 70vh;
|
||||
min-height: 380px;
|
||||
width: auto;
|
||||
overflow: auto;
|
||||
margin-right: -17px;
|
||||
margin-bottom: -17px;
|
||||
|
||||
@screen md {
|
||||
width: 680px;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-aside {
|
||||
|
||||
@@ -325,9 +325,12 @@
|
||||
|
||||
(when-let [coming (and (not downloading?)
|
||||
(get-in @state/state [:plugin/updates-coming id]))]
|
||||
(let [error-code (:error-code coming)
|
||||
error-code (if (= error-code (str :no-new-version)) nil error-code)]
|
||||
(when (or pending? (not error-code))
|
||||
(notification/show!
|
||||
(str "Checked: " (:title coming))
|
||||
:success))
|
||||
(str "[Checked]<" (:title coming) "> " error-code)
|
||||
(if error-code :error :success)))))
|
||||
|
||||
(if (and updated? downloading?)
|
||||
;; try to start consume downloading item
|
||||
@@ -348,6 +351,13 @@
|
||||
(when (and pending? (seq (state/all-available-coming-updates)))
|
||||
(plugin/open-waiting-updates-modal!))))))
|
||||
|
||||
(defmethod handle :plugin/hook-db-tx [[_ {:keys [blocks tx-data tx-meta] :as payload}]]
|
||||
(when-let [payload (and (seq blocks)
|
||||
(merge payload {:tx-data (map #(into [] %) tx-data)
|
||||
:tx-meta (dissoc tx-meta :editor-cursor)}))]
|
||||
(plugin-handler/hook-plugin-db :changed payload)
|
||||
(plugin-handler/hook-plugin-block-changes payload)))
|
||||
|
||||
(defmethod handle :backup/broken-config [[_ repo content]]
|
||||
(when (and repo content)
|
||||
(let [path (config/get-config-path)
|
||||
@@ -367,6 +377,9 @@
|
||||
:path
|
||||
js/decodeURI)))
|
||||
|
||||
(defmethod handle :rebuild-slash-commands-list [[_]]
|
||||
(page-handler/rebuild-slash-commands-list!))
|
||||
|
||||
(defn run!
|
||||
[]
|
||||
(let [chan (state/get-events-chan)]
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
|
||||
(defn show!
|
||||
([content status]
|
||||
(show! content status true nil))
|
||||
(show! content status true nil 1500))
|
||||
([content status clear?]
|
||||
(show! content status clear? nil))
|
||||
(show! content status clear? nil 1500))
|
||||
([content status clear? uid]
|
||||
(show! content status clear? uid 1500))
|
||||
([content status clear? uid timeout]
|
||||
(let [contents (state/get-notification-contents)
|
||||
uid (or uid (keyword (util/unique-id)))]
|
||||
(state/set-state! :notification/contents (assoc contents
|
||||
@@ -20,6 +22,6 @@
|
||||
:status status}))
|
||||
|
||||
(when (and clear? (not= status :error))
|
||||
(js/setTimeout #(clear! uid) 1500))
|
||||
(js/setTimeout #(clear! uid) (or timeout 1500)))
|
||||
|
||||
uid)))
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
[goog.object :as gobj]
|
||||
[lambdaisland.glogi :as log]
|
||||
[promesa.core :as p]
|
||||
[frontend.mobile.util :as mobile-util]))
|
||||
[frontend.mobile.util :as mobile-util]
|
||||
[goog.functions :refer [debounce]]))
|
||||
|
||||
(defn- get-directory
|
||||
[journal?]
|
||||
@@ -598,6 +599,9 @@
|
||||
[]
|
||||
(commands/init-commands! get-page-ref-text))
|
||||
|
||||
(def rebuild-slash-commands-list!
|
||||
(debounce init-commands! 1500))
|
||||
|
||||
(defn template-exists?
|
||||
[title]
|
||||
(when title
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
(:require [promesa.core :as p]
|
||||
[rum.core :as rum]
|
||||
[frontend.util :as util]
|
||||
[clojure.walk :as walk]
|
||||
[frontend.format.mldoc :as mldoc]
|
||||
[frontend.handler.notification :as notifications]
|
||||
[camel-snake-kebab.core :as csk]
|
||||
@@ -19,6 +20,16 @@
|
||||
(and (util/electron?)
|
||||
(state/lsp-enabled?-or-theme)))
|
||||
|
||||
(defn- normalize-keyword-for-json
|
||||
[input]
|
||||
(when input
|
||||
(walk/postwalk
|
||||
(fn [a]
|
||||
(cond
|
||||
(keyword? a) (csk/->camelCase (name a))
|
||||
(uuid? a) (str a)
|
||||
:else a)) input)))
|
||||
|
||||
(defn invoke-exported-api
|
||||
[type & args]
|
||||
(try
|
||||
@@ -84,7 +95,7 @@
|
||||
(p/create
|
||||
(fn [resolve]
|
||||
(state/set-state! :plugin/installing mft)
|
||||
(ipc/ipc "installMarketPlugin" mft)
|
||||
(ipc/ipc :installMarketPlugin mft)
|
||||
(resolve id)))))
|
||||
|
||||
(defn check-or-update-marketplace-plugin
|
||||
@@ -100,20 +111,21 @@
|
||||
(state/reset-all-updates-state)
|
||||
(throw e))))
|
||||
(fn [mfts]
|
||||
(if-let [mft (some #(when (= (:id %) id) %) mfts)]
|
||||
(ipc/ipc "updateMarketPlugin" (merge (dissoc pkg :logger) mft))
|
||||
(throw (js/Error. (str ":not-found-in-marketplace" id))))
|
||||
|
||||
(let [mft (some #(when (= (:id %) id) %) mfts)]
|
||||
;;TODO: (throw (js/Error. [:not-found-in-marketplace id]))
|
||||
(ipc/ipc :updateMarketPlugin (merge (dissoc pkg :logger) mft)))
|
||||
true))
|
||||
|
||||
(fn [^js e]
|
||||
(error-handler "Update Error: remote error")
|
||||
(error-handler e)
|
||||
(state/set-state! :plugin/installing nil)
|
||||
(js/console.error e)))))
|
||||
|
||||
(defn get-plugin-inst
|
||||
[id]
|
||||
(try
|
||||
(js/LSPluginCore.ensurePlugin id)
|
||||
(js/LSPluginCore.ensurePlugin (name id))
|
||||
(catch js/Error _e
|
||||
nil)))
|
||||
|
||||
@@ -175,7 +187,7 @@
|
||||
(str (t :plugin/installed) (t :plugins) ": " name) :success)))))
|
||||
|
||||
:error
|
||||
(let [error-code (keyword (string/replace (:error-code payload) #"^[\s\:]+" ""))
|
||||
(let [error-code (keyword (string/replace (:error-code payload) #"^[\s\:\[]+" ""))
|
||||
[msg type] (case error-code
|
||||
|
||||
:no-new-version
|
||||
@@ -195,7 +207,8 @@
|
||||
;; notify human tips
|
||||
(notifications/show!
|
||||
(str
|
||||
(if (= :error type) "[Install Error]" "")
|
||||
(if (= :error type) "[Error]" "")
|
||||
(str "<" (:id payload) "> ")
|
||||
msg) type)))
|
||||
|
||||
(js/console.error payload))
|
||||
@@ -228,13 +241,15 @@
|
||||
[pid [cmd actions]]
|
||||
(when-let [pid (keyword pid)]
|
||||
(when (contains? (:plugin/installed-plugins @state/state) pid)
|
||||
(swap! state/state update-in [:plugin/installed-commands pid]
|
||||
(swap! state/state update-in [:plugin/installed-slash-commands pid]
|
||||
(fnil merge {}) (hash-map cmd (mapv #(conj % {:pid pid}) actions)))
|
||||
(state/pub-event! [:rebuild-slash-commands-list])
|
||||
true)))
|
||||
|
||||
(defn unregister-plugin-slash-command
|
||||
[pid]
|
||||
(swap! state/state medley/dissoc-in [:plugin/installed-commands (keyword pid)]))
|
||||
(swap! state/state medley/dissoc-in [:plugin/installed-slash-commands (keyword pid)])
|
||||
(state/pub-event! [:rebuild-slash-commands-list]))
|
||||
|
||||
(def keybinding-mode-handler-map
|
||||
{:global :shortcut.handler/editor-global
|
||||
@@ -292,11 +307,43 @@
|
||||
[pid]
|
||||
(swap! state/state assoc-in [:plugin/installed-ui-items (keyword pid)] []))
|
||||
|
||||
(defn register-plugin-resources
|
||||
[pid type {:keys [key] :as opts}]
|
||||
(when-let [pid (keyword pid)]
|
||||
(when-let [type (and key (keyword type))]
|
||||
(let [path [:plugin/installed-resources pid type]]
|
||||
(when (contains? #{:error nil} (get-in @state/state (conj path key)))
|
||||
(swap! state/state update-in path
|
||||
(fnil assoc {}) key (merge opts {:pid pid}))
|
||||
true)))))
|
||||
|
||||
(defn unregister-plugin-resources
|
||||
[pid]
|
||||
(when-let [pid (keyword pid)]
|
||||
(swap! state/state medley/dissoc-in [:plugin/installed-resources pid])
|
||||
true))
|
||||
|
||||
(defn unregister-plugin-themes
|
||||
([pid] (unregister-plugin-themes pid true))
|
||||
([pid effect]
|
||||
(js/LSPluginCore.unregisterTheme (name pid) effect)))
|
||||
|
||||
(def *fenced-code-providers (atom #{}))
|
||||
|
||||
(defn register_fenced_code_renderer
|
||||
[pid type {:keys [before subs render edit] :as _opts}]
|
||||
(when-let [key (and type (keyword type))]
|
||||
(register-plugin-resources pid :fenced-code-renderers
|
||||
{:key key :edit edit :before before :subs subs :render render})
|
||||
(swap! *fenced-code-providers conj pid)
|
||||
#(swap! *fenced-code-providers disj pid)))
|
||||
|
||||
(defn hook-fenced-code-by-type
|
||||
[type]
|
||||
(when-let [key (and (seq @*fenced-code-providers) type (keyword type))]
|
||||
(first (map #(state/get-plugin-resource % :fenced-code-renderers key)
|
||||
@*fenced-code-providers))))
|
||||
|
||||
(defn select-a-plugin-theme
|
||||
[pid]
|
||||
(when-let [themes (get (group-by :pid (:plugin/installed-themes @state/state)) pid)]
|
||||
@@ -374,13 +421,16 @@
|
||||
(defn hook-plugin
|
||||
[tag type payload plugin-id]
|
||||
(when lsp-enabled?
|
||||
(try
|
||||
(js-invoke js/LSPluginCore
|
||||
(str "hook" (string/capitalize (name tag)))
|
||||
(name type)
|
||||
(if (coll? payload)
|
||||
(bean/->js (into {} (for [[k v] payload] [(csk/->camelCase k) (if (uuid? v) (str v) v)])))
|
||||
(bean/->js (normalize-keyword-for-json payload))
|
||||
payload)
|
||||
(if (keyword? plugin-id) (name plugin-id) plugin-id))))
|
||||
(if (keyword? plugin-id) (name plugin-id) plugin-id))
|
||||
(catch js/Error e
|
||||
(js/console.error "[Hook Plugin Err]" e)))))
|
||||
|
||||
(defn hook-plugin-app
|
||||
([type payload] (hook-plugin-app type payload nil))
|
||||
@@ -390,6 +440,18 @@
|
||||
([type payload] (hook-plugin-editor type payload nil))
|
||||
([type payload plugin-id] (hook-plugin :editor type payload plugin-id)))
|
||||
|
||||
(defn hook-plugin-db
|
||||
([type payload] (hook-plugin-db type payload nil))
|
||||
([type payload plugin-id] (hook-plugin :db type payload plugin-id)))
|
||||
|
||||
(defn hook-plugin-block-changes
|
||||
[{:keys [blocks tx-data tx-meta]}]
|
||||
|
||||
(doseq [b blocks
|
||||
:let [tx-data' (group-by first tx-data)
|
||||
type (str "block:" (:block/uuid b))]]
|
||||
(hook-plugin-db type {:block b :tx-data (get tx-data' (:db/id b)) :tx-meta tx-meta})))
|
||||
|
||||
(defn get-ls-dotdir-root
|
||||
[]
|
||||
(ipc/ipc "getLogseqDotDirRoot"))
|
||||
@@ -480,7 +542,9 @@
|
||||
;; commands
|
||||
(unregister-plugin-slash-command pid)
|
||||
(invoke-exported-api "unregister_plugin_simple_command" pid)
|
||||
(unregister-plugin-ui-items pid))
|
||||
(invoke-exported-api "uninstall_plugin_hook" pid)
|
||||
(unregister-plugin-ui-items pid)
|
||||
(unregister-plugin-resources pid))
|
||||
|
||||
_ (doto js/LSPluginCore
|
||||
(.on "registered"
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
[command]
|
||||
(ipc/ipc "runGit" command))
|
||||
|
||||
(defn run-git-command2!
|
||||
[command]
|
||||
(ipc/ipc "runGitWithinCurrentGraph" command))
|
||||
|
||||
;; TODO: export to pdf/html/word
|
||||
(defn run-pandoc-command!
|
||||
[command]
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
(ns frontend.loader
|
||||
(:require [goog.net.jsloader :as jsloader]
|
||||
[goog.html.legacyconversions :as conv]))
|
||||
[goog.html.legacyconversions :as conv]
|
||||
[cljs-bean.core :as bean]))
|
||||
|
||||
(defn load [url ok-handler]
|
||||
(let [loader (jsloader/safeLoad (conv/trustedResourceUrlFromString (str url)))]
|
||||
(.addCallback ^goog.net.jsloader loader ok-handler)))
|
||||
(defn load
|
||||
([url ok-handler] (load url ok-handler nil))
|
||||
([url ok-handler opts]
|
||||
(let [loader (jsloader/safeLoad
|
||||
(conv/trustedResourceUrlFromString (str url))
|
||||
(bean/->js opts))]
|
||||
(.addCallback ^goog.net.jsloader loader ok-handler))))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns frontend.modules.outliner.pipeline
|
||||
(:require [frontend.modules.datascript-report.core :as ds-report]
|
||||
[frontend.modules.outliner.file :as file]))
|
||||
[frontend.modules.outliner.file :as file]
|
||||
[frontend.state :as state]))
|
||||
|
||||
(defn updated-page-hook
|
||||
[page]
|
||||
@@ -8,7 +9,12 @@
|
||||
|
||||
(defn invoke-hooks
|
||||
[tx-report]
|
||||
(let [{:keys [pages]} (ds-report/get-blocks-and-pages tx-report)]
|
||||
(let [{:keys [pages blocks]} (ds-report/get-blocks-and-pages tx-report)]
|
||||
(doseq [p (seq pages)] (updated-page-hook p))
|
||||
(when (and state/lsp-enabled? (seq blocks))
|
||||
(state/pub-event! [:plugin/hook-db-tx
|
||||
{:blocks blocks
|
||||
:tx-data (:tx-data tx-report)
|
||||
:tx-meta (:tx-meta tx-report)}]))
|
||||
;; TODO: Add blocks to hooks
|
||||
#_(doseq [b (seq blocks)])))
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
:notification/show? false
|
||||
:notification/content nil
|
||||
:repo/cloning? false
|
||||
;; :repo/loading-files? is only for github repos
|
||||
;; :repo/loading-files? is only for GitHub repos
|
||||
:repo/loading-files? {}
|
||||
:repo/changed-files nil
|
||||
:nfs/user-granted? {}
|
||||
@@ -160,8 +160,10 @@
|
||||
:plugin/indicator-text nil
|
||||
:plugin/installed-plugins {}
|
||||
:plugin/installed-themes []
|
||||
:plugin/installed-commands {}
|
||||
:plugin/installed-slash-commands {}
|
||||
:plugin/installed-ui-items {}
|
||||
:plugin/installed-resources {}
|
||||
:plugin/installed-hooks {}
|
||||
:plugin/simple-commands {}
|
||||
:plugin/selected-theme nil
|
||||
:plugin/selected-unpacked-pkg nil
|
||||
@@ -1294,7 +1296,7 @@
|
||||
|
||||
(defn get-plugins-commands
|
||||
[]
|
||||
(mapcat seq (flatten (vals (:plugin/installed-commands @state)))))
|
||||
(mapcat seq (flatten (vals (:plugin/installed-slash-commands @state)))))
|
||||
|
||||
(defn get-plugins-commands-with-type
|
||||
[type]
|
||||
@@ -1306,6 +1308,43 @@
|
||||
(filterv #(= (keyword (first %)) (keyword type))
|
||||
(apply concat (vals (:plugin/installed-ui-items @state)))))
|
||||
|
||||
(defn get-plugin-resources-with-type
|
||||
[pid type]
|
||||
(when-let [pid (and type (keyword pid))]
|
||||
(get-in @state [:plugin/installed-resources pid (keyword type)])))
|
||||
|
||||
(defn get-plugin-resource
|
||||
[pid type key]
|
||||
(when-let [resources (get-plugin-resources-with-type pid type)]
|
||||
(get resources key)))
|
||||
|
||||
(defn upt-plugin-resource
|
||||
[pid type key attr val]
|
||||
(when-let [resource (get-plugin-resource pid type key)]
|
||||
(let [resource (assoc resource (keyword attr) val)]
|
||||
(set-state!
|
||||
[:plugin/installed-resources (keyword pid) (keyword type) key] resource)
|
||||
resource)))
|
||||
|
||||
(defn install-plugin-hook
|
||||
[pid hook]
|
||||
(when-let [pid (keyword pid)]
|
||||
(set-state!
|
||||
[:plugin/installed-hooks hook]
|
||||
(conj
|
||||
((fnil identity #{}) (get-in @state [:plugin/installed-hooks hook]))
|
||||
pid)) true))
|
||||
|
||||
(defn uninstall-plugin-hook
|
||||
[pid hook-or-all]
|
||||
(when-let [pid (keyword pid)]
|
||||
(if (nil? hook-or-all)
|
||||
(swap! state update :plugin/installed-hooks #(medley/map-vals (fn [ids] (disj ids pid)) %))
|
||||
(when-let [coll (get-in @state [:plugin/installed-hooks hook-or-all])]
|
||||
(set-state! [:plugin/installed-hooks hook-or-all] (disj coll pid))))
|
||||
true))
|
||||
|
||||
|
||||
(defn get-scheduled-future-days
|
||||
[]
|
||||
(let [days (:scheduled/future-days (get-config))]
|
||||
@@ -1594,6 +1633,9 @@
|
||||
[]
|
||||
(:plugin/enabled @state))
|
||||
|
||||
(def lsp-enabled?
|
||||
(lsp-enabled?-or-theme))
|
||||
|
||||
(defn consume-updates-coming-plugin
|
||||
[payload updated?]
|
||||
(when-let [id (keyword (:id payload))]
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
[frontend.state :as state]
|
||||
[frontend.util :as util]
|
||||
[frontend.util.cursor :as cursor]
|
||||
[frontend.loader :as loader]
|
||||
[goog.dom :as gdom]
|
||||
[lambdaisland.glogi :as log]
|
||||
[medley.core :as medley]
|
||||
@@ -56,6 +57,20 @@
|
||||
(catch js/Error e
|
||||
(js/console.error "[parse hiccup error]" e) input))))
|
||||
|
||||
(defn ^:export install-plugin-hook
|
||||
[pid hook]
|
||||
(state/install-plugin-hook pid hook))
|
||||
|
||||
(defn ^:export uninstall-plugin-hook
|
||||
[pid hook-or-all]
|
||||
(state/uninstall-plugin-hook pid hook-or-all))
|
||||
|
||||
(defn ^:export should-exec-plugin-hook
|
||||
[pid hook]
|
||||
(let [hooks (:plugin/installed-hooks @state/state)]
|
||||
(or (nil? (seq hooks))
|
||||
(contains? (get hooks hook) (keyword pid)))))
|
||||
|
||||
;; base
|
||||
(defn ^:export get_state_from_store
|
||||
[^js path]
|
||||
@@ -78,6 +93,7 @@
|
||||
:preferred-date-format (state/get-date-formatter)
|
||||
:preferred-start-of-week (state/get-start-of-week)
|
||||
:current-graph (state/get-current-repo)
|
||||
:show-brackets (state/show-brackets?)
|
||||
:me (state/get-me)}))))
|
||||
|
||||
(def ^:export get_current_graph
|
||||
@@ -234,19 +250,22 @@
|
||||
cmd (assoc cmd :key (string/replace (:key cmd) ":" "-"))
|
||||
key (:key cmd)
|
||||
keybinding (:keybinding cmd)
|
||||
palette-cmd (and palette? (plugin-handler/simple-cmd->palette-cmd pid cmd action))]
|
||||
palette-cmd (and palette? (plugin-handler/simple-cmd->palette-cmd pid cmd action))
|
||||
action' #(state/pub-event! [:exec-plugin-cmd {:type type :key key :pid pid :cmd cmd :action action}])]
|
||||
|
||||
;; handle simple commands
|
||||
(plugin-handler/register-plugin-simple-command pid cmd action)
|
||||
|
||||
;; handle palette commands
|
||||
(when palette-cmd
|
||||
(when palette?
|
||||
(palette-handler/register palette-cmd))
|
||||
|
||||
;; handle keybinding commands
|
||||
(when-let [shortcut-args (and palette-cmd keybinding
|
||||
(plugin-handler/simple-cmd-keybinding->shortcut-args pid key keybinding))]
|
||||
(let [dispatch-cmd (fn [_ _e] (palette-handler/invoke-command palette-cmd))
|
||||
(when-let [shortcut-args (and keybinding (plugin-handler/simple-cmd-keybinding->shortcut-args pid key keybinding))]
|
||||
(let [dispatch-cmd (fn [_e]
|
||||
(if palette?
|
||||
(palette-handler/invoke-command palette-cmd)
|
||||
(action')))
|
||||
[handler-id id shortcut-map] (update shortcut-args 2 assoc :fn dispatch-cmd)]
|
||||
(js/console.debug :shortcut/register-shortcut [handler-id id shortcut-map])
|
||||
(st/register-shortcut! handler-id id shortcut-map)))))))
|
||||
@@ -509,27 +528,29 @@
|
||||
(get_block (:db/id block) opts))))
|
||||
|
||||
(def ^:export get_previous_sibling_block
|
||||
(fn [uuid]
|
||||
(when-let [block (db-model/query-block-by-uuid uuid)]
|
||||
(fn [block-uuid]
|
||||
(when-let [block (db-model/query-block-by-uuid block-uuid)]
|
||||
(let [{:block/keys [parent left]} block
|
||||
block (when-not (= parent left) (db-utils/pull (:db/id left)))]
|
||||
(and block (bean/->js (normalize-keyword-for-json block)))))))
|
||||
|
||||
(def ^:export get_next_sibling_block
|
||||
(fn [uuid]
|
||||
(when-let [block (db-model/query-block-by-uuid uuid)]
|
||||
(fn [block-uuid]
|
||||
(when-let [block (db-model/query-block-by-uuid block-uuid)]
|
||||
(when-let [right-siblings (outliner/get-right-siblings (outliner/->Block block))]
|
||||
(bean/->js (normalize-keyword-for-json (:data (first right-siblings))))))))
|
||||
|
||||
(def ^:export set_block_collapsed
|
||||
(fn [uuid ^js opts]
|
||||
(when-let [block (db-model/get-block-by-uuid uuid)]
|
||||
(fn [block-uuid ^js opts]
|
||||
(when-let [block (db-model/get-block-by-uuid block-uuid)]
|
||||
(let [{:keys [flag]} (bean/->clj opts)
|
||||
block-uuid (uuid block-uuid)
|
||||
flag (if (= "toggle" flag)
|
||||
(not (util/collapsed? block))
|
||||
(boolean flag))]
|
||||
(if flag (editor-handler/collapse-block! uuid)
|
||||
(editor-handler/expand-block! uuid))))))
|
||||
(if flag (editor-handler/collapse-block! block-uuid)
|
||||
(editor-handler/expand-block! block-uuid))
|
||||
nil))))
|
||||
|
||||
(def ^:export upsert_block_property
|
||||
(fn [block-uuid key value]
|
||||
@@ -566,6 +587,76 @@
|
||||
blocks (normalize-keyword-for-json blocks)]
|
||||
(bean/->js blocks)))))
|
||||
|
||||
(defn ^:export get_page_linked_references
|
||||
[page-name-or-uuid]
|
||||
(when-let [page (and page-name-or-uuid (db-model/get-page page-name-or-uuid))]
|
||||
(let [page-name (:block/name page)
|
||||
ref-blocks (if page-name
|
||||
(db-model/get-page-referenced-blocks page-name)
|
||||
(db-model/get-block-referenced-blocks (:block/uuid page)))
|
||||
ref-blocks (and (seq ref-blocks) (into [] ref-blocks))]
|
||||
(bean/->js (normalize-keyword-for-json ref-blocks)))))
|
||||
|
||||
(defn ^:export get_pages_from_namespace
|
||||
[ns]
|
||||
(when-let [repo (and ns (state/get-current-repo))]
|
||||
(when-let [pages (db-model/get-namespace-pages repo ns)]
|
||||
(bean/->js (normalize-keyword-for-json pages)))))
|
||||
|
||||
(defn ^:export get_pages_tree_from_namespace
|
||||
[ns]
|
||||
(when-let [repo (and ns (state/get-current-repo))]
|
||||
(when-let [pages (db-model/get-namespace-hierarchy repo ns)]
|
||||
(bean/->js (normalize-keyword-for-json pages)))))
|
||||
|
||||
(defn first-child-of-block
|
||||
[block]
|
||||
(when-let [children (:block/_parent block)]
|
||||
(first (db-model/sort-by-left children block))))
|
||||
|
||||
(defn second-child-of-block
|
||||
[block]
|
||||
(when-let [children (:block/_parent block)]
|
||||
(second (db-model/sort-by-left children block))))
|
||||
|
||||
(defn last-child-of-block
|
||||
[block]
|
||||
(when-let [children (:block/_parent block)]
|
||||
(last (db-model/sort-by-left children block))))
|
||||
|
||||
(defn ^:export prepend_block_in_page
|
||||
[uuid-or-page-name content ^js opts]
|
||||
(let [page? (not (util/uuid-string? uuid-or-page-name))
|
||||
page-not-exist? (and page? (nil? (db-model/get-page uuid-or-page-name)))
|
||||
_ (and page-not-exist? (page-handler/create! uuid-or-page-name
|
||||
{:redirect? false
|
||||
:create-first-block? true
|
||||
:format (state/get-preferred-format)}))]
|
||||
(when-let [block (db-model/get-page uuid-or-page-name)]
|
||||
(let [block' (if page? (second-child-of-block block) (first-child-of-block block))
|
||||
sibling? (and page? (not (nil? block')))
|
||||
opts (bean/->clj opts)
|
||||
opts (merge opts {:isPageBlock (and page? (not sibling?)) :sibling sibling? :before sibling?})
|
||||
src (if sibling? (str (:block/uuid block')) uuid-or-page-name)]
|
||||
(insert_block src content (bean/->js opts))))))
|
||||
|
||||
(defn ^:export append_block_in_page
|
||||
[uuid-or-page-name content ^js opts]
|
||||
(let [page? (not (util/uuid-string? uuid-or-page-name))
|
||||
page-not-exist? (and page? (nil? (db-model/get-page uuid-or-page-name)))
|
||||
_ (and page-not-exist? (page-handler/create! uuid-or-page-name
|
||||
{:redirect? false
|
||||
:create-first-block? true
|
||||
:format (state/get-preferred-format)}))]
|
||||
(when-let [block (db-model/get-page uuid-or-page-name)]
|
||||
(let [block' (last-child-of-block block)
|
||||
sibling? (not (nil? block'))
|
||||
opts (bean/->clj opts)
|
||||
opts (merge opts {:isPageBlock (and page? (not sibling?)) :sibling sibling?}
|
||||
(when sibling? {:before false}))
|
||||
src (if sibling? (str (:block/uuid block')) uuid-or-page-name)]
|
||||
(insert_block src content (bean/->js opts))))))
|
||||
|
||||
;; plugins
|
||||
(def ^:export __install_plugin
|
||||
(fn [^js manifest]
|
||||
@@ -612,13 +703,79 @@
|
||||
(when-let [args (and args (seq (bean/->clj args)))]
|
||||
(shell/run-git-command! args)))
|
||||
|
||||
;; helpers
|
||||
(defn ^:export show_msg
|
||||
([content] (show_msg content :success))
|
||||
([content status] (let [hiccup? (and (string? content) (string/starts-with? (string/triml content) "[:"))
|
||||
content (if hiccup? (parse-hiccup-ui content) content)]
|
||||
(notification/show! content (keyword status)))))
|
||||
;; git
|
||||
(defn ^:export git_exec_command
|
||||
[^js args]
|
||||
(when-let [args (and args (seq (bean/->clj args)))]
|
||||
(shell/run-git-command2! args)))
|
||||
|
||||
(defn ^:export git_load_ignore_file
|
||||
[]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(p/let [file ".gitignore"
|
||||
dir (config/get-repo-dir repo)
|
||||
_ (fs/create-if-not-exists repo dir file)
|
||||
content (fs/read-file dir file)]
|
||||
content)))
|
||||
|
||||
(defn ^:export git_save_ignore_file
|
||||
[content]
|
||||
(when-let [repo (and (string? content) (state/get-current-repo))]
|
||||
(p/let [file ".gitignore"
|
||||
dir (config/get-repo-dir repo)
|
||||
_ (fs/write-file! repo dir file content {:skip-compare? true})])))
|
||||
|
||||
;; ui
|
||||
(defn ^:export show_msg
|
||||
([content] (show_msg content :success nil))
|
||||
([content status ^js opts]
|
||||
(let [{:keys [key timeout]} (bean/->clj opts)
|
||||
hiccup? (and (string? content) (string/starts-with? (string/triml content) "[:"))
|
||||
content (if hiccup? (parse-hiccup-ui content) content)
|
||||
uid (when (string? key) (keyword key))
|
||||
clear? (not= timeout 0)
|
||||
key' (notification/show! content (keyword status) clear? uid timeout)]
|
||||
(name key'))))
|
||||
|
||||
(defn ^:export ui_show_msg
|
||||
[& args]
|
||||
(apply show_msg args))
|
||||
|
||||
(defn ^:export ui_close_msg
|
||||
[key]
|
||||
(when (string? key)
|
||||
(notification/clear! (keyword key)) nil))
|
||||
|
||||
;; assets
|
||||
(defn ^:export assets_list_files_of_current_graph
|
||||
[^js exts]
|
||||
(p/let [files (ipc/ipc :getAssetsFiles {:exts exts})]
|
||||
(bean/->js files)))
|
||||
|
||||
;; experiments
|
||||
(defn ^:export exper_load_scripts
|
||||
[pid & scripts]
|
||||
(when-let [^js _pl (plugin-handler/get-plugin-inst pid)]
|
||||
(doseq [s scripts
|
||||
:let [upt-status #(state/upt-plugin-resource pid :scripts s :status %)
|
||||
init? (plugin-handler/register-plugin-resources pid :scripts {:key s :src s})]]
|
||||
(when init?
|
||||
(p/catch
|
||||
(p/then
|
||||
(do
|
||||
(upt-status :pending)
|
||||
(loader/load s nil {:attributes {:data-ref (name pid)}}))
|
||||
#(upt-status :done))
|
||||
#(upt-status :error))))))
|
||||
|
||||
(defn ^:export exper_register_fenced_code_renderer
|
||||
[pid type ^js opts]
|
||||
(when-let [^js _pl (plugin-handler/get-plugin-inst pid)]
|
||||
(plugin-handler/register_fenced_code_renderer
|
||||
(keyword pid) type (reduce #(assoc %1 %2 (aget opts (name %2))) {}
|
||||
[:edit :before :subs :render]))))
|
||||
|
||||
;; helpers
|
||||
(defn ^:export query_element_by_id
|
||||
[id]
|
||||
(let [^js el (gdom/getElement id)]
|
||||
|
||||
Reference in New Issue
Block a user