Browse Source

Enhance: plugin-related improvements (#8787)

 - fix: Select and Input elements rendered using provideUI via onMacroRendererSlotted don't function #8374
 - fix: logseq.Editor.getPageBlocksTree does not work when page uuid is passed in as param #4920
 - feat: add a plug-in flag for the plugin slash commands item
 - feat: add logseq.App.setCurrentGraphConfigs: (configs: {}) => Promise<void>
 - feat: add hook logseq.App.onTodayJournalCreated: IUserHook<{ title: string }
 - enhance: auto-check updates for the installed plugins from Marketplace
 - feat: expose template-related APIs to SDK
Charlie 2 years ago
parent
commit
60fbfdf2f7

+ 1 - 1
bb.edn

@@ -131,4 +131,4 @@
   {:paths ["src/main"]
   {:paths ["src/main"]
    ;; Ignore namespaces that won't be helpful to document initially
    ;; Ignore namespaces that won't be helpful to document initially
    ;; e.g. frontend.components.onboarding -> "Onboarding fns"
    ;; e.g. frontend.components.onboarding -> "Onboarding fns"
-   :ignore-regex "^(frontend.components|frontend.extensions|frontend.modules|frontend.mobile)"}}}
+   :ignore-regex "^(frontend.components|frontend.extensions|frontend.modules|frontend.mobile|logseq.sdk)"}}}

+ 14 - 0
libs/CHANGELOG.md

@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
 
 
 ## [Unreleased]
 ## [Unreleased]
 
 
+## [0.0.15]
+
+### Added
+- Support a plug-in flag for the plugin slash commands item
+- Support api of `logseq.App.setCurrentGraphConfigs: (configs: {}) => Promise<void>`
+- Support hook of `logseq.App.onTodayJournalCreated: IUserHook<{ title: string }`
+- Support more template-related APIs
+- Support auto-check updates for the installed plugins from Marketplace
+ 
+### Fixed
+- Select and Input elements rendered using provideUI via `onMacroRendererSlotted` don't function [#8374](https://github.com/logseq/logseq/issues/8374)
+- `logseq.Editor.getPageBlocksTree` does not work when page uuid is passed in as param [#4920](https://github.com/logseq/logseq/issues/4920)
+
+
 ## [0.0.14]
 ## [0.0.14]
 
 
 ### Fixed
 ### Fixed

+ 1 - 1
libs/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@logseq/libs",
   "name": "@logseq/libs",
-  "version": "0.0.14",
+  "version": "0.0.15",
   "description": "Logseq SDK libraries",
   "description": "Logseq SDK libraries",
   "main": "dist/lsplugin.user.js",
   "main": "dist/lsplugin.user.js",
   "typings": "index.d.ts",
   "typings": "index.d.ts",

+ 2 - 0
libs/src/LSPlugin.core.ts

@@ -23,6 +23,7 @@ import {
   PluginLogger,
   PluginLogger,
 } from './helpers'
 } from './helpers'
 import * as pluginHelpers from './helpers'
 import * as pluginHelpers from './helpers'
+import DOMPurify from 'dompurify'
 import Debug from 'debug'
 import Debug from 'debug'
 import {
 import {
   LSPluginCaller,
   LSPluginCaller,
@@ -1606,6 +1607,7 @@ function setupPluginCore(options: any) {
   debug('=== 🔗 Setup Logseq Plugin System 🔗 ===')
   debug('=== 🔗 Setup Logseq Plugin System 🔗 ===')
 
 
   window.LSPluginCore = pluginCore
   window.LSPluginCore = pluginCore
+  window.DOMPurify = DOMPurify
 }
 }
 
 
 export { PluginLocal, pluginHelpers, setupPluginCore }
 export { PluginLocal, pluginHelpers, setupPluginCore }

+ 22 - 2
libs/src/LSPlugin.ts

@@ -317,7 +317,10 @@ export interface IPluginSearchServiceHooks {
 
 
   onIndiceInit: (graph: string) => Promise<SearchIndiceInitStatus>
   onIndiceInit: (graph: string) => Promise<SearchIndiceInitStatus>
   onIndiceReset: (graph: string) => Promise<void>
   onIndiceReset: (graph: string) => Promise<void>
-  onBlocksChanged: (graph: string, changes: { added: Array<SearchBlockItem>, removed: Array<EntityID> }) => Promise<void>
+  onBlocksChanged: (graph: string, changes: {
+    added: Array<SearchBlockItem>,
+    removed: Array<EntityID>
+  }) => Promise<void>
   onGraphRemoved: (graph: string, opts?: {}) => Promise<any>
   onGraphRemoved: (graph: string, opts?: {}) => Promise<any>
 }
 }
 
 
