diff --git a/src/main/logseq/api.cljs b/src/main/logseq/api.cljs index 07f1cef32f..a9f0d9e8c0 100644 --- a/src/main/logseq/api.cljs +++ b/src/main/logseq/api.cljs @@ -2,1003 +2,136 @@ "Logseq API for plugins usage" (:require [cljs-bean.core :as bean] [cljs.reader] - [clojure.string :as string] - [datascript.core :as d] [electron.ipc :as ipc] - [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.query-custom :as query-custom] - [frontend.db.query-dsl :as query-dsl] - [frontend.db.query-react :as query-react] - [frontend.db.utils :as db-utils] - [frontend.fs :as fs] - [frontend.handler.code :as code-handler] - [frontend.handler.command-palette :as palette-handler] - [frontend.handler.common.plugin :as plugin-common-handler] - [frontend.handler.config :as config-handler] - [frontend.handler.db-based.property :as db-property-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.plugin :as plugin-handler] - [frontend.handler.property :as property-handler] - [frontend.handler.recent :as recent-handler] - [frontend.handler.route :as route-handler] [frontend.handler.search :as search-handler] - [frontend.handler.shell :as shell] - [frontend.idb :as idb] [frontend.loader :as loader] [frontend.modules.layout.core] - [frontend.modules.outliner.tree :as outliner-tree] - [frontend.modules.shortcut.config :as shortcut-config] - [frontend.modules.shortcut.core :as st] [frontend.state :as state] - [frontend.util :as util] - [frontend.util.cursor :as cursor] - [frontend.version :as fv] - [goog.date :as gdate] - [goog.dom :as gdom] - [goog.object :as gobj] - [lambdaisland.glogi :as log] - [logseq.api.block :as api-block] - [logseq.api.db :as db-api] - [logseq.api.file :as file-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.api.app :as api-app] + [logseq.api.db :as api-db] + [logseq.api.db-based :as db-based-api] + [logseq.api.editor :as api-editor] + [logseq.api.file-based :as file-based-api] + [logseq.api.plugin :as api-plugin] [logseq.sdk.assets :as sdk-assets] [logseq.sdk.core] [logseq.sdk.experiments] [logseq.sdk.git] [logseq.sdk.ui :as sdk-ui] - [logseq.sdk.utils :as sdk-utils] - [promesa.core :as p] - [reitit.frontend.easy :as rfe])) - -;; Alert: this namespace shouldn't invoke any reactive queries - -(defn- (state/get-current-repo) - (config/db-based-graph?))) - -(defn ^:export get-caller-plugin-id - [] (gobj/get js/window "$$callerPluginID")) - -;; helpers -(defn ^:export install-plugin-hook - [pid hook ^js opts] - (state/install-plugin-hook pid hook (bean/->clj opts))) - -(defn ^:export uninstall-plugin-hook - [pid hook-or-all] - (state/uninstall-plugin-hook pid hook-or-all)) - -(defn ^:export should-exec-plugin-hook - [pid hook] - (plugin-handler/plugin-hook-installed? pid hook)) - -;; base -(defn ^:export 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 ^:export 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 ^:export get_app_info - ;; get app base info - [] - (-> (sdk-utils/normalize-keyword-for-json - {:version fv/version - :supportDb true}) - (bean/->js))) - -(def ^:export 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 ^:export get_current_graph_configs - (fn [& keys] - (some-> (state/get-config) - (#(if (seq keys) (get-in % (map keyword keys)) %)) - (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 - (fn [] - (if (db-graph?) - (db-api/get-favorites) - (some->> (:favorites (state/get-config)) - (remove string/blank?) - (filter string?) - (bean/->js))))) - -(def ^:export 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 ^:export 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 ^:export check_current_is_db_graph db-graph?) - -(def ^:export show_themes - (fn [] - (state/pub-event! [:modal/show-themes-modal]))) - -(def ^:export set_theme_mode - (fn [mode] - (state/set-theme-mode! mode))) - -(def ^:export 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 ^:export load_plugin_readme - (fn [path] - (fs/read-file nil (util/node-path.join path "readme.md")))) - -(def ^:export 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})))) - -(def ^:export save_focused_code_editor_content - (fn [] - (code-handler/save-code-editor!))) - -(defn ^:private 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 ^:export write_dotdir_file - [file content sub-root] - (some-> (plugin-handler/get-ls-dotdir-root) - (p/then #(write_rootdir_file file content sub-root %)))) - -(defn ^:export 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 ^:private 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 ^:private read_dotdir_file - [file sub-root] - (some-> (plugin-handler/get-ls-dotdir-root) - (p/then #(read_rootdir_file file sub-root %)))) - -(defn ^:private 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 ^:private 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 ^:private unlink_dotdir_file! - [file sub-root] - (some-> (plugin-handler/get-ls-dotdir-root) - (p/then #(unlink_rootdir_file! file sub-root %)))) - -(defn ^:private 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 ^:export write_user_tmp_file - (fn [file content] - (write_dotdir_file file content "tmp"))) - -(def ^:export 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 ^:export 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 ^:export 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 ^:export 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 ^:export 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 ^:export 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 ^:export 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 ^:export 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 ^:export load_plugin_user_settings - ;; results [path data] - (plugin-handler/make-fn-to-load-dotdir-json "settings" #js {})) - -(def ^:export save_plugin_user_settings - (fn [key ^js data] - ((plugin-handler/make-fn-to-save-dotdir-json "settings") - key data))) - -(defn ^:export 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 ^:export 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 ^:export unlink_installed_web_plugin - [key] - (save_installed_web_plugin #js {:key key} true)) - -(def ^:export unlink_plugin_user_settings - (plugin-handler/make-fn-to-unlink-dotdir-json "settings")) - -(def ^:export 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 ^:export 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 ^:export 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 ^:export register_search_service - [pid name ^js opts] - (plugin-handler/register-plugin-search-service pid name (bean/->clj opts))) - -(defn ^:export unregister_search_services - [pid] - (plugin-handler/unregister-plugin-search-services pid)) - -(def ^:export 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))))) - -;; app -(def ^:export relaunch - (fn [] - (ipc/ipc "relaunchApp"))) - -(def ^:export quit - (fn [] - (ipc/ipc "quitApp"))) - -(def ^:export open_external_link - (fn [url] - (when (re-find #"https?://" url) - (js/apis.openExternal url)))) - -(def ^:export 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 ^:export 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 ^:export set_right_sidebar_visible - (fn [flag] - (if (= flag "toggle") - (state/toggle-sidebar-open?!) - (state/set-state! :ui/sidebar-open? (boolean flag))) - nil)) - -(def ^:export 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 ^:export 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 ^:export 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))))) - -(defn ^:export get_external_plugin - [pid] - (when-let [^js pl (plugin-handler/get-plugin-inst pid)] - (.toJSON pl))) - -(defn ^:export 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))) - -;; editor -(def ^:export check_editing - (fn [] - (if (state/get-edit-input-id) - (str (:block/uuid (state/get-edit-block))) false))) - -(def ^:export exit_editing_mode - (fn [select?] - (editor-handler/escape-editing {:select? select?}) - nil)) - -(def ^:export 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 ^:export 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 ^:export 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 ^:export get_editing_block_content - (fn [] - (state/get-edit-content))) - -(def ^:export 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)))))] - (db-api/result->js blocks))))) - -(def ^:export clear_selected_blocks - (fn [] - (state/clear-selection!))) - -(def ^:export get_current_page - (fn [] - (when-let [page (state/get-current-page)] - (p/let [page (js page)))))) - -(defn ^:export get_page - [id-or-page-name] - (p/let [page (js page)))) - -;; FIXME: this doesn't work because the ui doesn't have all pages -(defn ^:export 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 ^:export create_page - [name ^js properties ^js opts] - (this-as - this - (let [properties (bean/->clj properties) - db-based? (db-graph?) - {:keys [redirect format journal schema]} (bean/->clj opts)] - (p/let [page ( - {: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) - db-api/result->js))))) - -(defn ^:export 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 ^:export delete_page - [name] - (page-handler/clj opts)] - (editor-handler/edit-block! block pos {:container-id :unknown-container})))))) - -(defn- 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/js (sdk-utils/normalize-keyword-for-json new-block))))))))) - -(def ^:export insert_batch_block - (fn [block-uuid ^js batch-blocks-js ^js opts-js] - (this-as - this - (p/let [block (clj batch-blocks-js)] - (let [db-based? (db-graph?) - blocks' (if-not (vector? blocks) (vector blocks) blocks) - opts (bean/->clj opts-js) - {:keys [sibling before _schema keepUUID]} opts] - (if db-based? - (db-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 ^:export remove_block - (fn [block-uuid ^js _opts] - (p/let [repo (state/get-current-repo) - _ (clj opts)] - (when block - (if db-based? - (db-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 ^:export move_block - (fn [src-block-uuid target-block-uuid ^js opts] - (p/let [_ (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 ^:export get_block - (fn [id ^js opts] - (p/let [_ (db-async/ (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 ^:export get_previous_sibling_block - (fn [block-uuid ^js opts] - (p/let [id (sdk-utils/uuid-or-throw-error block-uuid) - block (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 ^:export 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 ^:export get_page_blocks_tree - (fn [id-or-page-name] - (p/let [_ (vec-tree blocks page-id) - blocks (sdk-utils/normalize-keyword-for-json blocks)] - (bean/->js blocks)))))) - -(defn ^:export get_page_linked_references - [page-name-or-uuid] - (p/let [repo (state/get-current-repo) - block (js (sdk-utils/normalize-keyword-for-json ref-blocks)))))) - -(defn ^:export 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 (clj opts) - opts' (assoc opts :before false :sibling false :start true)] - (insert_block (str (:block/uuid block)) content (bean/->js opts'))))) - -(defn ^:export 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 [_ ( (bean/->clj opts) - (assoc :sibling sibling?))] - (insert_block target-id content opts))))) - -;; plugins -(defn ^:export validate_external_plugins [urls] - (ipc/ipc :validateUserExternalPlugins urls)) - -(def ^:export __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))))) + [promesa.core :as p])) + +;; Alert: All apis shouldn't invoke any reactive queries + +;; plugin apis +(def ^:export get-caller-plugin-id api-plugin/get-caller-plugin-id) +(def ^:export install-plugin-hook api-plugin/install-plugin-hook) +(def ^:export uninstall-plugin-hook api-plugin/uninstall-plugin-hook) +(def ^:export should-exec-plugin-hook api-plugin/should-exec-plugin-hook) +(def ^:export load_plugin_config api-plugin/load_plugin_config) +(def ^:export load_plugin_readme api-plugin/load_plugin_readme) +(def ^:export save_plugin_package_json api-plugin/save_plugin_package_json) +(def ^:export write_dotdir_file api-plugin/write_dotdir_file) +(def ^:export write_assetsdir_file api-plugin/write_assetsdir_file) +(def ^:export write_user_tmp_file api-plugin/write_user_tmp_file) +(def ^:export write_plugin_storage_file api-plugin/write_plugin_storage_file) +(def ^:export read_plugin_storage_file api-plugin/read_plugin_storage_file) +(def ^:export unlink_plugin_storage_file api-plugin/unlink_plugin_storage_file) +(def ^:export exist_plugin_storage_file api-plugin/exist_plugin_storage_file) +(def ^:export clear_plugin_storage_files api-plugin/clear_plugin_storage_files) +(def ^:export list_plugin_storage_files api-plugin/list_plugin_storage_files) +(def ^:export load_user_preferences api-plugin/load_user_preferences) +(def ^:export save_user_preferences api-plugin/save_user_preferences) +(def ^:export load_plugin_user_settings api-plugin/load_plugin_user_settings) +(def ^:export save_plugin_user_settings api-plugin/save_plugin_user_settings) +(def ^:export load_installed_web_plugins api-plugin/load_installed_web_plugins) +(def ^:export save_installed_web_plugin api-plugin/save_installed_web_plugin) +(def ^:export unlink_installed_web_plugin api-plugin/unlink_installed_web_plugin) +(def ^:export unlink_plugin_user_settings api-plugin/unlink_plugin_user_settings) +(def ^:export register_plugin_slash_command api-plugin/register_plugin_slash_command) +(def ^:export register_plugin_simple_command api-plugin/register_plugin_simple_command) +(def ^:export unregister_plugin_simple_command api-plugin/unregister_plugin_simple_command) +(def ^:export register_search_service api-plugin/register_search_service) +(def ^:export unregister_search_services api-plugin/unregister_search_services) +(def ^:export register_plugin_ui_item api-plugin/register_plugin_ui_item) +(def ^:export get_external_plugin api-plugin/get_external_plugin) +(def ^:export invoke_external_plugin_cmd api-plugin/invoke_external_plugin_cmd) +(def ^:export validate_external_plugins api-plugin/validate_external_plugins) +(def ^:export __install_plugin api-plugin/__install_plugin) + +;; app/graph +(def ^:export check_current_is_db_graph config/db-based-graph?) +(def ^:export get_state_from_store api-app/get_state_from_store) +(def ^:export set_state_from_store api-app/set_state_from_store) +(def ^:export get_app_info api-app/get_app_info) +(def ^:export get_user_configs api-app/get_user_configs) +(def ^:export get_current_graph_configs api-app/get_current_graph_configs) +(def ^:export set_current_graph_configs api-app/set_current_graph_configs) +(def ^:export get_current_graph_favorites api-app/get_current_graph_favorites) +(def ^:export get_current_graph_recent api-app/get_current_graph_recent) +(def ^:export get_current_graph api-app/get_current_graph) +(def ^:export show_themes api-app/show_themes) +(def ^:export set_theme_mode api-app/set_theme_mode) +(def ^:export relaunch api-app/relaunch) +(def ^:export quit api-app/quit) +(def ^:export open_external_link api-app/open_external_link) +(def ^:export invoke_external_command api-app/invoke_external_command) +(def ^:export set_left_sidebar_visible api-app/set_left_sidebar_visible) +(def ^:export set_right_sidebar_visible api-app/set_right_sidebar_visible) +(def ^:export clear_right_sidebar_blocks api-app/clear_right_sidebar_blocks) +(def ^:export push_state api-app/push_state) +(def ^:export replace_state api-app/replace_state) ;; db -(defn ^:export 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)))))) +(def ^:export q api-db/q) +(def ^:export datascript_query api-db/datascript_query) +(def ^:export custom_query api-db/custom_query) -(defn ^:export 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/js (sdk-utils/normalize-keyword-for-json result false)))))) - -(defn ^:export 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))))) - -(defn ^:export download_graph_db - [] - (when-let [repo (state/get-current-repo)] - (export-handler/export-repo-as-sqlite-db! repo))) - -(defn ^:export download_graph_pages - [] - (when-let [repo (state/get-current-repo)] - (export-handler/export-repo-as-zip! repo))) - -(defn ^:export exec_git_command - [^js args] - (when-let [args (and args (seq (bean/->clj args)))] - (shell/run-git-command! args))) +;; editor +(def ^:export append_block_in_page api-editor/append_block_in_page) +(def ^:export check_editing api-editor/check_editing) +(def ^:export clear_selected_blocks api-editor/clear_selected_blocks) +(def ^:export create_journal_page api-editor/create_journal_page) +(def ^:export create_page api-editor/create_page) +(def ^:export delete_page api-editor/delete_page) +(def ^:export download_graph_db api-editor/download_graph_db) +(def ^:export download_graph_pages api-editor/download_graph_pages) +(def ^:export edit_block api-editor/edit_block) +(def ^:export exec_git_command api-editor/exec_git_command) +(def ^:export exit_editing_mode api-editor/exit_editing_mode) +(def ^:export get_all_pages api-editor/get_all_pages) +(def ^:export get_block api-editor/get_block) +(def ^:export get_block_properties api-editor/get_block_properties) +(def ^:export get_block_property api-editor/get_block_property) +(def ^:export get_current_block api-editor/get_current_block) +(def ^:export get_current_page api-editor/get_current_page) +(def ^:export get_current_page_blocks_tree api-editor/get_current_page_blocks_tree) +(def ^:export get_editing_block_content api-editor/get_editing_block_content) +(def ^:export get_editing_cursor_position api-editor/get_editing_cursor_position) +(def ^:export get_next_sibling_block api-editor/get_next_sibling_block) +(def ^:export get_page api-editor/get_page) +(def ^:export get_page_blocks_tree api-editor/get_page_blocks_tree) +(def ^:export get_page_linked_references api-editor/get_page_linked_references) +(def ^:export get_page_properties api-editor/get_page_properties) +(def ^:export get_previous_sibling_block api-editor/get_previous_sibling_block) +(def ^:export get_selected_blocks api-editor/get_selected_blocks) +(def ^:export insert_at_editing_cursor api-editor/insert_at_editing_cursor) +(def ^:export insert_batch_block api-editor/insert_batch_block) +(def ^:export insert_block api-editor/insert_block) +(def ^:export move_block api-editor/move_block) +(def ^:export new_block_uuid api-editor/new_block_uuid) +(def ^:export open_in_right_sidebar api-editor/open_in_right_sidebar) +(def ^:export prepend_block_in_page api-editor/prepend_block_in_page) +(def ^:export remove_block api-editor/remove_block) +(def ^:export remove_block_property api-editor/remove_block_property) +(def ^:export rename_page api-editor/rename_page) +(def ^:export restore_editing_cursor api-editor/restore_editing_cursor) +(def ^:export save_focused_code_editor_content api-editor/save_focused_code_editor_content) +(def ^:export select_block api-editor/select_block) +(def ^:export set_block_collapsed api-editor/set_block_collapsed) +(def ^:export update_block api-editor/update_block) +(def ^:export upsert_block_property api-editor/upsert_block_property) ;; ui (def ^:export show_msg sdk-ui/-show_msg) @@ -1025,7 +158,7 @@ (p/all)))) ;; http request -(defonce *request-k (volatile! 0)) +(defonce ^:private *request-k (volatile! 0)) (defn ^:export exper_request [pid ^js options] @@ -1058,145 +191,26 @@ [] true) -;; block properties -(defn ^:export 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 (clj value)] - (when block - (if db-based? - (db-api/upsert-block-property this block key' value (:schema opts)) - (property-handler/set-block-property! repo block-uuid key' value)))))) - -(defn ^:export 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 ( 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 ^:export get_block_properties - (fn [block-uuid] - (p/let [block-uuid (sdk-utils/uuid-or-throw-error block-uuid) - block (js properties)))))) - -(defn ^:export get_page_properties - [id-or-page-name] - (p/let [page ( prop - (assoc :type (:logseq.property/type prop)) - (sdk-utils/normalize-keyword-for-json) - (bean/->js))))) - -(defn ^:export 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-utils/pull (:db/id p))] - (bean/->js (sdk-utils/normalize-keyword-for-json p)))))) - -(defn ^:export remove_property - [k] - (this-as - this - (p/let [property (-get-property this k)] - (db-api/remove-property property)))) - -(def ^:export get_all_tags db-api/get-all-tags) -(def ^:export get_all_properties db-api/get-all-properties) -(def ^:export get_tag_objects db-api/get-tag-objects) +(def ^:export get_property db-based-api/get-property) +(def ^:export upsert_property db-based-api/upsert-property) +(def ^:export remove_property db-based-api/remove-property) +(def ^:export get_all_tags db-based-api/get-all-tags) +(def ^:export get_all_properties db-based-api/get-all-properties) +(def ^:export get_tag_objects db-based-api/get-tag-objects) ;; file based graph APIs -(def ^:export get_current_graph_templates file-api/get_current_graph_templates) -(def ^:export get_template file-api/get_template) -(def ^:export insert_template file-api/insert_template) -(def ^:export exist_template file-api/exist_template) -(def ^:export create_template file-api/create_template) -(def ^:export remove_template file-api/remove_template) -(def ^:export get_pages_from_namespace file-api/get_pages_from_namespace) -(def ^:export get_pages_tree_from_namespace file-api/get_pages_tree_from_namespace) -(def ^:export set_blocks_id file-api/set_blocks_id) +(def ^:export get_current_graph_templates file-based-api/get_current_graph_templates) +(def ^:export get_template file-based-api/get_template) +(def ^:export insert_template file-based-api/insert_template) +(def ^:export exist_template file-based-api/exist_template) +(def ^:export create_template file-based-api/create_template) +(def ^:export remove_template file-based-api/remove_template) +(def ^:export get_pages_from_namespace file-based-api/get_pages_from_namespace) +(def ^:export get_pages_tree_from_namespace file-based-api/get_pages_tree_from_namespace) +(def ^:export set_blocks_id file-based-api/set_blocks_id) + +(comment + ;; Use the following code to generate export APIs from specific namespaces + (doseq [k (sort (keys (ns-publics 'logseq.api.editor)))] + (println (frontend.util/format "(def ^:export %s api-editor/%s)" (str k) (str k))))) diff --git a/src/main/logseq/api/app.cljs b/src/main/logseq/api/app.cljs new file mode 100644 index 0000000000..8e51a0a0bd --- /dev/null +++ b/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))))) diff --git a/src/main/logseq/api/db.cljs b/src/main/logseq/api/db.cljs index 8c0a41079c..562e550195 100644 --- a/src/main/logseq/api/db.cljs +++ b/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 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 get-favorites - [] - (p/let [favorites (page-handler/get-favorites)] - (result->js favorites))) +(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)) -(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'))))) + (fn? %) + (fn [& args] + (.apply % nil (clj->js (mapv bean/->js args)))) -(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)))) + :else %) + inputs) + result (apply db-async/js (sdk-utils/normalize-keyword-for-json result false)))))) -(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/ (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/js result))))) +(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))))) diff --git a/src/main/logseq/api/db_based.cljs b/src/main/logseq/api/db_based.cljs new file mode 100644 index 0000000000..45f0f541cb --- /dev/null +++ b/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/ (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/js result))))) diff --git a/src/main/logseq/api/editor.cljs b/src/main/logseq/api/editor.cljs new file mode 100644 index 0000000000..48d6456e3d --- /dev/null +++ b/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- 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 (js page)))))) + +(defn get_page + [id-or-page-name] + (p/let [page (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 ( + {: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/clj opts)] + (editor-handler/edit-block! block pos {:container-id :unknown-container})))))) + +(defn- 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/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 (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) + _ (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 [_ (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/ (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 (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 [_ (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 (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 (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 [_ ( (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 (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 ( 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 (js properties)))))) + +(defn get_page_properties + [id-or-page-name] + (p/let [page (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))))) diff --git a/src/main/logseq/sdk/utils.cljs b/src/main/logseq/sdk/utils.cljs index 3f4a8a988a..e66c66921c 100644 --- a/src/main/logseq/sdk/utils.cljs +++ b/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)