plugin.cljs 33 KB

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