Files
logseq/src/main/frontend/handler/plugin_config.cljs
2026-04-05 17:06:46 +08:00

140 lines
5.9 KiB
Clojure

(ns frontend.handler.plugin-config
"This system component encapsulates the global plugin.edn and depends on the
global-config component. This component is only enabled? if both the
global-config and plugin components are enabled. plugins.edn is automatically updated
when a plugin is installed, updated or removed"
(:require [borkdude.rewrite-edn :as rewrite]
[cljs-bean.core :as bean]
[clojure.edn :as edn]
[clojure.pprint :as pprint]
[clojure.set :as set]
[frontend.fs :as fs]
[frontend.context.i18n :refer [t]]
[frontend.handler.common.plugin :as plugin-common-handler]
[frontend.handler.global-config :as global-config-handler]
[frontend.handler.notification :as notification]
[frontend.schema.handler.plugin-config :as plugin-config-schema]
[frontend.state :as state]
[frontend.util :as util]
[lambdaisland.glogi :as log]
[logseq.common.path :as path]
[malli.core :as m]
[malli.error :as me]
[promesa.core :as p]))
(defn plugin-config-path
"Full path to plugins.edn"
[]
(path/path-join (global-config-handler/global-config-dir) "plugins.edn"))
(def common-plugin-keys
"Vec of plugin keys to store in plugins.edn and to compare with installed-plugins state"
(->> plugin-config-schema/Plugin rest (mapv first)))
(defn add-or-update-plugin
"Adds or updates a plugin from plugins.edn"
[{:keys [id] :as plugin}]
(p/let [content (fs/read-file nil (plugin-config-path))
updated-content (-> content
rewrite/parse-string
(rewrite/assoc (keyword id) (select-keys plugin common-plugin-keys))
str)]
(fs/write-file! (plugin-config-path) updated-content)))
(defn remove-plugin
"Removes a plugin from plugins.edn"
[plugin-id]
(p/let [content (fs/read-file nil (plugin-config-path))
updated-content (-> content rewrite/parse-string (rewrite/dissoc (keyword plugin-id)) str)]
(fs/write-file! (plugin-config-path) updated-content)))
(defn- create-plugin-config-file-if-not-exists
[]
(let [content (-> (:plugin/installed-plugins @state/state)
(update-vals #(select-keys % common-plugin-keys))
pprint/pprint
with-out-str)]
(fs/create-if-not-exists (state/get-current-repo) nil (plugin-config-path) content)))
(defn- determine-plugins-to-change
"Given installed plugins state and plugins from plugins.edn,
returns map of plugins to install and uninstall"
[installed-plugins edn-plugins]
(let [installed-plugins-set (->> installed-plugins
vals
(map #(-> (select-keys % common-plugin-keys)
(assoc :id (keyword (:id %)))))
set)
edn-plugins-set (->> edn-plugins
(map (fn [[k v]] (assoc v :id k)))
set)]
(if (= installed-plugins-set edn-plugins-set)
{}
{:install (mapv #(assoc % :plugin-action "install")
(set/difference edn-plugins-set installed-plugins-set))
:uninstall (vec (set/difference installed-plugins-set edn-plugins-set))})))
(defn open-install-plugin-from-github-modal
[]
(state/pub-event! [:go/install-plugin-from-github]))
(defn open-replace-plugins-modal
[]
(p/catch
(p/let [edn-plugins* (fs/read-file nil (plugin-config-path))
edn-plugins (edn/read-string edn-plugins*)]
(if-let [errors (->> edn-plugins (m/explain plugin-config-schema/Plugins-edn) me/humanize)]
(do
(notification/show! (t :plugin/invalid-plugins-edn)
:error)
(log/error :plugin-edn-errors errors))
(let [plugins-to-change (determine-plugins-to-change
(:plugin/installed-plugins @state/state)
edn-plugins)]
(state/pub-event! [:go/plugins-from-file plugins-to-change]))))
(fn [e]
(if (= :reader-exception (:type (ex-data e)))
(notification/show! (t :plugin/malformed-plugins-edn)
:error)
(log/error :unexpected-error e)))))
(defn replace-plugins
"Replaces current plugins given plugins to install and uninstall"
[plugins]
(log/info :uninstall-plugins (:uninstall plugins))
(doseq [plugin (:uninstall plugins)]
(plugin-common-handler/unregister-plugin (name (:id plugin))))
(log/info :install-plugins (:install plugins))
(doseq [plugin (:install plugins)]
(plugin-common-handler/install-marketplace-plugin!
;; Add :name so that install notifications are readable
(assoc plugin :name (name (:id plugin))))))
(defn setup-install-listener!
"Sets up a listener for the lsp-installed event to update plugins.edn"
[]
(let [channel (name :lsp-updates)
listener (fn listener [_ e]
(when-let [{:keys [status payload only-check]} (bean/->clj e)]
(when (and (= status "completed") (not only-check))
(let [{:keys [theme effect]} payload]
(add-or-update-plugin
(assoc payload
:version (:installed-version payload)
:effect (boolean effect)
;; Manual installation doesn't have theme field but
;; plugin.edn requires this field
:theme (boolean theme)))))))]
(when (util/electron?)
(js/window.apis.addListener channel listener))
;;teardown
(fn []
(when (util/electron?)
(js/window.apis.removeListener channel listener)))))
(defn start
"This component has just one responsibility on start, to create a plugins.edn
if none exists"
[]
(create-plugin-config-file-if-not-exists))