浏览代码

refactor: separate api into multiple namespaces

Tienson Qin 2 周之前
父节点
当前提交
15de4a8b2d

文件差异内容过多而无法显示
+ 115 - 982
src/main/logseq/api.cljs


+ 182 - 0
src/main/logseq/api/app.cljs

@@ -0,0 +1,182 @@
+(ns logseq.api.app
+  "app state/ui related apis"
+  (:require [cljs-bean.core :as bean]
+            [cljs.reader]
+            [clojure.string :as string]
+            [electron.ipc :as ipc]
+            [frontend.config :as config]
+            [frontend.db.utils :as db-utils]
+            [frontend.handler.command-palette :as palette-handler]
+            [frontend.handler.config :as config-handler]
+            [frontend.handler.plugin :as plugin-handler]
+            [frontend.handler.recent :as recent-handler]
+            [frontend.handler.route :as route-handler]
+            [frontend.modules.layout.core]
+            [frontend.state :as state]
+            [frontend.util :as util]
+            [frontend.version :as fv]
+            [logseq.api.db-based :as db-based-api]
+            [logseq.sdk.core]
+            [logseq.sdk.experiments]
+            [logseq.sdk.git]
+            [logseq.sdk.utils :as sdk-utils]
+            [reitit.frontend.easy :as rfe]))
+
+(defn get_state_from_store
+  [^js path]
+  (when-let [path (if (string? path) [path] (bean/->clj path))]
+    (some->> path
+             (map #(if (string/starts-with? % "@")
+                     (subs % 1)
+                     (keyword %)))
+             (get-in @state/state)
+             (#(if (util/atom? %) @% %))
+             (sdk-utils/normalize-keyword-for-json)
+             (bean/->js))))
+
+(defn set_state_from_store
+  [^js path ^js value]
+  (when-let [path (if (string? path) [path] (bean/->clj path))]
+    (some->> path
+             (map #(if (string/starts-with? % "@")
+                     (subs % 1)
+                     (keyword %)))
+             (into [])
+             (#(state/set-state! % (bean/->clj value))))))
+
+(defn get_app_info
+  ;; get app base info
+  []
+  (-> (sdk-utils/normalize-keyword-for-json
+       {:version fv/version
+        :supportDb true})
+      (bean/->js)))
+
+(def get_user_configs
+  (fn []
+    (bean/->js
+     (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 get_current_graph_configs
+  (fn [& keys]
+    (some-> (state/get-config)
+            (#(if (seq keys) (get-in % (map keyword keys)) %))
+            (bean/->js))))
+
+(def 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 get_current_graph_favorites
+  (fn []
+    (if (config/db-based-graph?)
+      (db-based-api/get-favorites)
+      (some->> (:favorites (state/get-config))
+               (remove string/blank?)
+               (filter string?)
+               (bean/->js)))))
+
+(def get_current_graph_recent
+  (fn []
+    (some->> (recent-handler/get-recent-pages)
+             (map #(db-utils/entity (:db/id %)))
+             (remove nil?)
+             (sdk-utils/normalize-keyword-for-json)
+             (bean/->js))))
+
+(def get_current_graph
+  (fn []
+    (when-let [repo (state/get-current-repo)]
+      (when-not (= config/demo-repo repo)
+        (bean/->js {:url  repo
+                    :name (util/node-path.basename repo)
+                    :path (config/get-repo-dir repo)})))))
+
+(def show_themes
+  (fn []
+    (state/pub-event! [:modal/show-themes-modal])))
+
+(def set_theme_mode
+  (fn [mode]
+    (state/set-theme-mode! mode)))
+
+(def relaunch
+  (fn []
+    (ipc/ipc "relaunchApp")))
+
+(def quit
+  (fn []
+    (ipc/ipc "quitApp")))
+
+(def open_external_link
+  (fn [url]
+    (when (re-find #"https?://" url)
+      (js/apis.openExternal url))))
+
+(def invoke_external_command
+  (fn [type & args]
+    (when-let [id (and (string/starts-with? type "logseq.")
+                       (-> (string/replace type #"^logseq." "")
+                           (util/safe-lower-case)
+                           (keyword)))]
+      (when-let [action (get-in (palette-handler/get-commands-unique) [id :action])]
+        (apply plugin-handler/hook-lifecycle-fn! id action args)))))
+
+;; flag - boolean | 'toggle'
+(def set_left_sidebar_visible
+  (fn [flag]
+    (if (= flag "toggle")
+      (state/toggle-left-sidebar!)
+      (state/set-state! :ui/left-sidebar-open? (boolean flag)))
+    nil))
+
+;; flag - boolean | 'toggle'
+(def set_right_sidebar_visible
+  (fn [flag]
+    (if (= flag "toggle")
+      (state/toggle-sidebar-open?!)
+      (state/set-state! :ui/sidebar-open? (boolean flag)))
+    nil))
+
+(def clear_right_sidebar_blocks
+  (fn [^js opts]
+    (state/clear-sidebar-blocks!)
+    (when-let [opts (and opts (bean/->clj opts))]
+      (and (:close opts) (state/hide-right-sidebar!)))
+    nil))
+
+(def push_state
+  (fn [^js k ^js params ^js query]
+    (let [k (keyword k)
+          page? (= k :page)
+          params (bean/->clj params)
+          query (bean/->clj query)]
+      (if page?
+        (-> (:name params)
+            (route-handler/redirect-to-page! {:anchor (:anchor query) :push true}))
+        (rfe/push-state k params query)))))
+
+(def replace_state
+  (fn [^js k ^js params ^js query]
+    (let [k (keyword k)
+          page? (= k :page)
+          params (bean/->clj params)
+          query (bean/->clj query)]
+      (if-let [page-name (and page? (:name params))]
+        (route-handler/redirect-to-page! page-name {:anchor (:anchor query) :push false})
+        (rfe/replace-state k params query)))))

+ 39 - 112
src/main/logseq/api/db.cljs

@@ -1,124 +1,51 @@
 (ns logseq.api.db
-  "DB version related fns"
+  "DB APIs"
   (:require [cljs-bean.core :as bean]
             [cljs.reader]
-            [clojure.walk :as walk]
-            [datascript.core :as d]
             [frontend.db :as db]
-            [frontend.db.model :as db-model]
-            [frontend.handler.common.page :as page-common-handler]
-            [frontend.handler.editor :as editor-handler]
-            [frontend.handler.page :as page-handler]
+            [frontend.db.async :as db-async]
+            [frontend.db.query-custom :as query-custom]
+            [frontend.db.query-dsl :as query-dsl]
+            [frontend.db.query-react :as query-react]
             [frontend.modules.layout.core]
             [frontend.state :as state]
-            [frontend.util :as util]
-            [logseq.api.block :as api-block]
-            [logseq.db :as ldb]
-            [logseq.outliner.core :as outliner-core]
             [logseq.sdk.core]
             [logseq.sdk.experiments]
             [logseq.sdk.git]
             [logseq.sdk.utils :as sdk-utils]
             [promesa.core :as p]))
 
-(defn result->js
-  [result]
-  (-> result
-      sdk-utils/normalize-keyword-for-json
-      bean/->js))
-
-(defn get-favorites
-  []
-  (p/let [favorites (page-handler/get-favorites)]
-    (result->js favorites)))
-
-(defn insert-batch-blocks
-  [this target blocks opts]
-  (let [blocks' (walk/prewalk
-                 (fn [f]
-                   (if (and (map? f) (:content f) (nil? (:uuid f)))
-                     (assoc f :uuid (d/squuid))
-                     f))
-                 blocks)
-        {:keys [sibling before schema]} opts
-        block (if before
-                (db/pull (:db/id (ldb/get-left-sibling (db/entity (:db/id target))))) target)
-        sibling? (if (ldb/page? block) false sibling)
-        uuid->properties (let [blocks (outliner-core/tree-vec-flatten blocks' :children)]
-                           (when (some (fn [b] (seq (:properties b))) blocks)
-                             (zipmap (map :uuid blocks)
-                                     (map :properties blocks))))]
-    (p/let [result (editor-handler/insert-block-tree-after-target
-                    (:db/id block) sibling? blocks' (get block :block/format :markdown) true)
-            blocks (:blocks result)]
-      (when (seq blocks)
-        (p/doseq [block blocks]
-          (let [id (:block/uuid block)
-                b (db/entity [:block/uuid id])
-                properties (when uuid->properties (uuid->properties id))]
-            (when (seq properties)
-              (api-block/db-based-save-block-properties! b properties {:plugin this
-                                                                       :schema schema})))))
-      (let [blocks' (map (fn [b] (db/entity [:block/uuid (:block/uuid b)])) blocks)]
-        (result->js blocks')))))
-
-(defn insert-block
-  [this content properties schema opts]
-  (p/let [new-block (editor-handler/api-insert-new-block! content opts)]
-    (when (seq properties)
-      (api-block/db-based-save-block-properties! new-block properties {:plugin this
-                                                                       :schema schema}))
-    (let [block (db/entity [:block/uuid (:block/uuid new-block)])]
-      (result->js block))))
-
-(defn update-block
-  [this block content opts]
-  (when block
-    (let [repo (state/get-current-repo)
-          block-uuid (:block/uuid block)]
-      (p/do!
-       (when (seq (:properties opts))
-         (api-block/db-based-save-block-properties! block (:properties opts)
-                                                    {:plugin this
-                                                     :schema (:schema opts)}))
-       (editor-handler/save-block! repo
-                                   (sdk-utils/uuid-or-throw-error block-uuid) content
-                                   (dissoc opts :properties))))))
-
-(defn remove-property
-  [property]
-  (when-let [uuid (and (api-block/plugin-property-key? (:db/ident property))
-                       (:block/uuid property))]
-    (page-common-handler/<delete! uuid nil nil)))
-
-(defn upsert-block-property
-  [this block key' value schema]
-  (let [opts {:plugin this
-              :schema (when schema
-                        {key schema})}]
-    (api-block/db-based-save-block-properties! block {key' value} opts)))
-
-(defn get-all-tags
-  []
-  (-> (db-model/get-all-classes (state/get-current-repo)
-                                {:except-root-class? true})
-      result->js))
-
-(defn get-all-properties
-  []
-  (-> (ldb/get-all-properties (db/get-db))
-      result->js))
-
-(defn get-tag-objects
-  [class-uuid-or-ident]
-  (let [eid (if (util/uuid-string? class-uuid-or-ident)
-              (when-let [id (sdk-utils/uuid-or-throw-error class-uuid-or-ident)]
-                [:block/uuid id])
-              (keyword (api-block/sanitize-user-property-name class-uuid-or-ident)))
-        class (db/entity eid)]
-    (if-not class
-      (throw (ex-info (str "Tag not exists with id: " eid) {}))
-      (p/let [result (state/<invoke-db-worker :thread-api/get-class-objects
-                                              (state/get-current-repo)
-                                              (:db/id class))]
-        (result->js result)))))
+(defn q
+  [query-string]
+  (when-let [repo (state/get-current-repo)]
+    (p/let [result (query-dsl/query repo query-string
+                                    {:disable-reactive? true
+                                     :return-promise? true})]
+      (bean/->js (sdk-utils/normalize-keyword-for-json (flatten result))))))
+
+(defn datascript_query
+  [query & inputs]
+  (when-let [repo (state/get-current-repo)]
+    (when-let [db (db/get-db repo)]
+      (p/let [query           (cljs.reader/read-string query)
+              resolved-inputs (map #(cond
+                                      (string? %)
+                                      (some->> % (cljs.reader/read-string) (query-react/resolve-input db))
+
+                                      (fn? %)
+                                      (fn [& args]
+                                        (.apply % nil (clj->js (mapv bean/->js args))))
+
+                                      :else %)
+                                   inputs)
+              result          (apply db-async/<q repo {:transact-db? false}
+                                     (cons query resolved-inputs))]
+        (bean/->js (sdk-utils/normalize-keyword-for-json result false))))))
+
+(defn custom_query
+  [query-string]
+  (p/let [result (let [query (cljs.reader/read-string query-string)]
+                   (query-custom/custom-query {:query query
+                                               :disable-reactive? true
+                                               :return-promise? true}))]
+    (bean/->js (sdk-utils/normalize-keyword-for-json (flatten result)))))

+ 169 - 0
src/main/logseq/api/db_based.cljs

@@ -0,0 +1,169 @@
+(ns logseq.api.db-based
+  "DB version related fns"
+  (:require [cljs-bean.core :as bean]
+            [cljs.reader]
+            [clojure.string :as string]
+            [clojure.walk :as walk]
+            [datascript.core :as d]
+            [frontend.db :as db]
+            [frontend.db.model :as db-model]
+            [frontend.handler.common.page :as page-common-handler]
+            [frontend.handler.db-based.property :as db-property-handler]
+            [frontend.handler.editor :as editor-handler]
+            [frontend.handler.page :as page-handler]
+            [frontend.modules.layout.core]
+            [frontend.state :as state]
+            [frontend.util :as util]
+            [logseq.api.block :as api-block]
+            [logseq.db :as ldb]
+            [logseq.outliner.core :as outliner-core]
+            [logseq.sdk.core]
+            [logseq.sdk.experiments]
+            [logseq.sdk.git]
+            [logseq.sdk.utils :as sdk-utils]
+            [promesa.core :as p]))
+
+(defn -get-property
+  [^js plugin k]
+  (when-let [k' (and (string? k) (api-block/sanitize-user-property-name k))]
+    (let [property-ident (api-block/get-db-ident-from-property-name k' plugin)]
+      (db/entity property-ident))))
+
+(defn get-favorites
+  []
+  (p/let [favorites (page-handler/get-favorites)]
+    (sdk-utils/result->js favorites)))
+
+(defn insert-batch-blocks
+  [this target blocks opts]
+  (let [blocks' (walk/prewalk
+                 (fn [f]
+                   (if (and (map? f) (:content f) (nil? (:uuid f)))
+                     (assoc f :uuid (d/squuid))
+                     f))
+                 blocks)
+        {:keys [sibling before schema]} opts
+        block (if before
+                (db/pull (:db/id (ldb/get-left-sibling (db/entity (:db/id target))))) target)
+        sibling? (if (ldb/page? block) false sibling)
+        uuid->properties (let [blocks (outliner-core/tree-vec-flatten blocks' :children)]
+                           (when (some (fn [b] (seq (:properties b))) blocks)
+                             (zipmap (map :uuid blocks)
+                                     (map :properties blocks))))]
+    (p/let [result (editor-handler/insert-block-tree-after-target
+                    (:db/id block) sibling? blocks' (get block :block/format :markdown) true)
+            blocks (:blocks result)]
+      (when (seq blocks)
+        (p/doseq [block blocks]
+          (let [id (:block/uuid block)
+                b (db/entity [:block/uuid id])
+                properties (when uuid->properties (uuid->properties id))]
+            (when (seq properties)
+              (api-block/db-based-save-block-properties! b properties {:plugin this
+                                                                       :schema schema})))))
+      (let [blocks' (map (fn [b] (db/entity [:block/uuid (:block/uuid b)])) blocks)]
+        (sdk-utils/result->js blocks')))))
+
+(defn insert-block
+  [this content properties schema opts]
+  (p/let [new-block (editor-handler/api-insert-new-block! content opts)]
+    (when (seq properties)
+      (api-block/db-based-save-block-properties! new-block properties {:plugin this
+                                                                       :schema schema}))
+    (let [block (db/entity [:block/uuid (:block/uuid new-block)])]
+      (sdk-utils/result->js block))))
+
+(defn update-block
+  [this block content opts]
+  (when block
+    (let [repo (state/get-current-repo)
+          block-uuid (:block/uuid block)]
+      (p/do!
+       (when (seq (:properties opts))
+         (api-block/db-based-save-block-properties! block (:properties opts)
+                                                    {:plugin this
+                                                     :schema (:schema opts)}))
+       (editor-handler/save-block! repo
+                                   (sdk-utils/uuid-or-throw-error block-uuid) content
+                                   (dissoc opts :properties))))))
+
+(defn get-property
+  [k]
+  (this-as this
+           (p/let [prop (-get-property this k)]
+             (some-> prop
+                     (assoc :type (:logseq.property/type prop))
+                     (sdk-utils/normalize-keyword-for-json)
+                     (bean/->js)))))
+
+(defn upsert-property
+  "schema:
+    {:type :default | :number | :date | :datetime | :checkbox | :url | :node | :json | :string
+     :cardinality :many | :one
+     :hide? true
+     :view-context :page
+     :public? false}
+  "
+  [k ^js schema ^js opts]
+  (this-as
+   this
+   (when-not (string/blank? k)
+     (p/let [opts (or (some-> opts bean/->clj) {})
+             property-ident (api-block/get-db-ident-from-property-name k this)
+             _ (api-block/ensure-property-upsert-control this property-ident k)
+             schema (or (some-> schema (bean/->clj)
+                                (update-keys #(if (contains? #{:hide :public} %)
+                                                (keyword (str (name %) "?")) %))) {})
+             schema (cond-> schema
+                      (string? (:cardinality schema))
+                      (-> (assoc :db/cardinality (keyword (:cardinality schema)))
+                          (dissoc :cardinality))
+
+                      (string? (:type schema))
+                      (-> (assoc :logseq.property/type (keyword (:type schema)))
+                          (dissoc :type)))
+             p (db-property-handler/upsert-property! property-ident schema
+                                                     (assoc opts :property-name name))
+             p (db/entity (:db/id p))]
+       (sdk-utils/result->js p)))))
+
+(defn remove-property
+  [k]
+  (this-as
+   this
+   (p/let [property (-get-property this k)]
+     (when-let [uuid (and (api-block/plugin-property-key? (:db/ident property))
+                          (:block/uuid property))]
+       (page-common-handler/<delete! uuid nil nil)))))
+
+(defn upsert-block-property
+  [this block key' value schema]
+  (let [opts {:plugin this
+              :schema (when schema
+                        {key schema})}]
+    (api-block/db-based-save-block-properties! block {key' value} opts)))
+
+(defn get-all-tags
+  []
+  (-> (db-model/get-all-classes (state/get-current-repo)
+                                {:except-root-class? true})
+      sdk-utils/result->js))
+
+(defn get-all-properties
+  []
+  (-> (ldb/get-all-properties (db/get-db))
+      sdk-utils/result->js))
+
+(defn get-tag-objects
+  [class-uuid-or-ident]
+  (let [eid (if (util/uuid-string? class-uuid-or-ident)
+              (when-let [id (sdk-utils/uuid-or-throw-error class-uuid-or-ident)]
+                [:block/uuid id])
+              (keyword (api-block/sanitize-user-property-name class-uuid-or-ident)))
+        class (db/entity eid)]
+    (if-not class
+      (throw (ex-info (str "Tag not exists with id: " eid) {}))
+      (p/let [result (state/<invoke-db-worker :thread-api/get-class-objects
+                                              (state/get-current-repo)
+                                              (:db/id class))]
+        (sdk-utils/result->js result)))))

+ 528 - 0
src/main/logseq/api/editor.cljs

@@ -0,0 +1,528 @@
+(ns logseq.api.editor
+  "Editor related APIs"
+  (:require [cljs-bean.core :as bean]
+            [cljs.reader]
+            [clojure.string :as string]
+            [datascript.core :as d]
+            [frontend.commands :as commands]
+            [frontend.config :as config]
+            [frontend.date :as date]
+            [frontend.db :as db]
+            [frontend.db.async :as db-async]
+            [frontend.db.conn :as conn]
+            [frontend.db.model :as db-model]
+            [frontend.db.utils :as db-utils]
+            [frontend.handler.code :as code-handler]
+            [frontend.handler.dnd :as editor-dnd-handler]
+            [frontend.handler.editor :as editor-handler]
+            [frontend.handler.export :as export-handler]
+            [frontend.handler.page :as page-handler]
+            [frontend.handler.property :as property-handler]
+            [frontend.handler.shell :as shell]
+            [frontend.modules.layout.core]
+            [frontend.modules.outliner.tree :as outliner-tree]
+            [frontend.state :as state]
+            [frontend.util :as util]
+            [frontend.util.cursor :as cursor]
+            [goog.date :as gdate]
+            [goog.dom :as gdom]
+            [logseq.api.block :as api-block]
+            [logseq.api.db-based :as db-based-api]
+            [logseq.common.util :as common-util]
+            [logseq.common.util.date-time :as date-time-util]
+            [logseq.db :as ldb]
+            [logseq.outliner.core :as outliner-core]
+            [logseq.sdk.core]
+            [logseq.sdk.experiments]
+            [logseq.sdk.git]
+            [logseq.sdk.utils :as sdk-utils]
+            [promesa.core :as p]))
+
+(defn- <get-block
+  [id-or-name & opts]
+  (when id-or-name
+    (db-async/<get-block (state/get-current-repo) id-or-name opts)))
+
+(def save_focused_code_editor_content
+  (fn []
+    (code-handler/save-code-editor!)))
+
+(def check_editing
+  (fn []
+    (if (state/get-edit-input-id)
+      (str (:block/uuid (state/get-edit-block))) false)))
+
+(def exit_editing_mode
+  (fn [select?]
+    (editor-handler/escape-editing {:select? select?})
+    nil))
+
+(def insert_at_editing_cursor
+  (fn [content]
+    (when-let [input-id (state/get-edit-input-id)]
+      (commands/simple-insert! input-id content {})
+      (when-let [input (gdom/getElement input-id)]
+        (.focus input)))))
+
+(def restore_editing_cursor
+  (fn []
+    (when-let [input-id (state/get-edit-input-id)]
+      (when-let [input (gdom/getElement input-id)]
+        (when (util/el-visible-in-viewport? input)
+          (.focus input))))))
+
+(def get_editing_cursor_position
+  (fn []
+    (when-let [input-id (state/get-edit-input-id)]
+      (bean/->js (sdk-utils/normalize-keyword-for-json (cursor/get-caret-pos (gdom/getElement input-id)))))))
+
+(def get_editing_block_content
+  (fn []
+    (state/get-edit-content)))
+
+(def get_selected_blocks
+  (fn []
+    (when-let [blocks (state/selection?)]
+      (let [blocks (->> blocks
+                        (map (fn [^js el] (some->
+                                           (.getAttribute el "blockid")
+                                           (db-model/get-block-by-uuid)))))]
+        (sdk-utils/result->js blocks)))))
+
+(def clear_selected_blocks
+  (fn []
+    (state/clear-selection!)))
+
+(def get_current_page
+  (fn []
+    (when-let [page (state/get-current-page)]
+      (p/let [page (<get-block page {:children? false})]
+        (when page
+          (sdk-utils/result->js page))))))
+
+(defn get_page
+  [id-or-page-name]
+  (p/let [page (<get-block id-or-page-name {:children? false})]
+    (when page
+      (sdk-utils/result->js page))))
+
+;; FIXME: this doesn't work because the ui doesn't have all pages
+(defn get_all_pages
+  []
+  (let [db (conn/get-db (state/get-current-repo))]
+    (some->
+     (->>
+      (d/datoms db :avet :block/name)
+      (map #(db-utils/pull (:e %)))
+      (remove ldb/hidden?)
+      (remove (fn [page]
+                (common-util/uuid-string? (:block/name page)))))
+     (sdk-utils/normalize-keyword-for-json)
+     (bean/->js))))
+
+(defn create_page
+  [name ^js properties ^js opts]
+  (this-as
+   this
+   (let [properties (bean/->clj properties)
+         db-based? (config/db-based-graph?)
+         {:keys [redirect format journal schema]} (bean/->clj opts)]
+     (p/let [page (<get-block name {:children? false})
+             new-page (when-not page
+                        (page-handler/<create!
+                         name
+                         (cond->
+                          {:redirect? (if (boolean? redirect) redirect true)
+                           :journal? journal
+                           :format format}
+                           (not db-based?)
+                           (assoc :properties properties))))
+             _ (when (and db-based? (seq properties))
+                 (api-block/db-based-save-block-properties! new-page properties {:plugin this
+                                                                                 :schema schema}))]
+       (some-> (or page new-page)
+               sdk-utils/result->js)))))
+
+(defn create_journal_page
+  [^js date]
+  (let [date (js/Date. date)]
+    (when-let [datestr (and (not (js/isNaN (.getTime date)))
+                            (-> (gdate/Date. date)
+                                (date-time-util/format "yyyy-MM-dd")))]
+      (create_page datestr nil #js {:journal true :redirect false}))))
+
+(defn delete_page
+  [name]
+  (page-handler/<delete! name nil))
+
+(def rename_page
+  page-handler/rename!)
+
+(defn open_in_right_sidebar
+  [block-id-or-uuid]
+  (editor-handler/open-block-in-sidebar!
+   (if (number? block-id-or-uuid)
+     block-id-or-uuid
+     (sdk-utils/uuid-or-throw-error block-id-or-uuid))))
+
+(defn new_block_uuid []
+  (str (db/new-block-id)))
+
+(def select_block
+  (fn [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)))
+
+(def edit_block
+  (fn [block-uuid ^js opts]
+    (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)]
+        (let [{:keys [pos] :or {pos :max}} (bean/->clj opts)]
+          (editor-handler/edit-block! block pos {:container-id :unknown-container}))))))
+
+(defn- <ensure-page-loaded
+  [block-uuid-or-page-name]
+  (p/let [repo (state/get-current-repo)
+          block (db-async/<get-block repo (str block-uuid-or-page-name)
+                                     {:children? true
+                                      :include-collapsed-children? true})
+          _ (when-let [page-id (:db/id (:block/page block))]
+              (when-let [page-uuid (:block/uuid (db/entity page-id))]
+                (db-async/<get-block repo page-uuid)))]
+    block))
+
+(defn insert_block
+  [block-uuid-or-page-name content ^js opts]
+  (this-as this
+           (when (string/blank? block-uuid-or-page-name)
+             (throw (js/Error. "Page title or block UUID shouldn't be empty.")))
+
+           (p/let [block? (util/uuid-string? (str block-uuid-or-page-name))
+                   block (<get-block (str block-uuid-or-page-name))]
+             (if (and block? (not block))
+               (throw (js/Error. "Block not exists"))
+               (p/let [{:keys [before start end sibling focus customUUID properties autoOrderedList schema]} (bean/->clj opts)
+                       [page-name block-uuid] (if (util/uuid-string? block-uuid-or-page-name)
+                                                [nil (uuid block-uuid-or-page-name)]
+                                                [block-uuid-or-page-name nil])
+                       page-name (when page-name (util/page-name-sanity-lc page-name))
+                       _ (when (and page-name
+                                    (nil? (ldb/get-page (db/get-db) page-name)))
+                           (page-handler/<create! block-uuid-or-page-name {}))
+                       custom-uuid (or customUUID (:id properties))
+                       custom-uuid (when custom-uuid (sdk-utils/uuid-or-throw-error custom-uuid))
+                       edit-block? (if (nil? focus) true focus)
+                       _ (when (and custom-uuid (db-model/query-block-by-uuid custom-uuid))
+                           (throw (js/Error.
+                                   (util/format "Custom block UUID already exists (%s)." custom-uuid))))
+                       block-uuid' (if (and (not sibling) before block-uuid)
+                                     (let [block (db/entity [:block/uuid block-uuid])
+                                           first-child (ldb/get-first-child (db/get-db) (:db/id block))]
+                                       (if first-child
+                                         (:block/uuid first-child)
+                                         block-uuid))
+                                     block-uuid)
+                       insert-at-first-child? (not= block-uuid' block-uuid)
+                       [sibling? before?] (if insert-at-first-child?
+                                            [true true]
+                                            [sibling before])
+                       db-based? (config/db-based-graph?)
+                       before? (if (and (false? sibling?) before? (not insert-at-first-child?))
+                                 false
+                                 before?)
+                       opts' {:block-uuid block-uuid'
+                              :sibling? sibling?
+                              :before? before?
+                              :start? start
+                              :end? end
+                              :edit-block? edit-block?
+                              :page page-name
+                              :custom-uuid custom-uuid
+                              :ordered-list? (if (boolean? autoOrderedList) autoOrderedList false)
+                              :properties (when (not db-based?)
+                                            (merge properties
+                                                   (when custom-uuid {:id custom-uuid})))}]
+                 (if db-based?
+                   (db-based-api/insert-block this content properties schema opts')
+                   (p/let [new-block (editor-handler/api-insert-new-block! content opts')]
+                     (bean/->js (sdk-utils/normalize-keyword-for-json new-block)))))))))
+
+(def insert_batch_block
+  (fn [block-uuid ^js batch-blocks-js ^js opts-js]
+    (this-as
+     this
+     (p/let [block (<ensure-page-loaded block-uuid)]
+       (when block
+         (when-let [blocks (bean/->clj batch-blocks-js)]
+           (let [db-based? (config/db-based-graph?)
+                 blocks' (if-not (vector? blocks) (vector blocks) blocks)
+                 opts (bean/->clj opts-js)
+                 {:keys [sibling before _schema keepUUID]} opts]
+             (if db-based?
+               (db-based-api/insert-batch-blocks this block blocks' opts)
+               (let [keep-uuid? (or keepUUID false)
+                     _ (when keep-uuid? (doseq
+                                         [block (outliner-core/tree-vec-flatten blocks' :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)))))))
+                     block (if before
+                             (db/pull (:db/id (ldb/get-left-sibling (db/entity (:db/id block))))) block)
+                     sibling? (if (ldb/page? block) false sibling)]
+                 (p/let [result (editor-handler/insert-block-tree-after-target
+                                 (:db/id block) sibling? blocks' (get block :block/format :markdown) keep-uuid?)
+                         blocks (:blocks result)]
+                   (let [blocks' (map (fn [b] (db/entity [:block/uuid (:block/uuid b)])) blocks)]
+                     (-> blocks'
+                         sdk-utils/normalize-keyword-for-json
+                         bean/->js))))))))))))
+
+(def remove_block
+  (fn [block-uuid ^js _opts]
+    (p/let [repo            (state/get-current-repo)
+            _ (<get-block block-uuid {:children? false})]
+      (editor-handler/delete-block-aux!
+       {:block/uuid (sdk-utils/uuid-or-throw-error block-uuid) :repo repo}))))
+
+(def update_block
+  (fn [block-uuid content ^js opts]
+    (this-as
+     this
+     (p/let [repo (state/get-current-repo)
+             db-based? (config/db-based-graph?)
+             block (<get-block block-uuid {:children? false})
+             opts' (bean/->clj opts)]
+       (when block
+         (if db-based?
+           (db-based-api/update-block this block content opts')
+           (editor-handler/save-block! repo
+                                       (sdk-utils/uuid-or-throw-error block-uuid) content
+                                       (if db-based? (dissoc opts' :properties) opts'))))))))
+
+(def move_block
+  (fn [src-block-uuid target-block-uuid ^js opts]
+    (p/let [_ (<get-block src-block-uuid {:children? false})
+            _ (<get-block target-block-uuid {:children? false})]
+      (let [{:keys [before children]} (bean/->clj opts)
+            move-to      (cond
+                           (boolean before)
+                           :top
+
+                           (boolean children)
+                           :nested
+
+                           :else
+                           nil)
+            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 nil move-to)))))
+
+(def get_block
+  (fn [id ^js opts]
+    (p/let [_ (db-async/<get-block (state/get-current-repo) id {:children? true
+                                                                :include-collapsed-children? true})]
+      (api-block/get_block id (or opts #js {:includePage true})))))
+
+(def get_current_block
+  (fn [^js opts]
+    (let [block (state/get-edit-block)
+          block (or block
+                    (some-> (or (first (state/get-selection-blocks))
+                                (state/get-editor-block-container))
+                            (.getAttribute "blockid")
+                            (db-model/get-block-by-uuid)))]
+      (get_block (:block/uuid block) opts))))
+
+(def get_previous_sibling_block
+  (fn [block-uuid ^js opts]
+    (p/let [id (sdk-utils/uuid-or-throw-error block-uuid)
+            block (<get-block id)
+            ;; Load all children blocks
+            _ (api-block/<sync-children-blocks! block)]
+      (when block
+        (when-let [sibling (ldb/get-left-sibling (db/entity (:db/id block)))]
+          (get_block (:block/uuid sibling) opts))))))
+
+(def get_next_sibling_block
+  (fn [block-uuid ^js opts]
+    (p/let [id (sdk-utils/uuid-or-throw-error block-uuid)
+            block (<get-block id)
+            ;; Load all children blocks
+            _ (api-block/<sync-children-blocks! block)]
+      (when block
+        (p/let [sibling (ldb/get-right-sibling (db/entity (:db/id block)))]
+          (get_block (:block/uuid sibling) opts))))))
+
+(def set_block_collapsed
+  (fn [block-uuid ^js opts]
+    (p/let [block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
+            block (<get-block block-uuid {:children? false})]
+      (when block
+        (let [opts (bean/->clj opts)
+              opts (if (or (string? opts) (boolean? opts)) {:flag opts} opts)
+              {:keys [flag]} opts
+              flag (if (= "toggle" flag)
+                     (not (util/collapsed? block))
+                     (boolean flag))]
+          (if flag
+            (editor-handler/collapse-block! block-uuid)
+            (editor-handler/expand-block! block-uuid))
+          nil)))))
+
+(def get_current_page_blocks_tree
+  (fn []
+    (when-let [page (state/get-current-page)]
+      (let [page-id (:db/id (ldb/get-page (db/get-db) page))
+            blocks (db-model/get-page-blocks-no-cache page-id)
+            blocks (outliner-tree/blocks->vec-tree blocks page-id)
+            ;; clean key
+            blocks (sdk-utils/normalize-keyword-for-json blocks)]
+        (bean/->js blocks)))))
+
+(def get_page_blocks_tree
+  (fn [id-or-page-name]
+    (p/let [_ (<ensure-page-loaded id-or-page-name)]
+      (when-let [page-id (:db/id (db-model/get-page id-or-page-name))]
+        (let [blocks (db-model/get-page-blocks-no-cache page-id)
+              blocks (outliner-tree/blocks->vec-tree blocks page-id)
+              blocks (sdk-utils/normalize-keyword-for-json blocks)]
+          (bean/->js blocks))))))
+
+(defn get_page_linked_references
+  [page-name-or-uuid]
+  (p/let [repo (state/get-current-repo)
+          block (<get-block page-name-or-uuid {:children? false})]
+    (when-let [id (:db/id block)]
+      (p/let [result (db-async/<get-block-refs repo id)
+              ref-blocks (db-utils/group-by-page result)]
+        (bean/->js (sdk-utils/normalize-keyword-for-json ref-blocks))))))
+
+(defn prepend_block_in_page
+  [uuid-or-page-name content ^js opts]
+  (p/let [uuid-or-page-name (or
+                             uuid-or-page-name
+                             (state/get-current-page)
+                             (date/today))
+          block           (<get-block uuid-or-page-name)
+          new-page        (when (and (not block) (not (util/uuid-string? uuid-or-page-name))) ; page not exists
+                            (page-handler/<create! uuid-or-page-name
+                                                   {:redirect?           false
+                                                    :format              (state/get-preferred-format)}))]
+    (let [block (or block new-page)
+          opts (bean/->clj opts)
+          opts' (assoc opts :before false :sibling false :start true)]
+      (insert_block (str (:block/uuid block)) content (bean/->js opts')))))
+
+(defn append_block_in_page
+  [uuid-or-page-name content ^js opts]
+  (let [uuid-or-page-name (or
+                           uuid-or-page-name
+                           (state/get-current-page)
+                           (date/today))]
+    (p/let [_ (<ensure-page-loaded uuid-or-page-name)
+            page? (not (util/uuid-string? uuid-or-page-name))
+            page (db-model/get-page uuid-or-page-name)
+            page-not-exist? (and page? (nil? page))
+            new-page (when page-not-exist?
+                       (page-handler/<create! uuid-or-page-name
+                                              {:redirect? false
+                                               :format (state/get-preferred-format)}))
+            block (or page new-page)]
+      (let [children (:block/_parent block)
+            [target sibling?] (if (seq children)
+                                [(last (ldb/sort-by-order children)) true]
+                                [block false])
+            target-id (str (:block/uuid target))
+            opts (-> (bean/->clj opts)
+                     (assoc :sibling sibling?))]
+        (insert_block target-id content opts)))))
+
+(defn download_graph_db
+  []
+  (when-let [repo (state/get-current-repo)]
+    (export-handler/export-repo-as-sqlite-db! repo)))
+
+(defn download_graph_pages
+  []
+  (when-let [repo (state/get-current-repo)]
+    (export-handler/export-repo-as-zip! repo)))
+
+(defn exec_git_command
+  [^js args]
+  (when-let [args (and args (seq (bean/->clj args)))]
+    (shell/run-git-command! args)))
+
+;; block properties
+(defn upsert_block_property
+  [block-uuid key ^js value ^js options]
+  (this-as
+   this
+   (p/let [key' (api-block/sanitize-user-property-name key)
+           opts (bean/->clj options)
+           block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
+           repo (state/get-current-repo)
+           block (<get-block block-uuid {:children? false})
+           db-based? (config/db-based-graph?)
+           value (bean/->clj value)]
+     (when block
+       (if db-based?
+         (db-based-api/upsert-block-property this block key' value (:schema opts))
+         (property-handler/set-block-property! repo block-uuid key' value))))))
+
+(defn remove_block_property
+  [block-uuid key]
+  (this-as this
+           (p/let [key (api-block/sanitize-user-property-name key)
+                   block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
+                   _block (<get-block block-uuid {:children? false})
+                   db-based? (config/db-based-graph?)
+                   key-ns? (namespace (keyword key))
+                   key (if (and db-based? (not key-ns?))
+                         (api-block/get-db-ident-from-property-name
+                          key (api-block/resolve-property-prefix-for-db this))
+                         key)]
+             (property-handler/remove-block-property!
+              (state/get-current-repo)
+              block-uuid key))))
+
+(defn get_block_property
+  [block-uuid key]
+  (this-as this
+           (p/let [block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
+                   _block (<get-block block-uuid {:children? false})]
+             (when-let [properties (some-> block-uuid (db-model/get-block-by-uuid) (:block/properties))]
+               (when (seq properties)
+                 (let [property-name (api-block/sanitize-user-property-name key)
+                       ident (api-block/get-db-ident-from-property-name
+                              property-name (api-block/resolve-property-prefix-for-db this))
+                       property-value (or (get properties property-name)
+                                          (get properties (keyword property-name))
+                                          (get properties ident))
+                       property-value (if-let [property-id (:db/id property-value)]
+                                        (db/pull property-id) property-value)
+                       property-value (cond-> property-value
+                                        (map? property-value)
+                                        (assoc
+                                         :value (or (:logseq.property/value property-value)
+                                                    (:block/title property-value))
+                                         :ident ident))
+                       parsed-value (api-block/parse-property-json-value-if-need ident property-value)]
+                   (or parsed-value
+                       (bean/->js (sdk-utils/normalize-keyword-for-json property-value)))))))))
+
+(def get_block_properties
+  (fn [block-uuid]
+    (p/let [block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
+            block (<get-block block-uuid {:children? false})]
+      (when block
+        (let [properties (if (config/db-based-graph?)
+                           (api-block/into-readable-db-properties (:block/properties block))
+                           (:block/properties block))]
+          (sdk-utils/result->js properties))))))
+
+(defn get_page_properties
+  [id-or-page-name]
+  (p/let [page (<get-block id-or-page-name {:children? false})]
+    (when-let [id (:block/uuid page)]
+      (get_block_properties id))))

+ 1 - 1
src/main/logseq/api/file.cljs → src/main/logseq/api/file_based.cljs

@@ -1,4 +1,4 @@
-(ns logseq.api.file
+(ns logseq.api.file-based
   "File version related fns"
   (:require [cljs-bean.core :as bean]
             [cljs.reader]

+ 338 - 0
src/main/logseq/api/plugin.cljs

@@ -0,0 +1,338 @@
+(ns logseq.api.plugin
+  "Plugin related apis"
+  (:require [cljs-bean.core :as bean]
+            [cljs.reader]
+            [clojure.string :as string]
+            [electron.ipc :as ipc]
+            [frontend.config :as config]
+            [frontend.fs :as fs]
+            [frontend.handler.command-palette :as palette-handler]
+            [frontend.handler.common.plugin :as plugin-common-handler]
+            [frontend.handler.plugin :as plugin-handler]
+            [frontend.idb :as idb]
+            [frontend.modules.layout.core]
+            [frontend.modules.shortcut.config :as shortcut-config]
+            [frontend.modules.shortcut.core :as st]
+            [frontend.state :as state]
+            [frontend.util :as util]
+            [goog.object :as gobj]
+            [lambdaisland.glogi :as log]
+            [logseq.sdk.core]
+            [logseq.sdk.experiments]
+            [logseq.sdk.git]
+            [promesa.core :as p]))
+
+(defn get-caller-plugin-id
+  [] (gobj/get js/window "$$callerPluginID"))
+
+;; helpers
+(defn install-plugin-hook
+  [pid hook ^js opts]
+  (state/install-plugin-hook pid hook (bean/->clj opts)))
+
+(defn uninstall-plugin-hook
+  [pid hook-or-all]
+  (state/uninstall-plugin-hook pid hook-or-all))
+
+(defn should-exec-plugin-hook
+  [pid hook]
+  (plugin-handler/plugin-hook-installed? pid hook))
+
+(def load_plugin_config
+  (fn [path]
+    (if (util/electron?)
+      (fs/read-file nil (util/node-path.join path "package.json"))
+      (js/console.log "TODO: load plugin package.json from web plugin."))))
+
+(def load_plugin_readme
+  (fn [path]
+    (fs/read-file nil (util/node-path.join path "readme.md"))))
+
+(def save_plugin_package_json
+  (fn [path ^js data]
+    (let [repo ""
+          path (util/node-path.join path "package.json")]
+      (fs/write-plain-text-file! repo nil path (js/JSON.stringify data nil 2) {:skip-compare? true}))))
+
+(defn- write_rootdir_file
+  [file content sub-root root-dir]
+  (p/let [repo           ""
+          path           (util/node-path.join root-dir sub-root)
+          exist?         (fs/file-exists? path "")
+          _              (when-not exist? (fs/mkdir-recur! path))
+          user-path      (util/node-path.join path file)
+          sub-dir?       (string/starts-with? user-path path)
+          _              (when-not sub-dir?
+                           (log/info :debug user-path)
+                           (throw (js/Error. "write file denied")))
+          user-path-root (util/node-path.dirname user-path)
+          exist?         (fs/file-exists? user-path-root "")
+          _              (when-not exist? (fs/mkdir-recur! user-path-root))
+          _              (fs/write-plain-text-file! repo nil user-path content {:skip-compare? true})]
+    user-path))
+
+(defn write_dotdir_file
+  [file content sub-root]
+  (some-> (plugin-handler/get-ls-dotdir-root)
+          (p/then #(write_rootdir_file file content sub-root %))))
+
+(defn write_assetsdir_file
+  [file content sub-root]
+  (if-let [assets-dir (config/get-current-repo-assets-root)]
+    (write_rootdir_file file content sub-root assets-dir)
+    false))
+
+(defn- read_rootdir_file
+  [file sub-root root-dir]
+  (p/let [path      (util/node-path.join root-dir sub-root)
+          user-path (util/node-path.join path file)
+          sub-dir?  (string/starts-with? user-path path)
+          _         (when-not sub-dir? (log/info :debug user-path) (throw (js/Error. "read file denied")))
+          exist?    (fs/file-exists? "" user-path)
+          _         (when-not exist? (log/info :debug user-path) (throw (js/Error. "file not existed")))
+          content   (fs/read-file "" user-path)]
+    content))
+
+(defn- read_dotdir_file
+  [file sub-root]
+  (some-> (plugin-handler/get-ls-dotdir-root)
+          (p/then #(read_rootdir_file file sub-root %))))
+
+(defn- read_assetsdir_file
+  [file sub-root]
+  (when-let [root-dir (config/get-current-repo-assets-root)]
+    (read_rootdir_file file sub-root root-dir)))
+
+(defn- unlink_rootdir_file!
+  [file sub-root root-dir]
+  (p/let [repo      ""
+          path      (util/node-path.join root-dir sub-root)
+          user-path (util/node-path.join path file)
+          sub-dir?  (string/starts-with? user-path path)
+          _         (when-not sub-dir? (log/info :debug user-path) (throw (js/Error. "access file denied")))
+          exist?    (fs/file-exists? "" user-path)
+          _         (when-not exist? (log/info :debug user-path) (throw (js/Error. "file not existed")))
+          _         (fs/unlink! repo user-path {})]))
+
+(defn- unlink_dotdir_file!
+  [file sub-root]
+  (some-> (plugin-handler/get-ls-dotdir-root)
+          (p/then #(unlink_rootdir_file! file sub-root %))))
+
+(defn- unlink_assetsdir_file!
+  [file sub-root]
+  (when-let [root-dir (config/get-current-repo-assets-root)]
+    (unlink_rootdir_file! file sub-root root-dir)))
+
+(def write_user_tmp_file
+  (fn [file content]
+    (write_dotdir_file file content "tmp")))
+
+(def write_plugin_storage_file
+  (fn [plugin-id file content assets?]
+    (let [plugin-id (util/node-path.basename plugin-id)
+          sub-root  (util/node-path.join "storages" plugin-id)]
+      (if (true? assets?)
+        (write_assetsdir_file file content sub-root)
+        (write_dotdir_file file content sub-root)))))
+
+(def read_plugin_storage_file
+  (fn [plugin-id file assets?]
+    (let [plugin-id (util/node-path.basename plugin-id)
+          sub-root  (util/node-path.join "storages" plugin-id)]
+      (if (true? assets?)
+        (read_assetsdir_file file sub-root)
+        (read_dotdir_file file sub-root)))))
+
+(def unlink_plugin_storage_file
+  (fn [plugin-id file assets?]
+    (let [plugin-id (util/node-path.basename plugin-id)
+          sub-root  (util/node-path.join "storages" plugin-id)]
+      (if (true? assets?)
+        (unlink_assetsdir_file! file sub-root)
+        (unlink_dotdir_file! file sub-root)))))
+
+(def exist_plugin_storage_file
+  (fn [plugin-id file assets?]
+    (p/let [root      (if (true? assets?)
+                        (config/get-current-repo-assets-root)
+                        (plugin-handler/get-ls-dotdir-root))
+            plugin-id (util/node-path.basename plugin-id)
+            exist?    (fs/file-exists?
+                       (util/node-path.join root "storages" plugin-id)
+                       file)]
+      exist?)))
+
+(def clear_plugin_storage_files
+  (fn [plugin-id assets?]
+    (p/let [root      (if (true? assets?)
+                        (config/get-current-repo-assets-root)
+                        (plugin-handler/get-ls-dotdir-root))
+            plugin-id (util/node-path.basename plugin-id)]
+      (fs/rmdir! (util/node-path.join root "storages" plugin-id)))))
+
+(def list_plugin_storage_files
+  (fn [plugin-id assets?]
+    (p/let [root       (if (true? assets?)
+                         (config/get-current-repo-assets-root)
+                         (plugin-handler/get-ls-dotdir-root))
+            plugin-id  (util/node-path.basename plugin-id)
+            files-path (util/node-path.join root "storages" plugin-id)
+            ^js files  (ipc/ipc :listdir files-path)]
+      (when (js-iterable? files)
+        (bean/->js
+         (map #(some-> (string/replace-first % files-path "")
+                       (string/replace #"^/+" "")) files))))))
+
+(def load_user_preferences
+  (fn []
+    (let [repo ""
+          path (plugin-handler/get-ls-dotdir-root)
+          path (util/node-path.join path "preferences.json")]
+      (if (util/electron?)
+        (p/let [_ (fs/create-if-not-exists repo nil path)
+                json (fs/read-file nil path)
+                json (if (string/blank? json) "{}" json)]
+          (js/JSON.parse json))
+        (p/let [json (idb/get-item path)]
+          (or json #js {}))))))
+
+(def save_user_preferences
+  (fn [^js data]
+    (when data
+      (let [repo ""
+            path (plugin-handler/get-ls-dotdir-root)
+            path (util/node-path.join path "preferences.json")]
+        (if (util/electron?)
+          (fs/write-plain-text-file! repo nil path (js/JSON.stringify data nil 2) {:skip-compare? true})
+          (idb/set-item! path data))))))
+
+(def load_plugin_user_settings
+  ;; results [path data]
+  (plugin-handler/make-fn-to-load-dotdir-json "settings" #js {}))
+
+(def save_plugin_user_settings
+  (fn [key ^js data]
+    ((plugin-handler/make-fn-to-save-dotdir-json "settings")
+     key data)))
+
+(defn load_installed_web_plugins
+  []
+  (let [getter (plugin-handler/make-fn-to-load-dotdir-json "installed-plugins-for-web" #js {})]
+    (some-> (getter :all) (p/then second))))
+
+(defn save_installed_web_plugin
+  ([^js plugin] (save_installed_web_plugin plugin false))
+  ([^js plugin remove?]
+   (when-let [id (some-> plugin (.-key) (name))]
+     (let [setter (plugin-handler/make-fn-to-save-dotdir-json "installed-plugins-for-web")
+           plugin (js/JSON.parse (js/JSON.stringify plugin))]
+       (p/let [^js plugins (or (load_installed_web_plugins) #js {})]
+         (if (true? remove?)
+           (when (aget plugins id)
+             (js-delete plugins id))
+           (gobj/set plugins id plugin))
+         (setter :all plugins))))))
+
+(defn unlink_installed_web_plugin
+  [key]
+  (save_installed_web_plugin #js {:key key} true))
+
+(def unlink_plugin_user_settings
+  (plugin-handler/make-fn-to-unlink-dotdir-json "settings"))
+
+(def register_plugin_slash_command
+  (fn [pid ^js cmd-actions]
+    (when-let [[cmd actions] (bean/->clj cmd-actions)]
+      (plugin-handler/register-plugin-slash-command
+       pid [cmd (mapv #(into [(keyword (first %))]
+                             (rest %)) actions)]))))
+
+(def register_plugin_simple_command
+  (fn [pid ^js cmd-action palette?]
+    (when-let [[cmd action] (bean/->clj cmd-action)]
+      (let [action      (assoc action 0 (keyword (first action)))
+            cmd         (assoc cmd :key (-> (:key cmd) (string/trim) (string/replace ":" "-") (string/replace #"^([0-9])" "_$1")))
+            key         (:key cmd)
+            keybinding  (:keybinding cmd)
+            palette-cmd (plugin-handler/simple-cmd->palette-cmd pid cmd action)
+            action'     #(state/pub-event! [:exec-plugin-cmd {:type type :key key :pid pid :cmd cmd :action action}])]
+
+        ;; handle simple commands
+        (plugin-handler/register-plugin-simple-command pid cmd action)
+
+        ;; handle palette commands
+        (when palette?
+          (palette-handler/register palette-cmd))
+
+        ;; handle keybinding commands
+        (when-let [shortcut-args (and keybinding (plugin-handler/simple-cmd-keybinding->shortcut-args pid key keybinding))]
+          (let [dispatch-cmd (fn [_e]
+                               (if palette?
+                                 (palette-handler/invoke-command palette-cmd)
+                                 (action')))
+                [mode-id id shortcut-map] (update shortcut-args 2 merge cmd {:fn dispatch-cmd :cmd palette-cmd})]
+
+            (cond
+              ;; FIX ME: move to register logic
+              (= mode-id :shortcut.handler/block-editing-only)
+              (shortcut-config/add-shortcut! mode-id id shortcut-map)
+
+              :else
+              (do
+                (println :shortcut/register-shortcut [mode-id id shortcut-map])
+                (st/register-shortcut! mode-id id shortcut-map)))))))))
+
+(defn unregister_plugin_simple_command
+  [pid]
+  ;; remove simple commands
+  (plugin-handler/unregister-plugin-simple-command pid)
+
+  ;; remove palette commands
+  (let [cmds-matched (->> (vals @shortcut-config/*shortcut-cmds)
+                          (filter #(string/includes? (str (:id %)) (str "plugin." pid))))]
+    (when (seq cmds-matched)
+      (doseq [cmd cmds-matched]
+        (palette-handler/unregister (:id cmd))
+        ;; remove keybinding commands
+        (when (seq (:shortcut cmd))
+          (println :shortcut/unregister-shortcut cmd)
+          (st/unregister-shortcut! (:handler-id cmd) (:id cmd)))))))
+
+(defn register_search_service
+  [pid name ^js opts]
+  (plugin-handler/register-plugin-search-service pid name (bean/->clj opts)))
+
+(defn unregister_search_services
+  [pid]
+  (plugin-handler/unregister-plugin-search-services pid))
+
+(def register_plugin_ui_item
+  (fn [pid type ^js opts]
+    (when-let [opts (bean/->clj opts)]
+      (plugin-handler/register-plugin-ui-item
+       pid (assoc opts :type type)))))
+
+(defn get_external_plugin
+  [pid]
+  (when-let [^js pl (plugin-handler/get-plugin-inst pid)]
+    (.toJSON pl)))
+
+(defn invoke_external_plugin_cmd
+  [pid cmd-group cmd-key cmd-args]
+  (case (keyword cmd-group)
+    :models
+    (plugin-handler/call-plugin-user-model! pid cmd-key cmd-args)
+
+    :commands
+    (plugin-handler/call-plugin-user-command! pid cmd-key cmd-args)))
+
+(defn validate_external_plugins [urls]
+  (ipc/ipc :validateUserExternalPlugins urls))
+
+(def __install_plugin
+  (fn [^js manifest]
+    (when-let [{:keys [repo id] :as manifest} (bean/->clj manifest)]
+      (if-not (and repo id)
+        (throw (js/Error. "[required] :repo :id"))
+        (plugin-common-handler/install-marketplace-plugin! manifest)))))

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

@@ -79,6 +79,12 @@
         (reduce {} (gobj/getKeys obj)))
     obj))
 
+(defn result->js
+  [result]
+  (-> result
+      normalize-keyword-for-json
+      bean/->js))
+
 (def ^:export to-clj bean/->clj)
 (def ^:export jsx-to-clj jsx->clj)
 (def ^:export to-js bean/->js)

部分文件因为文件数量过多而无法显示