diff --git a/deps/common/src/logseq/common/path.cljs b/deps/common/src/logseq/common/path.cljs index c9fe3406c01..af22e2abc77 100644 --- a/deps/common/src/logseq/common/path.cljs +++ b/deps/common/src/logseq/common/path.cljs @@ -309,3 +309,11 @@ (string/starts-with? p "/") ;; is windows dir (re-find #"^[a-zA-Z]:[/\\]" p))))) + +(defn protocol-url? + "Whether path `p` is a protocol URL. + + This is a loose check, it only checks if there is a valid protocol prefix." + [p] + (boolean (and (re-find #"^[a-zA-Z0-9_+\-\.]{2,}:" p) ;; HACK: avoid matching windows drive + (not (string/includes? p " "))))) diff --git a/deps/common/test/logseq/common/path_test.cljs b/deps/common/test/logseq/common/path_test.cljs index 02bf0aa20c8..36fde4b7399 100644 --- a/deps/common/test/logseq/common/path_test.cljs +++ b/deps/common/test/logseq/common/path_test.cljs @@ -45,3 +45,15 @@ (is (false? (path/absolute? "test.md"))) (is (false? (path/absolute? "test"))) (is (false? (path/absolute? "D:test.md"))))) + +(deftest protocol-url + (testing "protocol url" + (is (true? (path/protocol-url? "mailto:help@logseq.com"))) + (is (true? (path/protocol-url? "https://logseq.com"))) + (is (true? (path/protocol-url? "ftp://logseq.com"))) + (is (true? (path/protocol-url? "file:///home/xxx/logseq/test.md"))) + (is (true? (path/protocol-url? "assets:///home/xxx/logseq/test.md"))) + (is (false? (path/protocol-url? "logseq/test.md"))) + (is (false? (path/protocol-url? "test.md"))) + (is (false? (path/protocol-url? "test"))) + (is (false? (path/protocol-url? "D:test.md"))))) diff --git a/deps/graph-parser/src/logseq/graph_parser/util.cljs b/deps/graph-parser/src/logseq/graph_parser/util.cljs index 9f201d8404d..e6774b09a18 100644 --- a/deps/graph-parser/src/logseq/graph_parser/util.cljs +++ b/deps/graph-parser/src/logseq/graph_parser/util.cljs @@ -80,6 +80,9 @@ (and (string? v) (>= (count v) 2) (= "\"" (first v) (last v)))) (defn url? + "Test if it is a `protocol://`-style URL. + + NOTE: Can not handle mailto: links, use this with caution." [s] (and (string? s) (try diff --git a/src/electron/electron/search.cljs b/src/electron/electron/search.cljs index c473e1c0535..3f79fcfebd0 100644 --- a/src/electron/electron/search.cljs +++ b/src/electron/electron/search.cljs @@ -6,7 +6,8 @@ [clojure.string :as string] ["electron" :refer [app]] [electron.logger :as logger] - [medley.core :as medley])) + [medley.core :as medley] + [electron.utils :as utils])) (defonce databases (atom nil)) @@ -28,14 +29,23 @@ [repo] (get @databases (sanitize-db-name repo))) +(declare delete-db!) + (defn prepare - [^object db sql] + [^object db sql db-name] (when db - (.prepare db sql))) + (try + (.prepare db sql) + (catch :default e + (logger/error (str "SQLite prepare failed: " e ": " db-name)) + ;; case 1: vtable constructor failed: blocks_fts https://github.com/logseq/logseq/issues/7467 + (delete-db! db-name) + (utils/send-to-renderer "rebuildSearchIndice" {}) + (throw e))))) (defn add-blocks-fts-triggers! "Table bindings of blocks tables and the blocks FTS virtual tables" - [db] + [db db-name] (let [triggers [;; add "CREATE TRIGGER IF NOT EXISTS blocks_ad AFTER DELETE ON blocks BEGIN @@ -55,12 +65,12 @@ VALUES (new.id, new.uuid, new.content, new.page); END;"]] (doseq [trigger triggers] - (let [stmt (prepare db trigger)] + (let [stmt (prepare db trigger db-name)] (.run ^object stmt))))) (defn add-pages-fts-triggers! "Table bindings of pages tables and the pages FTS virtual tables" - [db] + [db db-name] (let [triggers [;; add "CREATE TRIGGER IF NOT EXISTS pages_ad AFTER DELETE ON pages BEGIN @@ -80,34 +90,36 @@ VALUES (new.id, new.uuid, new.content); END;"]] (doseq [trigger triggers] - (let [stmt (prepare db trigger)] + (let [stmt (prepare db trigger db-name)] (.run ^object stmt))))) (defn create-blocks-table! - [db] + [db db-name] (let [stmt (prepare db "CREATE TABLE IF NOT EXISTS blocks ( id INTEGER PRIMARY KEY, uuid TEXT NOT NULL, content TEXT NOT NULL, - page INTEGER)")] + page INTEGER)" + db-name)] (.run ^object stmt))) (defn create-blocks-fts-table! - [db] - (let [stmt (prepare db "CREATE VIRTUAL TABLE IF NOT EXISTS blocks_fts USING fts5(uuid, content, page)")] + [db db-name] + (let [stmt (prepare db "CREATE VIRTUAL TABLE IF NOT EXISTS blocks_fts USING fts5(uuid, content, page)" db-name)] (.run ^object stmt))) (defn create-pages-table! - [db] + [db db-name] (let [stmt (prepare db "CREATE TABLE IF NOT EXISTS pages ( id INTEGER PRIMARY KEY, uuid TEXT NOT NULL, - content TEXT NOT NULL)")] + content TEXT NOT NULL)" + db-name)] (.run ^object stmt))) (defn create-pages-fts-table! - [db] - (let [stmt (prepare db "CREATE VIRTUAL TABLE IF NOT EXISTS pages_fts USING fts5(uuid, content)")] + [db db-name] + (let [stmt (prepare db "CREATE VIRTUAL TABLE IF NOT EXISTS pages_fts USING fts5(uuid, content)" db-name)] (.run ^object stmt))) (defn get-search-dir @@ -136,12 +148,12 @@ [db-name] (let [[db-sanitized-name db-full-path] (get-db-full-path db-name)] (try (let [db (sqlite3 db-full-path nil)] - (create-blocks-table! db) - (create-blocks-fts-table! db) - (create-pages-table! db) - (create-pages-fts-table! db) - (add-blocks-fts-triggers! db) - (add-pages-fts-triggers! db) + (create-blocks-table! db db-name) + (create-blocks-fts-table! db db-name) + (create-pages-table! db db-name) + (create-pages-fts-table! db db-name) + (add-blocks-fts-triggers! db db-name) + (add-pages-fts-triggers! db db-name) (swap! databases assoc db-sanitized-name db)) (catch :default e (logger/error (str e ": " db-name)) @@ -170,7 +182,7 @@ (if-let [db (get-db repo)] ;; TODO: what if a CONFLICT on uuid ;; Should update all values on id conflict - (let [insert (prepare db "INSERT INTO pages (id, uuid, content) VALUES (@id, @uuid, @content) ON CONFLICT (id) DO UPDATE SET (uuid, content) = (@uuid, @content)") + (let [insert (prepare db "INSERT INTO pages (id, uuid, content) VALUES (@id, @uuid, @content) ON CONFLICT (id) DO UPDATE SET (uuid, content) = (@uuid, @content)" repo) insert-many (.transaction ^object db (fn [pages] (doseq [page pages] @@ -184,7 +196,7 @@ [repo ids] (when-let [db (get-db repo)] (let [sql (str "DELETE from pages WHERE id IN " (clj-list->sql ids)) - stmt (prepare db sql)] + stmt (prepare db sql repo)] (.run ^object stmt)))) (defn upsert-blocks! @@ -192,7 +204,7 @@ (if-let [db (get-db repo)] ;; TODO: what if a CONFLICT on uuid ;; Should update all values on id conflict - (let [insert (prepare db "INSERT INTO blocks (id, uuid, content, page) VALUES (@id, @uuid, @content, @page) ON CONFLICT (id) DO UPDATE SET (uuid, content, page) = (@uuid, @content, @page)") + (let [insert (prepare db "INSERT INTO blocks (id, uuid, content, page) VALUES (@id, @uuid, @content, @page) ON CONFLICT (id) DO UPDATE SET (uuid, content, page) = (@uuid, @content, @page)" repo) insert-many (.transaction ^object db (fn [blocks] (doseq [block blocks] @@ -206,20 +218,13 @@ [repo ids] (when-let [db (get-db repo)] (let [sql (str "DELETE from blocks WHERE id IN " (clj-list->sql ids)) - stmt (prepare db sql)] + stmt (prepare db sql repo)] (.run ^object stmt)))) -;; (defn search-blocks-fts -;; [q] -;; (when-not (string/blank? q) -;; (let [stmt (prepare @database -;; "select id, uuid, content from blocks_fts where content match ? ORDER BY rank")] -;; (js->clj (.all ^object stmt q) :keywordize-keys true)))) - (defn- search-blocks-aux - [database sql input page limit] + [repo database sql input page limit] (try - (let [stmt (prepare database sql)] + (let [stmt (prepare database sql repo)] (js->clj (if page (.all ^object stmt (int page) input limit) @@ -264,12 +269,12 @@ matched-result (->> (map (fn [match-input] - (search-blocks-aux database match-sql match-input page limit)) + (search-blocks-aux repo database match-sql match-input page limit)) match-inputs) (apply concat))] (->> (concat matched-result - (search-blocks-aux database non-match-sql non-match-input page limit)) + (search-blocks-aux repo database non-match-sql non-match-input page limit)) (distinct-by :rowid) (take limit) (vec)))))) @@ -299,8 +304,8 @@ snippet)})) (defn- search-pages-aux - [database sql input limit] - (let [stmt (prepare database sql)] + [repo database sql input limit] + (let [stmt (prepare database sql repo)] (try (doall (map search-pages-res-unpack (-> (.raw ^object stmt) @@ -329,12 +334,12 @@ matched-result (->> (map (fn [match-input] - (search-pages-aux database match-sql match-input limit)) + (search-pages-aux repo database match-sql match-input limit)) match-inputs) (apply concat))] (->> (concat matched-result - (search-pages-aux database non-match-sql non-match-input limit)) + (search-pages-aux repo database non-match-sql non-match-input limit)) (distinct-by :id) (take limit) (vec)))))) @@ -342,23 +347,25 @@ (defn truncate-blocks-table! [repo] (when-let [database (get-db repo)] - (let [stmt (prepare database - "delete from blocks;") + (let [stmt (prepare database "delete from blocks;" repo) _ (.run ^object stmt) - stmt (prepare database - "delete from blocks_fts;")] + stmt (prepare database "delete from blocks_fts;" repo)] (.run ^object stmt)))) (defn truncate-pages-table! [repo] (when-let [database (get-db repo)] - (let [stmt (prepare database - "delete from pages;") + (let [stmt (prepare database "delete from pages;" repo) _ (.run ^object stmt) - stmt (prepare database - "delete from pages_fts;")] + stmt (prepare database "delete from pages_fts;" repo)] (.run ^object stmt)))) +(defn query + [repo sql] + (when-let [database (get-db repo)] + (let [stmt (prepare database sql repo)] + (.all ^object stmt)))) + (defn delete-db! [repo] (when-let [database (get-db repo)] @@ -367,9 +374,3 @@ (logger/info "Delete search indice: " db-full-path) (fs/unlinkSync db-full-path) (swap! databases dissoc db-name)))) - -(defn query - [repo sql] - (when-let [database (get-db repo)] - (let [stmt (prepare database sql)] - (.all ^object stmt)))) diff --git a/src/main/electron/listener.cljs b/src/main/electron/listener.cljs index 34d6878b2f1..983514b6ec5 100644 --- a/src/main/electron/listener.cljs +++ b/src/main/electron/listener.cljs @@ -17,6 +17,7 @@ [frontend.handler.route :as route-handler] [frontend.handler.ui :as ui-handler] [frontend.handler.user :as user] + [frontend.handler.search :as search-handler] [frontend.state :as state] [frontend.ui :as ui] [logseq.common.path :as path] @@ -76,6 +77,11 @@ (let [repo (bean/->clj data)] (repo-handler/remove-repo! repo)))) + (safe-api-call "rebuildSearchIndice" + (fn [_data] + (prn "Rebuild search indices") + (search-handler/rebuild-indices!))) + (safe-api-call "setGitUsernameAndEmail" (fn [] (state/pub-event! [:modal/set-git-username-and-email]))) diff --git a/src/main/frontend/components/block.cljs b/src/main/frontend/components/block.cljs index b1e06784117..8f300e2b8c7 100644 --- a/src/main/frontend/components/block.cljs +++ b/src/main/frontend/components/block.cljs @@ -998,7 +998,9 @@ (defn- relative-assets-path->absolute-path [path] - (if (path/absolute? path) + (when (path/protocol-url? path) + (js/console.error "BUG: relative-assets-path->absolute-path called with protocol url" path)) + (if (or (path/absolute? path) (path/protocol-url? path)) path (.. util/node-path (join (config/get-repo-dir (state/get-current-repo)) @@ -1080,7 +1082,7 @@ (not (string/includes? s ".")) (page-reference (:html-export? config) s config label) - (gp-util/url? s) + (path/protocol-url? s) (->elem :a {:href s :data-href s :target "_blank"} @@ -1102,7 +1104,7 @@ (->elem :a (cond-> - {:href (str "file://" path) + {:href (path/path-join "file://" path) :data-href path :target "_blank"} title @@ -1184,7 +1186,7 @@ href)] (->elem :a - (cond-> {:href (str "file://" href*) + (cond-> {:href (path/path-join "file://" href*) :data-href href* :target "_blank"} title (assoc :title title)) diff --git a/src/main/frontend/components/plugins.cljs b/src/main/frontend/components/plugins.cljs index ae07c2ebd3a..2d7d462d3d9 100644 --- a/src/main/frontend/components/plugins.cljs +++ b/src/main/frontend/components/plugins.cljs @@ -23,6 +23,12 @@ (declare open-waiting-updates-modal!) (defonce PER-PAGE-SIZE 15) +(def *dirties-toggle-items (atom {})) + +(defn- clear-dirties-states! + [] + (reset! *dirties-toggle-items {})) + (rum/defcs installed-themes < (rum/local [] ::themes) @@ -272,7 +278,9 @@ (ui/toggle (not disabled?) (fn [] - (js-invoke js/LSPluginCore (if disabled? "enable" "disable") id)) + (js-invoke js/LSPluginCore (if disabled? "enable" "disable") id) + (when (nil? (get @*dirties-toggle-items (keyword id))) + (swap! *dirties-toggle-items assoc (keyword id) (not disabled?)))) true)]]) (defn get-open-plugin-readme-handler @@ -796,11 +804,15 @@ filtered-plugins) sorted-plugins (if default-filter-by? (->> filtered-plugins - (reduce #(let [k (if (get-in %2 [:settings :disabled]) 1 0)] + (reduce #(let [disabled? (get-in %2 [:settings :disabled]) + old-dirty (get @*dirties-toggle-items (keyword (:id %2))) + k (if (if (boolean? old-dirty) (not old-dirty) disabled?) 1 0)] (update %1 k conj %2)) [[] []]) (#(update % 0 (fn [coll] (sort-by :iir coll)))) (flatten)) - filtered-plugins) + (do + (clear-dirties-states!) + filtered-plugins)) fn-query-flag (fn [] (string/join "_" (map #(str @%) [*filter-by *sort-by *search-key *category]))) str-query-flag (fn-query-flag) @@ -1120,9 +1132,15 @@ *el-ref (rum/create-ref)] (rum/use-effect! - #(state/load-app-user-cfgs) + (fn [] + (state/load-app-user-cfgs) + #(clear-dirties-states!)) []) + (rum/use-effect! + #(clear-dirties-states!) + [market?]) + [:div.cp__plugins-page {:ref *el-ref :tab-index "-1"} diff --git a/src/main/frontend/components/server.cljs b/src/main/frontend/components/server.cljs index 14bee0ac9fd..3fd3ae333e2 100644 --- a/src/main/frontend/components/server.cljs +++ b/src/main/frontend/components/server.cljs @@ -87,12 +87,12 @@ (swap! *configs assoc :host value))}]] [:label - [:strong "Port (0 ~ 65536)"] + [:strong "Port (1 ~ 65535)"] [:input.form-input {:auto-focus true :value port :min "1" - :max "65536" + :max "65535" :type "number" :on-change #(let [value (.-value (.-target %))] (swap! *configs assoc :port value))}]]] diff --git a/src/main/frontend/fs.cljs b/src/main/frontend/fs.cljs index e836b6a3e37..b411cc28759 100644 --- a/src/main/frontend/fs.cljs +++ b/src/main/frontend/fs.cljs @@ -194,14 +194,12 @@ (defn mkdir-if-not-exists [dir] - (-> - (when dir - (util/p-handle - (stat dir) - (fn [_stat]) - (fn [_error] - (mkdir! dir)))) - (p/catch (fn [error] (js/console.error error))))) + (when dir + (util/p-handle + (stat dir) + (fn [_stat]) + (fn [_error] + (mkdir! dir))))) ;; FIXME: counterintuitive return value (defn create-if-not-exists diff --git a/src/main/frontend/fs/node.cljs b/src/main/frontend/fs/node.cljs index 5a0cbb5267f..34321b1e156 100644 --- a/src/main/frontend/fs/node.cljs +++ b/src/main/frontend/fs/node.cljs @@ -80,24 +80,33 @@ (defrecord Node [] protocol/Fs (mkdir! [_this dir] - (ipc/ipc "mkdir" dir)) + (-> (ipc/ipc "mkdir" dir) + (p/then (fn [_] (js/console.log (str "Directory created: " dir)))) + (p/catch (fn [error] + (when (not= (.-code error) "EEXIST") + (js/console.error (str "Error creating directory: " dir) error)))))) + (mkdir-recur! [_this dir] (ipc/ipc "mkdir-recur" dir)) + (readdir [_this dir] ; recursive (p/then (ipc/ipc "readdir" dir) bean/->clj)) + (unlink! [_this repo path _opts] (ipc/ipc "unlink" (config/get-repo-dir repo) path)) (rmdir! [_this _dir] - ;; Too dangerious!!! We'll never implement this. + ;; !Too dangerous! We'll never implement this. nil) + (read-file [_this dir path _options] (let [path (if (nil? dir) path (path/path-join dir path))] (ipc/ipc "readFile" path))) + (write-file! [this repo dir path content opts] (p/let [fpath (path/path-join dir path) stat (p/catch @@ -106,18 +115,24 @@ parent-dir (path/parent fpath) _ (protocol/mkdir-recur! this parent-dir)] (write-file-impl! repo dir path content opts stat))) + (rename! [_this _repo old-path new-path] (ipc/ipc "rename" old-path new-path)) + (stat [_this fpath] (-> (ipc/ipc "stat" fpath) (p/then bean/->clj))) + (open-dir [_this dir] (open-dir dir)) + (get-files [_this dir] (-> (ipc/ipc "getFiles" dir) (p/then (fn [result] (:files (bean/->clj result)))))) + (watch-dir! [_this dir options] (ipc/ipc "addDirWatcher" dir options)) + (unwatch-dir! [_this dir] (ipc/ipc "unwatchDir" dir))) diff --git a/src/main/frontend/handler/whiteboard.cljs b/src/main/frontend/handler/whiteboard.cljs index c60c46f4608..7e75ed64098 100644 --- a/src/main/frontend/handler/whiteboard.cljs +++ b/src/main/frontend/handler/whiteboard.cljs @@ -182,7 +182,7 @@ (defn get-default-new-whiteboard-tx [page-name id] - [#:block{:name page-name, + [#:block{:name (util/page-name-sanity-lc page-name), :type "whiteboard", :properties {:ls-type :whiteboard-page, diff --git a/src/main/frontend/ui.cljs b/src/main/frontend/ui.cljs index 50578173ff8..6df7990a099 100644 --- a/src/main/frontend/ui.cljs +++ b/src/main/frontend/ui.cljs @@ -234,15 +234,15 @@ (if (keyword? status) (case status :success - (icon "circle-check" {:class "text-success" :size "32"}) + (icon "circle-check" {:class "text-success" :size "20"}) :warning - (icon "alert-circle" {:class "text-warning" :size "32"}) + (icon "alert-circle" {:class "text-warning" :size "20"}) :error - (icon "circle-x" {:class "text-error" :size "32"}) + (icon "circle-x" {:class "text-error" :size "20"}) - (icon "info-circle" {:class "text-indigo-500" :size "32"})) + (icon "info-circle" {:class "text-indigo-500" :size "20"})) status)] [:div.ui__notifications-content {:style