@@ -425,9 +428,11 @@ export interface IAppProxy {
 
 
   // graph
   // graph
   getCurrentGraph: () => Promise<AppGraphInfo | null>
   getCurrentGraph: () => Promise<AppGraphInfo | null>
-  getCurrentGraphConfigs: () => Promise<any>
+  getCurrentGraphConfigs: (...keys: string[]) => Promise<any>
+  setCurrentGraphConfigs: (configs: {}) => Promise<void>
   getCurrentGraphFavorites: () => Promise<Array<string> | null>
   getCurrentGraphFavorites: () => Promise<Array<string> | null>
   getCurrentGraphRecent: () => Promise<Array<string> | null>
   getCurrentGraphRecent: () => Promise<Array<string> | null>
+  getCurrentGraphTemplates: () => Promise<Record<string, BlockEntity> | null>
 
 
   // router
   // router
   pushState: (
   pushState: (
@@ -441,6 +446,13 @@ export interface IAppProxy {
     query?: Record<string, any>
     query?: Record<string, any>
   ) => void
   ) => void
 
 
+  // templates
+  getTemplate: (name: string) => Promise<BlockEntity | null>
+  existTemplate: (name: string) => Promise<Boolean>
+  createTemplate: (target: BlockUUID, name: string, opts?: { overwrite: boolean }) => Promise<any>
+  removeTemplate: (name: string) => Promise<any>
+  insertTemplate: (target: BlockUUID, name: string) => Promise<any>
+
   // ui
   // ui
   queryElementById: (id: string) => Promise<string | boolean>
   queryElementById: (id: string) => Promise<string | boolean>
 
 
@@ -481,6 +493,7 @@ export interface IAppProxy {
   onGraphAfterIndexed: IUserHook<{ repo: string }>
   onGraphAfterIndexed: IUserHook<{ repo: string }>
   onThemeModeChanged: IUserHook<{ mode: 'dark' | 'light' }>
   onThemeModeChanged: IUserHook<{ mode: 'dark' | 'light' }>
   onThemeChanged: IUserHook<Partial<{ name: string, mode: string, pid: string, url: string }>>
   onThemeChanged: IUserHook<Partial<{ name: string, mode: string, pid: string, url: string }>>
+  onTodayJournalCreated: IUserHook<{ title: string }>
 
 
   /**
   /**
    * provide ui slot to specific block with UUID
    * provide ui slot to specific block with UUID
@@ -894,6 +907,13 @@ export interface IAssetsProxy {
    * @added 0.0.10
    * @added 0.0.10
    */
    */
   makeSandboxStorage(): IAsyncStorage
   makeSandboxStorage(): IAsyncStorage
+
+  /**
+   * make assets scheme url based on current graph
+   * @added 0.0.15
+   * @param path
+   */
+  makeUrl(path: string): Promise<string>
 }
 }
 
 
 export interface ILSPluginThemeManager {
 export interface ILSPluginThemeManager {

+ 1 - 0
public/index.html

@@ -50,6 +50,7 @@
 </script>
 </script>
 <script defer src="/static/js/highlight.min.js"></script>
 <script defer src="/static/js/highlight.min.js"></script>
 <script defer src="/static/js/interact.min.js"></script>
 <script defer src="/static/js/interact.min.js"></script>
+<script defer src="/static/js/marked.min.js"></script>
 <script defer src="/static/js/html2canvas.min.js"></script>
 <script defer src="/static/js/html2canvas.min.js"></script>
 <script defer src="/static/js/main.js"></script>
 <script defer src="/static/js/main.js"></script>
 <script defer src="/static/js/amplify.js"></script>
 <script defer src="/static/js/amplify.js"></script>

+ 1 - 0
resources/electron.html

@@ -50,6 +50,7 @@ const portal = new MagicPortal(worker);
 </script>
 </script>
 <script defer src="./js/highlight.min.js"></script>
 <script defer src="./js/highlight.min.js"></script>
 <script defer src="./js/interact.min.js"></script>
 <script defer src="./js/interact.min.js"></script>
+<script defer src="./js/marked.min.js"></script>
 <script defer src="./js/html2canvas.min.js"></script>
 <script defer src="./js/html2canvas.min.js"></script>
 <script defer src="./js/lsplugin.core.js"></script>
 <script defer src="./js/lsplugin.core.js"></script>
 <script defer src="./js/main.js"></script>
 <script defer src="./js/main.js"></script>

+ 1 - 0
resources/index.html

@@ -49,6 +49,7 @@ const portal = new MagicPortal(worker);
 </script>
 </script>
 <script defer src="./js/highlight.min.js"></script>
 <script defer src="./js/highlight.min.js"></script>
 <script defer src="./js/interact.min.js"></script>
 <script defer src="./js/interact.min.js"></script>
+<script defer src="./js/marked.min.js"></script>
 <script defer src="./js/html2canvas.min.js"></script>
 <script defer src="./js/html2canvas.min.js"></script>
 <script defer src="./js/lsplugin.core.js"></script>
 <script defer src="./js/lsplugin.core.js"></script>
 <script defer src="./js/main.js"></script>
 <script defer src="./js/main.js"></script>

File diff suppressed because it is too large
+ 0 - 0
resources/js/lsplugin.core.js


+ 135 - 131
src/electron/electron/plugin.cljs

@@ -33,30 +33,33 @@
 
 
 (defn- fetch-release-asset
 (defn- fetch-release-asset
   [{:keys [repo theme]} url-suffix {:keys [response-transform]
   [{:keys [repo theme]} url-suffix {:keys [response-transform]
-                                    :or {response-transform identity}}]
-  (p/catch
-   (p/let [repo (some-> repo (string/trim) (string/replace #"^/+(.+?)/+$" "$1"))
-           api #(str "https://api.github.com/repos/" repo "/" %)
-           endpoint (api url-suffix)
-           ^js res (fetch endpoint)
-           _ (debug "[Release URL] " endpoint "[Response Status/Text]" (.-status res) "-")
-           res (response-transform res)
-           res (.json res)
-           res (bean/->clj res)
-           version (:tag_name res)
-           asset (first (filter #(string/ends-with? (:name %) ".zip") (:assets res)))]
-
-          [(if (and (nil? asset) theme)
-             (if-let [zipball (:zipball_url res)]
-               zipball
-               (api "zipball"))
-             asset)
-           version
-           (:body res)])
-
-   (fn [^js e]
-     (debug e)
-     (throw (js/Error. [:release-channel-issue (.-message e)])))))
+                                    :or   {response-transform identity}}]
+  (-> (p/let [repo         (some-> repo (string/trim) (string/replace #"^/+(.+?)/+$" "$1"))
+              api          #(str "https://api.github.com/repos/" repo "/" %)
+              endpoint     (api url-suffix)
+              ^js res      (fetch endpoint)
+              illegal-text (when-not (= 200 (.-status res)) (.text res))
+              _            (when-not (string/blank? illegal-text) (throw (js/Error. (str "Github API Failed(" (.-status res) ") " illegal-text))))
+              _            (debug "[Release URL] " endpoint "[Status/Text]" (.-status res))
+              res          (response-transform res)
+              res          (.json res)
+              res          (bean/->clj res)
+              version      (:tag_name res)
+              asset        (first (filter #(string/ends-with? (:name %) ".zip") (:assets res)))]
+
+
+        [(if (and (nil? asset) theme)
+           (if-let [zipball (:zipball_url res)]
+             zipball
+             (api "zipball"))
+           asset)
+         version
+         (:body res)])
+
+      (p/catch
+        (fn [^js e]
+          (debug e)
+          (throw (js/Error. [:release-channel-issue (.-message e)]))))))
 
 
 (defn fetch-latest-release-asset
 (defn fetch-latest-release-asset
   "Fetches latest release, normally when user clicks to install or update a plugin"
   "Fetches latest release, normally when user clicks to install or update a plugin"
@@ -80,42 +83,42 @@
                             ;; cases. Previous logseq versions did not store the
                             ;; cases. Previous logseq versions did not store the
                             ;; plugin's git tag required to correctly install it
                             ;; plugin's git tag required to correctly install it
                             (let [repo' (some-> repo (string/trim) (string/replace #"^/+(.+?)/+$" "$1"))
                             (let [repo' (some-> repo (string/trim) (string/replace #"^/+(.+?)/+$" "$1"))
-                                  api #(str "https://api.github.com/repos/" repo' "/" %)]
+                                  api   #(str "https://api.github.com/repos/" repo' "/" %)]
                               (fetch (api "releases/latest")))
                               (fetch (api "releases/latest")))
                             res))}))
                             res))}))
 
 
 (defn download-asset-zip
 (defn download-asset-zip
   [{:keys [id repo title author description effect sponsors]} dl-url dl-version dot-extract-to]
   [{:keys [id repo title author description effect sponsors]} dl-url dl-version dot-extract-to]
   (p/catch
   (p/catch
-    (p/let [^js res (fetch dl-url {:timeout 30000})
-            _ (when-not (.-ok res)
-                (throw (js/Error. [:download-channel-issue (.-statusText res)])))
-            frm-zip (p/create
-                      (fn [resolve1 reject1]
-                        (let [body (.-body res)
-                              *downloaded (atom 0)
-                              dest-basename (node-path/basename dl-url)
-                              dest-basename (if-not (string/ends-with? dest-basename ".zip")
-                                              (str id "_" dest-basename ".zip") dest-basename)
-                              tmp-dest-file (node-path/join (os/tmpdir) (str dest-basename ".pending"))
-                              dest-file (.createWriteStream fs tmp-dest-file)]
-                          (doto body
-                            (.on "data" (fn [chunk]
-                                          (let [downloaded (+ @*downloaded (.-length chunk))]
-                                            (.write dest-file chunk)
-                                            (reset! *downloaded downloaded))))
-                            (.on "error" (fn [^js e]
-                                           (reject1 e)))
-                            (.on "end" (fn [^js _e]
-                                         (.close dest-file)
-                                         (let [dest-file (string/replace tmp-dest-file ".pending" "")]
-                                           (fs/renameSync tmp-dest-file dest-file)
-                                           (resolve1 dest-file))))))))
+    (p/let [^js res            (fetch dl-url {:timeout 30000})
+            _                  (when-not (.-ok res)
+                                 (throw (js/Error. [:download-channel-issue (.-statusText res)])))
+            frm-zip            (p/create
+                                 (fn [resolve1 reject1]
+                                   (let [body          (.-body res)
+                                         *downloaded   (atom 0)
+                                         dest-basename (node-path/basename dl-url)
+                                         dest-basename (if-not (string/ends-with? dest-basename ".zip")
+                                                         (str id "_" dest-basename ".zip") dest-basename)
+                                         tmp-dest-file (node-path/join (os/tmpdir) (str dest-basename ".pending"))
+                                         dest-file     (.createWriteStream fs tmp-dest-file)]
+                                     (doto body
+                                       (.on "data" (fn [chunk]
+                                                     (let [downloaded (+ @*downloaded (.-length chunk))]
+                                                       (.write dest-file chunk)
+                                                       (reset! *downloaded downloaded))))
+                                       (.on "error" (fn [^js e]
+                                                      (reject1 e)))
+                                       (.on "end" (fn [^js _e]
+                                                    (.close dest-file)
+                                                    (let [dest-file (string/replace tmp-dest-file ".pending" "")]
+                                                      (fs/renameSync tmp-dest-file dest-file)
+                                                      (resolve1 dest-file))))))))
             ;; sync extract
             ;; sync extract
             zip-extracted-path (string/replace frm-zip ".zip" "")
             zip-extracted-path (string/replace frm-zip ".zip" "")
 
 
-            _ (extract-zip frm-zip (bean/->js
-                                     {:dir zip-extracted-path}))
+            _                  (extract-zip frm-zip (bean/->js
+                                                      {:dir zip-extracted-path}))
 
 
             tmp-extracted-root (let [dirs (fs/readdirSync zip-extracted-path)
             tmp-extracted-root (let [dirs (fs/readdirSync zip-extracted-path)
                                      pkg? (fn [root]
                                      pkg? (fn [root]
@@ -126,36 +129,36 @@
                                    "."
                                    "."
                                    (last (take-while #(pkg? (.join node-path zip-extracted-path %)) dirs))))
                                    (last (take-while #(pkg? (.join node-path zip-extracted-path %)) dirs))))
 
 
-            _ (when-not tmp-extracted-root
-                (throw (js/Error. :invalid-plugin-package)))
+            _                  (when-not tmp-extracted-root
+                                 (throw (js/Error. :invalid-plugin-package)))
 
 
             tmp-extracted-root (.join node-path zip-extracted-path tmp-extracted-root)
             tmp-extracted-root (.join node-path zip-extracted-path tmp-extracted-root)
 
 
-            _ (and (fs/existsSync dot-extract-to)
-                   (fs/removeSync dot-extract-to))
-
-            _ (fs/moveSync tmp-extracted-root dot-extract-to)
-
-            _ (let [src (.join node-path dot-extract-to "package.json")
-                    ^js sponsors (bean/->js sponsors)
-                    ^js pkg (fs/readJsonSync src)]
-                (set! (.-repo pkg) repo)
-                (set! (.-title pkg) title)
-                (set! (.-author pkg) author)
-                (set! (.-description pkg) description)
-                (set! (.-effect pkg) (boolean effect))
-                ;; Force overwrite version because of developers tend to
-                ;; forget to update the version number of package.json
-                (when dl-version (set! (.-version pkg) dl-version))
-                (when sponsors (set! (.-sponsors pkg) sponsors))
-                (fs/writeJsonSync src pkg))
-
-            _ (do
-                (fs/removeSync zip-extracted-path)
-                (fs/removeSync frm-zip))]
+            _                  (and (fs/existsSync dot-extract-to)
+                                    (fs/removeSync dot-extract-to))
+
+            _                  (fs/moveSync tmp-extracted-root dot-extract-to)
+
+            _                  (let [src          (.join node-path dot-extract-to "package.json")
+                                     ^js sponsors (bean/->js sponsors)
+                                     ^js pkg      (fs/readJsonSync src)]
+                                 (set! (.-repo pkg) repo)
+                                 (set! (.-title pkg) title)
+                                 (set! (.-author pkg) author)
+                                 (set! (.-description pkg) description)
+                                 (set! (.-effect pkg) (boolean effect))
+                                 ;; Force overwrite version because of developers tend to
+                                 ;; forget to update the version number of package.json
+                                 (when dl-version (set! (.-version pkg) dl-version))
+                                 (when sponsors (set! (.-sponsors pkg) sponsors))
+                                 (fs/writeJsonSync src pkg))
+
+            _                  (do
+                                 (fs/removeSync zip-extracted-path)
+                                 (fs/removeSync frm-zip))]
       true)
       true)
     (fn [^js e]
     (fn [^js e]
-      (emit :lsp-installed {:status :error :payload e})
+      (emit :lsp-updates {:status :error :payload e})
       (throw e))))
       (throw e))))
 
 
 (defn install-or-update!
 (defn install-or-update!
@@ -163,74 +166,75 @@
   includes the following keys:
   includes the following keys:
 * :only-check - When set to true, this only fetches the latest version without installing
 * :only-check - When set to true, this only fetches the latest version without installing
 * :plugin-action - When set to 'install', installs the specific :version given
 * :plugin-action - When set to 'install', installs the specific :version given
-* :repo - A github repo, not a logseq repo, e.g. user/repo"
+* :repo - A Github repo, not a logseq repo, e.g. user/repo"
   [{:keys [version repo only-check plugin-action] :as item}]
   [{:keys [version repo only-check plugin-action] :as item}]
   (if repo
   (if repo
-    (let [coerced-version (and version (. semver coerce version))
-          updating? (and version (. semver valid coerced-version)
-                         (not= plugin-action "install"))]
+    (let [action          (keyword plugin-action)
+          coerced-version (and version (. semver coerce version))
+          updating?       (and version (. semver valid coerced-version)
+                               (not= action :install))]
 
 
       (debug (if updating? "Updating:" "Installing:") repo)
       (debug (if updating? "Updating:" "Installing:") repo)
 
 
       (-> (p/create
       (-> (p/create
-           (fn [resolve _reject]
-             ;;(reset! *installing-or-updating item)
-             ;; get releases
-             (-> (p/let [[asset latest-version notes]
-                         (if (= plugin-action "install")
-                           (fetch-specific-release-asset item)
-                           (fetch-latest-release-asset item))
-
-                         _ (debug "[Release Asset] #" latest-version " =>" (:url asset))
-
-                         ;; compare latest version
-                         _ (when-let [coerced-latest-version
-                                      (and updating? latest-version
-                                           (. semver coerce latest-version))]
-
-                             (debug "[Updating Latest?] " version " > " latest-version)
-
-                             (if (. semver lt coerced-version coerced-latest-version)
-                               (debug "[Updating Latest] " latest-version)
-                               (throw (js/Error. :no-new-version))))
-
-                         dl-url (if-not (string? asset)
-                                  (:browser_download_url asset) asset)
-
-                         _ (when-not dl-url
-                             (debug "[Download URL Error]" asset)
-                             (throw (js/Error. [:release-asset-not-found (js/JSON.stringify asset)])))
-
-                         dest (.join node-path cfgs/dot-root "plugins" (:id item))
-                         _ (when-not only-check (download-asset-zip item dl-url latest-version dest))
-                         _ (debug (str "[" (if only-check "Checked" "Updated") "DONE]") latest-version)]
-
-                        (emit :lsp-installed
-                              {:status     :completed
-                               :only-check only-check
-                               :payload    (if only-check
-                                             (assoc item :latest-version latest-version :latest-notes notes)
-                                             (assoc item :zip dl-url :dst dest :installed-version latest-version))})
-
-                        (resolve nil))
-
-                 (p/catch
-                  (fn [^js e]
-                    (emit :lsp-installed
-                          {:status     :error
+            (fn [resolve _reject]
+              ;;(reset! *installing-or-updating item)
+              ;; get releases
+              (-> (p/let [[asset latest-version notes]
+                          (if (= action :install)
+                            (fetch-specific-release-asset item)
+                            (fetch-latest-release-asset item))
+
+                          _      (debug "[Release Asset] #" latest-version " =>" (:url asset))
+
+                          ;; compare latest version
+                          _      (when-let [coerced-latest-version
+                                            (and updating? latest-version
+                                                 (. semver coerce latest-version))]
+
+                                   (debug "[Updating Latest?] " version " > " latest-version)
+
+                                   (if (. semver lt coerced-version coerced-latest-version)
+                                     (debug "[Updating Latest] " latest-version)
+                                     (throw (js/Error. :no-new-version))))
+
+                          dl-url (if-not (string? asset)
+                                   (:browser_download_url asset) asset)
+
+                          _      (when-not dl-url
+                                   (debug "[Download URL Error]" asset)
+                                   (throw (js/Error. [:release-asset-not-found (js/JSON.stringify asset)])))
+
+                          dest   (.join node-path cfgs/dot-root "plugins" (:id item))
+                          _      (when-not only-check (download-asset-zip item dl-url latest-version dest))
+                          _      (debug (str "[" (if only-check "Checked" "Updated") "DONE]") latest-version)]
+
+                    (emit :lsp-updates
+                          {:status     :completed
                            :only-check only-check
                            :only-check only-check
-                           :payload    (assoc item :error-code (.-message e))})
-                    (debug e))
-                  (resolve nil)))))
+                           :payload    (if only-check
+                                         (assoc item :latest-version latest-version :latest-notes notes)
+                                         (assoc item :zip dl-url :dst dest :installed-version latest-version))})
+
+                    (resolve nil))
+
+                  (p/catch
+                    (fn [^js e]
+                      (emit :lsp-updates
+                            {:status     :error
+                             :only-check only-check
+                             :payload    (assoc item :error-code (.-message e))})
+                      (debug e))
+                    (resolve nil)))))
 
 
           (p/finally
           (p/finally
-           (fn []))))
+            (fn []))))
     (debug "Skip install because no repo was given for: " item)))
     (debug "Skip install because no repo was given for: " item)))
 
 
 (defn uninstall!
 (defn uninstall!
   [id]
   [id]
-  (let [id (string/replace id #"^[.\/]+" "")
-        plugin-path (.join node-path (utils/get-ls-dotdir-root) "plugins" id)
+  (let [id            (string/replace id #"^[.\/]+" "")
+        plugin-path   (.join node-path (utils/get-ls-dotdir-root) "plugins" id)
         settings-path (.join node-path (utils/get-ls-dotdir-root) "settings" (str id ".json"))]
         settings-path (.join node-path (utils/get-ls-dotdir-root) "settings" (str id ".json"))]
     (debug "[Uninstall]" plugin-path)
     (debug "[Uninstall]" plugin-path)
     (when (fs/pathExistsSync plugin-path)
     (when (fs/pathExistsSync plugin-path)

+ 18 - 15
src/main/frontend/components/editor.cljs

@@ -42,23 +42,26 @@
         :item-render
         :item-render
         (fn [item]
         (fn [item]
           (let [command-name (first item)
           (let [command-name (first item)
-                command-doc (get item 2)
-                doc (when (state/show-command-doc?) command-doc)]
+                command-doc  (get item 2)
+                plugin-id    (get-in item [1 1 1 :pid])
+                doc          (when (state/show-command-doc?) command-doc)]
             (cond
             (cond
-              (string? doc)
-              [:div {:title doc}
-               command-name]
-
-              (vector? doc)
+              (or plugin-id (vector? doc))
               [:div.has-help
               [:div.has-help
                command-name
                command-name
-               (ui/tippy
-                {:html doc
-                 :interactive true
-                 :fixed-position? true
-                 :position "right"}
+               (when doc (ui/tippy
+                          {:html            doc
+                           :interactive     true
+                           :fixed-position? true
+                           :position        "right"}
 
 
-                [:small (svg/help-circle)])]
+                          [:small (svg/help-circle)]))
+               (when plugin-id
+                 [:small {:title (str plugin-id)} (ui/icon "puzzle")])]
+
+              (string? doc)
+              [:div {:title doc}
+               command-name]
 
 
               :else
               :else
               [:div command-name])))
               [:div command-name])))
@@ -67,7 +70,7 @@
         (fn [chosen-item]
         (fn [chosen-item]
           (let [command (first chosen-item)]
           (let [command (first chosen-item)]
             (reset! commands/*current-command command)
             (reset! commands/*current-command command)
-            (let [command-steps (get (into {} matched) command)
+            (let [command-steps  (get (into {} matched) command)
                   restore-slash? (or
                   restore-slash? (or
                                   (contains? #{"Today" "Yesterday" "Tomorrow" "Current time"} command)
                                   (contains? #{"Today" "Yesterday" "Tomorrow" "Current time"} command)
                                   (and
                                   (and
@@ -77,7 +80,7 @@
               (editor-handler/insert-command! id command-steps
               (editor-handler/insert-command! id command-steps
                                               format
                                               format
                                               {:restore? restore-slash?
                                               {:restore? restore-slash?
-                                               :command command}))))
+                                               :command  command}))))
         :class
         :class
         "black"}))))
         "black"}))))
 
 

+ 3 - 1
src/main/frontend/components/header.cljs

@@ -238,7 +238,9 @@
         (login))
         (login))
 
 
       (when config/lsp-enabled?
       (when config/lsp-enabled?
-        (plugins/hook-ui-items :toolbar))
+        [:<>
+         (plugins/hook-ui-items :toolbar)
+         (plugins/updates-notifications)])
 
 
       (when (state/feature-http-server-enabled?)
       (when (state/feature-http-server-enabled?)
         (server/server-indicator (state/sub :electron/server)))
         (server/server-indicator (state/sub :electron/server)))

File diff suppressed because it is too large
+ 401 - 327
src/main/frontend/components/plugins.cljs


+ 7 - 0
src/main/frontend/components/plugins.css

@@ -628,6 +628,13 @@
       width: 100%;
       width: 100%;
       max-width: 760px;
       max-width: 760px;
     }
     }
+
+    .html-content {
+      > p {
+        padding-bottom: 2px;
+        margin: 0;
+      }
+    }
   }
   }
 }
 }
 
 

+ 19 - 6
src/main/frontend/components/plugins_settings.cljs

@@ -6,6 +6,18 @@
             [cljs-bean.core :as bean]
             [cljs-bean.core :as bean]
             [goog.functions :refer [debounce]]))
             [goog.functions :refer [debounce]]))
 
 
+(defn- dom-purify
+  [html opts]
+  (try
+    (js-invoke js/DOMPurify "sanitize" html (bean/->js opts))
+    (catch js/Error e
+      (js/console.warn e) html)))
+
+(rum/defc html-content
+  [html]
+  [:div.html-content.pl-1.flex-1.text-sm
+   {:dangerouslySetInnerHTML {:__html (dom-purify html nil)}}])
+
 (rum/defc edit-settings-file
 (rum/defc edit-settings-file
   [pid {:keys [class]}]
   [pid {:keys [class]}]
   [:a.text-sm.hover:underline
   [:a.text-sm.hover:underline
@@ -21,7 +33,7 @@
    [:h2 [:code key] (ui/icon "caret-right") [:strong title]]
    [:h2 [:code key] (ui/icon "caret-right") [:strong title]]
 
 
    [:label.form-control
    [:label.form-control
-    [:small.pl-1.flex-1 description]
+    (html-content description)
 
 
     (let [input-as (util/safe-lower-case (or inputAs (name type)))
     (let [input-as (util/safe-lower-case (or inputAs (name type)))
           input-as (if (= input-as "string") :text (keyword input-as))]
           input-as (if (= input-as "string") :text (keyword input-as))]
@@ -43,7 +55,7 @@
      [:label.form-control
      [:label.form-control
       (ui/checkbox {:checked   val
       (ui/checkbox {:checked   val
                     :on-change #(update-setting! key (not val))})
                     :on-change #(update-setting! key (not val))})
-      [:small.pl-1.flex-1 description]]]))
+      (html-content description)]]))
 
 
 (rum/defc render-item-enum
 (rum/defc render-item-enum
   [val {:keys [key title description default enumChoices enumPicker]} update-setting!]
   [val {:keys [key title description default enumChoices enumPicker]} update-setting!]
@@ -59,7 +71,7 @@
 
 
      [:div.form-control
      [:div.form-control
       [(if (contains? #{:radio :checkbox} picker) :div.wrap :label.wrap)
       [(if (contains? #{:radio :checkbox} picker) :div.wrap :label.wrap)
-       [:small.pl-1 description]
+       (html-content description)
 
 
        (case picker
        (case picker
          :radio (ui/radio-list options #(update-setting! key %) nil)
          :radio (ui/radio-list options #(update-setting! key %) nil)
@@ -76,7 +88,7 @@
    [:h2 [:code key] (ui/icon "caret-right") [:strong title]]
    [:h2 [:code key] (ui/icon "caret-right") [:strong title]]
 
 
    [:div.form-control
    [:div.form-control
-    [:small.pl-1.flex-1 description]
+    (html-content description)
     [:div.pl-1 (edit-settings-file pid nil)]]])
     [:div.pl-1 (edit-settings-file pid nil)]]])
 
 
 (rum/defc render-item-heading
 (rum/defc render-item-heading
@@ -85,7 +97,7 @@
   [:div.heading-item
   [:div.heading-item
    {:data-key key}
    {:data-key key}
    [:h2 title]
    [:h2 title]
-   [:small description]])
+   (html-content description)])
 
 
 (rum/defc settings-container
 (rum/defc settings-container
   [schema ^js pl]
   [schema ^js pl]
@@ -113,7 +125,8 @@
        (for [desc schema
        (for [desc schema
              :let [key (:key desc)
              :let [key (:key desc)
                    val (get settings (keyword key))
                    val (get settings (keyword key))
-                   type (keyword (:type desc))]]
+                   type (keyword (:type desc))
+                   desc (update desc :description #(plugin-handler/markdown-to-html %))]]
 
 
          (condp contains? type
          (condp contains? type
            #{:string :number} (render-item-input val desc update-setting!)
            #{:string :number} (render-item-input val desc update-setting!)

+ 1 - 1
src/main/frontend/components/settings.cljs

@@ -751,7 +751,7 @@
      (whiteboards-switcher-row enable-whiteboards?)
      (whiteboards-switcher-row enable-whiteboards?)
      (when (and (util/electron?) config/feature-plugin-system-on?)
      (when (and (util/electron?) config/feature-plugin-system-on?)
        (plugin-system-switcher-row))
        (plugin-system-switcher-row))
-     (when (and (util/electron?) (state/developer-mode?))
+     (when (util/electron?)
        (http-server-switcher-row))
        (http-server-switcher-row))
      (flashcards-switcher-row enable-flashcards?)
      (flashcards-switcher-row enable-flashcards?)
      (zotero-settings-row)
      (zotero-settings-row)

+ 1 - 1
src/main/frontend/components/theme.cljs

@@ -48,7 +48,7 @@
         (plugin-config-handler/setup-install-listener!)
         (plugin-config-handler/setup-install-listener!)
         (plugin-handler/load-plugin-preferences)
         (plugin-handler/load-plugin-preferences)
         (fn []
         (fn []
-          (js/window.apis.removeAllListeners "lsp-installed")))
+          (js/window.apis.removeAllListeners (name :lsp-updates))))
      [])
      [])
 
 
     (rum/use-effect!
     (rum/use-effect!

+ 8 - 0
src/main/frontend/dicts.cljc

@@ -315,6 +315,10 @@
         :plugin/update "Update"
         :plugin/update "Update"
         :plugin/check-update "Check update"
         :plugin/check-update "Check update"
         :plugin/check-all-updates "Check all updates"
         :plugin/check-all-updates "Check all updates"
+        :plugin/found-updates "New updates"
+        :plugin/found-n-updates "Found {1} updates"
+        :plugin/update-all-selected "Update all of selected"
+        :plugin/updates-downloading "Downloading for the updates"
         :plugin/refresh-lists "Refresh lists"
         :plugin/refresh-lists "Refresh lists"
         :plugin/enabled "Enabled"
         :plugin/enabled "Enabled"
         :plugin/disabled "Disabled"
         :plugin/disabled "Disabled"
@@ -1585,6 +1589,10 @@
            :plugin/update "更新"
            :plugin/update "更新"
            :plugin/check-update "检查更新"
            :plugin/check-update "检查更新"
            :plugin/check-all-updates "一键检查更新"
            :plugin/check-all-updates "一键检查更新"
+           :plugin/found-updates "有更新"
+           :plugin/found-n-updates "发现 {1} 个插件待更新"
+           :plugin/update-all-selected "更新已选插件"
+           :plugin/updates-downloading "正在下载更新"
            :plugin/refresh-lists "刷新插件列表"
            :plugin/refresh-lists "刷新插件列表"
            :plugin/delete-alert "确定删除插件 [{1}]?"
            :plugin/delete-alert "确定删除插件 [{1}]?"
            :plugin/enabled "已开启"
            :plugin/enabled "已开启"

+ 28 - 20
src/main/frontend/handler/events.cljs

@@ -573,36 +573,44 @@
               (file-sync-restart!))))
               (file-sync-restart!))))
         (state/pub-event! [:graph/ready (state/get-current-repo)])))))
         (state/pub-event! [:graph/ready (state/get-current-repo)])))))
 
 
-(defmethod handle :plugin/consume-updates [[_ id pending? updated?]]
-  (let [downloading? (:plugin/updates-downloading? @state/state)]
-
+(defmethod handle :plugin/consume-updates [[_ id prev-pending? updated?]]
+  (let [downloading?   (:plugin/updates-downloading? @state/state)
+        auto-checking? (plugin-handler/get-auto-checking?)]
     (when-let [coming (and (not downloading?)
     (when-let [coming (and (not downloading?)
                            (get-in @state/state [:plugin/updates-coming id]))]
                            (get-in @state/state [:plugin/updates-coming id]))]
       (let [error-code (:error-code coming)
       (let [error-code (:error-code coming)
-            error-code (if (= error-code (str :no-new-version)) nil error-code)]
-        (when (or pending? (not error-code))
-          (notification/show!
-            (str "[Checked]<" (:title coming) "> " error-code)
-            (if error-code :error :success)))))
+            error-code (if (= error-code (str :no-new-version)) nil error-code)
+            title      (:title coming)]
+        (when (and prev-pending? (not auto-checking?))
+          (if-not error-code
+            (plugin/set-updates-sub-content! (str title "...") 0)
+            (notification/show!
+              (str "[Checked]<" title "> " error-code) :error)))))
 
 
     (if (and updated? downloading?)
     (if (and updated? downloading?)
       ;; try to start consume downloading item
       ;; try to start consume downloading item
-      (if-let [n (state/get-next-selected-coming-update)]
-        (plugin-handler/check-or-update-marketplace-plugin
-         (assoc n :only-check false :error-code nil)
-         (fn [^js e] (js/console.error "[Download Err]" n e)))
+      (if-let [next-coming (state/get-next-selected-coming-update)]
+        (plugin-handler/check-or-update-marketplace-plugin!
+          (assoc next-coming :only-check false :error-code nil)
+          (fn [^js e] (js/console.error "[Download Err]" next-coming e)))
         (plugin-handler/close-updates-downloading))
         (plugin-handler/close-updates-downloading))
 
 
       ;; try to start consume pending item
       ;; try to start consume pending item
-      (if-let [n (second (first (:plugin/updates-pending @state/state)))]
-        (plugin-handler/check-or-update-marketplace-plugin
-         (assoc n :only-check true :error-code nil)
-         (fn [^js e]
-           (notification/show! (.toString e) :error)
-           (js/console.error "[Check Err]" n e)))
+      (if-let [next-pending (second (first (:plugin/updates-pending @state/state)))]
+        (do
+          (println "Updates: take next pending - " (:id next-pending))
+          (js/setTimeout
+            #(plugin-handler/check-or-update-marketplace-plugin!
+               (assoc next-pending :only-check true :auto-check auto-checking? :error-code nil)
+               (fn [^js e]
+                 (notification/show! (.toString e) :error)
+                 (js/console.error "[Check Err]" next-pending e))) 500))
+
         ;; try to open waiting updates list
         ;; try to open waiting updates list
-        (when (and pending? (seq (state/all-available-coming-updates)))
-          (plugin/open-waiting-updates-modal!))))))
+        (do (when (and prev-pending? (not auto-checking?)
+                       (seq (state/all-available-coming-updates)))
+              (plugin/open-waiting-updates-modal!))
+            (plugin-handler/set-auto-checking! false))))))
 
 
 (defmethod handle :plugin/hook-db-tx [[_ {:keys [blocks tx-data tx-meta] :as payload}]]
 (defmethod handle :plugin/hook-db-tx [[_ {:keys [blocks tx-data tx-meta] :as payload}]]
   (when-let [payload (and (seq blocks)
   (when-let [payload (and (seq blocks)

+ 2 - 2
src/main/frontend/handler/file_sync.cljs

@@ -69,10 +69,10 @@
               nil
               nil
 
 
               (contains? #{400 404} (get-in (ex-data r) [:err :status]))
               (contains? #{400 404} (get-in (ex-data r) [:err :status]))
-              (notification/show! (str "Create graph failed: already existed graph: " name) :warning true nil 4000)
+              (notification/show! (str "Create graph failed: already existed graph: " name) :warning true nil 4000 nil)
 
 
               :else
               :else
-              (notification/show! (str "Create graph failed: " (ex-message r)) :warning true nil 4000))))))))
+              (notification/show! (str "Create graph failed: " (ex-message r)) :warning true nil 4000 nil))))))))
 
 
 (defn <delete-graph
 (defn <delete-graph
   [graph-uuid]
   [graph-uuid]

+ 11 - 8
src/main/frontend/handler/notification.cljs

@@ -5,8 +5,10 @@
 
 
 (defn clear!
 (defn clear!
   [uid]
   [uid]
-  (let [contents (state/get-notification-contents)]
-    (state/set-state! :notification/contents (dissoc contents uid))))
+  (let [contents (state/get-notification-contents)
+        close-cb (:close-cb (get contents uid))]
+    (state/set-state! :notification/contents (dissoc contents uid))
+    (when (fn? close-cb) (close-cb uid))))
 
 
 (defn clear-all!
 (defn clear-all!
   []
   []
@@ -14,19 +16,20 @@
 
 
 (defn show!
 (defn show!
   ([content]
   ([content]
-   (show! content :info true nil 2000))
+   (show! content :info true nil 2000 nil))
   ([content status]
   ([content status]
-   (show! content status true nil 1500))
+   (show! content status true nil 1500 nil))
   ([content status clear?]
   ([content status clear?]
-   (show! content status clear? nil 1500))
+   (show! content status clear? nil 1500 nil))
   ([content status clear? uid]
   ([content status clear? uid]
-   (show! content status clear? uid 1500))
-  ([content status clear? uid timeout]
+   (show! content status clear? uid 1500 nil))
+  ([content status clear? uid timeout close-cb]
    (let [contents (state/get-notification-contents)
    (let [contents (state/get-notification-contents)
          uid (or uid (keyword (util/unique-id)))]
          uid (or uid (keyword (util/unique-id)))]
      (state/set-state! :notification/contents (assoc contents
      (state/set-state! :notification/contents (assoc contents
                                                      uid {:content content
                                                      uid {:content content
-                                                          :status status}))
+                                                          :status status
+                                                          :close-cb close-cb}))
 
 
      (when (and clear? (not= status :error))
      (when (and clear? (not= status :error))
        (js/setTimeout #(clear! uid) (or timeout 1500)))
        (js/setTimeout #(clear! uid) (or timeout 1500)))

+ 3 - 1
src/main/frontend/handler/page.cljs

@@ -17,6 +17,7 @@
             [frontend.handler.common :as common-handler]
             [frontend.handler.common :as common-handler]
             [frontend.handler.config :as config-handler]
             [frontend.handler.config :as config-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor :as editor-handler]
+            [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.notification :as notification]
             [frontend.handler.recent :as recent-handler]
             [frontend.handler.recent :as recent-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.route :as route-handler]
@@ -854,7 +855,8 @@
                               :create-first-block? (not template)
                               :create-first-block? (not template)
                               :journal? true})
                               :journal? true})
               (state/pub-event! [:journal/insert-template today-page])
               (state/pub-event! [:journal/insert-template today-page])
-              (ui-handler/re-render-root!))))))))
+              (ui-handler/re-render-root!)
+              (plugin-handler/hook-plugin-app :today-journal-created {:title today-page}))))))))
 
 
 (defn open-today-in-sidebar
 (defn open-today-in-sidebar
   []
   []

+ 175 - 120
src/main/frontend/handler/plugin.cljs

@@ -24,11 +24,11 @@
   [input]
   [input]
   (when input
   (when input
     (walk/postwalk
     (walk/postwalk
-     (fn [a]
-       (cond
-         (keyword? a) (csk/->camelCase (name a))
-         (uuid? a) (str a)
-         :else a)) input)))
+      (fn [a]
+        (cond
+          (keyword? a) (csk/->camelCase (name a))
+          (uuid? a) (str a)
+          :else a)) input)))
 
 
 (defn invoke-exported-api
 (defn invoke-exported-api
   [type & args]
   [type & args]
@@ -36,6 +36,14 @@
     (apply js-invoke (aget js/window.logseq "api") type args)
     (apply js-invoke (aget js/window.logseq "api") type args)
     (catch :default e (js/console.error e))))
     (catch :default e (js/console.error e))))
 
 
+(defn markdown-to-html
+  [s]
+  (try
+    (if (string? s)
+      (js/window.marked s) s)
+    (catch js/Error e
+      (js/console.error e) s)))
+
 ;; state handlers
 ;; state handlers
 (defonce central-endpoint "https://raw.githubusercontent.com/logseq/marketplace/master/")
 (defonce central-endpoint "https://raw.githubusercontent.com/logseq/marketplace/master/")
 (defonce plugins-url (str central-endpoint "plugins.json"))
 (defonce plugins-url (str central-endpoint "plugins.json"))
@@ -48,16 +56,16 @@
       (p/then #(bean/->clj %))
       (p/then #(bean/->clj %))
       (p/then #(state/set-state! :plugin/preferences %))
       (p/then #(state/set-state! :plugin/preferences %))
       (p/catch
       (p/catch
-       #(js/console.error %))))
+        #(js/console.error %))))
 
 
 (defn save-plugin-preferences!
 (defn save-plugin-preferences!
   ([input] (save-plugin-preferences! input true))
   ([input] (save-plugin-preferences! input true))
   ([input reload-state?]
   ([input reload-state?]
    (when-let [^js input (and (map? input) (bean/->js input))]
    (when-let [^js input (and (map? input) (bean/->js input))]
      (p/then
      (p/then
-      (js/LSPluginCore.saveUserPreferences input)
-      #(when reload-state?
-         (load-plugin-preferences))))))
+       (js/LSPluginCore.saveUserPreferences input)
+       #(when reload-state?
+          (load-plugin-preferences))))))
 
 
 (defn gh-repo-url [repo]
 (defn gh-repo-url [repo]
   (str "https://github.com/" repo))
   (str "https://github.com/" repo))
@@ -71,67 +79,61 @@
   [refresh?]
   [refresh?]
   (if (or refresh? (nil? (:plugin/marketplace-pkgs @state/state)))
   (if (or refresh? (nil? (:plugin/marketplace-pkgs @state/state)))
     (p/create
     (p/create
-     (fn [resolve reject]
-       (let [on-ok (fn [res]
-                     (if-let [res (and res (bean/->clj res))]
-                       (let [pkgs (:packages res)]
-                         (state/set-state! :plugin/marketplace-pkgs pkgs)
-                         (resolve pkgs))
-                       (reject nil)))]
-         (if (state/http-proxy-enabled-or-val?)
-           (-> (ipc/ipc :httpFetchJSON plugins-url)
-               (p/then on-ok)
-               (p/catch reject))
-           (util/fetch plugins-url on-ok reject)))))
+      (fn [resolve reject]
+        (let [on-ok (fn [res]
+                      (if-let [res (and res (bean/->clj res))]
+                        (let [pkgs (:packages res)]
+                          (state/set-state! :plugin/marketplace-pkgs pkgs)
+                          (resolve pkgs))
+                        (reject nil)))]
+          (if (state/http-proxy-enabled-or-val?)
+            (-> (ipc/ipc :httpFetchJSON plugins-url)
+                (p/then on-ok)
+                (p/catch reject))
+            (util/fetch plugins-url on-ok reject)))))
     (p/resolved (:plugin/marketplace-pkgs @state/state))))
     (p/resolved (:plugin/marketplace-pkgs @state/state))))
 
 
 (defn load-marketplace-stats
 (defn load-marketplace-stats
   [refresh?]
   [refresh?]
   (if (or refresh? (nil? (:plugin/marketplace-stats @state/state)))
   (if (or refresh? (nil? (:plugin/marketplace-stats @state/state)))
     (p/create
     (p/create
-     (fn [resolve reject]
-       (let [on-ok (fn [^js res]
-                     (if-let [res (and res (bean/->clj res))]
-                       (do
-                         (state/set-state!
-                          :plugin/marketplace-stats
-                          (into {} (map (fn [[k stat]]
-                                          [k (assoc stat
-                                                    :total_downloads
-                                                    (reduce (fn [a b] (+ a (get b 2))) 0 (:releases stat)))])
-                                        res)))
-                         (resolve nil))
-                       (reject nil)))]
-         (if (state/http-proxy-enabled-or-val?)
-           (-> (ipc/ipc :httpFetchJSON stats-url)
-               (p/then on-ok)
-               (p/catch reject))
-           (util/fetch stats-url on-ok reject)))))
+      (fn [resolve reject]
+        (let [on-ok (fn [^js res]
+                      (if-let [res (and res (bean/->clj res))]
+                        (do
+                          (state/set-state!
+                            :plugin/marketplace-stats
+                            (into {} (map (fn [[k stat]]
+                                            [k (assoc stat
+                                                 :total_downloads
+                                                 (reduce (fn [a b] (+ a (get b 2))) 0 (:releases stat)))])
+                                          res)))
+                          (resolve nil))
+                        (reject nil)))]
+          (if (state/http-proxy-enabled-or-val?)
+            (-> (ipc/ipc :httpFetchJSON stats-url)
+                (p/then on-ok)
+                (p/catch reject))
+            (util/fetch stats-url on-ok reject)))))
     (p/resolved nil)))
     (p/resolved nil)))
 
 
-(defn check-or-update-marketplace-plugin
+(defn check-or-update-marketplace-plugin!
   [{:keys [id] :as pkg} error-handler]
   [{:keys [id] :as pkg} error-handler]
   (when-not (and (:plugin/installing @state/state)
   (when-not (and (:plugin/installing @state/state)
                  (not (plugin-common-handler/installed? id)))
                  (not (plugin-common-handler/installed? id)))
-    (p/catch
-     (p/then
-      (do (state/set-state! :plugin/installing pkg)
-          (p/catch
-           (load-marketplace-plugins false)
-           (fn [^js e]
-             (state/reset-all-updates-state)
-             (throw e))))
-      (fn [mfts]
-
-        (let [mft (some #(when (= (:id %) id) %) mfts)]
-          ;;TODO: (throw (js/Error. [:not-found-in-marketplace id]))
-          (ipc/ipc :updateMarketPlugin (merge (dissoc pkg :logger) mft)))
-        true))
-
-     (fn [^js e]
-       (error-handler e)
-       (state/set-state! :plugin/installing nil)
-       (js/console.error e)))))
+    (state/set-state! :plugin/installing pkg)
+
+    (-> (load-marketplace-plugins false)
+        (p/then (fn [mfts]
+                  (let [mft (some #(when (= (:id %) id) %) mfts)]
+                    ;;TODO: (throw (js/Error. [:not-found-in-marketplace id]))
+                    (ipc/ipc :updateMarketPlugin (merge (dissoc pkg :logger) mft)))
+                  true))
+        (p/catch (fn [^js e]
+                   (state/reset-all-updates-state)
+                   (error-handler e)
+                   (state/set-state! :plugin/installing nil)
+                   (js/console.error e))))))
 
 
 (defn get-plugin-inst
 (defn get-plugin-inst
   [pid]
   [pid]
@@ -152,7 +154,7 @@
     (when-let [matched (medley/find-first #(= (:key (second %)) key) commands)]
     (when-let [matched (medley/find-first #(= (:key (second %)) key) commands)]
       (let [[_ cmd action pid] matched]
       (let [[_ cmd action pid] matched]
         (state/pub-event!
         (state/pub-event!
-         [:exec-plugin-cmd {:type type :key key :pid pid :cmd (assoc cmd :args args) :action action}])))))
+          [:exec-plugin-cmd {:type type :key key :pid pid :cmd (assoc cmd :args args) :action action}])))))
 
 
 (defn open-updates-downloading
 (defn open-updates-downloading
   []
   []
@@ -182,9 +184,9 @@
 
 
 (defn setup-install-listener!
 (defn setup-install-listener!
   []
   []
-  (let [channel  (name :lsp-installed)
-        listener (fn [^js _ ^js e]
-                   (js/console.debug :lsp-installed e)
+  (let [channel  (name :lsp-updates)
+        listener (fn [_ ^js e]
+                   (js/console.debug (str :lsp-updates) e)
 
 
                    (when-let [{:keys [status payload only-check]} (bean/->clj e)]
                    (when-let [{:keys [status payload only-check]} (bean/->clj e)]
                      (case (keyword status)
                      (case (keyword status)
@@ -193,52 +195,54 @@
                        (let [{:keys [id dst name title theme]} payload
                        (let [{:keys [id dst name title theme]} payload
                              name (or title name "Untitled")]
                              name (or title name "Untitled")]
                          (if only-check
                          (if only-check
-                           (state/consume-updates-coming-plugin payload false)
+                           (state/consume-updates-from-coming-plugin! payload false)
                            (if (plugin-common-handler/installed? id)
                            (if (plugin-common-handler/installed? id)
                              (when-let [^js pl (get-plugin-inst id)] ;; update
                              (when-let [^js pl (get-plugin-inst id)] ;; update
                                (p/then
                                (p/then
-                                (.reload pl)
-                                #(do
-                                   ;;(if theme (select-a-plugin-theme id))
-                                   (notification/show!
-                                    (str (t :plugin/update) (t :plugins) ": " name " - " (.-version (.-options pl))) :success)
-                                   (state/consume-updates-coming-plugin payload true))))
+                                 (.reload pl)
+                                 #(do
+                                    ;;(if theme (select-a-plugin-theme id))
+                                    (notification/show!
+                                      (str (t :plugin/update) (t :plugins) ": " name " - " (.-version (.-options pl))) :success)
+                                    (state/consume-updates-from-coming-plugin! payload true))))
 
 
                              (do                            ;; register new
                              (do                            ;; register new
                                (p/then
                                (p/then
-                                (js/LSPluginCore.register (bean/->js {:key id :url dst}))
-                                (fn [] (when theme (js/setTimeout #(select-a-plugin-theme id) 300))))
+                                 (js/LSPluginCore.register (bean/->js {:key id :url dst}))
+                                 (fn [] (when theme (js/setTimeout #(select-a-plugin-theme id) 300))))
                                (notification/show!
                                (notification/show!
-                                (str (t :plugin/installed) (t :plugins) ": " name) :success)))))
+                                 (str (t :plugin/installed) (t :plugins) ": " name) :success)))))
 
 
                        :error
                        :error
-                       (let [error-code (keyword (string/replace (:error-code payload) #"^[\s\:\[]+" ""))
+                       (let [error-code  (keyword (string/replace (:error-code payload) #"^[\s\:\[]+" ""))
+                             fake-error? (contains? #{:no-new-version} error-code)
                              [msg type] (case error-code
                              [msg type] (case error-code
 
 
                                           :no-new-version
                                           :no-new-version
                                           [(str (t :plugin/up-to-date) " :)") :success]
                                           [(str (t :plugin/up-to-date) " :)") :success]
 
 
                                           [error-code :error])
                                           [error-code :error])
-                             pending?   (seq (:plugin/updates-pending @state/state))]
+                             pending?    (seq (:plugin/updates-pending @state/state))]
 
 
                          (if (and only-check pending?)
                          (if (and only-check pending?)
-                           (state/consume-updates-coming-plugin payload false)
+                           (state/consume-updates-from-coming-plugin! payload false)
 
 
                            (do
                            (do
                              ;; consume failed download updates
                              ;; consume failed download updates
                              (when (and (not only-check) (not pending?))
                              (when (and (not only-check) (not pending?))
-                               (state/consume-updates-coming-plugin payload true))
+                               (state/consume-updates-from-coming-plugin! payload true))
 
 
                              ;; notify human tips
                              ;; notify human tips
                              (notification/show!
                              (notification/show!
-                              (str
-                               (if (= :error type) "[Error]" "")
-                               (str "<" (:id payload) "> ")
-                               msg) type)))
+                               (str
+                                 (if (= :error type) "[Error]" "")
+                                 (str "<" (:id payload) "> ")
+                                 msg) type)))
 
 
-                         (js/console.error payload))
+                         (when-not fake-error?
+                           (js/console.error "Update Error:" (:error-code payload))))
 
 
-                       :dunno))
+                       :default))
 
 
                    ;; reset
                    ;; reset
                    (js/setTimeout #(state/set-state! :plugin/installing nil) 512)
                    (js/setTimeout #(state/set-state! :plugin/installing nil) 512)
@@ -286,7 +290,7 @@
                                    (get keybinding-mode-handler-map (keyword mode)))
                                    (get keybinding-mode-handler-map (keyword mode)))
                      :action     (fn []
                      :action     (fn []
                                    (state/pub-event!
                                    (state/pub-event!
-                                    [:exec-plugin-cmd {:type type :key key :pid pid :cmd cmd :action action}]))}]
+                                     [:exec-plugin-cmd {:type type :key key :pid pid :cmd cmd :action action}]))}]
     palette-cmd))
     palette-cmd))
 
 
 (defn simple-cmd-keybinding->shortcut-args
 (defn simple-cmd-keybinding->shortcut-args
@@ -439,11 +443,11 @@
     (when-not (string/blank? content)
     (when-not (string/blank? content)
       (let [content (if-not (string/blank? url)
       (let [content (if-not (string/blank? url)
                       (string/replace
                       (string/replace
-                       content #"!\[[^\]]*\]\((.*?)\s*(\"(?:.*[^\"])\")?\s*\)"
-                       (fn [[matched link]]
-                         (if (and link (not (string/starts-with? link "http")))
-                           (string/replace matched link (util/node-path.join url link))
-                           matched)))
+                        content #"!\[[^\]]*\]\((.*?)\s*(\"(?:.*[^\"])\")?\s*\)"
+                        (fn [[matched link]]
+                          (if (and link (not (string/starts-with? link "http")))
+                            (string/replace matched link (util/node-path.join url link))
+                            matched)))
                       content)]
                       content)]
         (format/to-html content :markdown (gp-mldoc/default-config :markdown))))
         (format/to-html content :markdown (gp-mldoc/default-config :markdown))))
     (catch :default e
     (catch :default e
@@ -567,21 +571,64 @@
 (defn- get-user-default-plugins
 (defn- get-user-default-plugins
   []
   []
   (p/catch
   (p/catch
-   (p/let [files ^js (ipc/ipc "getUserDefaultPlugins")
-           files (js->clj files)]
-     (map #(hash-map :url %) files))
-   (fn [e]
-     (js/console.error e))))
+    (p/let [files ^js (ipc/ipc "getUserDefaultPlugins")
+            files (js->clj files)]
+      (map #(hash-map :url %) files))
+    (fn [e]
+      (js/console.error e))))
+
+(defn set-auto-checking!
+  [v]
+  (let [v (boolean v)]
+    (println "Updates: " (if v "start" "finish") " auto-checking...")
+    (state/set-state! :plugin/updates-auto-checking? v)))
+
+(defn get-auto-checking?
+  []
+  (:plugin/updates-auto-checking? @state/state))
 
 
-(defn check-enabled-for-updates
+(defn get-user-checking?
+  []
+  (boolean (seq (:plugin/updates-pending @state/state))))
+
+(defn get-updates-downloading?
+  []
+  (boolean (:plugin/updates-downloading? @state/state)))
+
+(defn cancel-user-checking!
+  []
+  (when (and (get-user-checking?)
+             (not (get-auto-checking?)))
+    (state/set-state! :plugin/updates-pending {})))
+
+(defn user-check-enabled-for-updates!
   [theme?]
   [theme?]
-  (let [pending? (seq (:plugin/updates-pending @state/state))]
-    (when-let [plugins (and (not pending?)
-                            ;; TODO: too many requests may be limited by Github api
-                            (seq (take 32 (state/get-enabled?-installed-plugins theme?))))]
-      (state/set-state! :plugin/updates-pending
-                        (into {} (map (fn [v] [(keyword (:id v)) v]) plugins)))
-      (state/pub-event! [:plugin/consume-updates]))))
+  (let [user-checking? (get-user-checking?)
+        auto-checking? (get-auto-checking?)]
+    (when auto-checking?
+      (set-auto-checking! false))
+    (when (or auto-checking? (not user-checking?))
+      ;; TODO: too many requests may be limited by Github api
+      (when-let [plugins (seq (take 32 (state/get-enabled?-installed-plugins theme?)))]
+        (->> plugins
+             (map (fn [v] [(keyword (:id v)) v]))
+             (into {})
+             (state/set-state! :plugin/updates-pending))
+        (state/pub-event! [:plugin/consume-updates])))))
+
+(defn auto-check-enabled-for-updates!
+  []
+  (when (and (not (get-updates-downloading?))
+             (not (get-auto-checking?))
+             (not (get-user-checking?)))
+    ;; TODO: take some plugins used recently
+    (when-let [plugins (seq (take 16 (shuffle (state/get-enabled?-installed-plugins nil))))]
+      (->> plugins
+           (map (fn [v] [(keyword (:id v)) v]))
+           (into {})
+           (state/set-state! :plugin/updates-pending))
+      (state/pub-event! [:plugin/consume-updates])
+      (set-auto-checking! true))))
 
 
 (defn call-plugin
 (defn call-plugin
   [^js pl type payload]
   [^js pl type payload]
@@ -617,7 +664,7 @@
   (let [el (js/document.createElement "div")]
   (let [el (js/document.createElement "div")]
     (.appendChild js/document.body el)
     (.appendChild js/document.body el)
     (rum/mount
     (rum/mount
-     (lsp-indicator) el))
+      (lsp-indicator) el))
 
 
   (state/set-state! :plugin/indicator-text "LOADING")
   (state/set-state! :plugin/indicator-text "LOADING")
 
 
@@ -637,12 +684,12 @@
                                 (.on "registered"
                                 (.on "registered"
                                      (fn [^js pl]
                                      (fn [^js pl]
                                        (register-plugin
                                        (register-plugin
-                                        (bean/->clj (.parse js/JSON (.stringify js/JSON pl))))))
+                                         (bean/->clj (.parse js/JSON (.stringify js/JSON pl))))))
 
 
                                 (.on "reloaded"
                                 (.on "reloaded"
                                      (fn [^js pl]
                                      (fn [^js pl]
                                        (register-plugin
                                        (register-plugin
-                                        (bean/->clj (.parse js/JSON (.stringify js/JSON pl))))))
+                                         (bean/->clj (.parse js/JSON (.stringify js/JSON pl))))))
 
 
                                 (.on "unregistered" (fn [pid]
                                 (.on "unregistered" (fn [pid]
                                                       (let [pid (keyword pid)]
                                                       (let [pid (keyword pid)]
@@ -698,15 +745,15 @@
                                                (when-let [plugins (and perf-table (.entries perf-table))]
                                                (when-let [plugins (and perf-table (.entries perf-table))]
                                                  (->> plugins
                                                  (->> plugins
                                                       (keep
                                                       (keep
-                                                       (fn [[_k ^js v]]
-                                                         (when-let [end (and (some-> v (.-o) (.-disabled) (not))
-                                                                             (.-e v))]
-                                                           (when (and (number? end)
-                                                                      ;; valid end time
-                                                                      (> end 0)
-                                                                      ;; greater than 3s
-                                                                      (> (- end (.-s v)) 3000))
-                                                             v))))
+                                                        (fn [[_k ^js v]]
+                                                          (when-let [end (and (some-> v (.-o) (.-disabled) (not))
+                                                                              (.-e v))]
+                                                            (when (and (number? end)
+                                                                       ;; valid end time
+                                                                       (> end 0)
+                                                                       ;; greater than 3s
+                                                                       (> (- end (.-s v)) 3000))
+                                                              v))))
                                                       ((fn [perfs]
                                                       ((fn [perfs]
                                                          (doseq [perf perfs]
                                                          (doseq [perf perfs]
                                                            (state/pub-event! [:plugin/loader-perf-tip (bean/->clj perf)])))))))))
                                                            (state/pub-event! [:plugin/loader-perf-tip (bean/->clj perf)])))))))))
@@ -716,13 +763,13 @@
               _               (.register js/LSPluginCore (bean/->js (if (seq default-plugins) default-plugins [])) true)])
               _               (.register js/LSPluginCore (bean/->js (if (seq default-plugins) default-plugins [])) true)])
 
 
       (p/then
       (p/then
-       (fn []
-         (state/set-state! :plugin/indicator-text "END")
-         (callback)))
+        (fn []
+          (state/set-state! :plugin/indicator-text "END")
+          (callback)))
       (p/catch
       (p/catch
-       (fn [^js e]
-         (log/error :setup-plugin-system-error e)
-         (state/set-state! :plugin/indicator-text (str "Fatal: " e))))))
+        (fn [^js e]
+          (log/error :setup-plugin-system-error e)
+          (state/set-state! :plugin/indicator-text (str "Fatal: " e))))))
 
 
 (defn setup!
 (defn setup!
   "setup plugin core handler"
   "setup plugin core handler"
@@ -730,3 +777,11 @@
   (if (not config/lsp-enabled?)
   (if (not config/lsp-enabled?)
     (callback)
     (callback)
     (init-plugins! callback)))
     (init-plugins! callback)))
+
+
+(comment
+  {:pending        (count (:plugin/updates-pending @state/state))
+   :auto-checking? (boolean (:plugin/updates-auto-checking? @state/state))
+   :coming         (count (:plugin/updates-coming @state/state))
+   :installing     (:plugin/installing @state/state)
+   :downloading?   (boolean (:plugin/updates-downloading? @state/state))})

+ 2 - 2
src/main/frontend/handler/plugin_config.cljs

@@ -118,10 +118,10 @@ returns map of plugins to install and uninstall"
                           (assoc payload
                           (assoc payload
                                  :version (:installed-version payload)
                                  :version (:installed-version payload)
                                  :effect (boolean effect)
                                  :effect (boolean effect)
-                                 ;; Manual install doesn't have theme field but
+                                 ;; Manual installation doesn't have theme field but
                                  ;; plugin.edn requires this field
                                  ;; plugin.edn requires this field
                                  :theme (boolean theme)))))))]
                                  :theme (boolean theme)))))))]
-    (js/window.apis.addListener "lsp-installed" listener)))
+    (js/window.apis.addListener (name :lsp-updates) listener)))
 
 
 (defn start
 (defn start
   "This component has just one responsibility on start, to create a plugins.edn
   "This component has just one responsibility on start, to create a plugins.edn

+ 2 - 2
src/main/frontend/handler/shell.cljs

@@ -12,11 +12,11 @@
 
 
 (defn run-git-command!
 (defn run-git-command!
   [command]
   [command]
-  (ipc/ipc "runGit" command))
+  (ipc/ipc :runGit command))
 
 
 (defn run-git-command2!
 (defn run-git-command2!
   [command]
   [command]
-  (ipc/ipc "runGitWithinCurrentGraph" command))
+  (ipc/ipc :runGitWithinCurrentGraph command))
 
 
 (defn run-cli-command!
 (defn run-cli-command!
   [command args]
   [command args]

+ 11 - 9
src/main/frontend/state.cljs

@@ -197,6 +197,7 @@
      :plugin/marketplace-stats              nil
      :plugin/marketplace-stats              nil
      :plugin/installing                     nil
      :plugin/installing                     nil
      :plugin/active-readme                  nil
      :plugin/active-readme                  nil
+     :plugin/updates-auto-checking?         false
      :plugin/updates-pending                {}
      :plugin/updates-pending                {}
      :plugin/updates-coming                 {}
      :plugin/updates-coming                 {}
      :plugin/updates-downloading?           false
      :plugin/updates-downloading?           false
@@ -1836,8 +1837,7 @@ Similar to re-frame subscriptions"
 
 
 (defn feature-http-server-enabled?
 (defn feature-http-server-enabled?
   []
   []
-  (and (developer-mode?)
-       (storage/get ::storage-spec/http-server-enabled)))
+  (boolean (storage/get ::storage-spec/http-server-enabled)))
 
 
 (defn get-plugin-by-id
 (defn get-plugin-by-id
   [id]
   [id]
@@ -1850,7 +1850,7 @@ Similar to re-frame subscriptions"
    (filterv
    (filterv
      #(and (if include-unpacked? true (:iir %))
      #(and (if include-unpacked? true (:iir %))
            (if-not (boolean? enabled?) true (= (not enabled?) (boolean (get-in % [:settings :disabled]))))
            (if-not (boolean? enabled?) true (= (not enabled?) (boolean (get-in % [:settings :disabled]))))
-           (or include-all? (= (boolean theme?) (:theme %))))
+           (or include-all? (if (boolean? theme?) (= (boolean theme?) (:theme %)) true)))
      (vals (:plugin/installed-plugins @state)))))
      (vals (:plugin/installed-plugins @state)))))
 
 
 (defn lsp-enabled?-or-theme
 (defn lsp-enabled?-or-theme
@@ -1860,17 +1860,18 @@ Similar to re-frame subscriptions"
 (def lsp-enabled?
 (def lsp-enabled?
   (lsp-enabled?-or-theme))
   (lsp-enabled?-or-theme))
 
 
-(defn consume-updates-coming-plugin
+(defn consume-updates-from-coming-plugin!
   [payload updated?]
   [payload updated?]
   (when-let [id (keyword (:id payload))]
   (when-let [id (keyword (:id payload))]
-    (let [pending? (boolean (seq (:plugin/updates-pending @state)))]
+    (let [prev-pending? (boolean (seq (:plugin/updates-pending @state)))]
+      (println "Updates: consumed pending - " id)
       (swap! state update :plugin/updates-pending dissoc id)
       (swap! state update :plugin/updates-pending dissoc id)
       (if updated?
       (if updated?
         (if-let [error (:error-code payload)]
         (if-let [error (:error-code payload)]
           (swap! state update-in [:plugin/updates-coming id] assoc :error-code error)
           (swap! state update-in [:plugin/updates-coming id] assoc :error-code error)
           (swap! state update :plugin/updates-coming dissoc id))
           (swap! state update :plugin/updates-coming dissoc id))
         (swap! state update :plugin/updates-coming assoc id payload))
         (swap! state update :plugin/updates-coming assoc id payload))
-      (pub-event! [:plugin/consume-updates id pending? updated?]))))
+      (pub-event! [:plugin/consume-updates id prev-pending? updated?]))))
 
 
 (defn coming-update-new-version?
 (defn coming-update-new-version?
   [pkg]
   [pkg]
@@ -1882,9 +1883,9 @@ Similar to re-frame subscriptions"
     (coming-update-new-version? pkg)))
     (coming-update-new-version? pkg)))
 
 
 (defn all-available-coming-updates
 (defn all-available-coming-updates
-  []
-  (when-let [updates (vals (:plugin/updates-coming @state))]
-    (filterv #(coming-update-new-version? %) updates)))
+  ([] (all-available-coming-updates (:plugin/updates-coming @state)))
+  ([updates] (when-let [updates (vals updates)]
+               (filterv #(coming-update-new-version? %) updates))))
 
 
 (defn get-next-selected-coming-update
 (defn get-next-selected-coming-update
   []
   []
@@ -1904,6 +1905,7 @@ Similar to re-frame subscriptions"
 (defn reset-all-updates-state
 (defn reset-all-updates-state
   []
   []
   (swap! state assoc
   (swap! state assoc
+         :plugin/updates-auto-checking?         false
          :plugin/updates-pending                {}
          :plugin/updates-pending                {}
          :plugin/updates-coming                 {}
          :plugin/updates-coming                 {}
          :plugin/updates-downloading?           false))
          :plugin/updates-downloading?           false))

+ 32 - 22
src/main/frontend/ui.cljs

@@ -231,17 +231,19 @@
   [state content status uid]
   [state content status uid]
   (when (and content status)
   (when (and content status)
     (let [svg
     (let [svg
-          (case status
-            :success
-            (icon "circle-check" {:class "text-success" :size "32"})
+          (if (keyword? status)
+            (case status
+              :success
+              (icon "circle-check" {:class "text-success" :size "32"})
 
 
-            :warning
-            (icon "alert-circle" {:class "text-warning" :size "32"})
+              :warning
+              (icon "alert-circle" {:class "text-warning" :size "32"})
 
 
-            :error
-            (icon "circle-x" {:class "text-error" :size "32"})
+              :error
+              (icon "circle-x" {:class "text-error" :size "32"})
 
 
-            (icon "info-circle" {:class "text-indigo-500" :size "32"}))]
+              (icon "info-circle" {:class "text-indigo-500" :size "32"}))
+            status)]
       [:div.ui__notifications-content
       [:div.ui__notifications-content
        {:style
        {:style
         (when (or (= state "exiting")
         (when (or (= state "exiting")
@@ -261,7 +263,7 @@
            [:div.flex-shrink-0
            [:div.flex-shrink-0
             svg]
             svg]
            [:div.ml-3.w-0.flex-1
            [:div.ml-3.w-0.flex-1
-            [:div.text-sm.leading-6.font-medium.whitespace-pre-line {:style {:margin 0}}
+            [:div.text-sm.leading-5.font-medium.whitespace-pre-line {:style {:margin 0}}
              content]]
              content]]
            [:div.ml-4.flex-shrink-0.flex
            [:div.ml-4.flex-shrink-0.flex
             [:button.inline-flex.text-gray-400.focus:outline-none.focus:text-gray-500.transition.ease-in-out.duration-150.notification-close-button
             [:button.inline-flex.text-gray-400.focus:outline-none.focus:text-gray-500.transition.ease-in-out.duration-150.notification-close-button
@@ -778,7 +780,7 @@
         (if (not @collapsed?) (content) nil)
         (if (not @collapsed?) (content) nil)
         content)]]))
         content)]]))
 
 
-(defn admonition
+(rum/defc admonition
   [type content]
   [type content]
   (let [type (name type)]
   (let [type (name type)]
     (when-let [icon (case (string/lower-case type)
     (when-let [icon (case (string/lower-case type)
@@ -947,14 +949,14 @@
                           [:div {:key "tippy"} ""])))
                           [:div {:key "tippy"} ""])))
            (rum/fragment {:key "tippy-children"} child))))
            (rum/fragment {:key "tippy-children"} child))))
 
 
-(defn slider
+(rum/defc slider
   [default-value {:keys [min max on-change]}]
   [default-value {:keys [min max on-change]}]
   [:input.cursor-pointer
   [:input.cursor-pointer
-   {:type  "range"
-    :value (int default-value)
-    :min   min
-    :max   max
-    :style {:width "100%"}
+   {:type      "range"
+    :value     (int default-value)
+    :min       min
+    :max       max
+    :style     {:width "100%"}
     :on-change #(let [value (util/evalue %)]
     :on-change #(let [value (util/evalue %)]
                   (on-change value))}])
                   (on-change value))}])
 
 
@@ -971,7 +973,7 @@
 (def get-adapt-icon-class
 (def get-adapt-icon-class
   (memoize (fn [klass] (r/adapt-class klass))))
   (memoize (fn [klass] (r/adapt-class klass))))
 
 
-(defn icon
+(rum/defc icon
   ([name] (icon name nil))
   ([name] (icon name nil))
   ([name {:keys [extension? font? class] :as opts}]
   ([name {:keys [extension? font? class] :as opts}]
    (when-not (string/blank? name)
    (when-not (string/blank? name)
@@ -979,10 +981,10 @@
        (if (or extension? font? (not jsTablerIcons))
        (if (or extension? font? (not jsTablerIcons))
          [:span.ui__icon (merge {:class
          [:span.ui__icon (merge {:class
                                  (util/format
                                  (util/format
-                                  (str "%s-" name
-                                       (when (:class opts)
-                                         (str " " (string/trim (:class opts)))))
-                                  (if extension? "tie tie" "ti ti"))}
+                                   (str "%s-" name
+                                        (when (:class opts)
+                                          (str " " (string/trim (:class opts)))))
+                                   (if extension? "tie tie" "ti ti"))}
                                 (dissoc opts :class :extension? :font?))]
                                 (dissoc opts :class :extension? :font?))]
 
 
          ;; tabler svg react
          ;; tabler svg react
@@ -992,7 +994,7 @@
               {:class (str "ls-icon-" name " " class)}
               {:class (str "ls-icon-" name " " class)}
               (f (merge {:size 18} (r/map-keys->camel-case (dissoc opts :class))))])))))))
               (f (merge {:size 18} (r/map-keys->camel-case (dissoc opts :class))))])))))))
 
 
-(defn button
+(rum/defc button
   [text & {:keys [background href class intent on-click small? large? title icon icon-props disabled?]
   [text & {:keys [background href class intent on-click small? large? title icon icon-props disabled?]
            :or   {small? false large? false}
            :or   {small? false large? false}
            :as   option}]
            :as   option}]
@@ -1015,6 +1017,14 @@
      (when icon (frontend.ui/icon icon (merge icon-props {:class (when-not (empty? text) "mr-1")})))
      (when icon (frontend.ui/icon icon (merge icon-props {:class (when-not (empty? text) "mr-1")})))
      text]))
      text]))
 
 
+(rum/defc point
+  ([] (point "bg-red-600" 5 nil))
+  ([klass size {:keys [class style] :as opts}]
+   [:span.ui__point.overflow-hidden.rounded-full.inline-block
+    (merge {:class (str (util/hiccup->class klass) " " class)
+            :style (merge {:width size :height size} style)}
+           (dissoc opts :style :class))]))
+
 (rum/defc type-icon
 (rum/defc type-icon
   [{:keys [name class title extension?]}]
   [{:keys [name class title extension?]}]
   [:.type-icon {:class class
   [:.type-icon {:class class

+ 3 - 3
src/main/frontend/ui.css

@@ -14,11 +14,11 @@
         justify-content: space-between;
         justify-content: space-between;
 
 
         small {
         small {
-          visibility: hidden;
+          visibility: visible;
           cursor: help;
           cursor: help;
 
 
           svg {
           svg {
-            opacity: .5;
+            opacity: .3;
           }
           }
 
 
           &:hover svg {
           &:hover svg {
@@ -26,7 +26,7 @@
           }
           }
         }
         }
       }
       }
-
+      
       &:hover, &.chosen {
       &:hover, &.chosen {
         .has-help small {
         .has-help small {
           visibility: visible;
           visibility: visible;

+ 176 - 203
src/main/logseq/api.cljs

@@ -1,14 +1,18 @@
 (ns ^:no-doc logseq.api
 (ns ^:no-doc logseq.api
-  (:require [camel-snake-kebab.core :as csk]
-            [cljs-bean.core :as bean]
+  (:require [cljs-bean.core :as bean]
             [cljs.reader]
             [cljs.reader]
+            [logseq.sdk.core]
+            [logseq.sdk.utils :as sdk-utils]
+            [logseq.sdk.ui :as sdk-ui]
+            [logseq.sdk.git :as sdk-git]
+            [logseq.sdk.assets :as sdk-assets]
             [clojure.string :as string]
             [clojure.string :as string]
-            [clojure.walk :as walk]
             [datascript.core :as d]
             [datascript.core :as d]
             [electron.ipc :as ipc]
             [electron.ipc :as ipc]
             [frontend.commands :as commands]
             [frontend.commands :as commands]
             [frontend.components.plugins :as plugins]
             [frontend.components.plugins :as plugins]
             [frontend.config :as config]
             [frontend.config :as config]
+            [frontend.handler.config :as config-handler]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.db.model :as db-model]
             [frontend.db.model :as db-model]
             [frontend.db.query-dsl :as query-dsl]
             [frontend.db.query-dsl :as query-dsl]
@@ -19,7 +23,6 @@
             [frontend.handler.dnd :as editor-dnd-handler]
             [frontend.handler.dnd :as editor-dnd-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.export :as export-handler]
             [frontend.handler.export :as export-handler]
-            [frontend.handler.notification :as notification]
             [frontend.handler.page :as page-handler]
             [frontend.handler.page :as page-handler]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.common.plugin :as plugin-common-handler]
             [frontend.handler.common.plugin :as plugin-common-handler]
@@ -36,7 +39,6 @@
             [lambdaisland.glogi :as log]
             [lambdaisland.glogi :as log]
             [promesa.core :as p]
             [promesa.core :as p]
             [reitit.frontend.easy :as rfe]
             [reitit.frontend.easy :as rfe]
-            [sci.core :as sci]
             [frontend.version :as fv]
             [frontend.version :as fv]
             [frontend.handler.shell :as shell]
             [frontend.handler.shell :as shell]
             [frontend.modules.layout.core]
             [frontend.modules.layout.core]
@@ -44,41 +46,6 @@
             [frontend.handler.search :as search-handler]))
             [frontend.handler.search :as search-handler]))
 
 
 ;; helpers
 ;; helpers
-(defn- normalize-keyword-for-json
-  ([input] (normalize-keyword-for-json input true))
-  ([input camel-case?]
-   (when input
-     (walk/postwalk
-      (fn [a]
-        (cond
-          (keyword? a)
-          (cond-> (name a)
-            camel-case?
-            (csk/->camelCase))
-
-          (uuid? a) (str a)
-          :else a)) input))))
-
-(defn- uuid-or-throw-error
-  [s]
-  (cond
-    (uuid? s)
-    s
-
-    (util/uuid-string? s)
-    (uuid s)
-
-    :else
-    (throw (js/Error. (str s " is not a valid UUID string.")))))
-
-(defn- parse-hiccup-ui
-  [input]
-  (when (string? input)
-    (try
-      (sci/eval-string input {:preset :termination-safe})
-      (catch :default e
-        (js/console.error "[parse hiccup error]" e) input))))
-
 (defn ^:export install-plugin-hook
 (defn ^:export install-plugin-hook
   [pid hook ^js opts]
   [pid hook ^js opts]
   (state/install-plugin-hook pid hook (bean/->clj opts)))
   (state/install-plugin-hook pid hook (bean/->clj opts)))
@@ -100,7 +67,7 @@
                      (subs % 1)
                      (subs % 1)
                      (keyword %)))
                      (keyword %)))
              (get-in @state/state)
              (get-in @state/state)
-             (normalize-keyword-for-json)
+             (sdk-utils/normalize-keyword-for-json)
              (bean/->js))))
              (bean/->js))))
 
 
 (defn ^:export set_state_from_store
 (defn ^:export set_state_from_store
@@ -117,32 +84,39 @@
   ;; get app base info
   ;; get app base info
   []
   []
   (bean/->js
   (bean/->js
-   (normalize-keyword-for-json
-    {:version fv/version})))
+    (sdk-utils/normalize-keyword-for-json
+      {:version fv/version})))
 
 
 (def ^:export get_user_configs
 (def ^:export get_user_configs
   (fn []
   (fn []
     (bean/->js
     (bean/->js
-     (normalize-keyword-for-json
-      {:preferred-language      (:preferred-language @state/state)
-       :preferred-theme-mode    (:ui/theme @state/state)
-       :preferred-format        (state/get-preferred-format)
-       :preferred-workflow      (state/get-preferred-workflow)
-       :preferred-todo          (state/get-preferred-todo)
-       :preferred-date-format   (state/get-date-formatter)
-       :preferred-start-of-week (state/get-start-of-week)
-       :current-graph           (state/get-current-repo)
-       :show-brackets           (state/show-brackets?)
-       :enabled-journals        (state/enable-journals?)
-       :enabled-flashcards      (state/enable-flashcards?)
-       :me                      (state/get-me)}))))
+      (sdk-utils/normalize-keyword-for-json
+        {:preferred-language      (:preferred-language @state/state)
+         :preferred-theme-mode    (:ui/theme @state/state)
+         :preferred-format        (state/get-preferred-format)
+         :preferred-workflow      (state/get-preferred-workflow)
+         :preferred-todo          (state/get-preferred-todo)
+         :preferred-date-format   (state/get-date-formatter)
+         :preferred-start-of-week (state/get-start-of-week)
+         :current-graph           (state/get-current-repo)
+         :show-brackets           (state/show-brackets?)
+         :enabled-journals        (state/enable-journals?)
+         :enabled-flashcards      (state/enable-flashcards?)
+         :me                      (state/get-me)}))))
 
 
 (def ^:export get_current_graph_configs
 (def ^:export get_current_graph_configs
-  (fn []
+  (fn [& keys]
     (some-> (state/get-config)
     (some-> (state/get-config)
-            (normalize-keyword-for-json)
+            (#(if (seq keys) (get-in % (map keyword keys)) %))
             (bean/->js))))
             (bean/->js))))
 
 
+(def ^:export set_current_graph_configs
+  (fn [^js configs]
+    (when-let [configs (bean/->clj configs)]
+      (when (map? configs)
+        (doseq [[k v] configs]
+          (config-handler/set-config! k v))))))
+
 (def ^:export get_current_graph_favorites
 (def ^:export get_current_graph_favorites
   (fn []
   (fn []
     (some->> (:favorites (state/get-config))
     (some->> (:favorites (state/get-config))
@@ -157,6 +131,14 @@
              (filter string?)
              (filter string?)
              (bean/->js))))
              (bean/->js))))
 
 
+(def ^:export get_current_graph_templates
+  (fn []
+    (when-let [_repo (state/get-current-repo)]
+      (some-> (db-model/get-all-templates)
+              (update-vals #(db-model/pull-block %))
+              (sdk-utils/normalize-keyword-for-json)
+              (bean/->js)))))
+
 (def ^:export get_current_graph
 (def ^:export get_current_graph
   (fn []
   (fn []
     (when-let [repo (state/get-current-repo)]
     (when-let [repo (state/get-current-repo)]
@@ -184,7 +166,7 @@
 (def ^:export save_plugin_config
 (def ^:export save_plugin_config
   (fn [path ^js data]
   (fn [path ^js data]
     (let [repo ""
     (let [repo ""
-          
+
           path (util/node-path.join path "package.json")]
           path (util/node-path.join path "package.json")]
       (fs/write-file! repo nil path (js/JSON.stringify data nil 2) {:skip-compare? true}))))
       (fs/write-file! repo nil path (js/JSON.stringify data nil 2) {:skip-compare? true}))))
 
 
@@ -297,8 +279,8 @@
                         (plugin-handler/get-ls-dotdir-root))
                         (plugin-handler/get-ls-dotdir-root))
             plugin-id (util/node-path.basename plugin-id)
             plugin-id (util/node-path.basename plugin-id)
             exist?    (fs/file-exists?
             exist?    (fs/file-exists?
-                       (util/node-path.join root "storages" plugin-id)
-                       file)]
+                        (util/node-path.join root "storages" plugin-id)
+                        file)]
       exist?)))
       exist?)))
 
 
 (def ^:export clear_plugin_storage_files
 (def ^:export clear_plugin_storage_files
@@ -319,8 +301,8 @@
             ^js files  (ipc/ipc :listdir files-path)]
             ^js files  (ipc/ipc :listdir files-path)]
       (when (js-iterable? files)
       (when (js-iterable? files)
         (bean/->js
         (bean/->js
-         (map #(some-> (string/replace-first % files-path "")
-                       (string/replace #"^/+" "")) files))))))
+          (map #(some-> (string/replace-first % files-path "")
+                        (string/replace #"^/+" "")) files))))))
 
 
 (def ^:export load_user_preferences
 (def ^:export load_user_preferences
   (fn []
   (fn []
@@ -356,8 +338,8 @@
   (fn [pid ^js cmd-actions]
   (fn [pid ^js cmd-actions]
     (when-let [[cmd actions] (bean/->clj cmd-actions)]
     (when-let [[cmd actions] (bean/->clj cmd-actions)]
       (plugin-handler/register-plugin-slash-command
       (plugin-handler/register-plugin-slash-command
-       pid [cmd (mapv #(into [(keyword (first %))]
-                             (rest %)) actions)]))))
+        pid [cmd (mapv #(into [(keyword (first %))]
+                              (rest %)) actions)]))))
 
 
 (def ^:export register_plugin_simple_command
 (def ^:export register_plugin_simple_command
   (fn [pid ^js cmd-action palette?]
   (fn [pid ^js cmd-action palette?]
@@ -414,7 +396,7 @@
   (fn [pid type ^js opts]
   (fn [pid type ^js opts]
     (when-let [opts (bean/->clj opts)]
     (when-let [opts (bean/->clj opts)]
       (plugin-handler/register-plugin-ui-item
       (plugin-handler/register-plugin-ui-item
-       pid (assoc opts :type type)))))
+        pid (assoc opts :type type)))))
 
 
 ;; app
 ;; app
 (def ^:export relaunch
 (def ^:export relaunch
@@ -465,16 +447,16 @@
 (def ^:export push_state
 (def ^:export push_state
   (fn [^js k ^js params ^js query]
   (fn [^js k ^js params ^js query]
     (rfe/push-state
     (rfe/push-state
-     (keyword k)
-     (bean/->clj params)
-     (bean/->clj query))))
+      (keyword k)
+      (bean/->clj params)
+      (bean/->clj query))))
 
 
 (def ^:export replace_state
 (def ^:export replace_state
   (fn [^js k ^js params ^js query]
   (fn [^js k ^js params ^js query]
     (rfe/replace-state
     (rfe/replace-state
-     (keyword k)
-     (bean/->clj params)
-     (bean/->clj query))))
+      (keyword k)
+      (bean/->clj params)
+      (bean/->clj query))))
 
 
 (defn ^:export get_external_plugin
 (defn ^:export get_external_plugin
   [pid]
   [pid]
@@ -517,7 +499,7 @@
 (def ^:export get_editing_cursor_position
 (def ^:export get_editing_cursor_position
   (fn []
   (fn []
     (when-let [input-id (state/get-edit-input-id)]
     (when-let [input-id (state/get-edit-input-id)]
-      (bean/->js (normalize-keyword-for-json (cursor/get-caret-pos (gdom/getElement input-id)))))))
+      (bean/->js (sdk-utils/normalize-keyword-for-json (cursor/get-caret-pos (gdom/getElement input-id)))))))
 
 
 (def ^:export get_editing_block_content
 (def ^:export get_editing_block_content
   (fn []
   (fn []
@@ -529,13 +511,13 @@
       (let [blocks (->> blocks
       (let [blocks (->> blocks
                         (map (fn [^js el] (some-> (.getAttribute el "blockid")
                         (map (fn [^js el] (some-> (.getAttribute el "blockid")
                                                   (db-model/query-block-by-uuid)))))]
                                                   (db-model/query-block-by-uuid)))))]
-        (bean/->js (normalize-keyword-for-json blocks))))))
+        (bean/->js (sdk-utils/normalize-keyword-for-json blocks))))))
 
 
 (def ^:export get_current_page
 (def ^:export get_current_page
   (fn []
   (fn []
     (when-let [page (state/get-current-page)]
     (when-let [page (state/get-current-page)]
       (when-let [page (db-model/get-page page)]
       (when-let [page (db-model/get-page page)]
-        (bean/->js (normalize-keyword-for-json (db-utils/pull (:db/id page))))))))
+        (bean/->js (sdk-utils/normalize-keyword-for-json (db-utils/pull (:db/id page))))))))
 
 
 (def ^:export get_page
 (def ^:export get_page
   (fn [id-or-page-name]
   (fn [id-or-page-name]
@@ -543,12 +525,12 @@
                       (number? id-or-page-name) (db-utils/pull id-or-page-name)
                       (number? id-or-page-name) (db-utils/pull id-or-page-name)
                       (string? id-or-page-name) (db-model/get-page id-or-page-name))]
                       (string? id-or-page-name) (db-model/get-page id-or-page-name))]
       (when-not (contains? page :block/left)
       (when-not (contains? page :block/left)
-        (bean/->js (normalize-keyword-for-json (db-utils/pull (:db/id page))))))))
+        (bean/->js (sdk-utils/normalize-keyword-for-json (db-utils/pull (:db/id page))))))))
 
 
 (def ^:export get_all_pages
 (def ^:export get_all_pages
   (fn [repo]
   (fn [repo]
     (let [pages (page-handler/get-all-pages repo)]
     (let [pages (page-handler/get-all-pages repo)]
-      (bean/->js (normalize-keyword-for-json pages)))))
+      (bean/->js (sdk-utils/normalize-keyword-for-json pages)))))
 
 
 (def ^:export create_page
 (def ^:export create_page
   (fn [name ^js properties ^js opts]
   (fn [name ^js properties ^js opts]
@@ -557,16 +539,16 @@
               (let [properties (bean/->clj properties)
               (let [properties (bean/->clj properties)
                     {:keys [redirect createFirstBlock format journal]} (bean/->clj opts)
                     {:keys [redirect createFirstBlock format journal]} (bean/->clj opts)
                     name       (page-handler/create!
                     name       (page-handler/create!
-                                name
-                                {:redirect?           (if (boolean? redirect) redirect true)
-                                 :journal?            journal
-                                 :create-first-block? (if (boolean? createFirstBlock) createFirstBlock true)
-                                 :format              format
-                                 :properties          properties})]
+                                 name
+                                 {:redirect?           (if (boolean? redirect) redirect true)
+                                  :journal?            journal
+                                  :create-first-block? (if (boolean? createFirstBlock) createFirstBlock true)
+                                  :format              format
+                                  :properties          properties})]
                 (db-model/get-page name)))
                 (db-model/get-page name)))
             (:db/id)
             (:db/id)
             (db-utils/pull)
             (db-utils/pull)
-            (normalize-keyword-for-json)
+            (sdk-utils/normalize-keyword-for-json)
             (bean/->js))))
             (bean/->js))))
 
 
 (def ^:export delete_page
 (def ^:export delete_page
@@ -578,19 +560,19 @@
 
 
 (defn ^:export open_in_right_sidebar
 (defn ^:export open_in_right_sidebar
   [block-uuid]
   [block-uuid]
-  (editor-handler/open-block-in-sidebar! (uuid-or-throw-error block-uuid)))
+  (editor-handler/open-block-in-sidebar! (sdk-utils/uuid-or-throw-error block-uuid)))
 
 
 (defn ^:export new_block_uuid []
 (defn ^:export new_block_uuid []
   (str (db/new-block-id)))
   (str (db/new-block-id)))
 
 
 (def ^:export select_block
 (def ^:export select_block
   (fn [block-uuid]
   (fn [block-uuid]
-    (when-let [block (db-model/get-block-by-uuid (uuid-or-throw-error block-uuid))]
+    (when-let [block (db-model/get-block-by-uuid (sdk-utils/uuid-or-throw-error block-uuid))]
       (editor-handler/select-block! (:block/uuid block)) nil)))
       (editor-handler/select-block! (:block/uuid block)) nil)))
 
 
 (def ^:export edit_block
 (def ^:export edit_block
   (fn [block-uuid ^js opts]
   (fn [block-uuid ^js opts]
-    (when-let [block-uuid (and block-uuid (uuid-or-throw-error block-uuid))]
+    (when-let [block-uuid (and block-uuid (sdk-utils/uuid-or-throw-error block-uuid))]
       (when-let [block (db-model/query-block-by-uuid block-uuid)]
       (when-let [block (db-model/query-block-by-uuid block-uuid)]
         (let [{:keys [pos] :or {pos :max}} (bean/->clj opts)]
         (let [{:keys [pos] :or {pos :max}} (bean/->clj opts)]
           (editor-handler/edit-block! block pos block-uuid))))))
           (editor-handler/edit-block! block pos block-uuid))))))
@@ -603,15 +585,15 @@
           [page-name block-uuid] (if (util/uuid-string? block-uuid-or-page-name)
           [page-name block-uuid] (if (util/uuid-string? block-uuid-or-page-name)
                                    [nil (uuid block-uuid-or-page-name)]
                                    [nil (uuid block-uuid-or-page-name)]
                                    [block-uuid-or-page-name nil])
                                    [block-uuid-or-page-name nil])
-          page-name (when page-name (util/page-name-sanity-lc page-name))
-          _ (when (and page-name (not (db/entity [:block/name page-name])))
-              (page-handler/create! block-uuid-or-page-name {:create-first-block? false}))
+          page-name              (when page-name (util/page-name-sanity-lc page-name))
+          _                      (when (and page-name (not (db/entity [:block/name page-name])))
+                                   (page-handler/create! block-uuid-or-page-name {:create-first-block? false}))
           custom-uuid            (or customUUID (:id properties))
           custom-uuid            (or customUUID (:id properties))
-          custom-uuid            (when custom-uuid (uuid-or-throw-error custom-uuid))
+          custom-uuid            (when custom-uuid (sdk-utils/uuid-or-throw-error custom-uuid))
           edit-block?            (if (nil? focus) true focus)
           edit-block?            (if (nil? focus) true focus)
           _                      (when (and custom-uuid (db-model/query-block-by-uuid custom-uuid))
           _                      (when (and custom-uuid (db-model/query-block-by-uuid custom-uuid))
                                    (throw (js/Error.
                                    (throw (js/Error.
-                                           (util/format "Custom block UUID already exists (%s)." custom-uuid))))
+                                            (util/format "Custom block UUID already exists (%s)." custom-uuid))))
           block-uuid'            (if (and (not sibling) before block-uuid)
           block-uuid'            (if (and (not sibling) before block-uuid)
                                    (let [block       (db/entity [:block/uuid block-uuid])
                                    (let [block       (db/entity [:block/uuid block-uuid])
                                          first-child (db-model/get-by-parent-&-left (db/get-db)
                                          first-child (db-model/get-by-parent-&-left (db/get-db)
@@ -629,32 +611,32 @@
                                    false
                                    false
                                    before?)
                                    before?)
           new-block              (editor-handler/api-insert-new-block!
           new-block              (editor-handler/api-insert-new-block!
-                                  content
-                                  {:block-uuid  block-uuid'
-                                   :sibling?    sibling?
-                                   :before?     before?
-                                   :edit-block? edit-block?
-                                   :page        page-name
-                                   :custom-uuid custom-uuid
-                                   :properties  (merge properties
-                                                       (when custom-uuid {:id custom-uuid}))})]
-      (bean/->js (normalize-keyword-for-json new-block)))))
+                                   content
+                                   {:block-uuid  block-uuid'
+                                    :sibling?    sibling?
+                                    :before?     before?
+                                    :edit-block? edit-block?
+                                    :page        page-name
+                                    :custom-uuid custom-uuid
+                                    :properties  (merge properties
+                                                        (when custom-uuid {:id custom-uuid}))})]
+      (bean/->js (sdk-utils/normalize-keyword-for-json new-block)))))
 
 
 (def ^:export insert_batch_block
 (def ^:export insert_batch_block
   (fn [block-uuid ^js batch-blocks ^js opts]
   (fn [block-uuid ^js batch-blocks ^js opts]
-    (when-let [block (db-model/query-block-by-uuid (uuid-or-throw-error block-uuid))]
+    (when-let [block (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error block-uuid))]
       (when-let [bb (bean/->clj batch-blocks)]
       (when-let [bb (bean/->clj batch-blocks)]
         (let [bb         (if-not (vector? bb) (vector bb) bb)
         (let [bb         (if-not (vector? bb) (vector bb) bb)
               {:keys [sibling keepUUID]} (bean/->clj opts)
               {:keys [sibling keepUUID]} (bean/->clj opts)
               keep-uuid? (or keepUUID false)
               keep-uuid? (or keepUUID false)
-              _ (when keep-uuid? (doseq
-                  [block (outliner/tree-vec-flatten bb :children)]
-                  (let [uuid (:id (:properties block))]
-                    (when (and uuid (db-model/query-block-by-uuid (uuid-or-throw-error uuid)))
-                      (throw (js/Error.
-                              (util/format "Custom block UUID already exists (%s)." uuid)))))))
-              _ (editor-handler/insert-block-tree-after-target
-                 (:db/id block) sibling bb (:block/format block) keep-uuid?)]
+              _          (when keep-uuid? (doseq
+                                            [block (outliner/tree-vec-flatten bb :children)]
+                                            (let [uuid (:id (:properties block))]
+                                              (when (and uuid (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error uuid)))
+                                                (throw (js/Error.
+                                                         (util/format "Custom block UUID already exists (%s)." uuid)))))))
+              _          (editor-handler/insert-block-tree-after-target
+                           (:db/id block) sibling bb (:block/format block) keep-uuid?)]
           nil)))))
           nil)))))
 
 
 (def ^:export remove_block
 (def ^:export remove_block
@@ -662,7 +644,7 @@
     (let [includeChildren true
     (let [includeChildren true
           repo            (state/get-current-repo)]
           repo            (state/get-current-repo)]
       (editor-handler/delete-block-aux!
       (editor-handler/delete-block-aux!
-       {:block/uuid (uuid-or-throw-error block-uuid) :repo repo} includeChildren)
+        {:block/uuid (sdk-utils/uuid-or-throw-error block-uuid) :repo repo} includeChildren)
       nil)))
       nil)))
 
 
 (def ^:export update_block
 (def ^:export update_block
@@ -672,7 +654,7 @@
           editing?   (and edit-input (string/ends-with? edit-input (str block-uuid)))]
           editing?   (and edit-input (string/ends-with? edit-input (str block-uuid)))]
       (if editing?
       (if editing?
         (state/set-edit-content! edit-input content)
         (state/set-edit-content! edit-input content)
-        (editor-handler/save-block! repo (uuid-or-throw-error block-uuid) content))
+        (editor-handler/save-block! repo (sdk-utils/uuid-or-throw-error block-uuid) content))
       nil)))
       nil)))
 
 
 (def ^:export move_block
 (def ^:export move_block
@@ -687,15 +669,15 @@
 
 
                          :else
                          :else
                          nil)
                          nil)
-          src-block    (db-model/query-block-by-uuid (uuid-or-throw-error src-block-uuid))
-          target-block (db-model/query-block-by-uuid (uuid-or-throw-error target-block-uuid))]
+          src-block    (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error src-block-uuid))
+          target-block (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error target-block-uuid))]
       (editor-dnd-handler/move-blocks nil [src-block] target-block move-to) nil)))
       (editor-dnd-handler/move-blocks nil [src-block] target-block move-to) nil)))
 
 
 (def ^:export get_block
 (def ^:export get_block
   (fn [id-or-uuid ^js opts]
   (fn [id-or-uuid ^js opts]
     (when-let [block (cond
     (when-let [block (cond
                        (number? id-or-uuid) (db-utils/pull id-or-uuid)
                        (number? id-or-uuid) (db-utils/pull id-or-uuid)
-                       (string? id-or-uuid) (db-model/query-block-by-uuid (uuid-or-throw-error id-or-uuid)))]
+                       (string? id-or-uuid) (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error id-or-uuid)))]
       (when-not (contains? block :block/name)
       (when-not (contains? block :block/name)
         (when-let [uuid (:block/uuid block)]
         (when-let [uuid (:block/uuid block)]
           (let [{:keys [includeChildren]} (bean/->clj opts)
           (let [{:keys [includeChildren]} (bean/->clj opts)
@@ -703,12 +685,12 @@
                 block (if includeChildren
                 block (if includeChildren
                         ;; nested children results
                         ;; nested children results
                         (first (outliner-tree/blocks->vec-tree
                         (first (outliner-tree/blocks->vec-tree
-                                (db-model/get-block-and-children repo uuid) uuid))
+                                 (db-model/get-block-and-children repo uuid) uuid))
                         ;; attached shallow children
                         ;; attached shallow children
                         (assoc block :block/children
                         (assoc block :block/children
-                               (map #(list :uuid (get-in % [:data :block/uuid]))
-                                    (db/get-block-immediate-children repo uuid))))]
-            (bean/->js (normalize-keyword-for-json block))))))))
+                                     (map #(list :uuid (get-in % [:data :block/uuid]))
+                                          (db/get-block-immediate-children repo uuid))))]
+            (bean/->js (sdk-utils/normalize-keyword-for-json block))))))))
 
 
 (def ^:export get_current_block
 (def ^:export get_current_block
   (fn [^js opts]
   (fn [^js opts]
@@ -722,20 +704,20 @@
 
 
 (def ^:export get_previous_sibling_block
 (def ^:export get_previous_sibling_block
   (fn [block-uuid]
   (fn [block-uuid]
-    (when-let [block (db-model/query-block-by-uuid (uuid-or-throw-error block-uuid))]
+    (when-let [block (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error block-uuid))]
       (let [{:block/keys [parent left]} block
       (let [{:block/keys [parent left]} block
             block (when-not (= parent left) (db-utils/pull (:db/id left)))]
             block (when-not (= parent left) (db-utils/pull (:db/id left)))]
-        (and block (bean/->js (normalize-keyword-for-json block)))))))
+        (and block (bean/->js (sdk-utils/normalize-keyword-for-json block)))))))
 
 
 (def ^:export get_next_sibling_block
 (def ^:export get_next_sibling_block
   (fn [block-uuid]
   (fn [block-uuid]
-    (when-let [block (db-model/query-block-by-uuid (uuid-or-throw-error block-uuid))]
+    (when-let [block (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error block-uuid))]
       (when-let [right-siblings (outliner/get-right-siblings (outliner/->Block block))]
       (when-let [right-siblings (outliner/get-right-siblings (outliner/->Block block))]
-        (bean/->js (normalize-keyword-for-json (:data (first right-siblings))))))))
+        (bean/->js (sdk-utils/normalize-keyword-for-json (:data (first right-siblings))))))))
 
 
 (def ^:export set_block_collapsed
 (def ^:export set_block_collapsed
   (fn [block-uuid ^js opts]
   (fn [block-uuid ^js opts]
-    (let [block-uuid (uuid-or-throw-error block-uuid)]
+    (let [block-uuid (sdk-utils/uuid-or-throw-error block-uuid)]
       (when-let [block (db-model/get-block-by-uuid block-uuid)]
       (when-let [block (db-model/get-block-by-uuid block-uuid)]
         (let [opts (bean/->clj opts)
         (let [opts (bean/->clj opts)
               opts (if (or (string? opts) (boolean? opts)) {:flag opts} opts)
               opts (if (or (string? opts) (boolean? opts)) {:flag opts} opts)
@@ -749,21 +731,21 @@
 
 
 (def ^:export upsert_block_property
 (def ^:export upsert_block_property
   (fn [block-uuid key value]
   (fn [block-uuid key value]
-    (editor-handler/set-block-property! (uuid-or-throw-error block-uuid) key value)))
+    (editor-handler/set-block-property! (sdk-utils/uuid-or-throw-error block-uuid) key value)))
 
 
 (def ^:export remove_block_property
 (def ^:export remove_block_property
   (fn [block-uuid key]
   (fn [block-uuid key]
-    (editor-handler/remove-block-property! (uuid-or-throw-error block-uuid) key)))
+    (editor-handler/remove-block-property! (sdk-utils/uuid-or-throw-error block-uuid) key)))
 
 
 (def ^:export get_block_property
 (def ^:export get_block_property
   (fn [block-uuid key]
   (fn [block-uuid key]
-    (when-let [block (db-model/query-block-by-uuid (uuid-or-throw-error block-uuid))]
+    (when-let [block (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error block-uuid))]
       (get (:block/properties block) (keyword key)))))
       (get (:block/properties block) (keyword key)))))
 
 
 (def ^:export get_block_properties
 (def ^:export get_block_properties
   (fn [block-uuid]
   (fn [block-uuid]
-    (when-let [block (db-model/query-block-by-uuid (uuid-or-throw-error block-uuid))]
-      (bean/->js (normalize-keyword-for-json (:block/properties block))))))
+    (when-let [block (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error block-uuid))]
+      (bean/->js (sdk-utils/normalize-keyword-for-json (:block/properties block))))))
 
 
 (def ^:export get_current_page_blocks_tree
 (def ^:export get_current_page_blocks_tree
   (fn []
   (fn []
@@ -771,15 +753,15 @@
       (let [blocks (db-model/get-page-blocks-no-cache page)
       (let [blocks (db-model/get-page-blocks-no-cache page)
             blocks (outliner-tree/blocks->vec-tree blocks page)
             blocks (outliner-tree/blocks->vec-tree blocks page)
             ;; clean key
             ;; clean key
-            blocks (normalize-keyword-for-json blocks)]
+            blocks (sdk-utils/normalize-keyword-for-json blocks)]
         (bean/->js blocks)))))
         (bean/->js blocks)))))
 
 
 (def ^:export get_page_blocks_tree
 (def ^:export get_page_blocks_tree
-  (fn [page-name]
-    (when-let [_ (db-model/get-page page-name)]
+  (fn [id-or-page-name]
+    (when-let [page-name (:block/name (db-model/get-page id-or-page-name))]
       (let [blocks (db-model/get-page-blocks-no-cache page-name)
       (let [blocks (db-model/get-page-blocks-no-cache page-name)
             blocks (outliner-tree/blocks->vec-tree blocks page-name)
             blocks (outliner-tree/blocks->vec-tree blocks page-name)
-            blocks (normalize-keyword-for-json blocks)]
+            blocks (sdk-utils/normalize-keyword-for-json blocks)]
         (bean/->js blocks)))))
         (bean/->js blocks)))))
 
 
 (defn ^:export get_page_linked_references
 (defn ^:export get_page_linked_references
@@ -790,19 +772,19 @@
                        (db-model/get-page-referenced-blocks-full page-name)
                        (db-model/get-page-referenced-blocks-full page-name)
                        (db-model/get-block-referenced-blocks (:block/uuid page)))
                        (db-model/get-block-referenced-blocks (:block/uuid page)))
           ref-blocks (and (seq ref-blocks) (into [] ref-blocks))]
           ref-blocks (and (seq ref-blocks) (into [] ref-blocks))]
-      (bean/->js (normalize-keyword-for-json ref-blocks)))))
+      (bean/->js (sdk-utils/normalize-keyword-for-json ref-blocks)))))
 
 
 (defn ^:export get_pages_from_namespace
 (defn ^:export get_pages_from_namespace
   [ns]
   [ns]
   (when-let [repo (and ns (state/get-current-repo))]
   (when-let [repo (and ns (state/get-current-repo))]
     (when-let [pages (db-model/get-namespace-pages repo ns)]
     (when-let [pages (db-model/get-namespace-pages repo ns)]
-      (bean/->js (normalize-keyword-for-json pages)))))
+      (bean/->js (sdk-utils/normalize-keyword-for-json pages)))))
 
 
 (defn ^:export get_pages_tree_from_namespace
 (defn ^:export get_pages_tree_from_namespace
   [ns]
   [ns]
   (when-let [repo (and ns (state/get-current-repo))]
   (when-let [repo (and ns (state/get-current-repo))]
     (when-let [pages (db-model/get-namespace-hierarchy repo ns)]
     (when-let [pages (db-model/get-namespace-hierarchy repo ns)]
-      (bean/->js (normalize-keyword-for-json pages)))))
+      (bean/->js (sdk-utils/normalize-keyword-for-json pages)))))
 
 
 (defn first-child-of-block
 (defn first-child-of-block
   [block]
   [block]
@@ -868,7 +850,7 @@
   [query-string]
   [query-string]
   (when-let [repo (state/get-current-repo)]
   (when-let [repo (state/get-current-repo)]
     (when-let [result (query-dsl/query repo query-string)]
     (when-let [result (query-dsl/query repo query-string)]
-      (bean/->js (normalize-keyword-for-json (flatten @result))))))
+      (bean/->js (sdk-utils/normalize-keyword-for-json (flatten @result))))))
 
 
 (defn ^:export datascript_query
 (defn ^:export datascript_query
   [query & inputs]
   [query & inputs]
@@ -886,13 +868,13 @@
                                     :else %)
                                     :else %)
                                  inputs)
                                  inputs)
             result          (apply d/q query db resolved-inputs)]
             result          (apply d/q query db resolved-inputs)]
-        (bean/->js (normalize-keyword-for-json result false))))))
+        (bean/->js (sdk-utils/normalize-keyword-for-json result false))))))
 
 
 (defn ^:export custom_query
 (defn ^:export custom_query
   [query-string]
   [query-string]
   (let [result (let [query (cljs.reader/read-string query-string)]
   (let [result (let [query (cljs.reader/read-string query-string)]
                  (db/custom-query {:query query}))]
                  (db/custom-query {:query query}))]
-    (bean/->js (normalize-keyword-for-json (flatten @result)))))
+    (bean/->js (sdk-utils/normalize-keyword-for-json (flatten @result)))))
 
 
 (defn ^:export download_graph_db
 (defn ^:export download_graph_db
   []
   []
@@ -916,54 +898,19 @@
     (shell/run-git-command! args)))
     (shell/run-git-command! args)))
 
 
 ;; git
 ;; git
-(defn ^:export git_exec_command
-  [^js args]
-  (when-let [args (and args (seq (bean/->clj args)))]
-    (shell/run-git-command2! args)))
-
-(defn ^:export git_load_ignore_file
-  []
-  (when-let [repo (state/get-current-repo)]
-    (p/let [file    ".gitignore"
-            dir     (config/get-repo-dir repo)
-            _       (fs/create-if-not-exists repo dir file)
-            content (fs/read-file dir file)]
-      content)))
-
-(defn ^:export git_save_ignore_file
-  [content]
-  (when-let [repo (and (string? content) (state/get-current-repo))]
-    (p/let [file ".gitignore"
-            dir  (config/get-repo-dir repo)
-            _    (fs/write-file! repo dir file content {:skip-compare? true})])))
+(def ^:export git_exec_command sdk-git/exec_command)
+(def ^:export git_load_ignore_file sdk-git/load_ignore_file)
+(def ^:export git_save_ignore_file sdk-git/save_ignore_file)
 
 
 ;; ui
 ;; ui
-(defn ^:export show_msg
-  ([content] (show_msg content :success nil))
-  ([content status] (show_msg content status nil))
-  ([content status ^js opts]
-   (let [{:keys [key timeout]} (bean/->clj opts)
-         hiccup? (and (string? content) (string/starts-with? (string/triml content) "[:"))
-         content (if hiccup? (parse-hiccup-ui content) content)
-         uid     (when (string? key) (keyword key))
-         clear?  (not= timeout 0)
-         key'    (notification/show! content (keyword status) clear? uid timeout)]
-     (name key'))))
-
-(defn ^:export ui_show_msg
-  [& args]
-  (apply show_msg args))
-
-(defn ^:export ui_close_msg
-  [key]
-  (when (string? key)
-    (notification/clear! (keyword key)) nil))
+(def ^:export show_msg sdk-ui/-show_msg)
+(def ^:export ui_show_msg sdk-ui/show_msg)
+(def ^:export ui_close_msg sdk-ui/close_msg)
 
 
 ;; assets
 ;; assets
-(defn ^:export assets_list_files_of_current_graph
-  [^js exts]
-  (p/let [files (ipc/ipc :getAssetsFiles {:exts exts})]
-    (bean/->js files)))
+(def ^:export assets_list_files_of_current_graph sdk-assets/list_files_of_current_graph)
+(def ^:export assets_make_url sdk-assets/make_url)
+(def ^:export make_asset_url sdk-assets/make_url)
 
 
 ;; experiments
 ;; experiments
 (defn ^:export exper_load_scripts
 (defn ^:export exper_load_scripts
@@ -974,25 +921,25 @@
                   init?      (plugin-handler/register-plugin-resources pid :scripts {:key s :src s})]]
                   init?      (plugin-handler/register-plugin-resources pid :scripts {:key s :src s})]]
       (when init?
       (when init?
         (p/catch
         (p/catch
-         (p/then
-          (do
-            (upt-status :pending)
-            (loader/load s nil {:attributes {:data-ref (name pid)}}))
-          #(upt-status :done))
-         #(upt-status :error))))))
+          (p/then
+            (do
+              (upt-status :pending)
+              (loader/load s nil {:attributes {:data-ref (name pid)}}))
+            #(upt-status :done))
+          #(upt-status :error))))))
 
 
 (defn ^:export exper_register_fenced_code_renderer
 (defn ^:export exper_register_fenced_code_renderer
   [pid type ^js opts]
   [pid type ^js opts]
   (when-let [^js _pl (plugin-handler/get-plugin-inst pid)]
   (when-let [^js _pl (plugin-handler/get-plugin-inst pid)]
     (plugin-handler/register-fenced-code-renderer
     (plugin-handler/register-fenced-code-renderer
-     (keyword pid) type (reduce #(assoc %1 %2 (aget opts (name %2))) {}
-                                [:edit :before :subs :render]))))
+      (keyword pid) type (reduce #(assoc %1 %2 (aget opts (name %2))) {}
+                                 [:edit :before :subs :render]))))
 
 
 (defn ^:export exper_register_extensions_enhancer
 (defn ^:export exper_register_extensions_enhancer
   [pid type enhancer]
   [pid type enhancer]
   (when-let [^js _pl (and (fn? enhancer) (plugin-handler/get-plugin-inst pid))]
   (when-let [^js _pl (and (fn? enhancer) (plugin-handler/get-plugin-inst pid))]
     (plugin-handler/register-extensions-enhancer
     (plugin-handler/register-extensions-enhancer
-     (keyword pid) type {:enhancer enhancer})))
+      (keyword pid) type {:enhancer enhancer})))
 
 
 (defonce *request-k (volatile! 0))
 (defonce *request-k (volatile! 0))
 
 
@@ -1010,10 +957,45 @@
   [req-id]
   [req-id]
   (ipc/ipc :httpRequestAbort req-id))
   (ipc/ipc :httpRequestAbort req-id))
 
 
+;; templates
+(defn ^:export get_template
+  [name]
+  (some-> name
+          (db-model/get-template-by-name)
+          (sdk-utils/normalize-keyword-for-json)
+          (bean/->js)))
+
+(defn ^:export insert_template
+  [target-uuid template-name]
+  (when-let [target (and (page-handler/template-exists? template-name)
+                         (db-model/get-block-by-uuid target-uuid))]
+    (editor-handler/insert-template! nil template-name {:target target}) nil))
+
+(defn ^:export exist_template
+  [name]
+  (page-handler/template-exists? name))
+
+(defn ^:export create_template
+  [target-uuid template-name ^js opts]
+  (when (and template-name (db-model/get-block-by-uuid target-uuid))
+    (let [{:keys [overwrite]} (bean/->clj opts)
+          exist? (page-handler/template-exists? template-name)]
+      (if (or (not exist?) (true? overwrite))
+        (do (when-let [old-target (and exist? (db-model/get-template-by-name template-name))]
+              (editor-handler/remove-block-property! (:block/uuid old-target) :template))
+            (editor-handler/set-block-property! target-uuid :template template-name))
+        (throw (js/Error. "Template already exists!"))))))
+
+(defn ^:export remove_template
+  [name]
+  (when-let [target (db-model/get-template-by-name name)]
+    (editor-handler/remove-block-property! (:block/uuid target) :template)))
+
 ;; search
 ;; search
 (defn ^:export search
 (defn ^:export search
   [q]
   [q]
-  (search-handler/search q))
+  (-> (search-handler/search q)
+      (p/then #(bean/->js %))))
 
 
 ;; helpers
 ;; helpers
 (defn ^:export query_element_by_id
 (defn ^:export query_element_by_id
@@ -1037,13 +1019,4 @@
   (p/let [_ (el/persist-dbs!)]
   (p/let [_ (el/persist-dbs!)]
     true))
     true))
 
 
-(def ^:export make_asset_url editor-handler/make-asset-url)
-
 (def ^:export set_blocks_id #(editor-handler/set-blocks-id! (map uuid %)))
 (def ^:export set_blocks_id #(editor-handler/set-blocks-id! (map uuid %)))
-
-(defn ^:export __debug_state
-  [path]
-  (-> (if (string? path)
-        (get @state/state (keyword path))
-        @state/state)
-      (bean/->js)))

+ 12 - 0
src/main/logseq/sdk/assets.cljs

@@ -0,0 +1,12 @@
+(ns logseq.sdk.assets
+  (:require [electron.ipc :as ipc]
+            [cljs-bean.core :as bean]
+            [promesa.core :as p]
+            [frontend.handler.editor :as editor-handler]))
+
+(def ^:export make_url editor-handler/make-asset-url)
+
+(defn ^:export list_files_of_current_graph
+  [^js exts]
+  (p/let [files (ipc/ipc :getAssetsFiles {:exts exts})]
+    (bean/->js files)))

+ 4 - 0
src/main/logseq/sdk/core.cljs

@@ -0,0 +1,4 @@
+(ns logseq.sdk.core
+  (:require [logseq.sdk.debug]))
+
+(def ^:export version "20230330")

+ 10 - 0
src/main/logseq/sdk/debug.cljs

@@ -0,0 +1,10 @@
+(ns ^:no-doc logseq.sdk.debug
+  (:require [frontend.state :as state]
+            [cljs-bean.core :as bean]))
+
+(defn ^:export log_app_state
+  [path]
+  (-> (if (string? path)
+        (get @state/state (keyword path))
+        @state/state)
+      (bean/->js)))

+ 28 - 0
src/main/logseq/sdk/git.cljs

@@ -0,0 +1,28 @@
+(ns logseq.sdk.git
+  (:require [cljs-bean.core :as bean]
+            [promesa.core :as p]
+            [frontend.state :as state]
+            [frontend.handler.shell :as shell]
+            [frontend.config :as config]
+            [frontend.fs :as fs]))
+
+(defn ^:export exec_command
+  [^js args]
+  (when-let [args (and args (seq (bean/->clj args)))]
+    (shell/run-git-command2! args)))
+
+(defn ^:export load_ignore_file
+  []
+  (when-let [repo (state/get-current-repo)]
+    (p/let [file ".gitignore"
+            dir (config/get-repo-dir repo)
+            _ (fs/create-if-not-exists repo dir file)
+            content (fs/read-file dir file)]
+           content)))
+
+(defn ^:export save_ignore_file
+  [content]
+  (when-let [repo (and (string? content) (state/get-current-repo))]
+    (p/let [file ".gitignore"
+            dir (config/get-repo-dir repo)
+            _ (fs/write-file! repo dir file content {:skip-compare? true})])))

+ 34 - 0
src/main/logseq/sdk/ui.cljs

@@ -0,0 +1,34 @@
+(ns logseq.sdk.ui
+  (:require [frontend.handler.notification :as notification]
+            [cljs-bean.core :as bean]
+            [sci.core :as sci]
+            [clojure.string :as string]))
+
+(defn- parse-hiccup-ui
+  [input]
+  (when (string? input)
+    (try
+      (sci/eval-string input {:preset :termination-safe})
+      (catch :default e
+        (js/console.error "[parse hiccup error]" e) input))))
+
+(defn -show_msg
+  ([content] (-show_msg content :success nil))
+  ([content status] (-show_msg content status nil))
+  ([content status ^js opts]
+   (let [{:keys [key timeout]} (bean/->clj opts)
+         hiccup? (and (string? content) (string/starts-with? (string/triml content) "[:"))
+         content (if hiccup? (parse-hiccup-ui content) content)
+         uid     (when (string? key) (keyword key))
+         clear?  (not= timeout 0)
+         key'    (notification/show! content (keyword status) clear? uid timeout nil)]
+     (name key'))))
+
+(defn ^:export show_msg
+  [& args]
+  (apply -show_msg args))
+
+(defn ^:export close_msg
+  [key]
+  (when (string? key)
+    (notification/clear! (keyword key)) nil))

+ 31 - 0
src/main/logseq/sdk/utils.cljs

@@ -0,0 +1,31 @@
+(ns logseq.sdk.utils
+  (:require [clojure.walk :as walk]
+            [camel-snake-kebab.core :as csk]
+            [frontend.util :as util]))
+
+(defn normalize-keyword-for-json
+  ([input] (normalize-keyword-for-json input true))
+  ([input camel-case?]
+   (when input
+     (walk/postwalk
+       (fn [a]
+         (cond
+           (keyword? a)
+           (cond-> (name a)
+                   camel-case?
+                   (csk/->camelCase))
+
+           (uuid? a) (str a)
+           :else a)) input))))
+
+(defn uuid-or-throw-error
+  [s]
+  (cond
+    (uuid? s)
+    s
+
+    (util/uuid-string? s)
+    (uuid s)
+
+    :else
+    (throw (js/Error. (str s " is not a valid UUID string.")))))

Some files were not shown because too many files changed in this diff