diff --git a/libs/src/LSPlugin.core.ts b/libs/src/LSPlugin.core.ts index 23cbefd896..f920c2ac89 100644 --- a/libs/src/LSPlugin.core.ts +++ b/libs/src/LSPlugin.core.ts @@ -9,6 +9,7 @@ import { invokeHostExportedApi, isObject, withFileProtocol } from './helpers' +import * as pluginHelpers from './helpers' import Debug from 'debug' import { LSPluginCaller, @@ -905,7 +906,7 @@ class LSPluginCore this.emit('beforeenable') p.settings?.set('disabled', false) - // this.emit('enabled', p) + this.emit('enabled', p.id) } async disable (plugin: PluginLocalIdentity) { @@ -914,7 +915,7 @@ class LSPluginCore this.emit('beforedisable') p.settings?.set('disabled', true) - // this.emit('disabled', p) + this.emit('disabled', p.id) } async _hook (ns: string, type: string, payload?: any, pid?: string) { @@ -1019,5 +1020,6 @@ function setupPluginCore (options: any) { export { PluginLocal, + pluginHelpers, setupPluginCore } diff --git a/libs/src/LSPlugin.ts b/libs/src/LSPlugin.ts index d97da16f46..77de1090d0 100644 --- a/libs/src/LSPlugin.ts +++ b/libs/src/LSPlugin.ts @@ -183,6 +183,11 @@ export interface IAppProxy { showMsg: (content: string, status?: 'success' | 'warning' | string) => void setZoomFactor: (factor: number) => void + registerUIItem: ( + type: 'toolbar' | 'page', + opts: { key: string, template: string } + ) => boolean + // events onCurrentGraphChanged: IUserHook onThemeModeChanged: IUserHook<{ mode: 'dark' | 'light' }> diff --git a/libs/src/LSPlugin.user.ts b/libs/src/LSPlugin.user.ts index 56aae932ec..a9fbae747a 100644 --- a/libs/src/LSPlugin.user.ts +++ b/libs/src/LSPlugin.user.ts @@ -25,7 +25,22 @@ declare global { const debug = Debug('LSPlugin:user') -const app: Partial = {} +const app: Partial = { + registerUIItem ( + type: 'toolbar' | 'page', + opts: { key: string, template: string } + ) { + const pid = this.baseInfo.id + // opts.key = `${pid}_${opts.key}` + + this.caller?.call(`api:call`, { + method: 'register-plugin-ui-item', + args: [pid, type, opts] + }) + + return false + } +} let registeredCmdUid = 0 @@ -101,7 +116,7 @@ const editor: Partial = { args: [this.baseInfo.id, [{ key, label, type }, ['editor/hook', eventKey]]] }) - return false + return true } } diff --git a/src/main/frontend/components/header.cljs b/src/main/frontend/components/header.cljs index e87d94f9c3..2077ee00fb 100644 --- a/src/main/frontend/components/header.cljs +++ b/src/main/frontend/components/header.cljs @@ -11,6 +11,7 @@ [frontend.context.i18n :as i18n] [frontend.handler.ui :as ui-handler] [frontend.handler.user :as user-handler] + [frontend.handler.plugin :as plugin-handler] [frontend.components.svg :as svg] [frontend.components.repo :as repo] [frontend.components.search :as search] @@ -186,6 +187,9 @@ (when-not (util/electron?) (login logged?)) + (when plugin-handler/lsp-enabled? + (plugins/hook-ui-items :toolbar)) + (repo/sync-status current-repo) [:div.repos diff --git a/src/main/frontend/components/plugins.cljs b/src/main/frontend/components/plugins.cljs index da6602bb07..3016eb2ee6 100644 --- a/src/main/frontend/components/plugins.cljs +++ b/src/main/frontend/components/plugins.cljs @@ -23,8 +23,8 @@ (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 "is-selected")] + {:key (:url opt) + :class [(if current-selected "is-selected")] :on-click #(do (js/LSPluginCore.selectTheme (if current-selected nil (clj->js opt))) (state/set-modal! nil))} [:section @@ -173,3 +173,36 @@ []) [:div.lsp-hook-ui-slot (merge opts {:id id})]))) + +(rum/defc ui-item-renderer + [pid type {:keys [key template]}] + (let [*el (rum/use-ref nil) + uni #(str "injected-ui-item-" %) + ^js pl (js/LSPluginCore.registeredPlugins.get (name pid))] + + (rum/use-effect! + (fn [] + (when-let [^js el (rum/deref *el)] + (js/LSPlugin.pluginHelpers.setupInjectedUI.call + pl #js {:slot (.-id el) :key key :template template} #js {}))) + []) + + (if-not (nil? pl) + [:div {:id (uni (str (name key) "-" (name pid))) + :class (uni (name type)) + :ref *el}] + [:span]))) + +(rum/defcs hook-ui-items < rum/reactive + "type + - :toolbar + - :page + " + [state type] + (when (state/sub [:plugin/installed-ui-items]) + (let [items (state/get-plugins-ui-items-with-type type)] + (when (seq items) + [:div {:class (str "ui-items-container") + :data-type (name type)} + (for [[_ {:keys [key template] :as opts} pid] items] + (rum/with-key (ui-item-renderer pid type opts) key))])))) diff --git a/src/main/frontend/components/plugins.css b/src/main/frontend/components/plugins.css index 24556b216e..52c633ded9 100644 --- a/src/main/frontend/components/plugins.css +++ b/src/main/frontend/components/plugins.css @@ -140,7 +140,8 @@ } } - &-details {} + &-details { + } } .cp__themes { @@ -180,6 +181,16 @@ } } +.ui-items-container { + &[data-type=toolbar] { + @apply flex items-center mt-1 pl-2; + + > .injected-ui-item-toolbar { + @apply px-2 opacity-50 hover:opacity-100 transition-opacity; + } + } +} + body { &[data-page=page] { .lsp-hook-ui-slot { diff --git a/src/main/frontend/components/repo.cljs b/src/main/frontend/components/repo.cljs index fdb5285805..0d8ecbd6d0 100644 --- a/src/main/frontend/components/repo.cljs +++ b/src/main/frontend/components/repo.cljs @@ -99,7 +99,7 @@ (when-not (= repo config/local-repo) (if (and nfs-repo? (nfs-handler/supported?)) (let [syncing? (state/sub :graph/syncing?)] - [:div.ml-3.mr-1.mt-1.opacity-60.refresh.hover:opacity-100 {:class (if syncing? "loader" "initial")} + [:div.ml-2.mr-1.mt-1.opacity-60.refresh.hover:opacity-100 {:class (if syncing? "loader" "initial")} [:a {:on-click #(nfs-handler/refresh! repo refresh-cb) :title (str "Import files from the local directory: " (config/get-local-dir repo) ".\nVersion: " diff --git a/src/main/frontend/handler/plugin.cljs b/src/main/frontend/handler/plugin.cljs index d8755132e5..78e930b89c 100644 --- a/src/main/frontend/handler/plugin.cljs +++ b/src/main/frontend/handler/plugin.cljs @@ -63,6 +63,18 @@ [pid] (swap! state/state md/dissoc-in [:plugin/simple-commands (keyword pid)])) +(defn register-plugin-ui-item + [pid {:keys [key type template] :as opts}] + (when-let [pid (keyword pid)] + (when (or true (contains? (:plugin/installed-plugins @state/state) pid)) + (do (swap! state/state update-in [:plugin/installed-ui-items pid] + (fnil conj []) [type opts pid]) + true)))) + +(defn unregister-plugin-ui-items + [pid] + (swap! state/state assoc-in [:plugin/installed-ui-items (keyword pid)] [])) + (defn update-plugin-settings [id settings] (swap! state/state update-in [:plugin/installed-plugins id] assoc :settings settings)) @@ -178,7 +190,14 @@ ;; plugins (swap! state/state md/dissoc-in [:plugin/installed-plugins (keyword pid)]) ;; commands - (unregister-plugin-slash-command pid)))) + (unregister-plugin-slash-command pid) + (unregister-plugin-simple-command pid) + (unregister-plugin-ui-items pid)))) + + (.on "disabled" (fn [pid] + (unregister-plugin-slash-command pid) + (unregister-plugin-simple-command pid) + (unregister-plugin-ui-items pid))) (.on "theme-changed" (fn [^js themes] (swap! state/state assoc :plugin/installed-themes diff --git a/src/main/frontend/state.cljs b/src/main/frontend/state.cljs index f9b87f98c9..934173019f 100644 --- a/src/main/frontend/state.cljs +++ b/src/main/frontend/state.cljs @@ -123,6 +123,7 @@ :plugin/installed-plugins {} :plugin/installed-themes [] :plugin/installed-commands {} + :plugin/installed-ui-items {} :plugin/simple-commands {} :plugin/selected-theme nil :plugin/selected-unpacked-pkg nil @@ -1113,6 +1114,12 @@ (filterv #(= (keyword (first %)) (keyword type)) (apply concat (vals (:plugin/simple-commands @state))))) +(defn get-plugins-ui-items-with-type + [type] + (filterv #(= (keyword (first %)) (keyword type)) + (apply concat (vals (:plugin/installed-ui-items @state))))) + + (defn get-scheduled-future-days [] (let [days (:scheduled/future-days (get-config))] diff --git a/src/main/logseq/api.cljs b/src/main/logseq/api.cljs index e37f485937..178ab7ece8 100644 --- a/src/main/logseq/api.cljs +++ b/src/main/logseq/api.cljs @@ -153,6 +153,12 @@ (plugin-handler/register-plugin-simple-command pid cmd (assoc action 0 (keyword (first action))))))) +(def ^:export register_plugin_ui_item + (fn [pid type ^js opts] + (when-let [opts (bean/->clj opts)] + (plugin-handler/register-plugin-ui-item + pid (assoc opts :type type))))) + ;; app (def ^:export relaunch (fn []