mirror of
https://github.com/logseq/logseq.git
synced 2026-04-24 22:25:01 +00:00
Enhance/plugin APIs (#7555)
* feat: WIP native cli command support * Add :shell/command-whitelist option * Integrate cli to code block * Add :code-block/command-whitelist option * fix: size of icon * improve(shell): cache user shell whitelist on application configures file * improve(electron): promisify run cli command * chore(libs): update version * fix(plugin): incorrect payload of pdf highlights section hook * improve(plugin): block renderer with specific block uuid * improve(plugin): expose logger for user lib * improve(plugin): block hooks type * improve(plugin): block slot hook with specific block * improve(plugin): auto generate key for provide UI options * improve(plugin): style of injected ui container * improve(plugin): types * improve(plugin): async messaging api from host to plugin * improve(plugin): add types * improve(apis): get external plugin metadata * improve(apis): invoke external plugin impls * improve(apis): call external plugin impls for simple commands * enhance(apis): datascript query api for predicate inputs * enhance(apis): datascript query api for predicate inputs * fix(apis): redundant args of datascript query api * enhance(plugins): position of float ui container * enhance(plugins): style of setting options * enhance(plugins): layouts data for float ui * chore(plugins): update CHANGELOG.md * improve(apis): add types * chore: fix some inclusive terms * improve(apis): types * chore(plugins): update CHANGELOG.md * chore(plugins): build libs * chore: update CHANGELOG.md * chore: remove experiemental alda integration * fix(lint): remove unused methods Co-authored-by: Tienson Qin <tiensonqin@gmail.com> Co-authored-by: Andelf <andelf@gmail.com>
This commit is contained in:
@@ -3,8 +3,27 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [Unreleased]
|
||||
## [0.0.13]
|
||||
|
||||
## [0.0.11]
|
||||
### Added
|
||||
- Support block content slot hook `App.onBlockRendererSlotted` with a specific block UUID
|
||||
- Support plugins calling each other `App.invokeExternalPlugin` with key of models & commands.
|
||||
E.g. (It is recommended that the caller plugin upgrade the SDK to the latest.)
|
||||
```typescript
|
||||
// Defined at https://github.com/xyhp915/logseq-journals-calendar/blob/main/src/main.js#L74
|
||||
await logseq.App.invokeExternalPlugin('logseq-journals-calendar.models.goToToday')
|
||||
|
||||
// Defined at https://github.com/vipzhicheng/logseq-plugin-vim-shortcuts/blob/bec05aeee8/src/keybindings/down.ts#L20
|
||||
await logseq.App.invokeExternalPlugin('logseq-plugin-vim-shortcuts.commands.vim-shortcut-down-0')
|
||||
```
|
||||
- Support api of `Editor.saveFocusedCodeEditorContent` [#FQ](https://github.com/logseq/logseq/issues/7714)
|
||||
- Support predicate for `DB.datascriptQuery` inputs
|
||||
|
||||
### Fixed
|
||||
- Incorrect hook payload from `Editor.registerHighlightContextMenuItem`
|
||||
- Auto generate key if not exist for `provideUI` options
|
||||
|
||||
## [0.0.12]
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@logseq/libs",
|
||||
"version": "0.0.11",
|
||||
"version": "0.0.13",
|
||||
"description": "Logseq SDK libraries",
|
||||
"main": "dist/lsplugin.user.js",
|
||||
"typings": "index.d.ts",
|
||||
|
||||
@@ -38,7 +38,7 @@ class LSPluginCaller extends EventEmitter {
|
||||
payload: any,
|
||||
actor?: DeferredActor
|
||||
) => Promise<any>
|
||||
private _callUserModel?: (type: string, payload: any) => Promise<any>
|
||||
private _callUserModel?: (type: string, ...payloads: any[]) => Promise<any>
|
||||
|
||||
private _debugTag = ''
|
||||
|
||||
@@ -205,8 +205,13 @@ class LSPluginCaller extends EventEmitter {
|
||||
return this._call?.call(this, type, payload, actor)
|
||||
}
|
||||
|
||||
async callUserModel(type: string, payload: any = {}) {
|
||||
return this._callUserModel?.call(this, type, payload)
|
||||
async callUserModel(type: string, ...args: any[]) {
|
||||
return this._callUserModel?.apply(this, [type, ...args])
|
||||
}
|
||||
|
||||
async callUserModelAsync(type: string, ...args: any[]) {
|
||||
type = AWAIT_LSPMSGFn(type)
|
||||
return this._callUserModel?.apply(this, [type, ...args])
|
||||
}
|
||||
|
||||
// run in host
|
||||
@@ -235,12 +240,21 @@ class LSPluginCaller extends EventEmitter {
|
||||
const mainLayoutInfo = (await this._pluginLocal._loadLayoutsData())?.$$0
|
||||
if (mainLayoutInfo) {
|
||||
cnt.dataset.inited_layout = 'true'
|
||||
const { width, height, left, top } = mainLayoutInfo
|
||||
let { width, height, left, top, vw, vh } = mainLayoutInfo
|
||||
|
||||
left = Math.max(left, 0)
|
||||
left = (typeof vw === 'number') ?
|
||||
`${Math.min(left * 100 / vw, 99)}%` : `${left}px`
|
||||
|
||||
// 45 is height of headbar
|
||||
top = Math.max(top, 45)
|
||||
top = (typeof vh === 'number') ?
|
||||
`${Math.min(top * 100 / vh, 99)}%` : `${top}px`
|
||||
|
||||
Object.assign(cnt.style, {
|
||||
width: width + 'px',
|
||||
height: height + 'px',
|
||||
left: left + 'px',
|
||||
top: top + 'px',
|
||||
left, top
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -292,12 +306,15 @@ class LSPluginCaller extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
this._callUserModel = async (type, payload: any) => {
|
||||
this._callUserModel = async (type, ...payloads: any[]) => {
|
||||
if (type.startsWith(FLAG_AWAIT)) {
|
||||
// TODO: attach payload with method call
|
||||
return await refChild.get(type.replace(FLAG_AWAIT, ''))
|
||||
// TODO: attach arguments with method call
|
||||
return await refChild.get(
|
||||
type.replace(FLAG_AWAIT, ''),
|
||||
...payloads
|
||||
)
|
||||
} else {
|
||||
refChild.call(type, payload)
|
||||
refChild.call(type, payloads?.[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
cleanInjectedScripts,
|
||||
safeSnakeCase,
|
||||
injectTheme,
|
||||
cleanInjectedUI,
|
||||
cleanInjectedUI, PluginLogger,
|
||||
} from './helpers'
|
||||
import * as pluginHelpers from './helpers'
|
||||
import Debug from 'debug'
|
||||
@@ -132,59 +132,6 @@ class PluginSettings extends EventEmitter<'change' | 'reset'> {
|
||||
}
|
||||
}
|
||||
|
||||
class PluginLogger extends EventEmitter<'change'> {
|
||||
private _logs: Array<[type: string, payload: any]> = []
|
||||
|
||||
constructor(private readonly _tag: string) {
|
||||
super()
|
||||
}
|
||||
|
||||
write(type: string, payload: any[], inConsole?: boolean) {
|
||||
if (payload?.length && (true === payload[payload.length - 1])) {
|
||||
inConsole = true
|
||||
payload.pop()
|
||||
}
|
||||
|
||||
const msg = payload.reduce((ac, it) => {
|
||||
if (it && it instanceof Error) {
|
||||
ac += `${it.message} ${it.stack}`
|
||||
} else {
|
||||
ac += it.toString()
|
||||
}
|
||||
return ac
|
||||
}, `[${this._tag}][${new Date().toLocaleTimeString()}] `)
|
||||
|
||||
this._logs.push([type, msg])
|
||||
|
||||
if (inConsole) {
|
||||
console?.['ERROR' === type ? 'error' : 'debug'](`${type}: ${msg}`)
|
||||
}
|
||||
|
||||
this.emit('change')
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._logs = []
|
||||
this.emit('change')
|
||||
}
|
||||
|
||||
info(...args: any[]) {
|
||||
this.write('INFO', args)
|
||||
}
|
||||
|
||||
error(...args: any[]) {
|
||||
this.write('ERROR', args)
|
||||
}
|
||||
|
||||
warn(...args: any[]) {
|
||||
this.write('WARN', args)
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this._logs
|
||||
}
|
||||
}
|
||||
|
||||
interface UserPreferences {
|
||||
theme: LegacyTheme
|
||||
themes: {
|
||||
@@ -204,7 +151,6 @@ interface PluginLocalOptions {
|
||||
mode: 'shadow' | 'iframe'
|
||||
settingsSchema?: SettingSchemaDesc[]
|
||||
settings?: PluginSettings
|
||||
logger?: PluginLogger
|
||||
effect?: boolean
|
||||
theme?: boolean
|
||||
|
||||
@@ -460,6 +406,7 @@ class PluginLocal extends EventEmitter<'loaded'
|
||||
private _localRoot?: string
|
||||
private _dotSettingsFile?: string
|
||||
private _caller?: LSPluginCaller
|
||||
private _logger?: PluginLogger
|
||||
|
||||
/**
|
||||
* @param _options
|
||||
@@ -483,7 +430,7 @@ class PluginLocal extends EventEmitter<'loaded'
|
||||
|
||||
async _setupUserSettings(reload?: boolean) {
|
||||
const { _options } = this
|
||||
const logger = (_options.logger = new PluginLogger('Loader'))
|
||||
const logger = (this._logger = new PluginLogger('Loader'))
|
||||
|
||||
if (_options.settings && !reload) {
|
||||
return
|
||||
@@ -1056,13 +1003,17 @@ class PluginLocal extends EventEmitter<'loaded'
|
||||
}
|
||||
|
||||
get logger() {
|
||||
return this.options.logger
|
||||
return this._logger
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
return this.settings?.get('disabled')
|
||||
}
|
||||
|
||||
get theme() {
|
||||
return this.options.theme
|
||||
}
|
||||
|
||||
get caller() {
|
||||
return this._caller
|
||||
}
|
||||
@@ -1123,6 +1074,8 @@ class PluginLocal extends EventEmitter<'loaded'
|
||||
json.usf = this.dotSettingsFile
|
||||
json.iir = this.isInstalledInDotRoot
|
||||
json.lsr = this._resolveResourceFullUrl('/')
|
||||
json.settings = json.settings?.toJSON()
|
||||
|
||||
return json
|
||||
}
|
||||
}
|
||||
@@ -1535,6 +1488,16 @@ class LSPluginCore
|
||||
return this._registeredThemes
|
||||
}
|
||||
|
||||
get enabledPlugins() {
|
||||
return [...this.registeredPlugins.entries()].reduce((a, b) => {
|
||||
let p = b?.[1]
|
||||
if (p?.disabled !== true) {
|
||||
a.set(b?.[0], p)
|
||||
}
|
||||
return a
|
||||
}, new Map())
|
||||
}
|
||||
|
||||
async registerTheme(id: PluginLocalIdentity, opt: Theme): Promise<void> {
|
||||
debug('Register theme #', id, opt)
|
||||
|
||||
|
||||
@@ -102,6 +102,10 @@ export type IUserHook<E = any, R = IUserOffHook> = (
|
||||
export type IUserSlotHook<E = any> = (
|
||||
callback: (e: IHookEvent & UISlotIdentity & E) => void
|
||||
) => void
|
||||
export type IUserConditionSlotHook<C = any, E = any> = (
|
||||
condition: C,
|
||||
callback: (e: IHookEvent & UISlotIdentity & E) => void
|
||||
) => void
|
||||
|
||||
export type EntityID = number
|
||||
export type BlockUUID = string
|
||||
@@ -366,11 +370,33 @@ export interface IAppProxy {
|
||||
action: SimpleCommandCallback
|
||||
) => void
|
||||
|
||||
/**
|
||||
* Supported all registered palette commands
|
||||
* @param type
|
||||
* @param args
|
||||
*/
|
||||
invokeExternalCommand: (
|
||||
type: ExternalCommandType,
|
||||
...args: Array<any>
|
||||
) => Promise<void>
|
||||
|
||||
/**
|
||||
* Call external plugin command provided by models or registerd commands
|
||||
* @added 0.0.13
|
||||
* @param type `xx-plugin-id.commands.xx-key`, `xx-plugin-id.models.xx-key`
|
||||
* @param args
|
||||
*/
|
||||
invokeExternalPlugin: (
|
||||
type: string,
|
||||
...args: Array<any>
|
||||
) => Promise<unknown>
|
||||
|
||||
/**
|
||||
* @added 0.0.13
|
||||
* @param pid
|
||||
*/
|
||||
getExternalPlugin: (pid: string) => Promise<{} | null>
|
||||
|
||||
/**
|
||||
* Get state from app store
|
||||
* valid state is here
|
||||
@@ -455,7 +481,13 @@ export interface IAppProxy {
|
||||
onGraphAfterIndexed: IUserHook<{ repo: string }>
|
||||
onThemeModeChanged: IUserHook<{ mode: 'dark' | 'light' }>
|
||||
onThemeChanged: IUserHook<Partial<{ name: string, mode: string, pid: string, url: string }>>
|
||||
onBlockRendererSlotted: IUserSlotHook<{ uuid: BlockUUID }>
|
||||
|
||||
/**
|
||||
* provide ui slot to specific block with UUID
|
||||
*
|
||||
* @added 0.0.13
|
||||
*/
|
||||
onBlockRendererSlotted: IUserConditionSlotHook<BlockUUID, Omit<BlockEntity, 'children' | 'page'>>
|
||||
|
||||
/**
|
||||
* provide ui slot to block `renderer` macro for `{{renderer arg1, arg2}}`
|
||||
@@ -636,7 +668,7 @@ export interface IEditorProxy extends Record<string, any> {
|
||||
|
||||
/**
|
||||
* @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-reddit-hot-news
|
||||
*
|
||||
*
|
||||
* `keepUUID` will allow you to set a custom UUID for blocks by setting their properties.id
|
||||
*/
|
||||
insertBatchBlock: (
|
||||
@@ -722,6 +754,8 @@ export interface IEditorProxy extends Record<string, any> {
|
||||
editBlock: (srcBlock: BlockIdentity, opts?: { pos: number }) => Promise<void>
|
||||
selectBlock: (srcBlock: BlockIdentity) => Promise<void>
|
||||
|
||||
saveFocusedCodeEditorContent: () => Promise<void>
|
||||
|
||||
upsertBlockProperty: (
|
||||
block: BlockIdentity,
|
||||
key: string,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
isValidUUID,
|
||||
deepMerge,
|
||||
mergeSettingsWithSchema,
|
||||
PluginLogger,
|
||||
safeSnakeCase,
|
||||
safetyPathJoin,
|
||||
} from './helpers'
|
||||
@@ -55,6 +57,7 @@ type callableMethods =
|
||||
|
||||
const PROXY_CONTINUE = Symbol.for('proxy-continue')
|
||||
const debug = Debug('LSPlugin:user')
|
||||
const logger = new PluginLogger('', { console: true })
|
||||
|
||||
/**
|
||||
* @param type (key of group commands)
|
||||
@@ -87,12 +90,26 @@ function registerSimpleCommand(
|
||||
method: 'register-plugin-simple-command',
|
||||
args: [
|
||||
this.baseInfo.id,
|
||||
// [cmd, action]
|
||||
[{ key, label, type, desc, keybinding, extras }, ['editor/hook', eventKey]],
|
||||
palette,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
function shouldValidUUID(uuid: string) {
|
||||
if (!isValidUUID(uuid)) {
|
||||
logger.error(`#${uuid} is not a valid UUID string.`)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function checkEffect(p: LSPluginUser) {
|
||||
return p && (p.baseInfo?.effect || !p.baseInfo?.iir)
|
||||
}
|
||||
|
||||
let _appBaseInfo: AppInfo = null
|
||||
let _searchServices: Map<string, LSPluginSearchService> = new Map()
|
||||
|
||||
@@ -188,6 +205,45 @@ const app: Partial<IAppProxy> = {
|
||||
)
|
||||
},
|
||||
|
||||
onBlockRendererSlotted(
|
||||
uuid,
|
||||
callback: (payload: any) => void) {
|
||||
if (!shouldValidUUID(uuid)) return
|
||||
|
||||
const pid = this.baseInfo.id
|
||||
const hook = `hook:editor:${safeSnakeCase(`slot:${uuid}`)}`
|
||||
|
||||
this.caller.on(hook, callback)
|
||||
this.App._installPluginHook(pid, hook)
|
||||
|
||||
return () => {
|
||||
this.caller.off(hook, callback)
|
||||
this.App._uninstallPluginHook(pid, hook)
|
||||
}
|
||||
},
|
||||
|
||||
invokeExternalPlugin(
|
||||
this: LSPluginUser,
|
||||
type: string,
|
||||
...args: Array<any>
|
||||
) {
|
||||
type = type?.trim()
|
||||
if (!type) return
|
||||
let [pid, group] = type.split('.')
|
||||
if (!['models', 'commands'].includes(group?.toLowerCase())) {
|
||||
throw new Error(`Type only support '.models' or '.commands' currently.`)
|
||||
}
|
||||
const key = type.replace(`${pid}.${group}.`, '')
|
||||
|
||||
if (!pid || !group || !key) {
|
||||
throw new Error(`Illegal type of #${type} to invoke external plugin.`)
|
||||
}
|
||||
return this._execCallableAPIAsync(
|
||||
'invoke_external_plugin_cmd',
|
||||
pid, group.toLowerCase(), key, args
|
||||
)
|
||||
},
|
||||
|
||||
setFullScreen(flag) {
|
||||
const sf = (...args) => this._callWin('setFullScreen', ...args)
|
||||
|
||||
@@ -328,6 +384,8 @@ const db: Partial<IDBProxy> = {
|
||||
txMeta?: { outlinerOp: string; [p: string]: any }
|
||||
) => void
|
||||
): IUserOffHook {
|
||||
if (!shouldValidUUID(uuid)) return
|
||||
|
||||
const pid = this.baseInfo.id
|
||||
const hook = `hook:db:${safeSnakeCase(`block:${uuid}`)}`
|
||||
const aBlockChange = ({ block, txData, txMeta }) => {
|
||||
@@ -346,6 +404,24 @@ const db: Partial<IDBProxy> = {
|
||||
this.App._uninstallPluginHook(pid, hook)
|
||||
}
|
||||
},
|
||||
|
||||
datascriptQuery<T = any>(
|
||||
this: LSPluginUser,
|
||||
query: string,
|
||||
...inputs: Array<any>
|
||||
): Promise<T> {
|
||||
inputs.pop()
|
||||
|
||||
if (inputs?.some(it => (typeof it === 'function'))) {
|
||||
const host = this.Experiments.ensureHostScope()
|
||||
return host.logseq.api.datascript_query(query, ...inputs)
|
||||
}
|
||||
|
||||
return this._execCallableAPIAsync(
|
||||
`datascript_query`,
|
||||
...inputs
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const git: Partial<IGitProxy> = {}
|
||||
@@ -454,6 +530,13 @@ export class LSPluginUser
|
||||
|
||||
baseInfo = deepMerge(this._baseInfo, baseInfo)
|
||||
|
||||
if (baseInfo?.id) {
|
||||
this._debugTag =
|
||||
this._caller.debugTag = `#${baseInfo.id} [${baseInfo.name}]`
|
||||
|
||||
this.logger.setTag(this._debugTag)
|
||||
}
|
||||
|
||||
if (this._settingsSchema) {
|
||||
baseInfo.settings = mergeSettingsWithSchema(
|
||||
baseInfo.settings,
|
||||
@@ -464,11 +547,6 @@ export class LSPluginUser
|
||||
await this.useSettingsSchema(this._settingsSchema)
|
||||
}
|
||||
|
||||
if (baseInfo?.id) {
|
||||
this._debugTag =
|
||||
this._caller.debugTag = `#${baseInfo.id} [${baseInfo.name}]`
|
||||
}
|
||||
|
||||
try {
|
||||
await this._execCallableAPIAsync('setSDKMetadata', {
|
||||
version: this._version,
|
||||
@@ -605,6 +683,14 @@ export class LSPluginUser
|
||||
return this._baseInfo
|
||||
}
|
||||
|
||||
get effect(): Boolean {
|
||||
return checkEffect(this)
|
||||
}
|
||||
|
||||
get logger() {
|
||||
return logger
|
||||
}
|
||||
|
||||
get settings() {
|
||||
return this.baseInfo?.settings
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import DOMPurify from 'dompurify'
|
||||
import { merge } from 'lodash-es'
|
||||
import { snakeCase } from 'snake-case'
|
||||
import * as callables from './callable.apis'
|
||||
import EventEmitter from 'eventemitter3'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -53,6 +54,74 @@ export function isObject(item: any) {
|
||||
|
||||
export const deepMerge = merge
|
||||
|
||||
export class PluginLogger extends EventEmitter<'change'> {
|
||||
private _logs: Array<[type: string, payload: any]> = []
|
||||
|
||||
constructor(
|
||||
private _tag?: string,
|
||||
private _opts?: {
|
||||
console: boolean
|
||||
}
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
write(type: string, payload: any[], inConsole?: boolean) {
|
||||
if (payload?.length && (true === payload[payload.length - 1])) {
|
||||
inConsole = true
|
||||
payload.pop()
|
||||
}
|
||||
|
||||
const msg = payload.reduce((ac, it) => {
|
||||
if (it && it instanceof Error) {
|
||||
ac += `${it.message} ${it.stack}`
|
||||
} else {
|
||||
ac += it.toString()
|
||||
}
|
||||
return ac
|
||||
}, `[${this._tag}][${new Date().toLocaleTimeString()}] `)
|
||||
|
||||
this._logs.push([type, msg])
|
||||
|
||||
if (inConsole || this._opts?.console) {
|
||||
console?.['ERROR' === type ? 'error' : 'debug'](`${type}: ${msg}`)
|
||||
}
|
||||
|
||||
this.emit('change')
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._logs = []
|
||||
this.emit('change')
|
||||
}
|
||||
|
||||
info(...args: any[]) {
|
||||
this.write('INFO', args)
|
||||
}
|
||||
|
||||
error(...args: any[]) {
|
||||
this.write('ERROR', args)
|
||||
}
|
||||
|
||||
warn(...args: any[]) {
|
||||
this.write('WARN', args)
|
||||
}
|
||||
|
||||
setTag(s: string) {
|
||||
this._tag = s
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this._logs
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidUUID(s: string) {
|
||||
return (typeof s === 'string' &&
|
||||
(s.length === 36) &&
|
||||
(/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi).test(s))
|
||||
}
|
||||
|
||||
export function genID() {
|
||||
// Math.random should be unique because of its seeding algorithm.
|
||||
// Convert it to base 36 (numbers + letters), and grab the first 9 characters
|
||||
@@ -190,9 +259,9 @@ export function setupInjectedStyle(
|
||||
el.textContent = style
|
||||
|
||||
attrs &&
|
||||
Object.entries(attrs).forEach(([k, v]) => {
|
||||
el.setAttribute(k, v)
|
||||
})
|
||||
Object.entries(attrs).forEach(([k, v]) => {
|
||||
el.setAttribute(k, v)
|
||||
})
|
||||
|
||||
document.head.append(el)
|
||||
|
||||
@@ -227,7 +296,7 @@ export function setupInjectedUI(
|
||||
float = true
|
||||
}
|
||||
|
||||
const id = `${pl.id}--${ui.key}`
|
||||
const id = `${pl.id}--${ui.key || genID()}`
|
||||
const key = id
|
||||
|
||||
const target = float
|
||||
@@ -268,22 +337,22 @@ export function setupInjectedUI(
|
||||
|
||||
// update attributes
|
||||
attrs &&
|
||||
Object.entries(attrs).forEach(([k, v]) => {
|
||||
el.setAttribute(k, v)
|
||||
})
|
||||
Object.entries(attrs).forEach(([k, v]) => {
|
||||
el.setAttribute(k, v)
|
||||
})
|
||||
|
||||
let positionDirty = el.dataset.dx != null
|
||||
ui.style &&
|
||||
Object.entries(ui.style).forEach(([k, v]) => {
|
||||
if (
|
||||
positionDirty &&
|
||||
['left', 'top', 'bottom', 'right', 'width', 'height'].includes(k)
|
||||
) {
|
||||
return
|
||||
}
|
||||
Object.entries(ui.style).forEach(([k, v]) => {
|
||||
if (
|
||||
positionDirty &&
|
||||
['left', 'top', 'bottom', 'right', 'width', 'height'].includes(k)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
el.style[k] = v
|
||||
})
|
||||
el.style[k] = v
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -303,18 +372,19 @@ export function setupInjectedUI(
|
||||
content.innerHTML = ui.template
|
||||
|
||||
attrs &&
|
||||
Object.entries(attrs).forEach(([k, v]) => {
|
||||
el.setAttribute(k, v)
|
||||
})
|
||||
Object.entries(attrs).forEach(([k, v]) => {
|
||||
el.setAttribute(k, v)
|
||||
})
|
||||
|
||||
ui.style &&
|
||||
Object.entries(ui.style).forEach(([k, v]) => {
|
||||
el.style[k] = v
|
||||
})
|
||||
Object.entries(ui.style).forEach(([k, v]) => {
|
||||
el.style[k] = v
|
||||
})
|
||||
|
||||
let teardownUI: () => void
|
||||
let disposeFloat: () => void
|
||||
|
||||
// seu up float container
|
||||
if (float) {
|
||||
el.setAttribute('draggable', 'true')
|
||||
el.setAttribute('resizable', 'true')
|
||||
@@ -322,11 +392,11 @@ export function setupInjectedUI(
|
||||
el.classList.add('lsp-ui-float-container', 'visible')
|
||||
disposeFloat =
|
||||
(pl._setupResizableContainer(el, key),
|
||||
pl._setupDraggableContainer(el, {
|
||||
key,
|
||||
close: () => teardownUI(),
|
||||
title: attrs?.title,
|
||||
}))
|
||||
pl._setupDraggableContainer(el, {
|
||||
key,
|
||||
close: () => teardownUI(),
|
||||
title: attrs?.title,
|
||||
}))
|
||||
}
|
||||
|
||||
if (!!slot && ui.reset) {
|
||||
@@ -364,7 +434,7 @@ export function setupInjectedUI(
|
||||
|
||||
const msgType = trigger.dataset[`on${ucFirst(type)}`]
|
||||
msgType &&
|
||||
pl.caller?.callUserModel(msgType, transformableEvent(trigger, e))
|
||||
pl.caller?.callUserModel(msgType, transformableEvent(trigger, e))
|
||||
},
|
||||
false
|
||||
)
|
||||
|
||||
@@ -79,9 +79,9 @@ export const sanitize = (message, allowedOrigin) => {
|
||||
* passed to functions in the child model
|
||||
* @return {Promise}
|
||||
*/
|
||||
export const resolveValue = (model, property) => {
|
||||
export const resolveValue = (model, property, args) => {
|
||||
const unwrappedContext =
|
||||
typeof model[property] === 'function' ? model[property]() : model[property]
|
||||
typeof model[property] === 'function' ? model[property].apply(null, args) : model[property]
|
||||
return Promise.resolve(unwrappedContext)
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ export class ParentAPI {
|
||||
}
|
||||
}
|
||||
|
||||
get(property) {
|
||||
get(property, ...args) {
|
||||
return new Promise((resolve) => {
|
||||
// Extract data from response and kill listeners
|
||||
const uid = generateNewMessageId()
|
||||
@@ -154,6 +154,7 @@ export class ParentAPI {
|
||||
postmate: 'request',
|
||||
type: messageType,
|
||||
property,
|
||||
args,
|
||||
uid,
|
||||
},
|
||||
this.childOrigin
|
||||
@@ -218,7 +219,7 @@ export class ChildAPI {
|
||||
log('Child: Received request', e.data)
|
||||
}
|
||||
|
||||
const { property, uid, data } = e.data
|
||||
const { property, uid, data, args } = e.data
|
||||
|
||||
if (e.data.postmate === 'call') {
|
||||
if (
|
||||
@@ -231,7 +232,7 @@ export class ChildAPI {
|
||||
}
|
||||
|
||||
// Reply to Parent
|
||||
resolveValue(this.model, property).then((value) => {
|
||||
resolveValue(this.model, property, args).then((value) => {
|
||||
;(e.source as WindowProxy).postMessage(
|
||||
{
|
||||
property,
|
||||
@@ -375,7 +376,6 @@ export class Postmate {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
destroy() {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
log('Postmate: Destroying Postmate instance')
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -39,7 +39,8 @@
|
||||
"posthog-js": "1.10.2",
|
||||
"@logseq/rsapi": "0.0.57",
|
||||
"electron-deeplink": "1.0.10",
|
||||
"abort-controller": "3.0.0"
|
||||
"abort-controller": "3.0.0",
|
||||
"command-exists": "1.2.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^6.0.0-beta.57",
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
(let [body (.toString (.readFileSync fs cfg-path))]
|
||||
(if (seq body) (reader/read-string body) {}))
|
||||
(catch :default e
|
||||
(js/console.error :cfg-error e)
|
||||
{})))
|
||||
(js/console.error :cfg-error e))))
|
||||
|
||||
(defn- write-cfg!
|
||||
[cfg]
|
||||
@@ -29,9 +28,9 @@
|
||||
|
||||
(defn set-item!
|
||||
[k v]
|
||||
(let [cfg (ensure-cfg)
|
||||
cfg (assoc cfg k v)]
|
||||
(write-cfg! cfg)))
|
||||
(when-let [cfg (ensure-cfg)]
|
||||
(some->> (assoc cfg k v)
|
||||
(write-cfg!))))
|
||||
|
||||
(defn get-item
|
||||
[k]
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
["diff-match-patch" :as google-diff]
|
||||
["/electron/utils" :as js-utils]
|
||||
["abort-controller" :as AbortController]
|
||||
[electron.shell :as shell]
|
||||
[electron.fs-watcher :as watcher]
|
||||
[electron.configs :as cfgs]
|
||||
[promesa.core :as p]
|
||||
@@ -410,11 +411,12 @@
|
||||
|
||||
(defmethod handle :userAppCfgs [_window [_ k v]]
|
||||
(let [config (cfgs/get-config)]
|
||||
(if-not k
|
||||
config
|
||||
(if-let [k (and k (keyword k))]
|
||||
(if-not (nil? v)
|
||||
(cfgs/set-item! (keyword k) v)
|
||||
(cfgs/get-item (keyword k))))))
|
||||
(do (cfgs/set-item! k v)
|
||||
(state/set-state! [:config k] v))
|
||||
(cfgs/get-item k))
|
||||
config)))
|
||||
|
||||
(defmethod handle :getDirname [_]
|
||||
js/__dirname)
|
||||
@@ -457,6 +459,24 @@
|
||||
(git/init!)
|
||||
(git/run-git2! (clj->js args))))
|
||||
|
||||
(defmethod handle :runCli [window [_ {:keys [command args returnResult]}]]
|
||||
(try
|
||||
(let [on-data-handler (fn [message]
|
||||
(let [result (str "Running " command ": " message)]
|
||||
(when returnResult
|
||||
(utils/send-to-renderer window "notification"
|
||||
{:type "success"
|
||||
:payload result}))))
|
||||
deferred (p/deferred)
|
||||
on-exit-handler (fn [code]
|
||||
(p/resolve! deferred code))
|
||||
_job (shell/run-command-safety! command args on-data-handler on-exit-handler)]
|
||||
deferred)
|
||||
(catch js/Error e
|
||||
(utils/send-to-renderer window "notification"
|
||||
{:type "error"
|
||||
:payload (.-message e)}))))
|
||||
|
||||
(defmethod handle :gitCommitAll [_ [_ message]]
|
||||
(git/add-all-and-commit! message))
|
||||
|
||||
|
||||
53
src/electron/electron/shell.cljs
Normal file
53
src/electron/electron/shell.cljs
Normal file
@@ -0,0 +1,53 @@
|
||||
(ns electron.shell
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[electron.state :as state]
|
||||
[clojure.set :as set]
|
||||
[electron.logger :as logger]
|
||||
["child_process" :as child-process]
|
||||
["command-exists" :as command-exists]))
|
||||
|
||||
(def commands-allowlist
|
||||
#{"git" "pandoc" "ag" "grep" "alda"})
|
||||
|
||||
;(def commands-denylist
|
||||
; #{"rm" "mv" "rename" "dd" ">" "command" "sudo"})
|
||||
|
||||
(defn- get-commands-allowlist
|
||||
[]
|
||||
(set/union (set (some->> (map #(some-> % str string/trim string/lower-case)
|
||||
(get-in @state/state [:config :commands-allowlist]))
|
||||
(remove nil?)))
|
||||
commands-allowlist))
|
||||
|
||||
(defn- run-command!
|
||||
[command args on-data on-exit]
|
||||
(logger/debug "Shell: " (str command " " args))
|
||||
(let [job (child-process/spawn (str command " " args)
|
||||
#js []
|
||||
#js {:shell true :detached false})]
|
||||
|
||||
(.on (.-stderr job) "data" on-data)
|
||||
(.on (.-stdout job) "data" on-data)
|
||||
(.on job "close" on-exit)
|
||||
|
||||
job))
|
||||
|
||||
(defn- ensure-command-exists
|
||||
[command]
|
||||
(when-not
|
||||
(some->> command (.sync command-exists))
|
||||
(throw (js/Error. (str "Shell: " command " not exist!")))) command)
|
||||
|
||||
(defn- ensure-command-in-allowlist
|
||||
[command]
|
||||
(when-not
|
||||
(some->> command (contains? (get-commands-allowlist)))
|
||||
(throw (js/Error. (str "Shell: " command " not be allowed!")))) command)
|
||||
|
||||
(defn run-command-safety!
|
||||
[command args on-data on-exit]
|
||||
(when (some-> command str string/trim string/lower-case
|
||||
(ensure-command-exists)
|
||||
(ensure-command-in-allowlist))
|
||||
(run-command! command args on-data on-exit)))
|
||||
@@ -3,10 +3,10 @@
|
||||
["electron" :refer [app BrowserWindow]]
|
||||
["fs-extra" :as fs]
|
||||
["path" :as path]
|
||||
[cljs-bean.core :as bean]
|
||||
[clojure.string :as string]
|
||||
[electron.configs :as cfgs]
|
||||
[electron.logger :as logger]
|
||||
[cljs-bean.core :as bean]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defonce *win (atom nil)) ;; The main window
|
||||
|
||||
@@ -294,7 +294,7 @@
|
||||
;; Allow user to modify or extend, should specify how to extend.
|
||||
|
||||
(state/get-commands)
|
||||
(state/get-plugins-commands))
|
||||
(state/get-plugins-slash-commands))
|
||||
(remove nil?)
|
||||
(util/distinct-by-last-wins first)))
|
||||
|
||||
|
||||
@@ -2236,6 +2236,7 @@
|
||||
(let [{:block/keys [title body] :as block} (if (:block/title block) block
|
||||
(merge block (block/parse-title-and-body uuid format pre-block? content)))
|
||||
collapsed? (util/collapsed? block)
|
||||
plugin-slotted? (and config/lsp-enabled? (state/slot-hook-exist? uuid))
|
||||
block-ref? (:block-ref? config)
|
||||
stop-events? (:stop-events? config)
|
||||
block-ref-with-title? (and block-ref? (seq title))
|
||||
@@ -2274,13 +2275,14 @@
|
||||
[:div.warning.text-sm
|
||||
"Large block will not be editable or searchable to not slow down the app, please use another editor to edit this block."])
|
||||
[:div.flex.flex-row.justify-between.block-content-inner
|
||||
[:div.flex-1.w-full
|
||||
(cond
|
||||
(seq title)
|
||||
(build-block-title config block)
|
||||
(when-not plugin-slotted?
|
||||
[:div.flex-1.w-full
|
||||
(cond
|
||||
(seq title)
|
||||
(build-block-title config block)
|
||||
|
||||
:else
|
||||
nil)]
|
||||
:else
|
||||
nil)])
|
||||
|
||||
(clock-summary-cp block body)]
|
||||
|
||||
@@ -2306,18 +2308,24 @@
|
||||
(not= block-type :whiteboard-shape))
|
||||
(properties-cp config block))
|
||||
|
||||
(let [title-collapse-enabled? (:outliner/block-title-collapse-enabled? (state/get-config))]
|
||||
(when (and (not block-ref-with-title?)
|
||||
(seq body)
|
||||
(or (not title-collapse-enabled?)
|
||||
(and title-collapse-enabled? (not collapsed?))))
|
||||
[:div.block-body
|
||||
;; TODO: consistent id instead of the idx (since it could be changed later)
|
||||
(let [body (block/trim-break-lines! (:block/body block))]
|
||||
(for [[idx child] (medley/indexed body)]
|
||||
(when-let [block (markup-element-cp config child)]
|
||||
(rum/with-key (block-child block)
|
||||
(str uuid "-" idx)))))]))
|
||||
(if plugin-slotted?
|
||||
[:div.block-slotted-body
|
||||
(plugins/hook-block-slot
|
||||
:block-content-slotted
|
||||
(-> block (dissoc :block/children :block/page)))]
|
||||
|
||||
(let [title-collapse-enabled? (:outliner/block-title-collapse-enabled? (state/get-config))]
|
||||
(when (and (not block-ref-with-title?)
|
||||
(seq body)
|
||||
(or (not title-collapse-enabled?)
|
||||
(and title-collapse-enabled? (not collapsed?))))
|
||||
[:div.block-body
|
||||
;; TODO: consistent id instead of the idx (since it could be changed later)
|
||||
(let [body (block/trim-break-lines! (:block/body block))]
|
||||
(for [[idx child] (medley/indexed body)]
|
||||
(when-let [block (markup-element-cp config child)]
|
||||
(rum/with-key (block-child block)
|
||||
(str uuid "-" idx)))))])))
|
||||
|
||||
(case (:block/warning block)
|
||||
:multiple-blocks
|
||||
@@ -2378,7 +2386,8 @@
|
||||
string/trim
|
||||
block-ref/block-ref?)]
|
||||
(if (and edit? editor-box)
|
||||
[:div.editor-wrapper {:id editor-id}
|
||||
[:div.editor-wrapper
|
||||
{:id editor-id}
|
||||
(ui/catch-error
|
||||
(ui/block-error "Something wrong in the editor" {})
|
||||
(editor-box {:block block
|
||||
|
||||
@@ -934,17 +934,15 @@
|
||||
(state/set-sub-modal! installed-themes))
|
||||
|
||||
(rum/defc hook-ui-slot
|
||||
([type payload] (hook-ui-slot type payload nil))
|
||||
([type payload opts]
|
||||
([type payload] (hook-ui-slot type payload nil #(plugin-handler/hook-plugin-app type % nil)))
|
||||
([type payload opts callback]
|
||||
(let [rs (util/rand-str 8)
|
||||
id (str "slot__" rs)
|
||||
*el-ref (rum/use-ref nil)]
|
||||
|
||||
(rum/use-effect!
|
||||
(fn []
|
||||
(let [timer (js/setTimeout
|
||||
#(plugin-handler/hook-plugin-app type {:slot id :payload payload} nil)
|
||||
100)]
|
||||
(let [timer (js/setTimeout #(callback {:type type :slot id :payload payload}) 50)]
|
||||
#(js/clearTimeout timer)))
|
||||
[id])
|
||||
|
||||
@@ -962,6 +960,10 @@
|
||||
:ref *el-ref
|
||||
:on-mouse-down (fn [e] (util/stop e))})])))
|
||||
|
||||
(rum/defc hook-block-slot < rum/static
|
||||
[type block]
|
||||
(hook-ui-slot type {} nil #(plugin-handler/hook-plugin-block-slot block %)))
|
||||
|
||||
(rum/defc ui-item-renderer
|
||||
[pid type {:keys [key template prefix]}]
|
||||
(let [*el (rum/use-ref nil)
|
||||
|
||||
@@ -559,10 +559,13 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 3px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
label {
|
||||
padding-right: 15px;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
input {
|
||||
@@ -895,6 +898,17 @@ html[data-theme='dark'] {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
.block-slotted-body {
|
||||
.lsp-hook-ui-slot {
|
||||
display: block;
|
||||
|
||||
[data-injected-ui] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ui__modal[label=plugins-dashboard] {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
[:div
|
||||
[:h1.title
|
||||
"Input command"]
|
||||
[:div.mt-4.mb-4.relative.rounded-md.shadow-sm.max-w-xs
|
||||
[:div.mt-4.mb-4.relative.rounded-md.shadow-sm
|
||||
[:input#run-command.form-input.font-mono.block.w-full.sm:text-sm.sm:leading-5
|
||||
{:autoFocus true
|
||||
:on-key-down util/stop-propagation
|
||||
|
||||
@@ -115,46 +115,48 @@
|
||||
id (:id highlight)
|
||||
new? (nil? id)
|
||||
content (:content highlight)
|
||||
text? (string/blank? (:image content))
|
||||
area? (not (string/blank? (:image content)))
|
||||
action-fn! (fn [action clear?]
|
||||
(when-let [action (and action (name action))]
|
||||
(case action
|
||||
"ref"
|
||||
(pdf-assets/copy-hl-ref! highlight)
|
||||
(let [highlight (if (fn? highlight) (highlight) highlight)
|
||||
content (:content highlight)]
|
||||
(case action
|
||||
"ref"
|
||||
(pdf-assets/copy-hl-ref! highlight)
|
||||
|
||||
"copy"
|
||||
(do
|
||||
(util/copy-to-clipboard!
|
||||
(or (:text content) (pdf-utils/fix-selection-text-breakline (.toString selection))))
|
||||
(pdf-utils/clear-all-selection))
|
||||
"copy"
|
||||
(do
|
||||
(util/copy-to-clipboard!
|
||||
(or (:text content) (pdf-utils/fix-selection-text-breakline (.toString selection))))
|
||||
(pdf-utils/clear-all-selection))
|
||||
|
||||
"link"
|
||||
(pdf-assets/goto-block-ref! highlight)
|
||||
"link"
|
||||
(pdf-assets/goto-block-ref! highlight)
|
||||
|
||||
"del"
|
||||
(do
|
||||
(del-hl! highlight)
|
||||
(pdf-assets/del-ref-block! highlight)
|
||||
(pdf-assets/unlink-hl-area-image$ viewer (:pdf/current @state/state) highlight))
|
||||
"del"
|
||||
(do
|
||||
(del-hl! highlight)
|
||||
(pdf-assets/del-ref-block! highlight)
|
||||
(pdf-assets/unlink-hl-area-image$ viewer (:pdf/current @state/state) highlight))
|
||||
|
||||
"hook"
|
||||
:dune
|
||||
"hook"
|
||||
:dune
|
||||
|
||||
;; colors
|
||||
(let [properties {:color action}]
|
||||
(if-not id
|
||||
;; add highlight
|
||||
(let [highlight (merge (if (fn? highlight) (highlight) highlight)
|
||||
{:id (pdf-utils/gen-uuid)
|
||||
:properties properties})]
|
||||
(add-hl! highlight)
|
||||
(pdf-utils/clear-all-selection)
|
||||
(pdf-assets/copy-hl-ref! highlight))
|
||||
;; colors
|
||||
(let [properties {:color action}]
|
||||
(if-not id
|
||||
;; add highlight
|
||||
(let [highlight (merge highlight
|
||||
{:id (pdf-utils/gen-uuid)
|
||||
:properties properties})]
|
||||
(add-hl! highlight)
|
||||
(pdf-utils/clear-all-selection)
|
||||
(pdf-assets/copy-hl-ref! highlight))
|
||||
|
||||
;; update highlight
|
||||
(upd-hl! (assoc highlight :properties properties)))
|
||||
;; update highlight
|
||||
(upd-hl! (assoc highlight :properties properties)))
|
||||
|
||||
(reset! *highlight-last-color (keyword action))))
|
||||
(reset! *highlight-last-color (keyword action)))))
|
||||
|
||||
(and clear? (js/setTimeout #(clear-ctx-tip!) 68))))]
|
||||
|
||||
@@ -185,20 +187,20 @@
|
||||
|
||||
(and id [:li.item {:data-action "ref"} (t :pdf/copy-ref)])
|
||||
|
||||
(and (not (:image content)) [:li.item {:data-action "copy"} (t :pdf/copy-text)])
|
||||
(and (not area?) [:li.item {:data-action "copy"} (t :pdf/copy-text)])
|
||||
|
||||
(and id [:li.item {:data-action "link"} (t :pdf/linked-ref)])
|
||||
|
||||
(and id [:li.item {:data-action "del"} (t :delete)])
|
||||
|
||||
(when (and config/lsp-enabled? text?)
|
||||
(when (and config/lsp-enabled? (not area?))
|
||||
(for [[_ {:keys [key label extras] :as _cmd} action pid]
|
||||
(state/get-plugins-commands-with-type :highlight-context-menu-item)]
|
||||
[:li.item {:key key
|
||||
:data-action "hook"
|
||||
:on-click #(do
|
||||
:on-click #(let [highlight (if (fn? highlight) (highlight) highlight)]
|
||||
(commands/exec-plugin-simple-command!
|
||||
pid {:key key :content content :point point} action)
|
||||
pid {:key key :content (:content highlight) :point point} action)
|
||||
|
||||
(when (true? (:clearSelection extras))
|
||||
(pdf-utils/clear-all-selection)))}
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
[frontend.handler.search :as search-handler]
|
||||
[frontend.handler.ui :as ui-handler]
|
||||
[frontend.handler.user :as user-handler]
|
||||
[frontend.handler.shell :as shell-handler]
|
||||
[frontend.handler.web.nfs :as nfs-handler]
|
||||
[frontend.mobile.core :as mobile]
|
||||
[frontend.mobile.util :as mobile-util]
|
||||
@@ -918,6 +919,10 @@
|
||||
[:p "Don't forget to re-index your graph when all the conflicts are resolved."]]
|
||||
:status :error}]))
|
||||
|
||||
(defmethod handle :run/cli-command [[_ command content]]
|
||||
(when (and command (not (string/blank? content)))
|
||||
(shell-handler/run-cli-command-wrapper! command content)))
|
||||
|
||||
(defn run!
|
||||
[]
|
||||
(let [chan (state/get-events-chan)]
|
||||
|
||||
@@ -24,11 +24,11 @@
|
||||
[input]
|
||||
(when input
|
||||
(walk/postwalk
|
||||
(fn [a]
|
||||
(cond
|
||||
(keyword? a) (csk/->camelCase (name a))
|
||||
(uuid? a) (str a)
|
||||
:else a)) input)))
|
||||
(fn [a]
|
||||
(cond
|
||||
(keyword? a) (csk/->camelCase (name a))
|
||||
(uuid? a) (str a)
|
||||
:else a)) input)))
|
||||
|
||||
(defn invoke-exported-api
|
||||
[type & args]
|
||||
@@ -71,42 +71,42 @@
|
||||
[refresh?]
|
||||
(if (or refresh? (nil? (:plugin/marketplace-pkgs @state/state)))
|
||||
(p/create
|
||||
(fn [resolve reject]
|
||||
(let [on-ok (fn [res]
|
||||
(if-let [res (and res (bean/->clj res))]
|
||||
(let [pkgs (:packages res)]
|
||||
(state/set-state! :plugin/marketplace-pkgs pkgs)
|
||||
(resolve pkgs))
|
||||
(reject nil)))]
|
||||
(if (state/http-proxy-enabled-or-val?)
|
||||
(-> (ipc/ipc :httpFetchJSON plugins-url)
|
||||
(p/then on-ok)
|
||||
(p/catch reject))
|
||||
(util/fetch plugins-url on-ok reject)))))
|
||||
(fn [resolve reject]
|
||||
(let [on-ok (fn [res]
|
||||
(if-let [res (and res (bean/->clj res))]
|
||||
(let [pkgs (:packages res)]
|
||||
(state/set-state! :plugin/marketplace-pkgs pkgs)
|
||||
(resolve pkgs))
|
||||
(reject nil)))]
|
||||
(if (state/http-proxy-enabled-or-val?)
|
||||
(-> (ipc/ipc :httpFetchJSON plugins-url)
|
||||
(p/then on-ok)
|
||||
(p/catch reject))
|
||||
(util/fetch plugins-url on-ok reject)))))
|
||||
(p/resolved (:plugin/marketplace-pkgs @state/state))))
|
||||
|
||||
(defn load-marketplace-stats
|
||||
[refresh?]
|
||||
(if (or refresh? (nil? (:plugin/marketplace-stats @state/state)))
|
||||
(p/create
|
||||
(fn [resolve reject]
|
||||
(let [on-ok (fn [^js res]
|
||||
(if-let [res (and res (bean/->clj res))]
|
||||
(do
|
||||
(state/set-state!
|
||||
:plugin/marketplace-stats
|
||||
(into {} (map (fn [[k stat]]
|
||||
[k (assoc stat
|
||||
:total_downloads
|
||||
(reduce (fn [a b] (+ a (get b 2))) 0 (:releases stat)))])
|
||||
res)))
|
||||
(resolve nil))
|
||||
(reject nil)))]
|
||||
(if (state/http-proxy-enabled-or-val?)
|
||||
(-> (ipc/ipc :httpFetchJSON stats-url)
|
||||
(p/then on-ok)
|
||||
(p/catch reject))
|
||||
(util/fetch stats-url on-ok reject)))))
|
||||
(fn [resolve reject]
|
||||
(let [on-ok (fn [^js res]
|
||||
(if-let [res (and res (bean/->clj res))]
|
||||
(do
|
||||
(state/set-state!
|
||||
:plugin/marketplace-stats
|
||||
(into {} (map (fn [[k stat]]
|
||||
[k (assoc stat
|
||||
:total_downloads
|
||||
(reduce (fn [a b] (+ a (get b 2))) 0 (:releases stat)))])
|
||||
res)))
|
||||
(resolve nil))
|
||||
(reject nil)))]
|
||||
(if (state/http-proxy-enabled-or-val?)
|
||||
(-> (ipc/ipc :httpFetchJSON stats-url)
|
||||
(p/then on-ok)
|
||||
(p/catch reject))
|
||||
(util/fetch stats-url on-ok reject)))))
|
||||
(p/resolved nil)))
|
||||
|
||||
(defn check-or-update-marketplace-plugin
|
||||
@@ -114,32 +114,46 @@
|
||||
(when-not (and (:plugin/installing @state/state)
|
||||
(not (plugin-common-handler/installed? id)))
|
||||
(p/catch
|
||||
(p/then
|
||||
(do (state/set-state! :plugin/installing pkg)
|
||||
(p/catch
|
||||
(load-marketplace-plugins false)
|
||||
(fn [^js e]
|
||||
(state/reset-all-updates-state)
|
||||
(throw e))))
|
||||
(fn [mfts]
|
||||
(p/then
|
||||
(do (state/set-state! :plugin/installing pkg)
|
||||
(p/catch
|
||||
(load-marketplace-plugins false)
|
||||
(fn [^js e]
|
||||
(state/reset-all-updates-state)
|
||||
(throw e))))
|
||||
(fn [mfts]
|
||||
|
||||
(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))
|
||||
(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 e)
|
||||
(state/set-state! :plugin/installing nil)
|
||||
(js/console.error e)))))
|
||||
(fn [^js e]
|
||||
(error-handler e)
|
||||
(state/set-state! :plugin/installing nil)
|
||||
(js/console.error e)))))
|
||||
|
||||
(defn get-plugin-inst
|
||||
[id]
|
||||
[pid]
|
||||
(try
|
||||
(js/LSPluginCore.ensurePlugin (name id))
|
||||
(js/LSPluginCore.ensurePlugin (name pid))
|
||||
(catch :default _e
|
||||
nil)))
|
||||
|
||||
(defn call-plugin-user-model!
|
||||
[pid key args]
|
||||
(when-let [^js pl (get-plugin-inst pid)]
|
||||
(let [^js caller (.-caller pl)]
|
||||
(.apply (.-callUserModelAsync caller) caller (bean/->js (list* (name key) args))))))
|
||||
|
||||
(defn call-plugin-user-command!
|
||||
[pid key args]
|
||||
(when-let [commands (and key (seq (get (:plugin/simple-commands @state/state) (keyword pid))))]
|
||||
(when-let [matched (medley/find-first #(= (:key (second %)) key) commands)]
|
||||
(let [[_ cmd action pid] matched]
|
||||
(state/pub-event!
|
||||
[:exec-plugin-cmd {:type type :key key :pid pid :cmd (assoc cmd :args args) :action action}])))))
|
||||
|
||||
(defn open-updates-downloading
|
||||
[]
|
||||
(when (and (not (:plugin/updates-downloading? @state/state))
|
||||
@@ -168,7 +182,7 @@
|
||||
|
||||
(defn setup-install-listener!
|
||||
[]
|
||||
(let [channel (name :lsp-installed)
|
||||
(let [channel (name :lsp-installed)
|
||||
listener (fn [^js _ ^js e]
|
||||
(js/console.debug :lsp-installed e)
|
||||
|
||||
@@ -205,7 +219,7 @@
|
||||
[(str (t :plugin/up-to-date) " :)") :success]
|
||||
|
||||
[error-code :error])
|
||||
pending? (seq (:plugin/updates-pending @state/state))]
|
||||
pending? (seq (:plugin/updates-pending @state/state))]
|
||||
|
||||
(if (and only-check pending?)
|
||||
(state/consume-updates-coming-plugin payload false)
|
||||
@@ -217,10 +231,10 @@
|
||||
|
||||
;; notify human tips
|
||||
(notification/show!
|
||||
(str
|
||||
(if (= :error type) "[Error]" "")
|
||||
(str "<" (:id payload) "> ")
|
||||
msg) type)))
|
||||
(str
|
||||
(if (= :error type) "[Error]" "")
|
||||
(str "<" (:id payload) "> ")
|
||||
msg) type)))
|
||||
|
||||
(js/console.error payload))
|
||||
|
||||
@@ -272,18 +286,18 @@
|
||||
(get keybinding-mode-handler-map (keyword mode)))
|
||||
:action (fn []
|
||||
(state/pub-event!
|
||||
[:exec-plugin-cmd {:type type :key key :pid pid :cmd cmd :action action}]))}]
|
||||
[:exec-plugin-cmd {:type type :key key :pid pid :cmd cmd :action action}]))}]
|
||||
palette-cmd))
|
||||
|
||||
(defn simple-cmd-keybinding->shortcut-args
|
||||
[pid key keybinding]
|
||||
(let [id (keyword (str "plugin." pid "/" key))
|
||||
(let [id (keyword (str "plugin." pid "/" key))
|
||||
binding (:binding keybinding)
|
||||
binding (if util/mac?
|
||||
(or (:mac keybinding) binding)
|
||||
binding)
|
||||
mode (or (:mode keybinding) :global)
|
||||
mode (get keybinding-mode-handler-map (keyword mode))]
|
||||
mode (or (:mode keybinding) :global)
|
||||
mode (get keybinding-mode-handler-map (keyword mode))]
|
||||
[mode id {:binding binding}]))
|
||||
|
||||
(defn register-plugin-simple-command
|
||||
@@ -320,7 +334,7 @@
|
||||
(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}))
|
||||
(fnil assoc {}) key (merge opts {:pid pid}))
|
||||
true)))))
|
||||
|
||||
(defn unregister-plugin-resources
|
||||
@@ -350,7 +364,7 @@
|
||||
[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})
|
||||
{:key key :edit edit :before before :subs subs :render render})
|
||||
(swap! *fenced-code-providers conj pid)
|
||||
#(swap! *fenced-code-providers disj pid)))
|
||||
|
||||
@@ -366,7 +380,7 @@
|
||||
[pid type {:keys [enhancer] :as _opts}]
|
||||
(when-let [key (and type (keyword type))]
|
||||
(register-plugin-resources pid :extensions-enhancers
|
||||
{:key key :enhancer enhancer})
|
||||
{:key key :enhancer enhancer})
|
||||
(swap! *extensions-enhancer-providers conj pid)
|
||||
#(swap! *extensions-enhancer-providers disj pid)))
|
||||
|
||||
@@ -411,11 +425,11 @@
|
||||
(when-not (string/blank? content)
|
||||
(let [content (if-not (string/blank? url)
|
||||
(string/replace
|
||||
content #"!\[[^\]]*\]\((.*?)\s*(\"(?:.*[^\"])\")?\s*\)"
|
||||
(fn [[matched link]]
|
||||
(if (and link (not (string/starts-with? link "http")))
|
||||
(string/replace matched link (util/node-path.join url link))
|
||||
matched)))
|
||||
content #"!\[[^\]]*\]\((.*?)\s*(\"(?:.*[^\"])\")?\s*\)"
|
||||
(fn [[matched link]]
|
||||
(if (and link (not (string/starts-with? link "http")))
|
||||
(string/replace matched link (util/node-path.join url link))
|
||||
matched)))
|
||||
content)]
|
||||
(format/to-html content :markdown (gp-mldoc/default-config :markdown))))
|
||||
(catch :default e
|
||||
@@ -429,9 +443,9 @@
|
||||
;; local
|
||||
(-> (p/let [content (invoke-exported-api "load_plugin_readme" url)
|
||||
content (parse-user-md-content content item)]
|
||||
(and (string/blank? (string/trim content)) (throw nil))
|
||||
(state/set-state! :plugin/active-readme [content item])
|
||||
(state/set-sub-modal! (fn [_] (display))))
|
||||
(and (string/blank? (string/trim content)) (throw nil))
|
||||
(state/set-state! :plugin/active-readme [content item])
|
||||
(state/set-sub-modal! (fn [_] (display))))
|
||||
(p/catch #(do (js/console.warn %)
|
||||
(notification/show! "No README content." :warn))))
|
||||
;; market
|
||||
@@ -441,8 +455,8 @@
|
||||
[]
|
||||
(when util/electron?
|
||||
(p/let [path (ipc/ipc "openDialog")]
|
||||
(when-not (:plugin/selected-unpacked-pkg @state/state)
|
||||
(state/set-state! :plugin/selected-unpacked-pkg path)))))
|
||||
(when-not (:plugin/selected-unpacked-pkg @state/state)
|
||||
(state/set-state! :plugin/selected-unpacked-pkg path)))))
|
||||
|
||||
(defn reset-unpacked-state
|
||||
[]
|
||||
@@ -482,6 +496,11 @@
|
||||
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 hook-plugin-block-slot
|
||||
[block payload]
|
||||
(when-let [type (and block (str "slot:" (:block/uuid block)))]
|
||||
(hook-plugin-editor type (merge payload block) nil)))
|
||||
|
||||
(defn get-ls-dotdir-root
|
||||
[]
|
||||
(ipc/ipc "getLogseqDotDirRoot"))
|
||||
@@ -533,11 +552,11 @@
|
||||
(defn- get-user-default-plugins
|
||||
[]
|
||||
(p/catch
|
||||
(p/let [files ^js (ipc/ipc "getUserDefaultPlugins")
|
||||
files (js->clj files)]
|
||||
(map #(hash-map :url %) files))
|
||||
(fn [e]
|
||||
(js/console.error e))))
|
||||
(p/let [files ^js (ipc/ipc "getUserDefaultPlugins")
|
||||
files (js->clj files)]
|
||||
(map #(hash-map :url %) files))
|
||||
(fn [e]
|
||||
(js/console.error e))))
|
||||
|
||||
(defn check-enabled-for-updates
|
||||
[theme?]
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
[promesa.core :as p]
|
||||
[frontend.db :as db]
|
||||
[frontend.state :as state]
|
||||
[frontend.config :as config]))
|
||||
[frontend.config :as config]
|
||||
[frontend.util :as util]))
|
||||
|
||||
(defn run-git-command!
|
||||
[command]
|
||||
@@ -17,14 +18,15 @@
|
||||
[command]
|
||||
(ipc/ipc "runGitWithinCurrentGraph" command))
|
||||
|
||||
;; TODO: export to pdf/html/word
|
||||
(defn run-pandoc-command!
|
||||
[command]
|
||||
(ipc/ipc "runPandoc" command))
|
||||
(defn run-cli-command!
|
||||
[command args]
|
||||
(ipc/ipc :runCli {:command command
|
||||
:args args
|
||||
:returnResult true}))
|
||||
|
||||
(defn wrap-notification!
|
||||
[command f args]
|
||||
(p/let [result (f args)]
|
||||
(p/let [result (f command args)]
|
||||
(notification/show!
|
||||
(if (string/blank? result)
|
||||
[:p [:code.mr-1 (str command " " args) ]
|
||||
@@ -33,22 +35,29 @@
|
||||
:success
|
||||
false)))
|
||||
|
||||
(def commands-denylist
|
||||
#{"rm" "mv" "rename" "dd" ">" "command" "sudo"})
|
||||
|
||||
(defn run-command!
|
||||
[command]
|
||||
(let [[command args] (gp-util/split-first " " command)
|
||||
command (and command (string/lower-case command))]
|
||||
(when (and (not (string/blank? command)) (not (string/blank? args)))
|
||||
(let [args (string/trim args)]
|
||||
(case (keyword command)
|
||||
:git
|
||||
(wrap-notification! command run-git-command! args)
|
||||
(let [[command args]
|
||||
(if (and (string? command) (string/includes? command " "))
|
||||
(gp-util/split-first " " command)
|
||||
[command ""])
|
||||
command (and command (string/lower-case command))
|
||||
args (-> args str string/trim)]
|
||||
(when-not (string/blank? command)
|
||||
(cond
|
||||
(contains? commands-denylist command)
|
||||
(notification/show!
|
||||
[:div (str command " is too dangerous!")]
|
||||
:error)
|
||||
|
||||
;; :pandoc
|
||||
;; (wrap-notification! command run-pandoc-command! args)
|
||||
(= "git" command)
|
||||
(wrap-notification! command (fn [_ args] (run-git-command! args)) args)
|
||||
|
||||
(notification/show!
|
||||
[:div (str command " is not supported yet!")]
|
||||
:error))))))
|
||||
:else
|
||||
(run-cli-command! command args)))))
|
||||
|
||||
;; git show $REV:$FILE
|
||||
(defn- get-versioned-file-content
|
||||
@@ -90,3 +99,11 @@
|
||||
(notification/show!
|
||||
[:div "git config successfully!"]
|
||||
:success)))
|
||||
|
||||
(defn run-cli-command-wrapper!
|
||||
[command content]
|
||||
(let [args (case command
|
||||
"alda" (util/format "play -c \"%s\"" content)
|
||||
;; TODO: plugin slot
|
||||
content)]
|
||||
(run-cli-command! command args)))
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
(ns frontend.modules.layout.core
|
||||
(:require [cljs-bean.core :as bean]
|
||||
[goog.object :as gobj]
|
||||
[frontend.util :as util]))
|
||||
|
||||
(defonce *movable-containers (atom {}))
|
||||
|
||||
(defn- calc-layout-data
|
||||
[^js cnt ^js _evt]
|
||||
(.toJSON (.getBoundingClientRect cnt)))
|
||||
(let [^js o (.toJSON (.getBoundingClientRect cnt))]
|
||||
(set! (.-vw o) (gobj/get js/visualViewport "width"))
|
||||
(set! (.-vh o) (gobj/get js/visualViewport "height"))
|
||||
o))
|
||||
|
||||
(defn ^:export move-container-to-top
|
||||
[identity]
|
||||
|
||||
@@ -1437,7 +1437,7 @@ Similar to re-frame subscriptions"
|
||||
[value]
|
||||
(set-state! :network/online? value))
|
||||
|
||||
(defn get-plugins-commands
|
||||
(defn get-plugins-slash-commands
|
||||
[]
|
||||
(mapcat seq (flatten (vals (:plugin/installed-slash-commands @state)))))
|
||||
|
||||
@@ -1542,6 +1542,12 @@ Similar to re-frame subscriptions"
|
||||
(set-state! [:plugin/installed-hooks hook-or-all] (disj coll pid))))
|
||||
true))
|
||||
|
||||
(defn slot-hook-exist?
|
||||
[uuid]
|
||||
(when-let [type (and uuid (string/replace (str uuid) "-" "_"))]
|
||||
(when-let [hooks (sub :plugin/installed-hooks)]
|
||||
(contains? hooks (str "hook:editor:slot_" type)))))
|
||||
|
||||
(defn active-tldraw-app
|
||||
[]
|
||||
(when-let [tldraw-el (.closest js/document.activeElement ".logseq-tldraw[data-tlapp]")]
|
||||
|
||||
@@ -118,25 +118,25 @@
|
||||
;; get app base info
|
||||
[]
|
||||
(bean/->js
|
||||
(normalize-keyword-for-json
|
||||
{:version fv/version})))
|
||||
(normalize-keyword-for-json
|
||||
{:version fv/version})))
|
||||
|
||||
(def ^:export get_user_configs
|
||||
(fn []
|
||||
(bean/->js
|
||||
(normalize-keyword-for-json
|
||||
{:preferred-language (:preferred-language @state/state)
|
||||
:preferred-theme-mode (:ui/theme @state/state)
|
||||
:preferred-format (state/get-preferred-format)
|
||||
:preferred-workflow (state/get-preferred-workflow)
|
||||
:preferred-todo (state/get-preferred-todo)
|
||||
: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?)
|
||||
:enabled-journals (state/enable-journals?)
|
||||
:enabled-flashcards (state/enable-flashcards?)
|
||||
:me (state/get-me)}))))
|
||||
(normalize-keyword-for-json
|
||||
{:preferred-language (:preferred-language @state/state)
|
||||
:preferred-theme-mode (:ui/theme @state/state)
|
||||
:preferred-format (state/get-preferred-format)
|
||||
:preferred-workflow (state/get-preferred-workflow)
|
||||
:preferred-todo (state/get-preferred-todo)
|
||||
: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?)
|
||||
:enabled-journals (state/enable-journals?)
|
||||
:enabled-flashcards (state/enable-flashcards?)
|
||||
:me (state/get-me)}))))
|
||||
|
||||
(def ^:export get_current_graph_configs
|
||||
(fn []
|
||||
@@ -188,7 +188,7 @@
|
||||
path (util/node-path.join path "package.json")]
|
||||
(fs/write-file! repo "" path (js/JSON.stringify data nil 2) {:skip-compare? true}))))
|
||||
|
||||
(def ^:export save_focused_code_editor
|
||||
(def ^:export save_focused_code_editor_content
|
||||
(fn []
|
||||
(code-handler/save-code-editor!)))
|
||||
|
||||
@@ -277,7 +277,7 @@
|
||||
(def ^:export read_plugin_storage_file
|
||||
(fn [plugin-id file assets?]
|
||||
(let [plugin-id (util/node-path.basename plugin-id)
|
||||
sub-root (util/node-path.join "storages" plugin-id)]
|
||||
sub-root (util/node-path.join "storages" plugin-id)]
|
||||
(if (true? assets?)
|
||||
(read_assetsdir_file file sub-root)
|
||||
(read_dotdir_file file sub-root)))))
|
||||
@@ -285,7 +285,7 @@
|
||||
(def ^:export unlink_plugin_storage_file
|
||||
(fn [plugin-id file assets?]
|
||||
(let [plugin-id (util/node-path.basename plugin-id)
|
||||
sub-root (util/node-path.join "storages" plugin-id)]
|
||||
sub-root (util/node-path.join "storages" plugin-id)]
|
||||
(if (true? assets?)
|
||||
(unlink_assetsdir_file! file sub-root)
|
||||
(unlink_dotdir_file! file sub-root)))))
|
||||
@@ -327,7 +327,7 @@
|
||||
(p/let [repo ""
|
||||
path (plugin-handler/get-ls-dotdir-root)
|
||||
path (util/node-path.join path "preferences.json")
|
||||
_ (fs/create-if-not-exists repo "" path)
|
||||
_ (fs/create-if-not-exists repo "" path)
|
||||
json (fs/read-file "" path)
|
||||
json (if (string/blank? json) "{}" json)]
|
||||
(js/JSON.parse json))))
|
||||
@@ -338,7 +338,7 @@
|
||||
(p/let [repo ""
|
||||
path (plugin-handler/get-ls-dotdir-root)
|
||||
path (util/node-path.join path "preferences.json")]
|
||||
(fs/write-file! repo "" path (js/JSON.stringify data nil 2) {:skip-compare? true})))))
|
||||
(fs/write-file! repo "" path (js/JSON.stringify data nil 2) {:skip-compare? true})))))
|
||||
|
||||
(def ^:export load_plugin_user_settings
|
||||
;; results [path data]
|
||||
@@ -356,18 +356,18 @@
|
||||
(fn [pid ^js cmd-actions]
|
||||
(when-let [[cmd actions] (bean/->clj cmd-actions)]
|
||||
(plugin-handler/register-plugin-slash-command
|
||||
pid [cmd (mapv #(into [(keyword (first %))]
|
||||
(rest %)) actions)]))))
|
||||
pid [cmd (mapv #(into [(keyword (first %))]
|
||||
(rest %)) actions)]))))
|
||||
|
||||
(def ^:export register_plugin_simple_command
|
||||
(fn [pid ^js cmd-action palette?]
|
||||
(when-let [[cmd action] (bean/->clj cmd-action)]
|
||||
(let [action (assoc action 0 (keyword (first action)))
|
||||
cmd (assoc cmd :key (string/replace (:key cmd) ":" "-"))
|
||||
key (:key cmd)
|
||||
keybinding (:keybinding cmd)
|
||||
(let [action (assoc action 0 (keyword (first action)))
|
||||
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))
|
||||
action' #(state/pub-event! [:exec-plugin-cmd {:type type :key key :pid pid :cmd cmd :action 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)
|
||||
@@ -414,7 +414,7 @@
|
||||
(fn [pid type ^js opts]
|
||||
(when-let [opts (bean/->clj opts)]
|
||||
(plugin-handler/register-plugin-ui-item
|
||||
pid (assoc opts :type type)))))
|
||||
pid (assoc opts :type type)))))
|
||||
|
||||
;; app
|
||||
(def ^:export relaunch
|
||||
@@ -465,16 +465,30 @@
|
||||
(def ^:export push_state
|
||||
(fn [^js k ^js params ^js query]
|
||||
(rfe/push-state
|
||||
(keyword k)
|
||||
(bean/->clj params)
|
||||
(bean/->clj query))))
|
||||
(keyword k)
|
||||
(bean/->clj params)
|
||||
(bean/->clj query))))
|
||||
|
||||
(def ^:export replace_state
|
||||
(fn [^js k ^js params ^js query]
|
||||
(rfe/replace-state
|
||||
(keyword k)
|
||||
(bean/->clj params)
|
||||
(bean/->clj query))))
|
||||
(keyword k)
|
||||
(bean/->clj params)
|
||||
(bean/->clj query))))
|
||||
|
||||
(defn ^:export get_external_plugin
|
||||
[pid]
|
||||
(when-let [^js pl (plugin-handler/get-plugin-inst pid)]
|
||||
(.toJSON pl)))
|
||||
|
||||
(defn ^:export invoke_external_plugin_cmd
|
||||
[pid cmd-group cmd-key cmd-args]
|
||||
(case (keyword cmd-group)
|
||||
:models
|
||||
(plugin-handler/call-plugin-user-model! pid cmd-key cmd-args)
|
||||
|
||||
:commands
|
||||
(plugin-handler/call-plugin-user-command! pid cmd-key cmd-args)))
|
||||
|
||||
;; editor
|
||||
(def ^:export check_editing
|
||||
@@ -543,13 +557,13 @@
|
||||
page
|
||||
(let [properties (bean/->clj properties)
|
||||
{:keys [redirect createFirstBlock format journal]} (bean/->clj opts)
|
||||
name (page-handler/create!
|
||||
name
|
||||
{:redirect? (if (boolean? redirect) redirect true)
|
||||
:journal? journal
|
||||
:create-first-block? (if (boolean? createFirstBlock) createFirstBlock true)
|
||||
:format format
|
||||
:properties properties})]
|
||||
name (page-handler/create!
|
||||
name
|
||||
{:redirect? (if (boolean? redirect) redirect true)
|
||||
:journal? journal
|
||||
:create-first-block? (if (boolean? createFirstBlock) createFirstBlock true)
|
||||
:format format
|
||||
:properties properties})]
|
||||
(db-model/get-page name)))
|
||||
(:db/id)
|
||||
(db-utils/pull)
|
||||
@@ -585,47 +599,47 @@
|
||||
(def ^:export insert_block
|
||||
(fn [block-uuid-or-page-name content ^js opts]
|
||||
(let [{:keys [before sibling focus isPageBlock customUUID properties]} (bean/->clj opts)
|
||||
page-name (and isPageBlock block-uuid-or-page-name)
|
||||
custom-uuid (or customUUID (:id properties))
|
||||
custom-uuid (when custom-uuid (uuid-or-throw-error custom-uuid))
|
||||
edit-block? (if (nil? focus) true focus)
|
||||
_ (when (and custom-uuid (db-model/query-block-by-uuid custom-uuid))
|
||||
(throw (js/Error.
|
||||
(util/format "Custom block UUID already exists (%s)." custom-uuid))))
|
||||
block-uuid (if isPageBlock nil (uuid block-uuid-or-page-name))
|
||||
block-uuid' (if (and (not sibling) before block-uuid)
|
||||
(let [block (db/entity [:block/uuid block-uuid])
|
||||
first-child (db-model/get-by-parent-&-left (db/get-db)
|
||||
(:db/id block)
|
||||
(:db/id block))]
|
||||
(if first-child
|
||||
(:block/uuid first-child)
|
||||
block-uuid))
|
||||
block-uuid)
|
||||
page-name (and isPageBlock block-uuid-or-page-name)
|
||||
custom-uuid (or customUUID (:id properties))
|
||||
custom-uuid (when custom-uuid (uuid-or-throw-error custom-uuid))
|
||||
edit-block? (if (nil? focus) true focus)
|
||||
_ (when (and custom-uuid (db-model/query-block-by-uuid custom-uuid))
|
||||
(throw (js/Error.
|
||||
(util/format "Custom block UUID already exists (%s)." custom-uuid))))
|
||||
block-uuid (if isPageBlock nil (uuid block-uuid-or-page-name))
|
||||
block-uuid' (if (and (not sibling) before block-uuid)
|
||||
(let [block (db/entity [:block/uuid block-uuid])
|
||||
first-child (db-model/get-by-parent-&-left (db/get-db)
|
||||
(:db/id block)
|
||||
(:db/id block))]
|
||||
(if first-child
|
||||
(:block/uuid first-child)
|
||||
block-uuid))
|
||||
block-uuid)
|
||||
insert-at-first-child? (not= block-uuid' block-uuid)
|
||||
[sibling? before?] (if insert-at-first-child?
|
||||
[true true]
|
||||
[sibling before])
|
||||
before? (if (and (false? sibling?) before? (not insert-at-first-child?))
|
||||
false
|
||||
before?)
|
||||
new-block (editor-handler/api-insert-new-block!
|
||||
content
|
||||
{:block-uuid block-uuid'
|
||||
:sibling? sibling?
|
||||
:before? before?
|
||||
:edit-block? edit-block?
|
||||
:page page-name
|
||||
:custom-uuid custom-uuid
|
||||
:properties (merge properties
|
||||
(when custom-uuid {:id custom-uuid}))})]
|
||||
before? (if (and (false? sibling?) before? (not insert-at-first-child?))
|
||||
false
|
||||
before?)
|
||||
new-block (editor-handler/api-insert-new-block!
|
||||
content
|
||||
{:block-uuid block-uuid'
|
||||
:sibling? sibling?
|
||||
:before? before?
|
||||
:edit-block? edit-block?
|
||||
:page page-name
|
||||
:custom-uuid custom-uuid
|
||||
:properties (merge properties
|
||||
(when custom-uuid {:id custom-uuid}))})]
|
||||
(bean/->js (normalize-keyword-for-json new-block)))))
|
||||
|
||||
(def ^:export insert_batch_block
|
||||
(fn [block-uuid ^js batch-blocks ^js opts]
|
||||
(when-let [block (db-model/query-block-by-uuid (uuid-or-throw-error block-uuid))]
|
||||
(when-let [bb (bean/->clj batch-blocks)]
|
||||
(let [bb (if-not (vector? bb) (vector bb) bb)
|
||||
(let [bb (if-not (vector? bb) (vector bb) bb)
|
||||
{:keys [sibling keepUUID]} (bean/->clj opts)
|
||||
keep-uuid? (or keepUUID false)
|
||||
_ (when keep-uuid? (doseq
|
||||
@@ -641,16 +655,16 @@
|
||||
(def ^:export remove_block
|
||||
(fn [block-uuid ^js _opts]
|
||||
(let [includeChildren true
|
||||
repo (state/get-current-repo)]
|
||||
repo (state/get-current-repo)]
|
||||
(editor-handler/delete-block-aux!
|
||||
{:block/uuid (uuid-or-throw-error block-uuid) :repo repo} includeChildren)
|
||||
nil)))
|
||||
|
||||
(def ^:export update_block
|
||||
(fn [block-uuid content ^js _opts]
|
||||
(let [repo (state/get-current-repo)
|
||||
(let [repo (state/get-current-repo)
|
||||
edit-input (state/get-edit-input-id)
|
||||
editing? (and edit-input (string/ends-with? edit-input (str block-uuid)))]
|
||||
editing? (and edit-input (string/ends-with? edit-input (str block-uuid)))]
|
||||
(if editing?
|
||||
(state/set-edit-content! edit-input content)
|
||||
(editor-handler/save-block! repo (uuid-or-throw-error block-uuid) content))
|
||||
@@ -659,16 +673,16 @@
|
||||
(def ^:export move_block
|
||||
(fn [src-block-uuid target-block-uuid ^js opts]
|
||||
(let [{:keys [before children]} (bean/->clj opts)
|
||||
move-to (cond
|
||||
(boolean before)
|
||||
:top
|
||||
move-to (cond
|
||||
(boolean before)
|
||||
:top
|
||||
|
||||
(boolean children)
|
||||
:nested
|
||||
(boolean children)
|
||||
:nested
|
||||
|
||||
:else
|
||||
nil)
|
||||
src-block (db-model/query-block-by-uuid (uuid-or-throw-error src-block-uuid))
|
||||
:else
|
||||
nil)
|
||||
src-block (db-model/query-block-by-uuid (uuid-or-throw-error src-block-uuid))
|
||||
target-block (db-model/query-block-by-uuid (uuid-or-throw-error target-block-uuid))]
|
||||
(editor-dnd-handler/move-blocks nil [src-block] target-block move-to) nil)))
|
||||
|
||||
@@ -680,15 +694,15 @@
|
||||
(when-not (contains? block :block/name)
|
||||
(when-let [uuid (:block/uuid block)]
|
||||
(let [{:keys [includeChildren]} (bean/->clj opts)
|
||||
repo (state/get-current-repo)
|
||||
repo (state/get-current-repo)
|
||||
block (if includeChildren
|
||||
;; nested children results
|
||||
(first (outliner-tree/blocks->vec-tree
|
||||
(db-model/get-block-and-children repo uuid) uuid))
|
||||
(db-model/get-block-and-children repo uuid) uuid))
|
||||
;; attached shallow children
|
||||
(assoc block :block/children
|
||||
(map #(list :uuid (get-in % [:data :block/uuid]))
|
||||
(db/get-block-immediate-children repo uuid))))]
|
||||
(map #(list :uuid (get-in % [:data :block/uuid]))
|
||||
(db/get-block-immediate-children repo uuid))))]
|
||||
(bean/->js (normalize-keyword-for-json block))))))))
|
||||
|
||||
(def ^:export get_current_block
|
||||
@@ -718,15 +732,15 @@
|
||||
(fn [block-uuid ^js opts]
|
||||
(let [block-uuid (uuid-or-throw-error block-uuid)]
|
||||
(when-let [block (db-model/get-block-by-uuid block-uuid)]
|
||||
(let [opts (bean/->clj opts)
|
||||
opts (if (or (string? opts) (boolean? opts)) {:flag opts} opts)
|
||||
{:keys [flag]} opts
|
||||
flag (if (= "toggle" flag)
|
||||
(not (util/collapsed? block))
|
||||
(boolean flag))]
|
||||
(if flag (editor-handler/collapse-block! block-uuid)
|
||||
(editor-handler/expand-block! block-uuid))
|
||||
nil)))))
|
||||
(let [opts (bean/->clj opts)
|
||||
opts (if (or (string? opts) (boolean? opts)) {:flag opts} opts)
|
||||
{:keys [flag]} opts
|
||||
flag (if (= "toggle" flag)
|
||||
(not (util/collapsed? block))
|
||||
(boolean flag))]
|
||||
(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]
|
||||
@@ -766,7 +780,7 @@
|
||||
(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)
|
||||
(let [page-name (:block/name page)
|
||||
ref-blocks (if page-name
|
||||
(db-model/get-page-referenced-blocks-full page-name)
|
||||
(db-model/get-block-referenced-blocks (:block/uuid page)))
|
||||
@@ -802,12 +816,12 @@
|
||||
|
||||
(defn ^:export prepend_block_in_page
|
||||
[uuid-or-page-name content ^js opts]
|
||||
(let [page? (not (util/uuid-string? uuid-or-page-name))
|
||||
(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)}))]
|
||||
_ (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')))
|
||||
@@ -818,18 +832,18 @@
|
||||
|
||||
(defn ^:export append_block_in_page
|
||||
[uuid-or-page-name content ^js opts]
|
||||
(let [page? (not (util/uuid-string? uuid-or-page-name))
|
||||
(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)}))]
|
||||
_ (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}))
|
||||
(when sibling? {:before false}))
|
||||
src (if sibling? (str (:block/uuid block')) uuid-or-page-name)]
|
||||
(insert_block src content (bean/->js opts))))))
|
||||
|
||||
@@ -855,13 +869,18 @@
|
||||
[query & inputs]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(when-let [db (db/get-db repo)]
|
||||
(let [query (cljs.reader/read-string query)
|
||||
(let [query (cljs.reader/read-string query)
|
||||
resolved-inputs (map #(cond
|
||||
(string? %)
|
||||
(some-> % (cljs.reader/read-string) (query-react/resolve-input))
|
||||
|
||||
(fn? %)
|
||||
(fn [& args]
|
||||
(.apply % nil (clj->js (mapv bean/->js args))))
|
||||
|
||||
:else %)
|
||||
inputs)
|
||||
result (apply d/q query db resolved-inputs)]
|
||||
result (apply d/q query db resolved-inputs)]
|
||||
(bean/->js (normalize-keyword-for-json result false))))))
|
||||
|
||||
(defn ^:export custom_query
|
||||
@@ -874,7 +893,7 @@
|
||||
[]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(when-let [db (db/get-db repo)]
|
||||
(let [db-str (if db (db/db->string db) "")
|
||||
(let [db-str (if db (db/db->string db) "")
|
||||
data-str (str "data:text/edn;charset=utf-8," (js/encodeURIComponent db-str))]
|
||||
(when-let [anchor (gdom/getElement "download")]
|
||||
(.setAttribute anchor "href" data-str)
|
||||
@@ -900,18 +919,18 @@
|
||||
(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)
|
||||
(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)))
|
||||
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})])))
|
||||
dir (config/get-repo-dir repo)
|
||||
_ (fs/write-file! repo dir file content {:skip-compare? true})])))
|
||||
|
||||
;; ui
|
||||
(defn ^:export show_msg
|
||||
@@ -921,9 +940,9 @@
|
||||
(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)]
|
||||
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
|
||||
@@ -939,7 +958,7 @@
|
||||
(defn ^:export assets_list_files_of_current_graph
|
||||
[^js exts]
|
||||
(p/let [files (ipc/ipc :getAssetsFiles {:exts exts})]
|
||||
(bean/->js files)))
|
||||
(bean/->js files)))
|
||||
|
||||
;; experiments
|
||||
(defn ^:export exper_load_scripts
|
||||
@@ -947,28 +966,28 @@
|
||||
(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})]]
|
||||
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))))))
|
||||
(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]))))
|
||||
(keyword pid) type (reduce #(assoc %1 %2 (aget opts (name %2))) {}
|
||||
[:edit :before :subs :render]))))
|
||||
|
||||
(defn ^:export exper_register_extensions_enhancer
|
||||
[pid type enhancer]
|
||||
(when-let [^js _pl (and (fn? enhancer) (plugin-handler/get-plugin-inst pid))]
|
||||
(plugin-handler/register-extensions-enhancer
|
||||
(keyword pid) type {:enhancer enhancer})))
|
||||
(keyword pid) type {:enhancer enhancer})))
|
||||
|
||||
(defonce *request-k (volatile! 0))
|
||||
|
||||
@@ -1006,7 +1025,7 @@
|
||||
(defn ^:export force_save_graph
|
||||
[]
|
||||
(p/let [_ (el/persist-dbs!)]
|
||||
true))
|
||||
true))
|
||||
|
||||
(def ^:export make_asset_url editor-handler/make-asset-url)
|
||||
|
||||
|
||||
@@ -1404,6 +1404,11 @@ combined-stream@^1.0.8:
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
command-exists@1.2.9:
|
||||
version "1.2.9"
|
||||
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
|
||||
integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
|
||||
|
||||
commander@2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
|
||||
|
||||
Reference in New Issue
Block a user