From 7f867236d7337b1ac791eb9a2d6211a081bff920 Mon Sep 17 00:00:00 2001 From: charlie Date: Fri, 7 May 2021 16:54:14 +0800 Subject: [PATCH] feat(plugin): merge implementation --- .gitignore | 1 + deps.edn | 3 +- libs/.npmignore | 2 + libs/README.md | 17 + libs/index.d.ts | 5 + libs/package.json | 34 + libs/src/LSPlugin.caller.ts | 286 +++++ libs/src/LSPlugin.core.ts | 1001 +++++++++++++++++ libs/src/LSPlugin.d.ts | 196 ++++ libs/src/LSPlugin.shadow.ts | 116 ++ libs/src/LSPlugin.user.ts | 303 +++++ libs/src/helpers.ts | 279 +++++ libs/tsconfig.json | 88 ++ libs/webpack.config.core.js | 32 + libs/webpack.config.js | 27 + libs/yarn.lock | 1251 +++++++++++++++++++++ src/main/api.cljs | 132 +++ src/main/frontend/components/plugins.cljs | 154 +++ src/main/frontend/components/plugins.css | 185 +++ src/main/frontend/handler/plugin.cljs | 181 +++ 20 files changed, 4292 insertions(+), 1 deletion(-) create mode 100644 libs/.npmignore create mode 100644 libs/README.md create mode 100644 libs/index.d.ts create mode 100644 libs/package.json create mode 100644 libs/src/LSPlugin.caller.ts create mode 100644 libs/src/LSPlugin.core.ts create mode 100644 libs/src/LSPlugin.d.ts create mode 100644 libs/src/LSPlugin.shadow.ts create mode 100644 libs/src/LSPlugin.user.ts create mode 100644 libs/src/helpers.ts create mode 100644 libs/tsconfig.json create mode 100644 libs/webpack.config.core.js create mode 100644 libs/webpack.config.js create mode 100644 libs/yarn.lock create mode 100644 src/main/frontend/components/plugins.cljs create mode 100644 src/main/frontend/components/plugins.css create mode 100644 src/main/frontend/handler/plugin.cljs diff --git a/.gitignore b/.gitignore index 904dc1ac803..f5d5f25ff27 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ strings.csv resources/electron.js .clj-kondo/ .lsp/ +/libs/dist/ diff --git a/deps.edn b/deps.edn index b2da01051a8..801c3db2b5d 100755 --- a/deps.edn +++ b/deps.edn @@ -34,7 +34,8 @@ thheller/shadow-cljs {:mvn/version "2.12.5"} expound/expound {:mvn/version "0.8.6"} com.lambdaisland/glogi {:mvn/version "1.0.116"} - binaryage/devtools {:mvn/version "1.0.2"}} + binaryage/devtools {:mvn/version "1.0.2"} + camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.2"}} :aliases {:cljs {:extra-paths ["src/dev-cljs/" "src/test/" "src/electron/"] :extra-deps {org.clojure/clojurescript {:mvn/version "1.10.844"} diff --git a/libs/.npmignore b/libs/.npmignore new file mode 100644 index 00000000000..ca4e81e138b --- /dev/null +++ b/libs/.npmignore @@ -0,0 +1,2 @@ +src/ +webpack.* \ No newline at end of file diff --git a/libs/README.md b/libs/README.md new file mode 100644 index 00000000000..306fa3e9477 --- /dev/null +++ b/libs/README.md @@ -0,0 +1,17 @@ +## @logseq/libs + +🚀 Logseq SDK libraries [WIP]. + +#### Installation + +```shell +yarn add @logseq/libs +``` + +#### Usage + +Load `logseq` plugin sdk as global namespace + +```js +import "@logseq/libs" +``` \ No newline at end of file diff --git a/libs/index.d.ts b/libs/index.d.ts new file mode 100644 index 00000000000..9e6545a5d29 --- /dev/null +++ b/libs/index.d.ts @@ -0,0 +1,5 @@ +import { ILSPluginUser } from './dist/LSPlugin' + +declare global { + var logseq: ILSPluginUser +} \ No newline at end of file diff --git a/libs/package.json b/libs/package.json new file mode 100644 index 00000000000..0d9e7144280 --- /dev/null +++ b/libs/package.json @@ -0,0 +1,34 @@ +{ + "name": "@logseq/libs", + "version": "0.0.1-alpha.6", + "description": "Logseq SDK libraries", + "main": "dist/lsplugin.user.js", + "typings": "index.d.ts", + "private": false, + "scripts": { + "build:user": "webpack --mode production", + "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 && cp src/*.d.ts dist/ && npm run build:user" + }, + "dependencies": { + "debug": "^4.3.1", + "dompurify": "^2.2.7", + "eventemitter3": "^4.0.7", + "path": "^0.12.7", + "postmate": "^1.5.2", + "snake-case": "^3.0.4" + }, + "devDependencies": { + "@types/debug": "^4.1.5", + "@types/dompurify": "^2.2.1", + "@types/lodash-es": "^4.17.4", + "@types/postmate": "^1.5.1", + "ts-loader": "^8.0.17", + "typescript": "^4.2.2", + "webpack": "^5.24.3", + "webpack-bundle-analyzer": "^4.4.0", + "webpack-cli": "^4.5.0" + } +} diff --git a/libs/src/LSPlugin.caller.ts b/libs/src/LSPlugin.caller.ts new file mode 100644 index 00000000000..fc0a0a508ad --- /dev/null +++ b/libs/src/LSPlugin.caller.ts @@ -0,0 +1,286 @@ +import Postmate from 'postmate' +import EventEmitter from 'eventemitter3' +import { PluginLocal } from './LSPlugin.core' +import Debug from 'debug' +import { deferred } from './helpers' +import { LSPluginShadowFrame } from './LSPlugin.shadow' + +const debug = Debug('LSPlugin:caller') + +type DeferredActor = ReturnType + +export const LSPMSG = '#lspmsg#' +export const LSPMSG_SETTINGS = '#lspmsg#settings#' +export const LSPMSG_SYNC = '#lspmsg#reply#' +export const LSPMSG_READY = '#lspmsg#ready#' +export const LSPMSGFn = (id: string) => `${LSPMSG}${id}` + +/** + * Call between core and user + */ +class LSPluginCaller extends EventEmitter { + private _connected: boolean = false + + private _parent?: Postmate.ParentAPI + private _child?: Postmate.ChildAPI + + private _shadow?: LSPluginShadowFrame + + private _status?: 'pending' | 'timeout' + private _userModel: any = {} + + private _call?: (type: string, payload: any, actor?: DeferredActor) => Promise + private _callUserModel?: (type: string, payload: any) => Promise + + constructor ( + private _pluginLocal: PluginLocal | null + ) { + super() + } + + async connectToChild () { + if (this._connected) return + + const { shadow } = this._pluginLocal! + + if (shadow) { + await this._setupShadowSandbox() + } else { + await this._setupIframeSandbox() + } + } + + async connectToParent (userModel = {}) { + if (this._connected) return + + const caller = this + const isShadowMode = this._pluginLocal != null + + let syncGCTimer: any = 0 + let syncTag = 0 + const syncActors = new Map() + const readyDeferred = deferred() + + const model: any = this._extendUserModel({ + [LSPMSG_READY]: async () => { + await readyDeferred.resolve() + }, + + [LSPMSG_SETTINGS]: async ({ type, payload }) => { + caller.emit('settings:changed', payload) + }, + + [LSPMSG]: async ({ ns, type, payload }: any) => { + debug(`[call from host #${this._pluginLocal?.id}]`, ns, type, payload) + + if (ns && ns.startsWith('hook')) { + caller.emit(`${ns}:${type}`, payload) + return + } + + caller.emit(type, payload) + }, + + [LSPMSG_SYNC]: ({ _sync, result }: any) => { + debug(`sync reply #${_sync}`, result) + if (syncActors.has(_sync)) { + // TODO: handle exception + syncActors.get(_sync)?.resolve(result) + syncActors.delete(_sync) + } + }, + + ...userModel + }) + + if (isShadowMode) { + await readyDeferred.promise + return JSON.parse(JSON.stringify(this._pluginLocal?.toJSON())) + } + + const handshake = new Postmate.Model(model) + + this._status = 'pending' + + await handshake.then(refParent => { + this._child = refParent + this._connected = true + + this._call = async (type, payload = {}, actor) => { + if (actor) { + const tag = ++syncTag + syncActors.set(tag, actor) + payload._sync = tag + + actor.setTag(`async call #${tag}`) + debug('async call #', tag) + } + + refParent.emit(LSPMSGFn(model.baseInfo.id), { type, payload }) + + return actor?.promise as Promise + } + + this._callUserModel = async (type, payload) => { + try { + model[type](payload) + } catch (e) { + debug(`model method #${type} not existed`) + } + } + + // actors GC + syncGCTimer = setInterval(() => { + if (syncActors.size > 100) { + for (const [k, v] of syncActors) { + if (v.settled) { + syncActors.delete(k) + } + } + } + }, 1000 * 60 * 30) + }).finally(() => { + this._status = undefined + }) + + // TODO: timeout + await readyDeferred.promise + + return model.baseInfo + } + + async call (type: any, payload: any = {}) { + // TODO: ? + this.emit(type, payload) + return this._call?.call(this, type, payload) + } + + async callAsync (type: any, payload: any = {}) { + const actor = deferred(1000 * 10) + return this._call?.call(this, type, payload, actor) + } + + async callUserModel (type: string, payload: any = {}) { + return this._callUserModel?.call(this, type, payload) + } + + async _setupIframeSandbox () { + const pl = this._pluginLocal! + + const handshake = new Postmate({ + container: document.body, + url: pl.options.entry!, + classListArray: ['lsp-iframe-sandbox'], + model: { baseInfo: JSON.parse(JSON.stringify(pl.toJSON())) } + }) + + this._status = 'pending' + + // timeout for handshake + let timer + + return new Promise((resolve, reject) => { + timer = setTimeout(() => { + reject(new Error(`handshake Timeout`)) + }, 3 * 1000) // 3secs + + handshake.then(refChild => { + this._parent = refChild + this._connected = true + this.emit('connected') + + refChild.frame.setAttribute('id', pl.id) + refChild.on(LSPMSGFn(pl.id), ({ type, payload }: any) => { + debug(`[call from plugin] `, type, payload) + + this._pluginLocal?.emit(type, payload || {}) + }) + + this._call = async (...args: any) => { + // parent all will get message + await refChild.call(LSPMSGFn(pl.id), { type: args[0], payload: args[1] || {} }) + } + + this._callUserModel = async (...args: any) => { + await refChild.call(args[0], args[1] || {}) + } + + resolve(null) + }).catch(e => { + reject(e) + }).finally(() => { + clearTimeout(timer) + }) + }).catch(e => { + debug('iframe sandbox error', e) + throw e + }).finally(() => { + this._status = undefined + }) + } + + async _setupShadowSandbox () { + const pl = this._pluginLocal! + const shadow = this._shadow = new LSPluginShadowFrame(pl) + + try { + this._status = 'pending' + + await shadow.load() + + this._connected = true + this.emit('connected') + + this._call = async (type, payload = {}, actor) => { + actor && (payload.actor = actor) + + // TODO: support sync call + // @ts-ignore Call in same thread + this._pluginLocal?.emit(type, payload) + + return actor?.promise + } + + this._callUserModel = async (...args: any) => { + const type = args[0] + const payload = args[1] || {} + const fn = this._userModel[type] + + if (typeof fn === 'function') { + await fn.call(null, payload) + } + } + } catch (e) { + debug('shadow sandbox error', e) + throw e + } finally { + this._status = undefined + } + } + + _extendUserModel (model: any) { + return Object.assign(this._userModel, model) + } + + _getSandboxIframeContainer () { + return this._parent?.frame + } + + _getSandboxShadowContainer () { + return this._shadow?.frame + } + + async destroy () { + if (this._parent) { + await this._parent.destroy() + } + + if (this._shadow) { + this._shadow.destroy() + } + } +} + +export { + LSPluginCaller +} diff --git a/libs/src/LSPlugin.core.ts b/libs/src/LSPlugin.core.ts new file mode 100644 index 00000000000..51f37e0ed73 --- /dev/null +++ b/libs/src/LSPlugin.core.ts @@ -0,0 +1,1001 @@ +import EventEmitter from 'eventemitter3' +import { + deepMerge, + setupInjectedStyle, + genID, + setupInjectedTheme, + setupInjectedUI, + deferred, + invokeHostExportedApi, + isObject, withFileProtocol +} from './helpers' +import Debug from 'debug' +import { LSPluginCaller, LSPMSG_READY, LSPMSG_SYNC, LSPMSG, LSPMSG_SETTINGS } from './LSPlugin.caller' +import { + ILSPluginThemeManager, + LSPluginPkgConfig, + StyleOptions, + StyleString, + ThemeOptions, + UIOptions +} from './LSPlugin' +import { snakeCase } from 'snake-case' +import DOMPurify from 'dompurify' +import * as path from 'path' + +const debug = Debug('LSPlugin:core') + +declare global { + interface Window { + LSPluginCore: LSPluginCore + } +} + +type DeferredActor = ReturnType +type LSPluginCoreOptions = { + localUserConfigRoot: string +} + +/** + * User settings + */ +class PluginSettings extends EventEmitter<'change'> { + private _settings: Record = { + disabled: false + } + + constructor (private _userPluginSettings: any) { + super() + + Object.assign(this._settings, _userPluginSettings) + } + + get (k: string): T { + return this._settings[k] + } + + set (k: string | Record, v?: any) { + const o = deepMerge({}, this._settings) + + if (typeof k === 'string') { + if (this._settings[k] == v) return + this._settings[k] = v + } else if (isObject(k)) { + deepMerge(this._settings, k) + } else { + return + } + + this.emit('change', + Object.assign({}, this._settings), o) + } + + toJSON () { + return this._settings + } +} + +class PluginLogger extends EventEmitter<'change'> { + private _logs: Array<[type: string, payload: any]> = [] + + constructor (private _tag: string) { + super() + } + + write (type: string, payload: any[]) { + let 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]) + 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 + } +} + +type UserPreferences = { + theme: ThemeOptions + externals: Array // external plugin locations + + [key: string]: any +} + +type PluginLocalOptions = { + key?: string // Unique from Logseq Plugin Store + entry: string // Plugin main file + url: string // Plugin package fs location + name: string + version: string + mode: 'shadow' | 'iframe' + settings?: PluginSettings + logger?: PluginLogger + + [key: string]: any +} + +type PluginLocalUrl = Pick & { [key: string]: any } +type RegisterPluginOpts = PluginLocalOptions | PluginLocalUrl + +type PluginLocalIdentity = string + +enum PluginLocalLoadStatus { + LOADING = 'loading', + UNLOADING = 'unloading', + LOADED = 'loaded', + UNLOADED = 'unload', + ERROR = 'error' +} + +function initUserSettingsHandlers (pluginLocal: PluginLocal) { + const _ = (label: string): any => `settings:${label}` + + pluginLocal.on(_('update'), (attrs) => { + if (!attrs) return + pluginLocal.settings?.set(attrs) + }) +} + +function initMainUIHandlers (pluginLocal: PluginLocal) { + const _ = (label: string): any => `main-ui:${label}` + + pluginLocal.on(_('visible'), ({ visible, toggle }) => { + const el = pluginLocal.getMainUI() + el?.classList[toggle ? 'toggle' : (visible ? 'add' : 'remove')]('visible') + // pluginLocal.caller!.callUserModel(LSPMSG, { type: _('visible'), payload: visible }) + // auto focus frame + if (!pluginLocal.shadow && el) { + (el as HTMLIFrameElement).contentWindow?.focus() + } + }) + + pluginLocal.on(_('attrs'), (attrs: Record) => { + const el = pluginLocal.getMainUI() + Object.entries(attrs).forEach(([k, v]) => { + el?.setAttribute(k, v) + }) + }) + + pluginLocal.on(_('style'), (style: Record) => { + const el = pluginLocal.getMainUI() + Object.entries(style).forEach(([k, v]) => { + el!.style[k] = v + }) + }) +} + +function initProviderHandlers (pluginLocal: PluginLocal) { + let _ = (label: string): any => `provider:${label}` + let themed = false + + pluginLocal.on(_('theme'), (theme: ThemeOptions) => { + pluginLocal.themeMgr.registerTheme( + pluginLocal.id, + theme + ) + + if (!themed) { + pluginLocal._dispose(() => { + pluginLocal.themeMgr.unregisterTheme(pluginLocal.id) + }) + + themed = true + } + }) + + pluginLocal.on(_('style'), (style: StyleString | StyleOptions) => { + let key: string | undefined + + if (typeof style !== 'string') { + key = style.key + style = style.style + } + + if (!style || !style.trim()) return + + pluginLocal._dispose( + setupInjectedStyle(style, { + 'data-injected-style': key ? `${key}-${pluginLocal.id}` : '', + 'data-ref': pluginLocal.id + }) + ) + }) + + pluginLocal.on(_('ui'), (ui: UIOptions) => { + pluginLocal._onHostMounted(() => { + // safe template + ui.template = DOMPurify.sanitize(ui.template) + + pluginLocal._dispose( + setupInjectedUI.call(pluginLocal, + ui, { + 'data-ref': pluginLocal.id + }) + ) + }) + }) +} + +function initApiProxyHandlers (pluginLocal: PluginLocal) { + let _ = (label: string): any => `api:${label}` + + pluginLocal.on(_('call'), (payload) => { + const rt = invokeHostExportedApi(payload.method, ...payload.args) + const { _sync } = payload + + if (pluginLocal.shadow) { + if (payload.actor) { + payload.actor.resolve(rt) + } + return + } + + if (_sync != null) { + const reply = (result: any) => { + pluginLocal.caller?.callUserModel(LSPMSG_SYNC, { + result, _sync + }) + } + + Promise.resolve(rt).then(reply, reply) + } + }) +} + +class IllegalPluginPackageError extends Error { + constructor (message: string) { + super(message) + this.name = IllegalPluginPackageError.name + } +} + +class ExistedImportedPluginPackageError extends Error { + constructor (message: string) { + super(message) + this.name = ExistedImportedPluginPackageError.name + } +} + +/** + * Host plugin for local + */ +class PluginLocal + extends EventEmitter<'loaded' | 'unloaded' | 'beforeunload' | 'error'> { + + private _disposes: Array<() => Promise> = [] + private _id: PluginLocalIdentity + private _status: PluginLocalLoadStatus = PluginLocalLoadStatus.UNLOADED + private _loadErr?: Error + private _localRoot?: string + private _userSettingsFile?: string + private _caller?: LSPluginCaller + + /** + * @param _options + * @param _themeMgr + * @param _ctx + */ + constructor ( + private _options: PluginLocalOptions, + private _themeMgr: ILSPluginThemeManager, + private _ctx: LSPluginCore + ) { + super() + + this._id = _options.key || genID() + + initUserSettingsHandlers(this) + initMainUIHandlers(this) + initProviderHandlers(this) + initApiProxyHandlers(this) + } + + async _setupUserSettings () { + const { _options } = this + const key = _options.name.replace(/[^a-z0-9]/gi, '_').toLowerCase() + '_' + this.id + const logger = _options.logger = new PluginLogger('Loader') + + try { + const [userSettingsFilePath, userSettings] = await invokeHostExportedApi('load_plugin_user_settings', key) + this._userSettingsFile = userSettingsFilePath + + const settings = _options.settings = new PluginSettings(userSettings) + + // observe settings + settings.on('change', (a, b) => { + debug('linked settings change', a) + + if (!a.disabled && b.disabled) { + // Enable plugin + this.load() + } + + if (a.disabled && !b.disabled) { + // Disable plugin + this.unload() + } + + if (a) { + invokeHostExportedApi(`save_plugin_user_settings`, key, a) + } + }) + } catch (e) { + debug('[load plugin user settings Error]', e) + logger?.error(e) + } + } + + getMainUI (): HTMLElement | undefined { + if (this.shadow) { + return this.caller?._getSandboxShadowContainer() + } + + return this.caller?._getSandboxIframeContainer() + } + + async _preparePackageConfigs () { + const { url } = this._options + let pkg: any + + try { + if (!url) { + throw new Error('Can not resolve package config location') + } + + debug('prepare package root', url) + + pkg = await invokeHostExportedApi('load_plugin_config', url) + + if (!pkg || (pkg = JSON.parse(pkg), !pkg)) { + throw new Error(`Parse package config error #${url}/package.json`) + } + } catch (e) { + throw new IllegalPluginPackageError(e.message) + } + + // Pick legal attrs + ['name', 'author', 'version', 'description'].forEach(k => { + this._options[k] = pkg[k] + }) + + // TODO: How with local protocol + const localRoot = this._localRoot = url + const logseq: Partial = pkg.logseq || {} + const makeFullUrl = (loc, useFileProtocol = false) => { + if (!loc) return + const reg = /^(http|file|assets)/ + if (!reg.test(loc)) { + const url = path.join(localRoot, loc) + loc = reg.test(url) ? url : ('file://' + url) + } + return useFileProtocol ? loc : loc.replace('file:', 'assets:') + } + const validateMain = (main) => main && /\.(js|html)$/.test(main) + + // Entry from main + if (validateMain(pkg.main)) { + this._options.entry = makeFullUrl(pkg.main, true) + + if (logseq.mode) { + this._options.mode = logseq.mode + } + } + + const icon = logseq.icon || pkg.icon + + if (icon) { + this._options.icon = makeFullUrl(icon) + } + + // TODO: strategy for Logseq plugins center + if (logseq.id) { + this._id = logseq.id + } else { + logseq.id = this.id + try { + await invokeHostExportedApi('save_plugin_config', url, { ...pkg, logseq }) + } catch (e) { + debug('[save plugin ID Error] ', e) + } + } + + // Validate id + const { registeredPlugins, isRegistering } = this._ctx + if (isRegistering && registeredPlugins.has(logseq.id)) { + throw new ExistedImportedPluginPackageError('prepare package Error') + } + + return async () => { + try { + // 0. Install Themes + let themes = logseq.themes + + if (themes) { + await this._loadConfigThemes( + Array.isArray(themes) ? themes : [themes] + ) + } + } catch (e) { + debug('[prepare package effect Error]', e) + } + } + } + + async _tryToNormalizeEntry () { + let { entry, settings } = this.options + let devEntry = settings?.get('_devEntry') + + if (devEntry) { + this._options.entry = devEntry + return + } + + if (!entry.endsWith('.js')) return + + let sdkPath = await invokeHostExportedApi('_callApplication', 'getAppPath') + let entryPath = await invokeHostExportedApi( + 'write_user_tmp_file', + `${this._id}_index.html`, + ` + + + + logseq plugin entry + + + +
+ + +`) + + this._options.entry = withFileProtocol(entryPath) + } + + async _loadConfigThemes (themes: Array) { + themes.forEach((options) => { + if (!options.url) return + + if (!options.url.startsWith('http') && this._localRoot) { + options.url = path.join(this._localRoot, options.url) + // file:// for native + if (!options.url.startsWith('file:')) { + options.url = 'assets://' + options.url + } + } + + // @ts-ignore + this.emit('provider:theme', options) + }) + } + + async load (readyIndicator?: DeferredActor) { + if (this.pending) { + return + } + + this._status = PluginLocalLoadStatus.LOADING + this._loadErr = undefined + + try { + let installPackageThemes: () => Promise = () => Promise.resolve() + + if (!this.options.entry) { // Themes package no entry field + installPackageThemes = await this._preparePackageConfigs() + } + + if (!this.settings) { + await this._setupUserSettings() + } + + if (!this.disabled) { + await installPackageThemes.call(null) + } + + if (this.disabled || !this.options.entry) { + return + } + + await this._tryToNormalizeEntry() + + this._caller = new LSPluginCaller(this) + await this._caller.connectToChild() + + const readyFn = () => { + this._caller?.callUserModel(LSPMSG_READY) + } + + if (readyIndicator) { + readyIndicator.promise.then(readyFn) + } else { + readyFn() + } + + this._disposes.push(async () => { + await this._caller?.destroy() + }) + } catch (e) { + debug('[Load Plugin Error] ', e) + this.logger?.error(e) + + this._status = PluginLocalLoadStatus.ERROR + this._loadErr = e + } finally { + if (!this._loadErr) { + this._status = PluginLocalLoadStatus.LOADED + } + } + } + + async reload () { + debug('TODO: reload plugin', this.id) + } + + /** + * @param unregister If true delete plugin files + */ + async unload (unregister: boolean = false) { + if (this.pending) { + return + } + + if (unregister) { + await this.unload() + + if (this.isInstalledInUserRoot) { + debug('TODO: remove plugin local files from user home root :)') + } + + return + } + + try { + this._status = PluginLocalLoadStatus.UNLOADING + + const eventBeforeUnload = {} + + // sync call + try { + this.emit('beforeunload', eventBeforeUnload) + } catch (e) { + console.error('[beforeunload Error]', e) + } + + await this.dispose() + + this.emit('unloaded') + } catch (e) { + debug('[plugin unload Error]', e) + } finally { + this._status = PluginLocalLoadStatus.UNLOADED + } + } + + private async dispose () { + for (const fn of this._disposes) { + try { + fn && (await fn()) + } catch (e) { + console.error(this.debugTag, 'dispose Error', e) + } + } + + // clear + this._disposes = [] + } + + _dispose (fn: any) { + if (!fn) return + this._disposes.push(fn) + } + + _onHostMounted (callback: () => void) { + const actor = this._ctx.hostMountedActor + + if (!actor || actor.settled) { + callback() + } else { + actor?.promise.then(callback) + } + } + + get isInstalledInUserRoot () { + const userRoot = this._ctx.options.localUserConfigRoot + const plugRoot = this._localRoot + return userRoot && plugRoot && plugRoot.startsWith(userRoot) + } + + get loaded () { + return this._status === PluginLocalLoadStatus.LOADED + } + + get pending () { + return [PluginLocalLoadStatus.LOADING, PluginLocalLoadStatus.UNLOADING] + .includes(this._status) + } + + get status (): PluginLocalLoadStatus { + return this._status + } + + get settings () { + return this.options.settings + } + + get logger () { + return this.options.logger + } + + get disabled () { + return this.settings?.get('disabled') + } + + get caller () { + return this._caller + } + + get id (): string { + return this._id + } + + get shadow (): boolean { + return this.options.mode === 'shadow' + } + + get options (): PluginLocalOptions { + return this._options + } + + get themeMgr (): ILSPluginThemeManager { + return this._themeMgr + } + + get debugTag () { + return `[${this._options?.name} #${this._id}]` + } + + get localRoot (): string { + return this._localRoot || this._options.url + } + + get loadErr (): Error | undefined { + return this._loadErr + } + + get userSettingsFile (): string | undefined { + return this._userSettingsFile + } + + toJSON () { + const json = { ...this.options } as any + json.id = this.id + json.err = this.loadErr + json.usf = this.userSettingsFile + return json + } +} + +/** + * Host plugin core + */ +class LSPluginCore + extends EventEmitter<'beforeenable' | 'enabled' | 'beforedisable' | 'disabled' | 'registered' | 'error' | 'unregistered' | + 'theme-changed' | 'theme-selected' | 'settings-changed'> + implements ILSPluginThemeManager { + + private _isRegistering = false + private _readyIndicator?: DeferredActor + private _hostMountedActor: DeferredActor = deferred() + private _userPreferences: Partial = {} + private _registeredThemes = new Map>() + private _registeredPlugins = new Map() + + /** + * @param _options + */ + constructor (private _options: Partial) { + super() + } + + async loadUserPreferences () { + try { + const settings = await invokeHostExportedApi(`load_user_preferences`) + + if (settings) { + Object.assign(this._userPreferences, settings) + } + } catch (e) { + debug('[load user preferences Error]', e) + } + } + + async saveUserPreferences (settings: Partial) { + try { + if (settings) { + Object.assign(this._userPreferences, settings) + } + + await invokeHostExportedApi(`save_user_preferences`, this._userPreferences) + } catch (e) { + debug('[save user preferences Error]', e) + } + } + + async activateUserPreferences () { + const { theme } = this._userPreferences + + // 0. theme + if (theme) { + await this.selectTheme(theme, false) + } + } + + /** + * @param plugins + * @param initial + */ + async register ( + plugins: Array | RegisterPluginOpts, + initial = false + ) { + if (!Array.isArray(plugins)) { + await this.register([plugins]) + return + } + + try { + this._isRegistering = true + + const userConfigRoot = this._options.localUserConfigRoot + const readyIndicator = this._readyIndicator = deferred() + + await this.loadUserPreferences() + + const externals = new Set(this._userPreferences.externals || []) + + if (initial) { + plugins = plugins.concat([...externals].filter(url => { + return !plugins.length || (plugins as RegisterPluginOpts[]).every((p) => !p.entry && (p.url !== url)) + }).map(url => ({ url }))) + } + + for (const pluginOptions of plugins) { + const { url } = pluginOptions as PluginLocalOptions + const pluginLocal = new PluginLocal(pluginOptions as PluginLocalOptions, this, this) + + const timeLabel = `Load plugin #${pluginLocal.id}` + console.time(timeLabel) + + await pluginLocal.load(readyIndicator) + + const { loadErr } = pluginLocal + + if (loadErr) { + debug(`Failed load plugin #`, pluginOptions) + + this.emit('error', loadErr) + + if ( + loadErr instanceof IllegalPluginPackageError || + loadErr instanceof ExistedImportedPluginPackageError) { + // TODO: notify global log system? + continue + } + } + + console.timeEnd(timeLabel) + + pluginLocal.settings?.on('change', (a) => { + this.emit('settings-changed', pluginLocal.id, a) + pluginLocal.caller?.callUserModel(LSPMSG_SETTINGS, { payload: a }) + }) + + this._registeredPlugins.set(pluginLocal.id, pluginLocal) + this.emit('registered', pluginLocal) + + // external plugins + if (!pluginLocal.isInstalledInUserRoot) { + externals.add(url) + } + } + + await this.saveUserPreferences({ externals: Array.from(externals) }) + await this.activateUserPreferences() + + readyIndicator.resolve('ready') + } catch (e) { + console.error(e) + } finally { + this._isRegistering = false + } + } + + async reload (plugins: Array | PluginLocalIdentity) { + if (!Array.isArray(plugins)) { + await this.reload([plugins]) + return + } + + for (const identity of plugins) { + const p = this.ensurePlugin(identity) + await p.reload() + } + } + + async unregister (plugins: Array | PluginLocalIdentity) { + if (!Array.isArray(plugins)) { + await this.unregister([plugins]) + return + } + + const unregisteredExternals: Array = [] + + for (const identity of plugins) { + const p = this.ensurePlugin(identity) + + if (!p.isInstalledInUserRoot) { + unregisteredExternals.push(p.options.url) + } + + await p.unload(true) + + this._registeredPlugins.delete(identity) + this.emit('unregistered', identity) + } + + let externals = this._userPreferences.externals || [] + if (externals.length && unregisteredExternals.length) { + await this.saveUserPreferences({ + externals: externals.filter((it) => { + return !unregisteredExternals.includes(it) + }) + }) + } + } + + async enable (plugin: PluginLocalIdentity) { + const p = this.ensurePlugin(plugin) + if (p.pending) return + + this.emit('beforeenable') + p.settings?.set('disabled', false) + // this.emit('enabled', p) + } + + async disable (plugin: PluginLocalIdentity) { + const p = this.ensurePlugin(plugin) + if (p.pending) return + + this.emit('beforedisable') + p.settings?.set('disabled', true) + // this.emit('disabled', p) + } + + async _hook (ns: string, type: string, payload?: any, pid?: string) { + for (const [_, p] of this._registeredPlugins) { + if (!pid || pid === p.id) { + p.caller?.callUserModel(LSPMSG, { + ns, type: snakeCase(type), payload + }) + } + } + } + + hookApp (type: string, payload?: any, pid?: string) { + this._hook(`hook:app`, type, payload, pid) + } + + hookEditor (type: string, payload?: any, pid?: string) { + this._hook(`hook:editor`, type, payload, pid) + } + + _execDirective (tag: string, ...params: any[]) { + + } + + ensurePlugin (plugin: PluginLocalIdentity | PluginLocal) { + if (plugin instanceof PluginLocal) { + return plugin + } + + const p = this._registeredPlugins.get(plugin) + + if (!p) { + throw new Error(`plugin #${plugin} not existed.`) + } + + return p + } + + hostMounted () { + this._hostMountedActor.resolve() + } + + get registeredPlugins (): Map { + return this._registeredPlugins + } + + get options () { + return this._options + } + + get readyIndicator (): DeferredActor | undefined { + return this._readyIndicator + } + + get hostMountedActor (): DeferredActor { + return this._hostMountedActor + } + + get isRegistering (): boolean { + return this._isRegistering + } + + get themes (): Map> { + return this._registeredThemes + } + + async registerTheme (id: PluginLocalIdentity, opt: ThemeOptions): Promise { + debug('registered Theme #', id, opt) + + if (!id) return + let themes: Array = this._registeredThemes.get(id)! + if (!themes) { + this._registeredThemes.set(id, themes = []) + } + + themes.push(opt) + this.emit('theme-changed', this.themes, { id, ...opt }) + } + + async selectTheme (opt?: ThemeOptions, effect = true): Promise { + setupInjectedTheme(opt?.url) + this.emit('theme-selected', opt) + effect && this.saveUserPreferences({ theme: opt }) + } + + async unregisterTheme (id: PluginLocalIdentity): Promise { + debug('unregistered Theme #', id) + + if (!this._registeredThemes.has(id)) return + this._registeredThemes.delete(id) + this.emit('theme-changed', this.themes, { id }) + } +} + +function setupPluginCore (options: any) { + const pluginCore = new LSPluginCore(options) + + debug('=== 🔗 Setup Logseq Plugin System 🔗 ===') + + window.LSPluginCore = pluginCore +} + +export { + PluginLocal, + setupPluginCore +} diff --git a/libs/src/LSPlugin.d.ts b/libs/src/LSPlugin.d.ts new file mode 100644 index 00000000000..4aeb2c6b227 --- /dev/null +++ b/libs/src/LSPlugin.d.ts @@ -0,0 +1,196 @@ +import EventEmitter from 'eventemitter3' +import { LSPluginCaller } from './LSPlugin.caller' +import { LSPluginUser } from './LSPlugin.user' + +type PluginLocalIdentity = string + +type ThemeOptions = { + name: string + url: string + description?: string + mode?: 'dark' | 'light' + + [key: string]: any +} + +type StyleString = string +type StyleOptions = { + key?: string + style: StyleString +} + +type UIBaseOptions = { + key?: string + replace?: boolean + template: string +} + +type UIPathIdentity = { + path: string // dom selector +} + +type UISlotIdentity = { + slot: string // slot key +} + +type UISlotOptions = UIBaseOptions & UISlotIdentity +type UIPathOptions = UIBaseOptions & UIPathIdentity +type UIOptions = UIPathOptions & UISlotOptions + +interface LSPluginPkgConfig { + id: PluginLocalIdentity + mode: 'shadow' | 'iframe' + themes: Array + icon: string +} + +interface LSPluginBaseInfo { + id: string // should be unique + mode: 'shadow' | 'iframe' + + settings: { + disabled: boolean + [key: string]: any + }, + + [key: string]: any +} + +type IHookEvent = { + [key: string]: any +} + +type IUserHook = (callback: (e: IHookEvent) => void) => void +type IUserSlotHook = (callback: (e: IHookEvent & UISlotIdentity) => void) => void + +interface BlockEntity { + uuid: string + content: string + + [key: string]: any +} + +type BlockIdentity = 'string' | Pick +type SlashCommandActionTag = 'editor/input' | 'editor/hook' | 'editor/clear-current-slash' +type SlashCommandAction = [SlashCommandActionTag, ...args: any] + +interface IAppProxy { + pushState: (k: string, params?: {}) => void + replaceState: (k: string, params?: {}) => void + getUserState: () => Promise + showMsg: (content: string, status?: 'success' | 'warning' | string) => void + setZoomFactor: (factor: number) => void + onThemeModeChanged: IUserHook + onPageFileMounted: IUserSlotHook + onBlockRendererMounted: IUserSlotHook +} + +interface IEditorProxy { + registerSlashCommand: (this: LSPluginUser, tag: string, actions: Array) => boolean + registerBlockContextMenu: (this: LSPluginUser, tag: string, action: () => void) => boolean + + // TODO: Block related APIs + getCurrentBlock: () => Promise + getCurrentPageBlocksTree: () => Promise + + insertBlock: (srcBlock: BlockIdentity, content: string, opts: Partial<{ before: boolean, sibling: boolean, props: {} }>) => Promise + updateBlock: (srcBlock: BlockIdentity, content: string, opts: Partial<{ props: {} }>) => Promise + removeBlock: (srcBlock: BlockIdentity, opts: Partial<{ includeChildren: boolean }>) => Promise + touchBlock: (srcBlock: BlockIdentity) => Promise + moveBlock: (srcBlock: BlockIdentity, targetBlock: BlockIdentity, opts: Partial<{ before: boolean, sibling: boolean }>) => Promise + + updateBlockProperty: (block: BlockIdentity, key: string, value: any) => Promise + removeBlockProperty: (block: BlockIdentity) => Promise +} + +interface IDBProxy { + datascriptQuery: (query: string) => Promise +} + +interface ILSPluginThemeManager extends EventEmitter { + themes: Map> + + registerTheme (id: PluginLocalIdentity, opt: ThemeOptions): Promise + + unregisterTheme (id: PluginLocalIdentity): Promise + + selectTheme (opt?: ThemeOptions): Promise +} + +type LSPluginUserEvents = 'ui:visible:changed' | 'settings:changed' + +interface ILSPluginUser extends EventEmitter { + /** + * Indicate connected with host + */ + connected: boolean + + /** + * Duplex message caller + */ + caller: LSPluginCaller + + /** + * Most from packages + */ + baseInfo: LSPluginBaseInfo + + /** + * Plugin user settings + */ + settings?: LSPluginBaseInfo['settings'] + + /** + * Ready for host connected + */ + ready (model?: Record): Promise + + ready (callback?: (e: any) => void | {}): Promise + + ready (model?: Record, callback?: (e: any) => void | {}): Promise + + /** + * @param model + */ + provideModel (model: Record): this + + /** + * @param theme options + */ + provideTheme (theme: ThemeOptions): this + + /** + * @param style + */ + provideStyle (style: StyleString | StyleOptions): this + + /** + * @param ui options + */ + provideUI (ui: UIOptions): this + + /** + * @param attrs + */ + updateSettings (attrs: Record): void + + /** + * MainUI for index.html + * @param attrs + */ + setMainUIAttrs (attrs: Record): void + + setMainUIInlineStyle (style: CSSStyleDeclaration): void + + showMainUI (): void + + hideMainUI (): void + + toggleMainUI (): void + + isMainUIVisible: boolean + + App: IAppProxy + Editor: IEditorProxy + DB: IDBProxy +} diff --git a/libs/src/LSPlugin.shadow.ts b/libs/src/LSPlugin.shadow.ts new file mode 100644 index 00000000000..2a6cd5bdb3b --- /dev/null +++ b/libs/src/LSPlugin.shadow.ts @@ -0,0 +1,116 @@ +import EventEmitter from 'eventemitter3' +import { PluginLocal } from './LSPlugin.core' +import { LSPluginUser } from './LSPlugin.user' + +// @ts-ignore +const { importHTML, createSandboxContainer } = window.QSandbox || {} + +function userFetch (url, opts) { + if (!url.startsWith('http')) { + url = url.replace('file://', '') + return new Promise(async (resolve, reject) => { + try { + const content = await window.apis.doAction(['readFile', url]) + resolve({ + text () { + return content + } + }) + } catch (e) { + console.error(e) + reject(e) + } + }) + } + + return fetch(url, opts) +} + +class LSPluginShadowFrame extends EventEmitter<'mounted' | 'unmounted'> { + private _frame?: HTMLElement + private _root?: ShadowRoot + private _loaded = false + private _unmountFns: Array<() => Promise> = [] + + constructor ( + private _pluginLocal: PluginLocal + ) { + super() + + _pluginLocal._dispose(() => { + this._unmount() + }) + } + + async load () { + const { name, entry } = this._pluginLocal.options + + if (this.loaded || !entry) return + + const { template, execScripts } = await importHTML(entry, { fetch: userFetch }) + + this._mount(template, document.body) + + const sandbox = createSandboxContainer( + name, { + elementGetter: () => this._root?.firstChild, + } + ) + + const global = sandbox.instance.proxy as any + + global.__shadow_mode__ = true + global.LSPluginLocal = this._pluginLocal + global.LSPluginShadow = this + global.LSPluginUser = global.logseq = new LSPluginUser( + this._pluginLocal.toJSON() as any, + this._pluginLocal.caller! + ) + + // TODO: {mount, unmount} + const execResult: any = await execScripts(global, true) + + this._unmountFns.push(execResult.unmount) + + this._loaded = true + } + + _mount (content: string, container: HTMLElement) { + const frame = this._frame = document.createElement('div') + frame.classList.add('lsp-shadow-sandbox') + frame.id = this._pluginLocal.id + + this._root = frame.attachShadow({ mode: 'open' }) + this._root.innerHTML = `
${content}
` + + container.appendChild(frame) + + this.emit('mounted') + } + + _unmount () { + for (const fn of this._unmountFns) { + fn && fn.call(null) + } + } + + destroy () { + this.frame?.parentNode?.removeChild(this.frame) + } + + get loaded (): boolean { + return this._loaded + } + + get document () { + return this._root?.firstChild as HTMLElement + } + + get frame (): HTMLElement { + return this._frame! + } +} + +export { + LSPluginShadowFrame +} diff --git a/libs/src/LSPlugin.user.ts b/libs/src/LSPlugin.user.ts new file mode 100644 index 00000000000..5f322a56d70 --- /dev/null +++ b/libs/src/LSPlugin.user.ts @@ -0,0 +1,303 @@ +import { deepMerge, invokeHostExportedApi } from './helpers' +import { LSPluginCaller } from './LSPlugin.caller' +import { + IAppProxy, IDBProxy, + IEditorProxy, + ILSPluginUser, + LSPluginBaseInfo, LSPluginUserEvents, SlashCommandAction, + StyleString, + ThemeOptions, + UIOptions +} from './LSPlugin' +import Debug from 'debug' +import { snakeCase } from 'snake-case' +import EventEmitter from 'eventemitter3' + +declare global { + interface Window { + __LSP__HOST__: boolean + logseq: ILSPluginUser + } +} + +const debug = Debug('LSPlugin:user') + +const app: Partial = {} + +let registeredCmdUid = 0 + +const editor: Partial = { + registerSlashCommand ( + this: LSPluginUser, + tag: string, + actions: Array + ) { + debug('Register slash command #', this.baseInfo.id, tag, actions) + + actions = actions.map((it) => { + const [tag, ...args] = it + + switch (tag) { + case 'editor/hook': + let key = args[0] + let fn = () => { + this.caller?.callUserModel(key) + } + + if (typeof key === 'function') { + fn = key + } + + const eventKey = `SlashCommandHook${tag}${++registeredCmdUid}` + + it[1] = eventKey + + // register command listener + this.Editor['on' + eventKey](fn) + break + default: + } + + return it + }) + + this.caller?.call(`api:call`, { + method: 'register-plugin-slash-command', + args: [this.baseInfo.id, [tag, actions]] + }) + + return false + }, + + registerBlockContextMenu ( + this: LSPluginUser, + tag: string, + action: () => void + ): boolean { + if (typeof action !== 'function') { + return false + } + + const key = tag + const label = tag + const type = 'block-context-menu' + const eventKey = `SimpleCommandHook${tag}${++registeredCmdUid}` + + this.Editor['on' + eventKey](action) + + this.caller?.call(`api:call`, { + method: 'register-plugin-simple-command', + args: [this.baseInfo.id, [{ key, label, type }, ['editor/hook', eventKey]]] + }) + + return false + } +} + +const db: Partial = {} + +type uiState = { + key?: number, + visible: boolean +} + +const KEY_MAIN_UI = 0 + +/** + * User plugin instance + */ +export class LSPluginUser extends EventEmitter implements ILSPluginUser { + /** + * Indicate connected with host + * @private + */ + private _connected: boolean = false + private _ui = new Map() + + /** + * @param _baseInfo + * @param _caller + */ + constructor ( + private _baseInfo: LSPluginBaseInfo, + private _caller: LSPluginCaller + ) { + super() + + _caller.on('settings:changed', (payload) => { + const b = Object.assign({}, this.settings) + const a = Object.assign(this._baseInfo.settings, payload) + this.emit('settings:changed', { ...a }, b) + }) + } + + async ready ( + model?: any, + callback?: any + ) { + if (this._connected) return + + try { + + if (typeof model === 'function') { + callback = model + model = {} + } + + let baseInfo = await this._caller.connectToParent(model) + + baseInfo = deepMerge(this._baseInfo, baseInfo) + + this._connected = true + + callback && callback.call(this, baseInfo) + } catch (e) { + console.error('[LSPlugin Ready Error]', e) + } + } + + provideModel (model: Record) { + this.caller._extendUserModel(model) + return this + } + + provideTheme (theme: ThemeOptions) { + this.caller.call('provider:theme', theme) + return this + } + + provideStyle (style: StyleString) { + this.caller.call('provider:style', style) + return this + } + + provideUI (ui: UIOptions) { + this.caller.call('provider:ui', ui) + return this + } + + updateSettings (attrs: Record) { + this.caller.call('settings:update', attrs) + // TODO: update associated baseInfo settings + } + + setMainUIAttrs (attrs: Record): void { + this.caller.call('main-ui:attrs', attrs) + } + + setMainUIInlineStyle (style: CSSStyleDeclaration): void { + this.caller.call('main-ui:style', style) + } + + hideMainUI (): void { + const payload = { key: KEY_MAIN_UI, visible: false } + this.caller.call('main-ui:visible', payload) + this.emit('ui:visible:changed', payload) + this._ui.set(payload.key, payload) + } + + showMainUI (): void { + const payload = { key: KEY_MAIN_UI, visible: true } + this.caller.call('main-ui:visible', payload) + this.emit('ui:visible:changed', payload) + this._ui.set(payload.key, payload) + } + + toggleMainUI (): void { + const payload = { key: KEY_MAIN_UI, toggle: true } + const state = this._ui.get(payload.key) + if (state && state.visible) { + this.hideMainUI() + } else { + this.showMainUI() + } + } + + get isMainUIVisible (): boolean { + const state = this._ui.get(0) + return Boolean(state && state.visible) + } + + get connected (): boolean { + return this._connected + } + + get baseInfo (): LSPluginBaseInfo { + return this._baseInfo + } + + get settings () { + return this.baseInfo?.settings + } + + get caller (): LSPluginCaller { + return this._caller + } + + _makeUserProxy ( + target: any, + tag?: 'app' | 'editor' | 'db' + ) { + const that = this + const caller = this.caller + + return new Proxy(target, { + get (target: any, propKey, receiver) { + const origMethod = target[propKey] + + return function (this: any, ...args: any) { + if (origMethod) { + const ret = origMethod.apply(that, args) + if (ret === false) return + } + + // Handle hook + if (tag) { + const hookMatcher = propKey.toString().match(/^(once|off|on)/i) + + if (hookMatcher != null) { + const f = hookMatcher[0] + const s = hookMatcher.input! + const e = s.slice(f.length) + + caller[f.toLowerCase()](`hook:${tag}:${snakeCase(e)}`, args[0]) + return + } + } + + // Call host + return caller.callAsync(`api:call`, { + method: propKey, + args: args + }) + } + } + }) + } + + get App (): IAppProxy { + return this._makeUserProxy(app, 'app') + } + + get Editor () { + return this._makeUserProxy(editor, 'editor') + } + + get DB (): IDBProxy { + return this._makeUserProxy(db) + } +} + +export function setupPluginUserInstance ( + pluginBaseInfo: LSPluginBaseInfo, + pluginCaller: LSPluginCaller +) { + return new LSPluginUser(pluginBaseInfo, pluginCaller) +} + +if (window.__LSP__HOST__ == null) { // Entry of iframe mode + debug('Entry of iframe mode.') + + const caller = new LSPluginCaller(null) + window.logseq = setupPluginUserInstance({} as any, caller) +} diff --git a/libs/src/helpers.ts b/libs/src/helpers.ts new file mode 100644 index 00000000000..0a08a1a42f4 --- /dev/null +++ b/libs/src/helpers.ts @@ -0,0 +1,279 @@ +import { StyleString, UIOptions } from './LSPlugin' +import { PluginLocal } from './LSPlugin.core' +import { snakeCase } from 'snake-case' + +interface IObject { + [key: string]: any; +} + +declare global { + interface Window { + api: any + apis: any + } +} + +export function isObject (item: any) { + return (item === Object(item) && !Array.isArray(item)) +} + +export function deepMerge ( + target: IObject, + ...sources: Array +) { + // return the target if no sources passed + if (!sources.length) { + return target + } + + const result: IObject = target + + if (isObject(result)) { + const len: number = sources.length + + for (let i = 0; i < len; i += 1) { + const elm: any = sources[i] + + if (isObject(elm)) { + for (const key in elm) { + if (elm.hasOwnProperty(key)) { + if (isObject(elm[key])) { + if (!result[key] || !isObject(result[key])) { + result[key] = {} + } + deepMerge(result[key], elm[key]) + } else { + if (Array.isArray(result[key]) && Array.isArray(elm[key])) { + // concatenate the two arrays and remove any duplicate primitive values + result[key] = Array.from(new Set(result[key].concat(elm[key]))) + } else { + result[key] = elm[key] + } + } + } + } + } + } + } + + return result +} + +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 + // after the decimal. + return '_' + Math.random().toString(36).substr(2, 9) +} + +export function ucFirst (str: string) { + return str.charAt(0).toUpperCase() + str.slice(1) +} + +export function withFileProtocol (path: string) { + if (!path) return '' + const reg = /^(http|file|assets)/ + + if (!reg.test(path)) { + path = 'file://' + path + } + + return path +} + +/** + * @param timeout milliseconds + * @param tag string + */ +export function deferred (timeout?: number, tag?: string) { + let resolve: any, reject: any + let settled = false + const timeFn = (r: Function) => { + return (v: T) => { + timeout && clearTimeout(timeout) + r(v) + settled = true + } + } + + const promise = new Promise((resolve1, reject1) => { + resolve = timeFn(resolve1) + reject = timeFn(reject1) + + if (timeout) { + // @ts-ignore + timeout = setTimeout(() => reject(new Error(`[deferred timeout] ${tag}`)), timeout) + } + }) + + return { + created: Date.now(), + setTag: (t: string) => tag = t, + resolve, reject, promise, + get settled () { + return settled + } + } +} + +export function invokeHostExportedApi ( + method: string, + ...args: Array +) { + const method1 = snakeCase(method) + const fn = window.api[method1] || window.apis[method1] || + window.api[method] || window.apis[method] + + if (!fn) { + throw new Error(`Not existed method #${method}`) + } + return typeof fn !== 'function' ? fn : fn.apply(null, args) +} + +export function setupIframeSandbox ( + props: Record, + target: HTMLElement +) { + const iframe = document.createElement('iframe') + + iframe.classList.add('lsp-iframe-sandbox') + + Object.entries(props).forEach(([k, v]) => { + iframe.setAttribute(k, v) + }) + + target.appendChild(iframe) + + return async () => { + target.removeChild(iframe) + } +} + +export function setupInjectedStyle ( + style: StyleString, + attrs: Record +) { + const key = attrs['data-injected-style'] + let el = key && document.querySelector(`[data-injected-style=${key}]`) + + if (el) { + el.textContent = style + return + } + + el = document.createElement('style') + el.textContent = style + + attrs && Object.entries(attrs).forEach(([k, v]) => { + el.setAttribute(k, v) + }) + + document.head.append(el) + + return () => { + document.head.removeChild(el) + } +} + +export function setupInjectedUI ( + this: PluginLocal, + ui: UIOptions, + attrs: Record +) { + const pl = this + const selector = ui.path || `#${ui.slot}` + + const target = selector && document.querySelector(selector) + if (!target) { + console.error(`${this.debugTag} can not resolve selector target ${selector}`) + return + } + + const key = `${ui.key}-${pl.id}` + + let el = document.querySelector(`div[data-injected-ui="${key}"]`) as HTMLElement + + if (el) { + el.innerHTML = ui.template + return + } + + el = document.createElement('div') + el.dataset.injectedUi = key || '' + + // TODO: Support more + el.innerHTML = ui.template + + attrs && Object.entries(attrs).forEach(([k, v]) => { + el.setAttribute(k, v) + }) + + target.appendChild(el); + + // TODO: How handle events + ['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) + }) + + return () => { + target!.removeChild(el) + } +} + +export function transformableEvent (target: HTMLElement, e: Event) { + const obj: any = {} + + if (target) { + const ds = target.dataset + const FLAG_RECT = 'rect' + + ;['value', 'id', 'className', + 'dataset', FLAG_RECT + ].forEach((k) => { + let v: any + + switch (k) { + case FLAG_RECT: + if (!ds.hasOwnProperty(FLAG_RECT)) return + v = target.getBoundingClientRect().toJSON() + break + default: + v = target[k] + } + + if (typeof v === 'object') { + v = { ...v } + } + + obj[k] = v + }) + } + + return obj +} + +let injectedThemeEffect: any = null + +export function setupInjectedTheme (url?: string) { + injectedThemeEffect?.call() + + if (!url) return + + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = url + document.head.appendChild(link) + + return (injectedThemeEffect = () => { + document.head.removeChild(link) + injectedThemeEffect = null + }) +} diff --git a/libs/tsconfig.json b/libs/tsconfig.json new file mode 100644 index 00000000000..799d5f9631a --- /dev/null +++ b/libs/tsconfig.json @@ -0,0 +1,88 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ESNext", + /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "ESNext", + /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + "allowJs": true, + /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + "jsx": "react", + /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, + /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": false, + /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "dist", + /* Redirect output structure to the directory. */ + "rootDir": "src", + /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, + /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + // "strict": true, + /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, + /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + "strictPropertyInitialization": true, + /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + "moduleResolution": "node", + /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, + /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, + /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true + /* Disallow inconsistently-cased references to the same file. */ + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/libs/webpack.config.core.js b/libs/webpack.config.core.js new file mode 100644 index 00000000000..a1ff73588ff --- /dev/null +++ b/libs/webpack.config.core.js @@ -0,0 +1,32 @@ +const webpack = require('webpack') +const path = require('path') +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin + +module.exports = { + entry: './src/LSPlugin.core.ts', + devtool: 'inline-source-map', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + }, + plugins: [ + new webpack.ProvidePlugin({ + process: 'process/browser', + }), + // new BundleAnalyzerPlugin() + ], + output: { + library: 'LSPlugin', + libraryTarget: 'umd', + filename: 'lsplugin.core.js', + path: path.resolve(__dirname, '../static/js'), + }, +} \ No newline at end of file diff --git a/libs/webpack.config.js b/libs/webpack.config.js new file mode 100644 index 00000000000..8c511c3053e --- /dev/null +++ b/libs/webpack.config.js @@ -0,0 +1,27 @@ +const path = require('path') +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin + +module.exports = { + entry: './src/LSPlugin.user.ts', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + }, + plugins: [ + // new BundleAnalyzerPlugin() + ], + output: { + library: "LSPluginEntry", + libraryTarget: "umd", + filename: 'lsplugin.user.js', + path: path.resolve(__dirname, 'dist') + }, +} \ No newline at end of file diff --git a/libs/yarn.lock b/libs/yarn.lock new file mode 100644 index 00000000000..57b80161dd9 --- /dev/null +++ b/libs/yarn.lock @@ -0,0 +1,1251 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@discoveryjs/json-ext@^0.5.0": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" + integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg== + +"@polka/url@^1.0.0-next.9": + version "1.0.0-next.11" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71" + integrity sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA== + +"@types/debug@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" + integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== + +"@types/dompurify@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.2.1.tgz#eebf3af8afe2f577a53acab9d98a3a4cb04bbbe7" + integrity sha512-3JwbEeRVQ3n6+JgBW/hCdkydRk9/vWT+UEglcXEJqLJEcUganDH37zlfLznxPKTZZfDqA9K229l1qN458ubcOQ== + dependencies: + "@types/trusted-types" "*" + +"@types/eslint-scope@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86" + integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "7.2.6" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.6.tgz#5e9aff555a975596c03a98b59ecd103decc70c3c" + integrity sha512-I+1sYH+NPQ3/tVqCeUSBwTE/0heyvtXqpIopUUArlBm0Kpocb8FbMa3AZ/ASKIFpN3rnEx932TTXDbt9OXsNDw== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^0.0.46": + version "0.0.46" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" + integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== + +"@types/json-schema@*", "@types/json-schema@^7.0.6": + version "7.0.7" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" + integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== + +"@types/lodash-es@^4.17.4": + version "4.17.4" + resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.4.tgz#b2e440d2bf8a93584a9fd798452ec497986c9b97" + integrity sha512-BBz79DCJbD2CVYZH67MBeHZRX++HF+5p8Mo5MzjZi64Wac39S3diedJYHZtScbRVf4DjZyN6LzA0SB0zy+HSSQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.168" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" + integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q== + +"@types/node@*": + version "14.14.31" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055" + integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g== + +"@types/postmate@^1.5.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@types/postmate/-/postmate-1.5.1.tgz#56a506c371a7b4388beebacb4420838c2c3c4a2b" + integrity sha512-sFia2ycFNrxf1YZS59ShTDkMwhUlOYWAw7EslLePF52x5czUDrvdqB4f3eCXCiQp7yXpqHqwPCf4OBvCTqbaWw== + +"@types/trusted-types@*": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.0.tgz#aee6e868fcef74f2b8c71614b6df81a601a42f17" + integrity sha512-I8MnZqNXsOLHsU111oHbn3khtvKMi5Bn4qVFsIWSJcCP1KKDiXX5AEw8UPk0nSopeC+Hvxt6yAy1/a5PailFqg== + +"@webassemblyjs/ast@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" + integrity sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + +"@webassemblyjs/floating-point-hex-parser@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz#34d62052f453cd43101d72eab4966a022587947c" + integrity sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA== + +"@webassemblyjs/helper-api-error@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz#aaea8fb3b923f4aaa9b512ff541b013ffb68d2d4" + integrity sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w== + +"@webassemblyjs/helper-buffer@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz#d026c25d175e388a7dbda9694e91e743cbe9b642" + integrity sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA== + +"@webassemblyjs/helper-numbers@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz#7ab04172d54e312cc6ea4286d7d9fa27c88cd4f9" + integrity sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz#85fdcda4129902fe86f81abf7e7236953ec5a4e1" + integrity sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA== + +"@webassemblyjs/helper-wasm-section@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz#9ce2cc89300262509c801b4af113d1ca25c1a75b" + integrity sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + +"@webassemblyjs/ieee754@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz#46975d583f9828f5d094ac210e219441c4e6f5cf" + integrity sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.0.tgz#f7353de1df38aa201cba9fb88b43f41f75ff403b" + integrity sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.0.tgz#86e48f959cf49e0e5091f069a709b862f5a2cadf" + integrity sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw== + +"@webassemblyjs/wasm-edit@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz#ee4a5c9f677046a210542ae63897094c2027cb78" + integrity sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/helper-wasm-section" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-opt" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + "@webassemblyjs/wast-printer" "1.11.0" + +"@webassemblyjs/wasm-gen@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz#3cdb35e70082d42a35166988dda64f24ceb97abe" + integrity sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" + +"@webassemblyjs/wasm-opt@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz#1638ae188137f4bb031f568a413cd24d32f92978" + integrity sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + +"@webassemblyjs/wasm-parser@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz#3e680b8830d5b13d1ec86cc42f38f3d4a7700754" + integrity sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" + +"@webassemblyjs/wast-printer@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz#680d1f6a5365d6d401974a8e949e05474e1fab7e" + integrity sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@xtuc/long" "4.2.2" + +"@webpack-cli/configtest@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.0.1.tgz#241aecfbdc715eee96bed447ed402e12ec171935" + integrity sha512-B+4uBUYhpzDXmwuo3V9yBH6cISwxEI4J+NO5ggDaGEEHb0osY/R7MzeKc0bHURXQuZjMM4qD+bSJCKIuI3eNBQ== + +"@webpack-cli/info@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.2.2.tgz#ef3c0cd947a1fa083e174a59cb74e0b6195c236c" + integrity sha512-5U9kUJHnwU+FhKH4PWGZuBC1hTEPYyxGSL5jjoBI96Gx8qcYJGOikpiIpFoTq8mmgX3im2zAo2wanv/alD74KQ== + dependencies: + envinfo "^7.7.3" + +"@webpack-cli/serve@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.3.0.tgz#2730c770f5f1f132767c63dcaaa4ec28f8c56a6c" + integrity sha512-k2p2VrONcYVX1wRRrf0f3X2VGltLWcv+JzXRBDmvCxGlCeESx4OXw91TsWeKOkp784uNoVQo313vxJFHXPPwfw== + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +acorn-walk@^8.0.0: + version "8.0.2" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.0.2.tgz#d4632bfc63fd93d0f15fd05ea0e984ffd3f5a8c3" + integrity sha512-+bpA9MJsHdZ4bgfDcpk0ozQyhhVct7rzOmO0s1IIr0AGGgKBljss8n2zp11rRP2wid5VGeh04CgeKzgat5/25A== + +acorn@^8.0.4: + version "8.0.5" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.5.tgz#a3bfb872a74a6a7f661bc81b9849d9cac12601b7" + integrity sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg== + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.14.5: + version "4.16.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717" + integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw== + dependencies: + caniuse-lite "^1.0.30001181" + colorette "^1.2.1" + electron-to-chromium "^1.3.649" + escalade "^3.1.1" + node-releases "^1.1.70" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +caniuse-lite@^1.0.30001181: + version "1.0.30001196" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001196.tgz#00518a2044b1abf3e0df31fadbe5ed90b63f4e64" + integrity sha512-CPvObjD3ovWrNBaXlAIGWmg2gQQuJ5YhuciUOjPRox6hIQttu8O+b51dx6VIpIY9ESd2d0Vac1RKpICdG4rGUg== + +chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chrome-trace-event@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" + integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== + dependencies: + tslib "^1.9.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colorette@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +commander@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff" + integrity sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg== + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +dompurify@^2.2.7: + version "2.2.7" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.7.tgz#a5f055a2a471638680e779bd08fc334962d11fd8" + integrity sha512-jdtDffdGNY+C76jvodNTu9jt5yYj59vuTUyx+wXdzcSwAGTYZDAQkQ7Iwx9zcGrA4ixC1syU4H3RZROqRxokxg== + +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +electron-to-chromium@^1.3.649: + version "1.3.680" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.680.tgz#88cc44bd2a85b46cf7521f714db57dd74d0cd488" + integrity sha512-XBACJT9RdpdWtoMXQPR8Be3ZtmizWWbxfw8cY2b5feUwiDO3FUl8qo4W2jXoq/WnnA3xBRqafu1XbpczqyUvlA== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +enhanced-resolve@^4.0.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + +enhanced-resolve@^5.7.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz#525c5d856680fbd5052de453ac83e32049958b5c" + integrity sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +enquirer@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +envinfo@^7.7.3: + version "7.7.4" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.4.tgz#c6311cdd38a0e86808c1c9343f667e4267c4a320" + integrity sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ== + +errno@^0.1.3: + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + +es-module-lexer@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.4.1.tgz#dda8c6a14d8f340a24e34331e0fab0cb50438e0e" + integrity sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + +eventemitter3@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" + integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fastest-levenshtein@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" + integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-stream@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.0.tgz#3e0012cb6827319da2706e601a1583e8629a6718" + integrity sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg== + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +graceful-fs@^4.1.2, graceful-fs@^4.2.4: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +import-local@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" + integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + +is-core-module@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +jest-worker@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json5@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +loader-runner@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" + integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== + +loader-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" + integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash@^4.17.20: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +mime-db@1.46.0: + version "1.46.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" + integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== + +mime-types@^2.1.27: + version "2.1.29" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" + integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== + dependencies: + mime-db "1.46.0" + +mime@^2.3.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-releases@^1.1.70: + version "1.1.71" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" + integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path@^0.12.7: + version "0.12.7" + resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" + integrity sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8= + dependencies: + process "^0.11.1" + util "^0.10.3" + +picomatch@^2.0.5: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +postmate@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/postmate/-/postmate-1.5.2.tgz#d59a78b3780023c5d32225fd40633b364958bdb3" + integrity sha512-EHLlEmrUA/hALls49oBrtE7BzDXXjB9EiO4MZpsoO3R/jRuBmD+2WKQuYAbeuVEpTzrPpUTT79z2cz4qaFgPRg== + +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" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.1: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +readable-stream@^2.0.1: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +rechoir@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" + integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q== + dependencies: + resolve "^1.9.0" + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve@^1.9.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +schema-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" + integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== + dependencies: + "@types/json-schema" "^7.0.6" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +semver@^7.3.4: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + +serialize-javascript@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +sirv@^1.0.7: + version "1.0.11" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.11.tgz#81c19a29202048507d6ec0d8ba8910fda52eb5a4" + integrity sha512-SR36i3/LSWja7AJNRBz4fF/Xjpn7lQFI30tZ434dIy+bitLYSP+ZEenHg36i23V2SGEz+kqjksg0uOGZ5LPiqg== + dependencies: + "@polka/url" "^1.0.0-next.9" + mime "^2.3.1" + totalist "^1.0.0" + +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +source-list-map@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + +source-map-support@~0.5.19: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@~0.7.2: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +tapable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" + integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== + +terser-webpack-plugin@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz#7effadee06f7ecfa093dbbd3e9ab23f5f3ed8673" + integrity sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q== + dependencies: + jest-worker "^26.6.2" + p-limit "^3.1.0" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + source-map "^0.6.1" + terser "^5.5.1" + +terser@^5.5.1: + version "5.6.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.0.tgz#138cdf21c5e3100b1b3ddfddf720962f88badcd2" + integrity sha512-vyqLMoqadC1uR0vywqOZzriDYzgEkNJFK4q9GeyOBHIbiECHiWLKcWfbQWAUaPfxkjDhapSlZB9f7fkMrvkVjA== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +totalist@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" + integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== + +ts-loader@^8.0.17: + version "8.0.17" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.17.tgz#98f2ccff9130074f4079fd89b946b4c637b1f2fc" + integrity sha512-OeVfSshx6ot/TCxRwpBHQ/4lRzfgyTkvi7ghDVrLXOHzTbSK413ROgu/xNqM72i3AFeAIJgQy78FwSMKmOW68w== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^4.0.0" + loader-utils "^2.0.0" + micromatch "^4.0.0" + semver "^7.3.4" + +tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== + +typescript@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.2.tgz#1450f020618f872db0ea17317d16d8da8ddb8c4c" + integrity sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util@^0.10.3: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== + dependencies: + inherits "2.0.3" + +v8-compile-cache@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" + integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== + +watchpack@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7" + integrity sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +webpack-bundle-analyzer@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.0.tgz#74013106e7e2b07cbd64f3a5ae847f7e814802c7" + integrity sha512-9DhNa+aXpqdHk8LkLPTBU/dMfl84Y+WE2+KnfI6rSpNRNVKa0VGLjPd2pjFubDeqnWmulFggxmWBxhfJXZnR0g== + dependencies: + acorn "^8.0.4" + acorn-walk "^8.0.0" + chalk "^4.1.0" + commander "^6.2.0" + gzip-size "^6.0.0" + lodash "^4.17.20" + opener "^1.5.2" + sirv "^1.0.7" + ws "^7.3.1" + +webpack-cli@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.5.0.tgz#b5213b84adf6e1f5de6391334c9fa53a48850466" + integrity sha512-wXg/ef6Ibstl2f50mnkcHblRPN/P9J4Nlod5Hg9HGFgSeF8rsqDGHJeVe4aR26q9l62TUJi6vmvC2Qz96YJw1Q== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^1.0.1" + "@webpack-cli/info" "^1.2.2" + "@webpack-cli/serve" "^1.3.0" + colorette "^1.2.1" + commander "^7.0.0" + enquirer "^2.3.6" + execa "^5.0.0" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^2.2.0" + rechoir "^0.7.0" + v8-compile-cache "^2.2.0" + webpack-merge "^5.7.3" + +webpack-merge@^5.7.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.7.3.tgz#2a0754e1877a25a8bbab3d2475ca70a052708213" + integrity sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA== + dependencies: + clone-deep "^4.0.1" + wildcard "^2.0.0" + +webpack-sources@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac" + integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w== + dependencies: + source-list-map "^2.0.1" + source-map "^0.6.1" + +webpack@^5.24.3: + version "5.24.3" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.24.3.tgz#6ec0f5059f8d7c7961075fa553cfce7b7928acb3" + integrity sha512-x7lrWZ7wlWAdyKdML6YPvfVZkhD1ICuIZGODE5SzKJjqI9A4SpqGTjGJTc6CwaHqn19gGaoOR3ONJ46nYsn9rw== + dependencies: + "@types/eslint-scope" "^3.7.0" + "@types/estree" "^0.0.46" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/wasm-edit" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + acorn "^8.0.4" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.7.0" + es-module-lexer "^0.4.0" + eslint-scope "^5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.4" + json-parse-better-errors "^1.0.2" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.0.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.1" + watchpack "^2.0.0" + webpack-sources "^2.1.1" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wildcard@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" + integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + +ws@^7.3.1: + version "7.4.4" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59" + integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/src/main/api.cljs b/src/main/api.cljs index 6411258d5c8..0fed595f9d1 100644 --- a/src/main/api.cljs +++ b/src/main/api.cljs @@ -1,10 +1,137 @@ (ns ^:no-doc api (:require [frontend.db :as db] + [frontend.db.model :as db-model] + [frontend.handler.block :as block-handler] + [frontend.util :as util] + [electron.ipc :as ipc] + [promesa.core :as p] + [camel-snake-kebab.core :as csk] + [cljs-bean.core :as bean] [frontend.state :as state] + [frontend.components.plugins :as plugins] + [frontend.handler.plugin :as plugin-handler] + [frontend.handler.notification :as notification] [datascript.core :as d] + [frontend.fs :as fs] + [clojure.string :as string] + [clojure.walk :as walk] [cljs.reader] + [reitit.frontend.easy :as rfe] [frontend.db.query-dsl :as query-dsl])) +;; base +(def ^:export show_themes + (fn [] + (plugins/open-select-theme!))) + +(def ^:export set_theme_mode + (fn [mode] + (state/set-theme! (if (= mode "light") "white" "dark")))) + +(def ^:export load_plugin_config + (fn [path] + (fs/read-file "" (util/node-path.join path "package.json")))) + +(def ^:export load_plugin_readme + (fn [path] + (fs/read-file "" (util/node-path.join path "readme.md")))) + +(def ^:export save_plugin_config + (fn [path ^js data] + (let [repo "" + path (util/node-path.join path "package.json")] + (fs/write-file! repo "" path (js/JSON.stringify data nil 2) {:skip-mtime? true})))) + +(def ^:export write_user_tmp_file + (fn [file content] + (p/let [repo "" + path (plugin-handler/get-ls-dotdir-root) + path (util/node-path.join path "tmp") + exist? (fs/file-exists? path "") + _ (when-not exist? (fs/mkdir! path)) + path (util/node-path.join path file) + _ (fs/write-file! repo "" path content {:skip-mtime? true})] + path))) + +(def ^:export load_user_preferences + (fn [] + (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) + json (fs/read-file "" path) + json (if (string/blank? json) "{}" json)] + (js/JSON.parse json)))) + +(def ^:export save_user_preferences + (fn [^js data] + (when data + (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-mtime? true}))))) + +(def ^:export load_plugin_user_settings + (fn [key] + (p/let [repo "" + path (plugin-handler/get-ls-dotdir-root) + exist? (fs/file-exists? path "settings") + _ (when-not exist? (fs/mkdir! (util/node-path.join path "settings"))) + path (util/node-path.join path "settings" (str key ".json")) + _ (fs/create-if-not-exists repo "" path "{}") + json (fs/read-file "" path)] + [path (js/JSON.parse json)]))) + +(def ^:export save_plugin_user_settings + (fn [key ^js data] + (p/let [repo "" + path (plugin-handler/get-ls-dotdir-root) + path (util/node-path.join path "settings" (str key ".json"))] + (fs/write-file! repo "" path (js/JSON.stringify data nil 2) {:skip-mtime? true})))) + +(def ^:export register_plugin_slash_command + (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)])))) + +(def ^:export register_plugin_simple_command + (fn [pid ^js cmd-action] + (when-let [[cmd action] (bean/->clj cmd-action)] + (plugin-handler/register-plugin-simple-command + pid cmd (assoc action 0 (keyword (first action))))))) + +;; app +(def ^:export push_state + (fn [^js k ^js params] + (rfe/push-state + (keyword k) (bean/->clj params)))) + +(def ^:export replace_state + (fn [^js k ^js params] + (rfe/replace-state + (keyword k) (bean/->clj params)))) + +;; editor +(def ^:export get_current_page_blocks_tree + (fn [] + (when-let [page (state/get-current-page)] + (let [blocks (db-model/get-page-blocks-no-cache page) + blocks (mapv #(-> % + (dissoc :block/children) + (assoc :block/uuid (str (:block/uuid %)))) + blocks) + blocks (block-handler/blocks->vec-tree blocks) + ;; clean key + blocks (walk/postwalk + (fn [a] + (if (keyword? a) + (csk/->camelCase (name a)) + a)) blocks)] + (bean/->js blocks))))) + +;; db (defn ^:export q [query-string] (when-let [repo (state/get-current-repo)] @@ -21,3 +148,8 @@ (clj->js result))))) (def ^:export custom_query db/custom-query) + +;; helpers +(defn ^:export show_msg + ([content] (show_msg content :success)) + ([content status] (notification/show! content (keyword status)))) diff --git a/src/main/frontend/components/plugins.cljs b/src/main/frontend/components/plugins.cljs new file mode 100644 index 00000000000..ee088072a7f --- /dev/null +++ b/src/main/frontend/components/plugins.cljs @@ -0,0 +1,154 @@ +(ns frontend.components.plugins + (:require [rum.core :as rum] + [frontend.state :as state] + [cljs-bean.core :as bean] + [frontend.ui :as ui] + [frontend.util :as util] + [electron.ipc :as ipc] + [promesa.core :as p] + [frontend.components.svg :as svg] + [frontend.handler.notification :as notification] + [frontend.handler.plugin :as plugin-handler])) + +(rum/defc installed-themes + < rum/reactive + [] + (let [themes (state/sub :plugin/installed-themes) + selected (state/sub :plugin/selected-theme)] + [:div.cp__themes-installed + [:h2.mb-4.text-xl "Installed Themes"] + (for [opt themes] + (let [current-selected (= selected (:url opt))] + [:div.it.flex.px-3.py-2.mb-2.rounded-sm.justify-between + {:key (:url opt) + :class [(if current-selected "selected")] + :on-click #(do (js/LSPluginCore.selectTheme (if current-selected nil (clj->js opt))) + (state/set-modal! nil))} + [:section + [:strong.block (:name opt)] + [:small.opacity-30 (:description opt)]] + [:small.flex-shrink-0.flex.items-center.opacity-10 + (if current-selected "current")]]))])) + +(rum/defc unpacked-plugin-loader + [unpacked-pkg-path] + (rum/use-effect! + (fn [] + (let [err-handle + (fn [^js e] + (case (keyword (aget e "name")) + :IllegalPluginPackageError + (notification/show! "Illegal Logseq plugin package." :error) + :ExistedImportedPluginPackageError + (notification/show! "Existed Imported plugin package." :error) + :default) + (plugin-handler/reset-unpacked-state)) + reg-handle #(plugin-handler/reset-unpacked-state)] + (when unpacked-pkg-path + (doto js/LSPluginCore + (.once "error" err-handle) + (.once "registered" reg-handle) + (.register (bean/->js {:url unpacked-pkg-path})))) + #(doto js/LSPluginCore + (.off "error" err-handle) + (.off "registered" reg-handle)))) + [unpacked-pkg-path]) + + (when unpacked-pkg-path + [:strong.inline-flex.px-3 "Loading ..."])) + +(rum/defc simple-markdown-display + < rum/reactive + [] + (let [content (state/sub :plugin/active-readme)] + [:textarea.p-1.bg-transparent.border-none + {:style {:width "700px" :min-height "60vw"}} + content])) + +(rum/defc plugin-item-card + [{:keys [id name settings version url description author icon usf] :as item}] + (let [disabled (:disabled settings)] + [:div.cp__plugins-item-card + [:div.l.link-block + {:on-click #(plugin-handler/open-readme! url simple-markdown-display)} + (if icon + [:img.icon {:src icon}] + svg/folder)] + [:div.r + [:h3.head.text-xl.font-bold.pt-1.5 + {:on-click #(plugin-handler/open-readme! url simple-markdown-display)} + [:span name] + [:sup.inline-block.px-1.text-xs.opacity-30 version]] + [:div.desc.text-xs.opacity-60 + [:p description] + [:small (js/JSON.stringify (bean/->js settings))]] + [:div.flag + [:p.text-xs.text-gray-300.pr-2.flex.justify-between.dark:opacity-40 + [:small author] + [:small (str "ID: " id)]]] + + [:div.ctl + [:div.l + [:div.de + [:strong svg/settings-sm] + [:ul.menu-list + [:li {:on-click #(if usf (js/apis.openPath usf))} "Open settings"] + [:li {:on-click + #(let [confirm-fn + (ui/make-confirm-modal + {:title (str "Are you sure uninstall plugin - " name "?") + :on-confirm (fn [_ {:keys [close-fn]}] + (close-fn) + (plugin-handler/unregister-plugin id))})] + (state/set-modal! confirm-fn))} + "Uninstall plugin"]]]] + + [:div.flex.items-center + [:small.de (if disabled "Disabled" "Enabled")] + (ui/toggle (not disabled) + (fn [] + (js-invoke js/LSPluginCore (if disabled "enable" "disable") id)) + true)]]]])) + +(rum/defc installed-page + < rum/reactive + [] + (let [installed-plugins (state/sub :plugin/installed-plugins) + selected-unpacked-pkg (state/sub :plugin/selected-unpacked-pkg)] + [:div.cp__plugins-page-installed + [:h1 "Installed Plugins"] + [:div.mb-6.flex.items-center.justify-between + (ui/button + "Load unpacked plugin" + :intent "logseq" + :on-click plugin-handler/load-unpacked-plugin) + (unpacked-plugin-loader selected-unpacked-pkg) + (when (util/electron?) + (ui/button + [:span.flex.items-center + ;;svg/settings-sm + "Open plugin preferences file"] + :intent "logseq" + :on-click (fn [] + (p/let [root (plugin-handler/get-ls-dotdir-root)] + (js/apis.openPath (str root "/preferences.json"))))))] + + [:div.cp__plugins-item-lists.grid-cols-1.md:grid-cols-2.lg:grid-cols-3 + (for [[_ item] installed-plugins] + (rum/with-key (plugin-item-card item) (:id item)))]])) + +(defn open-select-theme! + [] + (state/set-modal! installed-themes)) + +(rum/defc hook-ui-slot + ([type payload] (hook-ui-slot type payload nil)) + ([type payload opts] + (let [id (str "slot__" (util/rand-str 8))] + (rum/use-effect! + (fn [] + (plugin-handler/hook-plugin-app type {:slot id :payload payload} nil) + #()) + []) + [:div.lsp-hook-ui-slot + (merge opts {:id id})]))) \ No newline at end of file diff --git a/src/main/frontend/components/plugins.css b/src/main/frontend/components/plugins.css new file mode 100644 index 00000000000..cb9a053a586 --- /dev/null +++ b/src/main/frontend/components/plugins.css @@ -0,0 +1,185 @@ +.cp__plugins { + &-page-installed { + min-height: 60vh; + padding-top: 20px; + + > h1 { + padding: 20px 0; + font-size: 38px; + } + } + + &-item-lists { + @apply w-full grid grid-flow-row gap-3 pt-1; + } + + &-item-card { + @apply flex py-3 px-1 rounded-md; + + background-color: var(--ls-secondary-background-color); + height: 180px; + + svg, .icon { + width: 70px; + height: 70px; + opacity: .8; + + &:hover { + opacity: 1; + } + } + + .head { + max-height: 60px; + overflow: hidden; + + cursor: pointer; + + &:active { + opacity: .8; + } + } + + .desc { + height: 60px; + overflow: hidden; + } + + .flag { + position: absolute; + bottom: 20px; + left: 0; + width: 100%; + } + + > .l { + padding: 8px; + } + + > .r { + flex: 1; + position: relative; + + p { + @apply py-1 m-0; + } + + .ctl { + @apply flex pl-2 items-center justify-between absolute w-full; + + bottom: -8px; + right: 8px; + + .de { + font-size: 10px; + padding: 5px 0; + padding-right: 10px; + border-radius: 2px; + user-select: none; + transition: none; + opacity: .2; + position: relative; + + .menu-list { + @apply shadow-md rounded-sm absolute hidden list-none overflow-hidden m-0 p-0; + + background-color: var(--ls-primary-background-color); + top: 20px; + left: 0; + min-width: 100px; + + > li { + margin: 0; + padding: 3px; + transition: background-color .2s; + user-select: none; + opacity: .8; + + &:hover { + background-color: var(--ls-quaternary-background-color); + + &:active { + opacity: 1; + } + } + } + } + + &.err { + @apply text-red-500 opacity-100; + } + + &.log { + padding: 5px; + } + + svg { + width: 13px; + height: 13px; + } + } + + > .l { + @apply flex items-center; + + margin-left: -80px; + + .de { + &:hover { + opacity: .9; + + .menu-list { + display: block; + } + } + } + } + } + } + } +} + +.cp__themes { + &-installed { + min-width: 480px; + + > .it { + user-select: none; + cursor: pointer; + background-color: var(--ls-secondary-background-color); + border: 1px solid transparent; + transition: background-color .3s; + + &:hover, &.selected { + background-color: var(--ls-quaternary-background-color); + } + } + } +} + +.lsp-iframe-sandbox, .lsp-shadow-sandbox { + position: absolute; + top: 0; + left: 0; + z-index: -1; + visibility: hidden; + height: 0; + width: 0; + padding: 0; + margin: 0; + + &.visible { + z-index: 1; + width: 100vw; + height: 100vh; + visibility: visible; + } +} + +body { + &[data-page=page] { + .lsp-hook-ui-slot { + @apply flex items-center px-1 opacity-70; + } + } +} diff --git a/src/main/frontend/handler/plugin.cljs b/src/main/frontend/handler/plugin.cljs new file mode 100644 index 00000000000..40a961dc07a --- /dev/null +++ b/src/main/frontend/handler/plugin.cljs @@ -0,0 +1,181 @@ +(ns frontend.handler.plugin + (:require [promesa.core :as p] + [rum.core :as rum] + [frontend.util :as util] + [frontend.fs :as fs] + [frontend.handler.notification :as notifications] + [camel-snake-kebab.core :as csk] + [frontend.state :as state] + [medley.core :as md] + [electron.ipc :as ipc] + [cljs-bean.core :as bean] + [clojure.string :as string])) + +(defonce lsp-enabled? (util/electron?)) + +;; state handlers +(defn register-plugin + [pl] + (swap! state/state update-in [:plugin/installed-plugins] assoc (keyword (:id pl)) pl)) + +(defn unregister-plugin + [id] + (js/LSPluginCore.unregister id)) + +(defn host-mounted! + [] + (and lsp-enabled? (js/LSPluginCore.hostMounted))) + +(defn register-plugin-slash-command + [pid [cmd actions]] + (prn (if-let [pid (keyword pid)] + (when (contains? (:plugin/installed-plugins @state/state) pid) + (do (swap! state/state update-in [:plugin/installed-commands pid] + (fnil merge {}) (hash-map cmd (mapv #(conj % {:pid pid}) actions))) + true))))) + +(defn unregister-plugin-slash-command + [pid] + (swap! state/state md/dissoc-in [:plugin/installed-commands (keyword pid)])) + +(defn register-plugin-simple-command + ;; action => [:action-key :event-key] + [pid {:keys [key label type] :as cmd} action] + (if-let [pid (keyword pid)] + (when (contains? (:plugin/installed-plugins @state/state) pid) + (do (swap! state/state update-in [:plugin/simple-commands pid] + (fnil conj []) [type cmd action pid]) + true)))) + +(defn unregister-plugin-simple-command + [pid] + (swap! state/state md/dissoc-in [:plugin/simple-commands (keyword pid)])) + +(defn update-plugin-settings + [id settings] + (swap! state/state update-in [:plugin/installed-plugins id] assoc :settings settings)) + +(defn open-readme! + [url display] + (when url + (-> (p/let [content (js/api.load_plugin_readme url)] + (state/set-state! :plugin/active-readme content) + (state/set-modal! display)) + (p/catch #(notifications/show! "No README file." :warn))))) + +(defn load-unpacked-plugin + [] + (if util/electron? + (p/let [path (ipc/ipc "openDialogSync")] + (when-not (:plugin/selected-unpacked-pkg @state/state) + (state/set-state! :plugin/selected-unpacked-pkg path))))) + +(defn reset-unpacked-state + [] + (state/set-state! :plugin/selected-unpacked-pkg nil)) + +(defn hook-plugin + [tag type payload plugin-id] + (when lsp-enabled? + (js-invoke js/LSPluginCore + (str "hook" (string/capitalize (name tag))) + (name type) + (if (map? payload) + (bean/->js (into {} (for [[k v] payload] [(csk/->camelCase k) (if (uuid? v) (str v) v)])))) + (if (keyword? plugin-id) (name plugin-id) plugin-id)))) + +(defn hook-plugin-app + ([type payload] (hook-plugin-app type payload nil)) + ([type payload plugin-id] (hook-plugin :app type payload plugin-id))) + +(defn hook-plugin-editor + ([type payload] (hook-plugin-editor type payload nil)) + ([type payload plugin-id] (hook-plugin :editor type payload plugin-id))) + +(defn get-ls-dotdir-root + [] + (ipc/ipc "getLogseqDotDirRoot")) + +(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)))) + +;; components +(rum/defc lsp-indicator < rum/reactive + [] + (let [text (state/sub :plugin/indicator-text)] + (if (= text "END") + [:span] + [:div + {:style + {:width "100%" + :height "100vh" + :display "flex" + :align-items "center" + :justify-content "center"}} + [:span + {:style + {:color "#aaa" + :font-size "38px"}} (or text "Loading ...")]]))) + +(defn init-plugins + [callback] + + (let [el (js/document.createElement "div")] + (.appendChild js/document.body el) + (rum/mount + (lsp-indicator) el)) + + (state/set-state! :plugin/indicator-text "Loading...") + + (p/then + (p/let [root (get-ls-dotdir-root) + _ (.setupPluginCore js/LSPlugin (bean/->js {:localUserConfigRoot root})) + _ (doto js/LSPluginCore + (.on "registered" + (fn [^js pl] + (register-plugin + (bean/->clj (.parse js/JSON (.stringify js/JSON pl)))))) + + (.on "unregistered" (fn [pid] + (let [pid (keyword pid)] + ;; plugins + (swap! state/state md/dissoc-in [:plugin/installed-plugins (keyword pid)]) + ;; commands + (unregister-plugin-slash-command pid)))) + + (.on "theme-changed" (fn [^js themes] + (swap! state/state assoc :plugin/installed-themes + (vec (mapcat (fn [[_ vs]] (bean/->clj vs)) (bean/->clj themes)))))) + + (.on "theme-selected" (fn [^js opts] + (let [opts (bean/->clj opts) + url (:url opts) + mode (:mode opts)] + (when mode (state/set-theme! mode)) + (state/set-state! :plugin/selected-theme url)))) + + (.on "settings-changed" (fn [id ^js settings] + (let [id (keyword id)] + (when (and settings + (contains? (:plugin/installed-plugins @state/state) id)) + (update-plugin-settings id (bean/->clj settings))))))) + + default-plugins (get-user-default-plugins) + + _ (.register js/LSPluginCore (bean/->js (if (seq default-plugins) default-plugins [])) true)]) + #(do + (state/set-state! :plugin/indicator-text "END") + (callback)))) + +(defn setup! + "setup plugin core handler" + [callback] + (if (not lsp-enabled?) + (callback) + (init-plugins callback)))