plugins.cljs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. (ns frontend.components.plugins
  2. (:require [rum.core :as rum]
  3. [frontend.state :as state]
  4. [cljs-bean.core :as bean]
  5. [frontend.context.i18n :as i18n]
  6. [frontend.ui :as ui]
  7. [frontend.handler.ui :as ui-handler]
  8. [frontend.search :as search]
  9. [frontend.util :as util]
  10. [frontend.mixins :as mixins]
  11. [promesa.core :as p]
  12. [frontend.components.svg :as svg]
  13. [frontend.handler.notification :as notification]
  14. [frontend.handler.plugin :as plugin-handler]
  15. [frontend.handler.page :as page-handler]
  16. [clojure.string :as string]))
  17. (rum/defcs installed-themes
  18. < rum/reactive
  19. (rum/local 0 ::cursor)
  20. (rum/local 0 ::total)
  21. (mixins/event-mixin
  22. (fn [state]
  23. (let [*cursor (::cursor state)
  24. *total (::total state)
  25. ^js target (rum/dom-node state)]
  26. (.focus target)
  27. (mixins/on-key-down
  28. state {38 ;; up
  29. (fn [^js _e]
  30. (reset! *cursor
  31. (if (zero? @*cursor)
  32. (dec @*total) (dec @*cursor))))
  33. 40 ;; down
  34. (fn [^js _e]
  35. (reset! *cursor
  36. (if (= @*cursor (dec @*total))
  37. 0 (inc @*cursor))))
  38. 13 ;; enter
  39. #(when-let [^js active (.querySelector target ".is-active")]
  40. (.click active))
  41. }))))
  42. [state]
  43. (let [*cursor (::cursor state)
  44. *total (::total state)
  45. themes (state/sub :plugin/installed-themes)
  46. selected (state/sub :plugin/selected-theme)
  47. themes (cons {:name "Default Theme" :url nil :description "Logseq default light/dark theme."} themes)
  48. themes (sort #(:selected %) (map #(assoc % :selected (= (:url %) selected)) themes))
  49. _ (reset! *total (count themes))]
  50. (rum/with-context
  51. [[t] i18n/*tongue-context*]
  52. [:div.cp__themes-installed
  53. {:tab-index -1}
  54. [:h1.mb-4.text-2xl.p-1 (t :themes)]
  55. (map-indexed
  56. (fn [idx opt]
  57. (let [current-selected (:selected opt)
  58. plg (get (:plugin/installed-plugins @state/state) (keyword (:pid opt)))]
  59. [:div.it.flex.px-3.py-1.5.rounded-sm.justify-between
  60. {:key (str idx (:url opt))
  61. :title (when current-selected "Cancel selected theme")
  62. :class (util/classnames
  63. [{:is-selected current-selected
  64. :is-active (= idx @*cursor)}])
  65. :on-click #(do (js/LSPluginCore.selectTheme (if current-selected nil (clj->js opt)))
  66. (state/close-modal!))}
  67. [:section
  68. [:strong.block
  69. [:small.opacity-60 (str (or (:name plg) "Logseq") " • ")]
  70. (:name opt)]]
  71. [:small.flex-shrink-0.flex.items-center.opacity-10
  72. (when current-selected (ui/icon "check"))]]))
  73. themes)])))
  74. (rum/defc unpacked-plugin-loader
  75. [unpacked-pkg-path]
  76. (rum/use-effect!
  77. (fn []
  78. (let [err-handle
  79. (fn [^js e]
  80. (case (keyword (aget e "name"))
  81. :IllegalPluginPackageError
  82. (notification/show! "Illegal Logseq plugin package." :error)
  83. :ExistedImportedPluginPackageError
  84. (notification/show! "Existed Imported plugin package." :error)
  85. :default)
  86. (plugin-handler/reset-unpacked-state))
  87. reg-handle #(plugin-handler/reset-unpacked-state)]
  88. (when unpacked-pkg-path
  89. (doto js/LSPluginCore
  90. (.once "error" err-handle)
  91. (.once "registered" reg-handle)
  92. (.register (bean/->js {:url unpacked-pkg-path}))))
  93. #(doto js/LSPluginCore
  94. (.off "error" err-handle)
  95. (.off "registered" reg-handle))))
  96. [unpacked-pkg-path])
  97. (when unpacked-pkg-path
  98. [:strong.inline-flex.px-3 "Loading ..."]))
  99. (rum/defc category-tabs
  100. [t category on-action]
  101. [:div.secondary-tabs.categories
  102. (ui/button
  103. [:span (ui/icon "puzzle") (t :plugins)]
  104. :intent "logseq"
  105. :on-click #(on-action :plugins)
  106. :class (if (= category :plugins) "active" ""))
  107. (ui/button
  108. [:span (ui/icon "palette") (t :themes)]
  109. :intent "logseq"
  110. :on-click #(on-action :themes)
  111. :class (if (= category :themes) "active" ""))])
  112. (rum/defc local-markdown-display
  113. < rum/reactive
  114. []
  115. (let [[content item] (state/sub :plugin/active-readme)]
  116. [:div.cp__plugins-details
  117. {:on-click (fn [^js/MouseEvent e]
  118. (when-let [target (.-target e)]
  119. (when (and (= (string/lower-case (.-nodeName target)) "a")
  120. (not (string/blank? (. target getAttribute "href"))))
  121. (js/apis.openExternal (. target getAttribute "href"))
  122. (.preventDefault e))))}
  123. (when-let [repo (:repository item)]
  124. (when-let [repo (if (string? repo) repo (:url repo))]
  125. [:div.p-4.rounded-md.bg-base-3
  126. [:strong [:a.flex.items-center {:target "_blank" :href repo}
  127. [:span.mr-1 (svg/github {:width 25 :height 25})] repo]]]))
  128. [:div.p-1.bg-transparent.border-none.ls-block
  129. {:style {:min-height "60vw"
  130. :max-width 900}
  131. :dangerouslySetInnerHTML {:__html content}}]]))
  132. (rum/defc remote-readme-display
  133. [repo _content]
  134. (let [src (str "lsp://logseq.com/marketplace.html?repo=" repo)]
  135. [:iframe.lsp-frame-readme {:src src}]))
  136. (defn security-warning
  137. []
  138. (ui/admonition
  139. :warning
  140. [:div.max-w-4xl
  141. "Plugins can access your graph and your local files, issue network requests.
  142. They can also cause data corruption or loss. We're working on proper access rules for your graphs.
  143. Meanwhile, make sure you have regular backups of your graphs and only install the plugins when you can read and
  144. understand the source code."]))
  145. (rum/defc plugin-item-card < rum/static
  146. [{:keys [id name title settings version url description author icon usf iir repo sponsors] :as item}
  147. market? *search-key has-other-pending?
  148. installing-or-updating? installed? stat coming-update]
  149. (let [disabled (:disabled settings)
  150. name (or title name "Untitled")
  151. unpacked? (not iir)
  152. new-version (and coming-update (:latest-version coming-update))]
  153. (rum/with-context
  154. [[t] i18n/*tongue-context*]
  155. [:div.cp__plugins-item-card
  156. {:class (util/classnames
  157. [{:market market?
  158. :installed installed?
  159. :updating installing-or-updating?
  160. :has-new-version new-version}])}
  161. [:div.l.link-block
  162. {:on-click #(plugin-handler/open-readme!
  163. url item (if repo remote-readme-display local-markdown-display))}
  164. (if (and icon (not (string/blank? icon)))
  165. [:img.icon {:src (if market? (plugin-handler/pkg-asset id icon) icon)}]
  166. svg/folder)
  167. (when (and (not market?) unpacked?)
  168. [:span.flex.justify-center.text-xs.text-red-500.pt-2 "unpacked"])]
  169. [:div.r
  170. [:h3.head.text-xl.font-bold.pt-1.5
  171. [:span name]
  172. (when (not market?) [:sup.inline-block.px-1.text-xs.opacity-50 version])]
  173. [:div.desc.text-xs.opacity-70
  174. [:p description]
  175. ;;[:small (js/JSON.stringify (bean/->js settings))]
  176. ]
  177. ;; Author & Identity
  178. [:div.flag
  179. [:p.text-xs.pr-2.flex.justify-between
  180. [:small {:on-click #(when-let [^js el (js/document.querySelector ".cp__plugins-page .search-ctls input")]
  181. (reset! *search-key (str "@" author))
  182. (.select el))} author]
  183. [:small {:on-click #(do
  184. (notification/show! "Copied!" :success)
  185. (util/copy-to-clipboard! id))}
  186. (str "ID: " id)]]]
  187. ;; Github repo
  188. [:div.flag.is-top.opacity-50
  189. (when repo
  190. [:a.flex {:target "_blank"
  191. :href (plugin-handler/gh-repo-url repo)}
  192. (svg/github {:width 16 :height 16})])]
  193. (if market?
  194. ;; market ctls
  195. [:div.ctl
  196. [:ul.l.flex.items-center
  197. ;; stars
  198. [:li.flex.text-sm.items-center.pr-3
  199. (svg/star 16) [:span.pl-1 (:stargazers_count stat)]]
  200. ;; downloads
  201. (when-let [downloads (and stat (:total_downloads stat))]
  202. (when (and downloads (> downloads 0))
  203. [:li.flex.text-sm.items-center.pr-3
  204. (svg/cloud-down 16) [:span.pl-1 downloads]]))]
  205. [:div.r.flex.items-center
  206. [:a.btn
  207. {:class (util/classnames [{:disabled (or installed? installing-or-updating?)
  208. :installing installing-or-updating?}])
  209. :on-click #(plugin-handler/install-marketplace-plugin item)}
  210. (if installed?
  211. (t :plugin/installed)
  212. (if installing-or-updating?
  213. [:span.flex.items-center [:small svg/loading]
  214. (t :plugin/installing)]
  215. (t :plugin/install)))]]]
  216. ;; installed ctls
  217. [:div.ctl
  218. [:div.l
  219. [:div.de
  220. [:strong (ui/icon "settings")]
  221. [:ul.menu-list
  222. [:li {:on-click #(when usf (js/apis.openPath usf))} (t :plugin/open-settings)]
  223. [:li {:on-click #(js/apis.openPath url)} (t :plugin/open-package)]
  224. [:li {:on-click
  225. #(let [confirm-fn
  226. (ui/make-confirm-modal
  227. {:title (t :plugin/delete-alert name)
  228. :on-confirm (fn [_ {:keys [close-fn]}]
  229. (close-fn)
  230. (plugin-handler/unregister-plugin id))})]
  231. (state/set-sub-modal! confirm-fn {:center? true}))}
  232. (t :plugin/uninstall)]]]
  233. (when (seq sponsors)
  234. [:div.de.sponsors
  235. [:strong (ui/icon "coffee")]
  236. [:ul.menu-list
  237. (for [link sponsors]
  238. [:li [:a {:href link :target "_blank"}
  239. [:span.flex.items-center link (ui/icon "external-link")]]])]])
  240. ]
  241. [:div.r.flex.items-center
  242. (when (and unpacked? (not disabled))
  243. [:a.btn
  244. {:on-click #(js-invoke js/LSPluginCore "reload" id)}
  245. (t :plugin/reload)])
  246. (when (not unpacked?)
  247. [:div.updates-actions
  248. [:a.btn
  249. {:class (util/classnames [{:disabled installing-or-updating?}])
  250. :on-click #(when-not has-other-pending?
  251. (plugin-handler/check-or-update-marketplace-plugin
  252. (assoc item :only-check (not new-version))
  253. (fn [e] (notification/show! e :error))))}
  254. (if installing-or-updating?
  255. (t :plugin/updating)
  256. (if new-version
  257. (str (t :plugin/update) " 👉 " new-version)
  258. (t :plugin/check-update))
  259. )]])
  260. (ui/toggle (not disabled)
  261. (fn []
  262. (js-invoke js/LSPluginCore (if disabled "enable" "disable") id)
  263. (page-handler/init-commands!))
  264. true)]])]])))
  265. (rum/defc panel-control-tabs
  266. < rum/static
  267. [t search-key *search-key category *category
  268. sort-by *sort-by selected-unpacked-pkg
  269. market? develop-mode? reload-market-fn]
  270. (let [*search-ref (rum/create-ref)]
  271. [:div.mb-2.flex.justify-between.control-tabs.relative
  272. [:div.flex.items-center.l
  273. (category-tabs t category #(reset! *category %))
  274. (when (and develop-mode? (not market?))
  275. [:div
  276. (ui/tippy {:html [:div (t :plugin/unpacked-tips)]
  277. :arrow true}
  278. (ui/button
  279. [:span (ui/icon "upload") (t :plugin/load-unpacked)]
  280. :intent "logseq"
  281. :class "load-unpacked"
  282. :on-click plugin-handler/load-unpacked-plugin))
  283. (unpacked-plugin-loader selected-unpacked-pkg)])]
  284. [:div.flex.items-center.r
  285. ;;(ui/button
  286. ;; (t :plugin/open-preferences)
  287. ;; :intent "logseq"
  288. ;; :on-click (fn []
  289. ;; (p/let [root (plugin-handler/get-ls-dotdir-root)]
  290. ;; (js/apis.openPath (str root "/preferences.json")))))
  291. ;; search
  292. [:div.search-ctls
  293. [:small.absolute.s1
  294. (ui/icon "search")]
  295. (when-not (string/blank? search-key)
  296. [:small.absolute.s2
  297. {:on-click #(when-let [^js target (rum/deref *search-ref)]
  298. (reset! *search-key nil)
  299. (.focus target))}
  300. (ui/icon "x")])
  301. [:input.form-input.is-small
  302. {:placeholder "Search plugins"
  303. :ref *search-ref
  304. :on-key-down (fn [^js e]
  305. (when (= 27 (.-keyCode e))
  306. (when-not (string/blank? search-key)
  307. (util/stop e)
  308. (reset! *search-key nil))))
  309. :on-change #(let [^js target (.-target %)]
  310. (reset! *search-key (util/trim-safe (.-value target))))
  311. :value (or search-key "")}]]
  312. ;; sorter
  313. (ui/dropdown-with-links
  314. (fn [{:keys [toggle-fn]}]
  315. (ui/button
  316. [:span (ui/icon "arrows-sort") ""]
  317. :class "sort-by"
  318. :on-click toggle-fn
  319. :intent "link"))
  320. (let [aim-icon #(if (= sort-by %) "check" "circle")]
  321. (if market?
  322. [{:title "Downloads (Desc)"
  323. :options {:on-click #(reset! *sort-by :downloads)}
  324. :icon (ui/icon (aim-icon :downloads))}
  325. {:title "Stars (Desc)"
  326. :options {:on-click #(reset! *sort-by :stars)}
  327. :icon (ui/icon (aim-icon :stars))}
  328. {:title "Title (A - Z)"
  329. :options {:on-click #(reset! *sort-by :letters)}
  330. :icon (ui/icon (aim-icon :letters))}]
  331. [{:title (t :plugin/enabled)
  332. :options {:on-click #(reset! *sort-by :enabled)}
  333. :icon (ui/icon (aim-icon :enabled))}]))
  334. {})
  335. ;; more - updater
  336. (ui/dropdown-with-links
  337. (fn [{:keys [toggle-fn]}]
  338. (ui/button
  339. [:span (ui/icon "dots-vertical")]
  340. :class "more-do"
  341. :on-click toggle-fn
  342. :intent "link"))
  343. (concat (if market?
  344. [{:title [:span (ui/icon "rotate-clockwise") (t :plugin/refresh-lists)]
  345. :options {:on-click #(reload-market-fn)}}]
  346. [{:title [:span (ui/icon "rotate-clockwise") (t :plugin/check-all-updates)]
  347. :options {:on-click #(plugin-handler/check-enabled-for-updates (not= :plugins category))}}])
  348. (when (state/developer-mode?)
  349. [{:hr true}
  350. {:title [:span (ui/icon "file-code") "Open Preferences"]
  351. :options {:on-click
  352. #(p/let [root (plugin-handler/get-ls-dotdir-root)]
  353. (js/apis.openPath (str root "/preferences.json")))}}
  354. {:title [:span (ui/icon "bug") "Open " [:code " ~/.logseq"]]
  355. :options {:on-click
  356. #(p/let [root (plugin-handler/get-ls-dotdir-root)]
  357. (js/apis.openPath root))}}]))
  358. {})
  359. ;; developer
  360. (ui/button
  361. (t :plugin/contribute)
  362. :href "https://github.com/logseq/marketplace"
  363. :class "contribute"
  364. :intent "logseq"
  365. :target "_blank")
  366. ]]))
  367. (rum/defcs marketplace-plugins
  368. < rum/static rum/reactive
  369. (rum/local false ::fetching)
  370. (rum/local "" ::search-key)
  371. (rum/local :plugins ::category)
  372. (rum/local :downloads ::sort-by) ;; downloads / stars / letters / updates
  373. (rum/local nil ::error)
  374. {:did-mount (fn [s]
  375. (let [reload-fn (fn [force-refresh?]
  376. (when-not @(::fetching s)
  377. (reset! (::fetching s) true)
  378. (reset! (::error s) nil)
  379. (-> (plugin-handler/load-marketplace-plugins force-refresh?)
  380. (p/then #(plugin-handler/load-marketplace-stats false))
  381. (p/catch #(do (js/console.error %) (reset! (::error s) %)))
  382. (p/finally #(reset! (::fetching s) false)))))]
  383. (reload-fn false)
  384. (assoc s ::reload (partial reload-fn true))))}
  385. [state]
  386. (let [pkgs (state/sub :plugin/marketplace-pkgs)
  387. stats (state/sub :plugin/marketplace-stats)
  388. installed-plugins (state/sub :plugin/installed-plugins)
  389. installing (state/sub :plugin/installing)
  390. online? (state/sub :network/online?)
  391. develop-mode? (state/sub :ui/developer-mode?)
  392. *search-key (::search-key state)
  393. *category (::category state)
  394. *sort-by (::sort-by state)
  395. *fetching (::fetching state)
  396. *error (::error state)
  397. filtered-pkgs (when (seq pkgs)
  398. (if (= @*category :themes)
  399. (filter #(:theme %) pkgs)
  400. (filter #(not (:theme %)) pkgs)))
  401. filtered-pkgs (if-not (string/blank? @*search-key)
  402. (if-let [author (and (string/starts-with? @*search-key "@")
  403. (subs @*search-key 1))]
  404. (filter #(= author (:author %)) filtered-pkgs)
  405. (search/fuzzy-search
  406. filtered-pkgs @*search-key
  407. :limit 30
  408. :extract-fn :title))
  409. filtered-pkgs)
  410. filtered-pkgs (map #(if-let [stat (get stats (keyword (:id %)))]
  411. (let [downloads (:total_downloads stat)
  412. stars (:stargazers_count stat)]
  413. (assoc % :stat stat
  414. :stars stars
  415. :downloads downloads))
  416. %) filtered-pkgs)
  417. sorted-pkgs (apply sort-by
  418. (conj
  419. (case @*sort-by
  420. :letters [:title #(compare %1 %2)]
  421. [@*sort-by #(compare %2 %1)])
  422. filtered-pkgs))]
  423. (rum/with-context
  424. [[t] i18n/*tongue-context*]
  425. [:div.cp__plugins-marketplace
  426. (panel-control-tabs
  427. t
  428. @*search-key *search-key
  429. @*category *category
  430. @*sort-by *sort-by nil true
  431. develop-mode? (::reload state))
  432. (cond
  433. (not online?)
  434. [:p.flex.justify-center.pt-20.opacity-50
  435. (svg/offline 30)]
  436. @*fetching
  437. [:p.flex.justify-center.pt-20
  438. svg/loading]
  439. @*error
  440. [:p.flex.justify-center.pt-20.opacity-50
  441. "Remote error: " (.-message @*error)]
  442. :else
  443. [:div.cp__plugins-marketplace-cnt
  444. {:class (util/classnames [{:has-installing (boolean installing)}])}
  445. [:div.cp__plugins-item-lists.grid-cols-1.md:grid-cols-2.lg:grid-cols-3
  446. (for [item sorted-pkgs]
  447. (rum/with-key
  448. (let [pid (keyword (:id item))
  449. stat (:stat item)]
  450. (plugin-item-card
  451. item true *search-key installing
  452. (and installing (= (keyword (:id installing)) pid))
  453. (contains? installed-plugins pid) stat nil))
  454. (:id item)))]])])))
  455. (rum/defcs installed-plugins
  456. < rum/static rum/reactive
  457. (rum/local "" ::search-key)
  458. (rum/local :enabled ::sort-by) ;; enabled / letters / updates
  459. (rum/local :plugins ::category)
  460. [state]
  461. (let [installed-plugins (state/sub :plugin/installed-plugins)
  462. installed-plugins (vals installed-plugins)
  463. updating (state/sub :plugin/installing)
  464. develop-mode? (state/sub :ui/developer-mode?)
  465. selected-unpacked-pkg (state/sub :plugin/selected-unpacked-pkg)
  466. coming-updates (state/sub :plugin/updates-coming)
  467. *sort-by (::sort-by state)
  468. *search-key (::search-key state)
  469. *category (::category state)
  470. filtered-plugins (when (seq installed-plugins)
  471. (if (= @*category :themes)
  472. (filter #(:theme %) installed-plugins)
  473. (filter #(not (:theme %)) installed-plugins)))
  474. filtered-plugins (if-not (string/blank? @*search-key)
  475. (if-let [author (and (string/starts-with? @*search-key "@")
  476. (subs @*search-key 1))]
  477. (filter #(= author (:author %)) filtered-plugins)
  478. (search/fuzzy-search
  479. filtered-plugins @*search-key
  480. :limit 30
  481. :extract-fn :name))
  482. filtered-plugins)
  483. sorted-plugins (->> filtered-plugins
  484. (reduce #(let [k (if (get-in %2 [:settings :disabled]) 1 0)]
  485. (update %1 k conj %2)) [[] []])
  486. (#(update % 0 (fn [it] (sort-by :iir it))))
  487. (flatten))]
  488. (rum/with-context
  489. [[t] i18n/*tongue-context*]
  490. [:div.cp__plugins-installed
  491. (panel-control-tabs
  492. t
  493. @*search-key *search-key
  494. @*category *category
  495. @*sort-by *sort-by
  496. selected-unpacked-pkg
  497. false develop-mode? nil)
  498. [:div.cp__plugins-item-lists.grid-cols-1.md:grid-cols-2.lg:grid-cols-3
  499. (for [item sorted-plugins]
  500. (rum/with-key
  501. (let [pid (keyword (:id item))]
  502. (plugin-item-card
  503. item false *search-key updating
  504. (and updating (= (keyword (:id updating)) pid))
  505. true nil (get coming-updates pid))) (:id item)))]])))
  506. (defn open-select-theme!
  507. []
  508. (state/set-sub-modal! installed-themes))
  509. (rum/defc hook-ui-slot
  510. ([type payload] (hook-ui-slot type payload nil))
  511. ([type payload opts]
  512. (let [rs (util/rand-str 8)
  513. id (str "slot__" rs)]
  514. (rum/use-effect!
  515. (fn []
  516. (plugin-handler/hook-plugin-app type {:slot id :payload payload} nil)
  517. #())
  518. [id])
  519. [:div.lsp-hook-ui-slot
  520. (merge opts {:id id
  521. :on-mouse-down (fn [e] (util/stop e))})])))
  522. (rum/defc ui-item-renderer
  523. [pid type {:keys [key template]}]
  524. (let [*el (rum/use-ref nil)
  525. uni #(str "injected-ui-item-" %)
  526. ^js pl (js/LSPluginCore.registeredPlugins.get (name pid))]
  527. (rum/use-effect!
  528. (fn []
  529. (when-let [^js el (rum/deref *el)]
  530. (js/LSPlugin.pluginHelpers.setupInjectedUI.call
  531. pl #js {:slot (.-id el) :key key :template template} #js {})))
  532. [])
  533. (if-not (nil? pl)
  534. [:div {:id (uni (str (name key) "-" (name pid)))
  535. :class (uni (name type))
  536. :ref *el}]
  537. [:span])))
  538. (rum/defcs hook-ui-items < rum/reactive
  539. "type
  540. - :toolbar
  541. - :pagebar
  542. "
  543. [state type]
  544. (when (state/sub [:plugin/installed-ui-items])
  545. (let [items (state/get-plugins-ui-items-with-type type)]
  546. (when (seq items)
  547. [:div {:class (str "ui-items-container")
  548. :data-type (name type)}
  549. (for [[_ {:keys [key] :as opts} pid] items]
  550. (rum/with-key (ui-item-renderer pid type opts) key))]))))
  551. (rum/defc plugins-page
  552. []
  553. (let [[active set-active!] (rum/use-state :installed)
  554. market? (= active :marketplace)
  555. *el-ref (rum/create-ref)]
  556. (rum/use-effect!
  557. #(let [^js el (rum/deref *el-ref)]
  558. (js/setTimeout (fn [] (.focus el)) 100))
  559. [])
  560. (rum/with-context
  561. [[t] i18n/*tongue-context*]
  562. [:div.cp__plugins-page
  563. {:ref *el-ref
  564. :tab-index "-1"}
  565. [:h1 (t :plugins)]
  566. (security-warning)
  567. [:hr]
  568. [:div.tabs.flex.items-center.justify-center
  569. [:div.tabs-inner.flex.items-center
  570. (ui/button [:span.it (t :plugin/installed)]
  571. :on-click #(set-active! :installed)
  572. :intent "logseq" :class (if-not market? "active" ""))
  573. (ui/button [:span.mk (svg/apps 16) (t :plugin/marketplace)]
  574. :on-click #(set-active! :marketplace)
  575. :intent "logseq" :class (if market? "active" ""))]]
  576. [:div.panels
  577. (if market?
  578. (marketplace-plugins)
  579. (installed-plugins))]])))
  580. (rum/defc custom-js-installer
  581. [{:keys [t current-repo db-restoring? nfs-granted?]}]
  582. (rum/use-effect!
  583. (fn []
  584. (when (and (not db-restoring?)
  585. (or (not util/nfs?) nfs-granted?))
  586. (ui-handler/exec-js-if-exists-&-allowed! t)))
  587. [current-repo db-restoring? nfs-granted?])
  588. nil)
  589. (defn open-plugins-modal!
  590. []
  591. (state/set-modal!
  592. (fn [_close!]
  593. (plugins-page))))