plugin.cljs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. (ns frontend.handler.plugin
  2. (:require [promesa.core :as p]
  3. [rum.core :as rum]
  4. [frontend.util :as util]
  5. [clojure.walk :as walk]
  6. [logseq.graph-parser.mldoc :as gp-mldoc]
  7. [frontend.handler.notification :as notifications]
  8. [camel-snake-kebab.core :as csk]
  9. [frontend.state :as state]
  10. [medley.core :as medley]
  11. [frontend.fs :as fs]
  12. [electron.ipc :as ipc]
  13. [cljs-bean.core :as bean]
  14. [clojure.string :as string]
  15. [lambdaisland.glogi :as log]
  16. [frontend.components.svg :as svg]
  17. [frontend.context.i18n :refer [t]]
  18. [frontend.format :as format]))
  19. (defonce lsp-enabled?
  20. (and (util/electron?)
  21. (state/lsp-enabled?-or-theme)))
  22. (defn- normalize-keyword-for-json
  23. [input]
  24. (when input
  25. (walk/postwalk
  26. (fn [a]
  27. (cond
  28. (keyword? a) (csk/->camelCase (name a))
  29. (uuid? a) (str a)
  30. :else a)) input)))
  31. (defn invoke-exported-api
  32. [type & args]
  33. (try
  34. (apply js-invoke (aget js/window.logseq "api") type args)
  35. (catch js/Error e (js/console.error e))))
  36. ;; state handlers
  37. (defonce central-endpoint "https://raw.githubusercontent.com/logseq/marketplace/master/")
  38. (defonce plugins-url (str central-endpoint "plugins.json"))
  39. (defonce stats-url (str central-endpoint "stats.json"))
  40. (declare select-a-plugin-theme)
  41. (defn load-plugin-preferences
  42. []
  43. (-> (invoke-exported-api "load_user_preferences")
  44. (p/then #(bean/->clj %))
  45. (p/then #(state/set-state! :plugin/preferences %))
  46. (p/catch
  47. #(js/console.error %))))
  48. (defn save-plugin-preferences!
  49. ([input] (save-plugin-preferences! input true))
  50. ([input reload-state?]
  51. (when-let [^js input (and (map? input) (bean/->js input))]
  52. (p/then
  53. (js/LSPluginCore.saveUserPreferences input)
  54. #(when reload-state?
  55. (load-plugin-preferences))))))
  56. (defn gh-repo-url [repo]
  57. (str "https://github.com/" repo))
  58. (defn pkg-asset [id asset]
  59. (if (and asset (string/starts-with? asset "http"))
  60. asset (when-let [asset (and asset (string/replace asset #"^[./]+" ""))]
  61. (str central-endpoint "packages/" id "/" asset))))
  62. (defn load-marketplace-plugins
  63. [refresh?]
  64. (if (or refresh? (nil? (:plugin/marketplace-pkgs @state/state)))
  65. (p/create
  66. (fn [resolve reject]
  67. (let [on-ok (fn [res]
  68. (if-let [res (and res (bean/->clj res))]
  69. (let [pkgs (:packages res)]
  70. (state/set-state! :plugin/marketplace-pkgs pkgs)
  71. (resolve pkgs))
  72. (reject nil)))]
  73. (if (state/http-proxy-enabled-or-val?)
  74. (-> (ipc/ipc :httpFetchJSON plugins-url)
  75. (p/then on-ok)
  76. (p/catch reject))
  77. (util/fetch plugins-url on-ok reject)))))
  78. (p/resolved (:plugin/marketplace-pkgs @state/state))))
  79. (defn load-marketplace-stats
  80. [refresh?]
  81. (if (or refresh? (nil? (:plugin/marketplace-stats @state/state)))
  82. (p/create
  83. (fn [resolve reject]
  84. (let [on-ok (fn [^js res]
  85. (if-let [res (and res (bean/->clj res))]
  86. (do
  87. (state/set-state!
  88. :plugin/marketplace-stats
  89. (into {} (map (fn [[k stat]]
  90. [k (assoc stat
  91. :total_downloads
  92. (reduce (fn [a b] (+ a (get b 2))) 0 (:releases stat)))])
  93. res)))
  94. (resolve nil))
  95. (reject nil)))]
  96. (if (state/http-proxy-enabled-or-val?)
  97. (-> (ipc/ipc :httpFetchJSON stats-url)
  98. (p/then on-ok)
  99. (p/catch reject))
  100. (util/fetch stats-url on-ok reject)))))
  101. (p/resolved nil)))
  102. (defn installed?
  103. [id]
  104. (and (contains? (:plugin/installed-plugins @state/state) (keyword id))
  105. (get-in @state/state [:plugin/installed-plugins (keyword id) :iir])))
  106. (defn install-marketplace-plugin
  107. [{:keys [id] :as mft}]
  108. (when-not (and (:plugin/installing @state/state)
  109. (installed? id))
  110. (p/create
  111. (fn [resolve]
  112. (state/set-state! :plugin/installing mft)
  113. (ipc/ipc :installMarketPlugin mft)
  114. (resolve id)))))
  115. (defn check-or-update-marketplace-plugin
  116. [{:keys [id] :as pkg} error-handler]
  117. (when-not (and (:plugin/installing @state/state)
  118. (not (installed? id)))
  119. (p/catch
  120. (p/then
  121. (do (state/set-state! :plugin/installing pkg)
  122. (p/catch
  123. (load-marketplace-plugins false)
  124. (fn [^js e]
  125. (state/reset-all-updates-state)
  126. (throw e))))
  127. (fn [mfts]
  128. (let [mft (some #(when (= (:id %) id) %) mfts)]
  129. ;;TODO: (throw (js/Error. [:not-found-in-marketplace id]))
  130. (ipc/ipc :updateMarketPlugin (merge (dissoc pkg :logger) mft)))
  131. true))
  132. (fn [^js e]
  133. (error-handler e)
  134. (state/set-state! :plugin/installing nil)
  135. (js/console.error e)))))
  136. (defn get-plugin-inst
  137. [id]
  138. (try
  139. (js/LSPluginCore.ensurePlugin (name id))
  140. (catch js/Error _e
  141. nil)))
  142. (defn open-updates-downloading
  143. []
  144. (when (and (not (:plugin/updates-downloading? @state/state))
  145. (seq (state/all-available-coming-updates)))
  146. (->> (:plugin/updates-coming @state/state)
  147. (map #(if (state/coming-update-new-version? (second %1))
  148. (update % 1 dissoc :error-code) %1))
  149. (into {})
  150. (state/set-state! :plugin/updates-coming))
  151. (state/set-state! :plugin/updates-downloading? true)))
  152. (defn close-updates-downloading
  153. []
  154. (when (:plugin/updates-downloading? @state/state)
  155. (state/set-state! :plugin/updates-downloading? false)))
  156. (defn has-setting-schema?
  157. [id]
  158. (when-let [pl (and id (get-plugin-inst (name id)))]
  159. (boolean (.-settingsSchema pl))))
  160. (defn get-enabled-plugins-if-setting-schema
  161. []
  162. (when-let [plugins (seq (state/get-enabled?-installed-plugins false nil true))]
  163. (filter #(has-setting-schema? (:id %)) plugins)))
  164. (defn setup-install-listener!
  165. []
  166. (let [channel (name :lsp-installed)
  167. listener (fn [^js _ ^js e]
  168. (js/console.debug :lsp-installed e)
  169. (when-let [{:keys [status payload only-check]} (bean/->clj e)]
  170. (case (keyword status)
  171. :completed
  172. (let [{:keys [id dst name title theme]} payload
  173. name (or title name "Untitled")]
  174. (if only-check
  175. (state/consume-updates-coming-plugin payload false)
  176. (if (installed? id)
  177. (when-let [^js pl (get-plugin-inst id)] ;; update
  178. (p/then
  179. (.reload pl)
  180. #(do
  181. ;;(if theme (select-a-plugin-theme id))
  182. (notifications/show!
  183. (str (t :plugin/update) (t :plugins) ": " name " - " (.-version (.-options pl))) :success)
  184. (state/consume-updates-coming-plugin payload true))))
  185. (do ;; register new
  186. (p/then
  187. (js/LSPluginCore.register (bean/->js {:key id :url dst}))
  188. (fn [] (when theme (js/setTimeout #(select-a-plugin-theme id) 300))))
  189. (notifications/show!
  190. (str (t :plugin/installed) (t :plugins) ": " name) :success)))))
  191. :error
  192. (let [error-code (keyword (string/replace (:error-code payload) #"^[\s\:\[]+" ""))
  193. [msg type] (case error-code
  194. :no-new-version
  195. [(str (t :plugin/up-to-date) " :)") :success]
  196. [error-code :error])
  197. pending? (seq (:plugin/updates-pending @state/state))]
  198. (if (and only-check pending?)
  199. (state/consume-updates-coming-plugin payload false)
  200. (do
  201. ;; consume failed download updates
  202. (when (and (not only-check) (not pending?))
  203. (state/consume-updates-coming-plugin payload true))
  204. ;; notify human tips
  205. (notifications/show!
  206. (str
  207. (if (= :error type) "[Error]" "")
  208. (str "<" (:id payload) "> ")
  209. msg) type)))
  210. (js/console.error payload))
  211. :dunno))
  212. ;; reset
  213. (js/setTimeout #(state/set-state! :plugin/installing nil) 512)
  214. true)]
  215. (js/window.apis.addListener channel listener)
  216. ;; clear
  217. (fn []
  218. (js/window.apis.removeAllListeners channel))))
  219. (defn register-plugin
  220. [pl]
  221. (swap! state/state update-in [:plugin/installed-plugins] assoc (keyword (:id pl)) pl))
  222. (defn unregister-plugin
  223. [id]
  224. (js/LSPluginCore.unregister id))
  225. (defn host-mounted!
  226. []
  227. (and lsp-enabled? (js/LSPluginCore.hostMounted)))
  228. (defn register-plugin-slash-command
  229. [pid [cmd actions]]
  230. (when-let [pid (keyword pid)]
  231. (when (contains? (:plugin/installed-plugins @state/state) pid)
  232. (swap! state/state update-in [:plugin/installed-slash-commands pid]
  233. (fnil merge {}) (hash-map cmd (mapv #(conj % {:pid pid}) actions)))
  234. (state/pub-event! [:rebuild-slash-commands-list])
  235. true)))
  236. (defn unregister-plugin-slash-command
  237. [pid]
  238. (swap! state/state medley/dissoc-in [:plugin/installed-slash-commands (keyword pid)])
  239. (state/pub-event! [:rebuild-slash-commands-list]))
  240. (def keybinding-mode-handler-map
  241. {:global :shortcut.handler/editor-global
  242. :non-editing :shortcut.handler/global-non-editing-only
  243. :editing :shortcut.handler/block-editing-only})
  244. (defn simple-cmd->palette-cmd
  245. [pid {:keys [key label type desc keybinding] :as cmd} action]
  246. (let [palette-cmd {:id (keyword (str "plugin." pid "/" key))
  247. :desc (or desc label)
  248. :shortcut (when-let [shortcut (:binding keybinding)]
  249. (if util/mac?
  250. (or (:mac keybinding) shortcut)
  251. shortcut))
  252. :handler-id (let [mode (or (:mode keybinding) :global)]
  253. (get keybinding-mode-handler-map (keyword mode)))
  254. :action (fn []
  255. (state/pub-event!
  256. [:exec-plugin-cmd {:type type :key key :pid pid :cmd cmd :action action}]))}]
  257. palette-cmd))
  258. (defn simple-cmd-keybinding->shortcut-args
  259. [pid key keybinding]
  260. (let [id (keyword (str "plugin." pid "/" key))
  261. binding (:binding keybinding)
  262. binding (if util/mac?
  263. (or (:mac keybinding) binding)
  264. binding)
  265. mode (or (:mode keybinding) :global)
  266. mode (get keybinding-mode-handler-map (keyword mode))]
  267. [mode id {:binding binding}]))
  268. (defn register-plugin-simple-command
  269. ;; action => [:action-key :event-key]
  270. [pid {:keys [type] :as cmd} action]
  271. (when-let [pid (keyword pid)]
  272. (when (contains? (:plugin/installed-plugins @state/state) pid)
  273. (swap! state/state update-in [:plugin/simple-commands pid]
  274. (fnil conj []) [type cmd action pid])
  275. true)))
  276. (defn unregister-plugin-simple-command
  277. [pid]
  278. (swap! state/state medley/dissoc-in [:plugin/simple-commands (keyword pid)]))
  279. (defn register-plugin-ui-item
  280. [pid {:keys [key type] :as opts}]
  281. (when-let [pid (keyword pid)]
  282. (when (contains? (:plugin/installed-plugins @state/state) pid)
  283. (let [items (or (get-in @state/state [:plugin/installed-ui-items pid]) [])
  284. items (filter #(not= key (:key (second %))) items)]
  285. (swap! state/state assoc-in [:plugin/installed-ui-items pid]
  286. (conj items [type opts pid])))
  287. true)))
  288. (defn unregister-plugin-ui-items
  289. [pid]
  290. (swap! state/state assoc-in [:plugin/installed-ui-items (keyword pid)] []))
  291. (defn register-plugin-resources
  292. [pid type {:keys [key] :as opts}]
  293. (when-let [pid (keyword pid)]
  294. (when-let [type (and key (keyword type))]
  295. (let [path [:plugin/installed-resources pid type]]
  296. (when (contains? #{:error nil} (get-in @state/state (conj path key)))
  297. (swap! state/state update-in path
  298. (fnil assoc {}) key (merge opts {:pid pid}))
  299. true)))))
  300. (defn unregister-plugin-resources
  301. [pid]
  302. (when-let [pid (keyword pid)]
  303. (swap! state/state medley/dissoc-in [:plugin/installed-resources pid])
  304. true))
  305. (defn unregister-plugin-themes
  306. ([pid] (unregister-plugin-themes pid true))
  307. ([pid effect]
  308. (js/LSPluginCore.unregisterTheme (name pid) effect)))
  309. (def *fenced-code-providers (atom #{}))
  310. (defn register-fenced-code-renderer
  311. [pid type {:keys [before subs render edit] :as _opts}]
  312. (when-let [key (and type (keyword type))]
  313. (register-plugin-resources pid :fenced-code-renderers
  314. {:key key :edit edit :before before :subs subs :render render})
  315. (swap! *fenced-code-providers conj pid)
  316. #(swap! *fenced-code-providers disj pid)))
  317. (defn hook-fenced-code-by-type
  318. [type]
  319. (when-let [key (and (seq @*fenced-code-providers) type (keyword type))]
  320. (first (map #(state/get-plugin-resource % :fenced-code-renderers key)
  321. @*fenced-code-providers))))
  322. (def *extensions-enhancer-providers (atom #{}))
  323. (defn register-extensions-enhancer
  324. [pid type {:keys [enhancer] :as _opts}]
  325. (when-let [key (and type (keyword type))]
  326. (register-plugin-resources pid :extensions-enhancers
  327. {:key key :enhancer enhancer})
  328. (swap! *extensions-enhancer-providers conj pid)
  329. #(swap! *extensions-enhancer-providers disj pid)))
  330. (defn hook-extensions-enhancer-by-type
  331. [type]
  332. (when-let [key (and type (keyword type))]
  333. (map #(state/get-plugin-resource % :extensions-enhancers key)
  334. @*extensions-enhancer-providers)))
  335. (defn select-a-plugin-theme
  336. [pid]
  337. (when-let [themes (get (group-by :pid (:plugin/installed-themes @state/state)) pid)]
  338. (when-let [theme (first themes)]
  339. (js/LSPluginCore.selectTheme (bean/->js theme)))))
  340. (defn update-plugin-settings-state
  341. [id settings]
  342. (state/set-state! [:plugin/installed-plugins id :settings]
  343. ;; TODO: force settings related ui reactive
  344. ;; Sometimes toggle to `disable` not working
  345. ;; But related-option data updated?
  346. (assoc settings :disabled (boolean (:disabled settings)))))
  347. (defn open-settings-file-in-default-app!
  348. [id-or-plugin]
  349. (when-let [plugin (if (coll? id-or-plugin)
  350. id-or-plugin (state/get-plugin-by-id id-or-plugin))]
  351. (when-let [file-path (:usf plugin)]
  352. (js/apis.openPath file-path))))
  353. (defn open-plugin-settings!
  354. ([id] (open-plugin-settings! id false))
  355. ([id nav?]
  356. (when-let [plugin (and id (state/get-plugin-by-id id))]
  357. (if (has-setting-schema? id)
  358. (state/pub-event! [:go/plugins-settings id nav? (or (:name plugin) (:title plugin))])
  359. (open-settings-file-in-default-app! plugin)))))
  360. (defn parse-user-md-content
  361. [content {:keys [url]}]
  362. (try
  363. (when-not (string/blank? content)
  364. (let [content (if-not (string/blank? url)
  365. (string/replace
  366. content #"!\[[^\]]*\]\((.*?)\s*(\"(?:.*[^\"])\")?\s*\)"
  367. (fn [[matched link]]
  368. (if (and link (not (string/starts-with? link "http")))
  369. (string/replace matched link (util/node-path.join url link))
  370. matched)))
  371. content)]
  372. (format/to-html content :markdown (gp-mldoc/default-config :markdown))))
  373. (catch js/Error e
  374. (log/error :parse-user-md-exception e)
  375. content)))
  376. (defn open-readme!
  377. [url item display]
  378. (let [repo (:repo item)]
  379. (if (nil? repo)
  380. ;; local
  381. (-> (p/let [content (invoke-exported-api "load_plugin_readme" url)
  382. content (parse-user-md-content content item)]
  383. (and (string/blank? (string/trim content)) (throw nil))
  384. (state/set-state! :plugin/active-readme [content item])
  385. (state/set-sub-modal! (fn [_] (display))))
  386. (p/catch #(do (js/console.warn %)
  387. (notifications/show! "No README content." :warn))))
  388. ;; market
  389. (state/set-sub-modal! (fn [_] (display repo nil))))))
  390. (defn load-unpacked-plugin
  391. []
  392. (when util/electron?
  393. (p/let [path (ipc/ipc "openDialog")]
  394. (when-not (:plugin/selected-unpacked-pkg @state/state)
  395. (state/set-state! :plugin/selected-unpacked-pkg path)))))
  396. (defn reset-unpacked-state
  397. []
  398. (state/set-state! :plugin/selected-unpacked-pkg nil))
  399. (defn hook-plugin
  400. [tag type payload plugin-id]
  401. (when lsp-enabled?
  402. (try
  403. (js-invoke js/LSPluginCore
  404. (str "hook" (string/capitalize (name tag)))
  405. (name type)
  406. (if (coll? payload)
  407. (bean/->js (normalize-keyword-for-json payload))
  408. payload)
  409. (if (keyword? plugin-id) (name plugin-id) plugin-id))
  410. (catch js/Error e
  411. (js/console.error "[Hook Plugin Err]" e)))))
  412. (defn hook-plugin-app
  413. ([type payload] (hook-plugin-app type payload nil))
  414. ([type payload plugin-id] (hook-plugin :app type payload plugin-id)))
  415. (defn hook-plugin-editor
  416. ([type payload] (hook-plugin-editor type payload nil))
  417. ([type payload plugin-id] (hook-plugin :editor type payload plugin-id)))
  418. (defn hook-plugin-db
  419. ([type payload] (hook-plugin-db type payload nil))
  420. ([type payload plugin-id] (hook-plugin :db type payload plugin-id)))
  421. (defn hook-plugin-block-changes
  422. [{:keys [blocks tx-data tx-meta]}]
  423. (doseq [b blocks
  424. :let [tx-data' (group-by first tx-data)
  425. type (str "block:" (:block/uuid b))]]
  426. (hook-plugin-db type {:block b :tx-data (get tx-data' (:db/id b)) :tx-meta tx-meta})))
  427. (defn get-ls-dotdir-root
  428. []
  429. (ipc/ipc "getLogseqDotDirRoot"))
  430. (defn make-fn-to-load-dotdir-json
  431. [dirname default]
  432. (fn [key]
  433. (when-let [key (and key (name key))]
  434. (p/let [repo ""
  435. path (get-ls-dotdir-root)
  436. exist? (fs/file-exists? path dirname)
  437. _ (when-not exist? (fs/mkdir! (util/node-path.join path dirname)))
  438. path (util/node-path.join path dirname (str key ".json"))
  439. _ (fs/create-if-not-exists repo "" path (or default "{}"))
  440. json (fs/read-file "" path)]
  441. [path (js/JSON.parse json)]))))
  442. (defn make-fn-to-save-dotdir-json
  443. [dirname]
  444. (fn [key content]
  445. (when-let [key (and key (name key))]
  446. (p/let [repo ""
  447. path (get-ls-dotdir-root)
  448. path (util/node-path.join path dirname (str key ".json"))]
  449. (fs/write-file! repo "" path content {:skip-compare? true})))))
  450. (defn make-fn-to-unlink-dotdir-json
  451. [dirname]
  452. (fn [key]
  453. (when-let [key (and key (name key))]
  454. (p/let [repo ""
  455. path (get-ls-dotdir-root)
  456. path (util/node-path.join path dirname (str key ".json"))]
  457. (fs/unlink! repo path nil)))))
  458. (defn show-themes-modal!
  459. []
  460. (state/pub-event! [:modal/show-themes-modal]))
  461. (defn goto-plugins-dashboard!
  462. []
  463. (state/pub-event! [:go/plugins]))
  464. (defn- get-user-default-plugins
  465. []
  466. (p/catch
  467. (p/let [files ^js (ipc/ipc "getUserDefaultPlugins")
  468. files (js->clj files)]
  469. (map #(hash-map :url %) files))
  470. (fn [e]
  471. (js/console.error e))))
  472. (defn check-enabled-for-updates
  473. [theme?]
  474. (let [pending? (seq (:plugin/updates-pending @state/state))]
  475. (when-let [plugins (and (not pending?)
  476. ;; TODO: too many requests may be limited by Github api
  477. (seq (take 32 (state/get-enabled?-installed-plugins theme?))))]
  478. (state/set-state! :plugin/updates-pending
  479. (into {} (map (fn [v] [(keyword (:id v)) v]) plugins)))
  480. (state/pub-event! [:plugin/consume-updates]))))
  481. (defn call-plugin
  482. [^js pl type payload]
  483. (when pl
  484. (.call (.-caller pl) (name type) (bean/->js payload))))
  485. (defn request-callback
  486. [^js pl req-id payload]
  487. (call-plugin pl :#lsp#request#callback {:requestId req-id :payload payload}))
  488. (defn op-pinned-toolbar-item!
  489. [key op]
  490. (let [pinned (state/sub [:plugin/preferences :pinnedToolbarItems])
  491. pinned (into #{} pinned)]
  492. (when-let [op-fn (case op
  493. :add conj
  494. :remove disj)]
  495. (save-plugin-preferences! {:pinnedToolbarItems (op-fn pinned (name key))}))))
  496. ;; components
  497. (rum/defc lsp-indicator < rum/reactive
  498. []
  499. (let [text (state/sub :plugin/indicator-text)]
  500. (when-not (= text "END")
  501. [:div.flex.align-items.justify-center.h-screen.w-full.preboot-loading
  502. [:span.flex.items-center.justify-center.w-60.flex-col
  503. [:small.scale-250.opacity-70.mb-10.animate-pulse (svg/logo false)]
  504. [:small.block.text-sm.relative.opacity-50 {:style {:right "-8px"}} text]]])))
  505. (defn init-plugins!
  506. [callback]
  507. (let [el (js/document.createElement "div")]
  508. (.appendChild js/document.body el)
  509. (rum/mount
  510. (lsp-indicator) el))
  511. (state/set-state! :plugin/indicator-text "LOADING")
  512. (-> (p/let [root (get-ls-dotdir-root)
  513. _ (.setupPluginCore js/LSPlugin (bean/->js {:localUserConfigRoot root :dotConfigRoot root}))
  514. clear-commands! (fn [pid]
  515. ;; commands
  516. (unregister-plugin-slash-command pid)
  517. (invoke-exported-api "unregister_plugin_simple_command" pid)
  518. (invoke-exported-api "uninstall_plugin_hook" pid)
  519. (unregister-plugin-ui-items pid)
  520. (unregister-plugin-resources pid))
  521. _ (doto js/LSPluginCore
  522. (.on "registered"
  523. (fn [^js pl]
  524. (register-plugin
  525. (bean/->clj (.parse js/JSON (.stringify js/JSON pl))))))
  526. (.on "reloaded"
  527. (fn [^js pl]
  528. (register-plugin
  529. (bean/->clj (.parse js/JSON (.stringify js/JSON pl))))))
  530. (.on "unregistered" (fn [pid]
  531. (let [pid (keyword pid)]
  532. ;; effects
  533. (unregister-plugin-themes pid)
  534. ;; plugins
  535. (swap! state/state medley/dissoc-in [:plugin/installed-plugins pid])
  536. ;; commands
  537. (clear-commands! pid))))
  538. (.on "unlink-plugin" (fn [pid]
  539. (let [pid (keyword pid)]
  540. (ipc/ipc "uninstallMarketPlugin" (name pid)))))
  541. (.on "beforereload" (fn [^js pl]
  542. (let [pid (.-id pl)]
  543. (clear-commands! pid)
  544. (unregister-plugin-themes pid false))))
  545. (.on "disabled" (fn [pid]
  546. (clear-commands! pid)
  547. (unregister-plugin-themes pid)))
  548. (.on "themes-changed" (fn [^js themes]
  549. (swap! state/state assoc :plugin/installed-themes
  550. (vec (mapcat (fn [[pid vs]] (mapv #(assoc % :pid pid) (bean/->clj vs))) (bean/->clj themes))))))
  551. (.on "theme-selected" (fn [^js theme]
  552. (let [theme (bean/->clj theme)
  553. url (:url theme)
  554. mode (:mode theme)]
  555. (when mode
  556. (state/set-custom-theme! mode theme)
  557. (state/set-theme-mode! mode))
  558. (hook-plugin-app :theme-changed theme)
  559. (state/set-state! :plugin/selected-theme url))))
  560. (.on "reset-custom-theme" (fn [^js themes]
  561. (let [themes (bean/->clj themes)
  562. custom-theme (dissoc themes :mode)
  563. mode (:mode themes)]
  564. (state/set-custom-theme! {:light (if (nil? (:light custom-theme)) {:mode "light"} (:light custom-theme))
  565. :dark (if (nil? (:dark custom-theme)) {:mode "dark"} (:dark custom-theme))})
  566. (state/set-theme-mode! mode))))
  567. (.on "settings-changed" (fn [id ^js settings]
  568. (let [id (keyword id)]
  569. (when (and settings
  570. (contains? (:plugin/installed-plugins @state/state) id))
  571. (update-plugin-settings-state id (bean/->clj settings)))))))
  572. default-plugins (get-user-default-plugins)
  573. _ (.register js/LSPluginCore (bean/->js (if (seq default-plugins) default-plugins [])) true)])
  574. (p/then
  575. (fn []
  576. (state/set-state! :plugin/indicator-text "END")
  577. (callback)))
  578. (p/catch
  579. (fn [^js e]
  580. (log/error :setup-plugin-system-error e)
  581. (state/set-state! :plugin/indicator-text (str "Fatal: " e))))))
  582. (defn setup!
  583. "setup plugin core handler"
  584. [callback]
  585. (if (not lsp-enabled?)
  586. (callback)
  587. (init-plugins! callback)))