diff --git a/.github/workflows/build-desktop-release.yml b/.github/workflows/build-desktop-release.yml index 7f67227db1..55d607ada0 100644 --- a/.github/workflows/build-desktop-release.yml +++ b/.github/workflows/build-desktop-release.yml @@ -43,8 +43,8 @@ on: # type: boolean # required: true # default: true - schedule: # Every workday at the 2 P.M. (UTC) we run a scheduled nightly build - - cron: '0 14 * * MON-FRI' + # schedule: # Every workday at the 2 P.M. (UTC) we run a scheduled nightly build + # - cron: '0 14 * * MON-FRI' env: CLOJURE_VERSION: '1.11.1.1413' diff --git a/.github/workflows/clj-e2e.yml b/.github/workflows/clj-e2e.yml index 120a6dd4c1..1b2b7c8e82 100644 --- a/.github/workflows/clj-e2e.yml +++ b/.github/workflows/clj-e2e.yml @@ -83,10 +83,10 @@ jobs: ls -lR ./public - name: Run e2e tests - run: cd clj-e2e && bb dev + run: cd clj-e2e && timeout 30m bb dev env: DEBUG: "pw:api" - + - name: Collect screenshots if: ${{ failure() }} uses: actions/upload-artifact@v4 diff --git a/android/app/build.gradle b/android/app/build.gradle index 1304c9d4ee..a54a0555fc 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,7 +7,7 @@ android { applicationId "com.logseq.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 84 + versionCode 87 versionName "0.11.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { diff --git a/clj-e2e/bb.edn b/clj-e2e/bb.edn index 6bc9419ff3..c2e4c782b4 100644 --- a/clj-e2e/bb.edn +++ b/clj-e2e/bb.edn @@ -12,9 +12,13 @@ prn {:task (clojure "-X clojure.core/prn" cli-opts)} - test {:task (do - (clojure "-T:build test") - (System/exit 0))} + test {:doc "run tests (ns'es ending in '-basic-test')" + :task (do (clojure "-M:test -r \".*\\-basic\\-test$\"") + (System/exit 0))} + + extra-test {:doc "run tests (ns'es ending in '-extra-test')" + :task (do (clojure "-M:test -r \".*\\-extra\\-test$\"") + (System/exit 0))} -dev {:depends [serve prn test]} diff --git a/clj-e2e/dev/user.clj b/clj-e2e/dev/user.clj index b2b8273cc8..5a77adebfc 100644 --- a/clj-e2e/dev/user.clj +++ b/clj-e2e/dev/user.clj @@ -2,16 +2,17 @@ "fns used on repl" (:require [clojure.test :refer [run-tests run-test]] [logseq.e2e.block :as b] - [logseq.e2e.commands-test] + [logseq.e2e.commands-basic-test] [logseq.e2e.config :as config] [logseq.e2e.fixtures :as fixtures] [logseq.e2e.graph :as graph] [logseq.e2e.keyboard :as k] - [logseq.e2e.multi-tabs-test] - [logseq.e2e.outliner-test] - [logseq.e2e.plugins-test] - [logseq.e2e.reference-test] + [logseq.e2e.multi-tabs-basic-test] + [logseq.e2e.outliner-basic-test] + [logseq.e2e.plugins-basic-test] + [logseq.e2e.reference-basic-test] [logseq.e2e.rtc-basic-test] + [logseq.e2e.rtc-extra-test] [logseq.e2e.util :as util] [wally.main :as w] [wally.repl :as repl])) @@ -31,12 +32,12 @@ (defn run-commands-test [] - (->> (future (run-tests 'logseq.e2e.commands-test)) + (->> (future (run-tests 'logseq.e2e.commands-basic-test)) (swap! *futures assoc :commands-test))) (defn run-outliner-test [] - (->> (future (run-tests 'logseq.e2e.outliner-test)) + (->> (future (run-tests 'logseq.e2e.outliner-basic-test)) (swap! *futures assoc :outliner-test))) (defn run-rtc-basic-test @@ -46,27 +47,32 @@ (defn run-multi-tabs-test [] - (->> (future (run-tests 'logseq.e2e.multi-tabs-test)) + (->> (future (run-tests 'logseq.e2e.multi-tabs-basic-test)) (swap! *futures assoc :multi-tabs-test))) (defn run-reference-test [] - (->> (future (run-tests 'logseq.e2e.reference-test)) + (->> (future (run-tests 'logseq.e2e.reference-basic-test)) (swap! *futures assoc :reference-test))) (defn run-plugins-test [] - (->> (future (run-tests 'logseq.e2e.plugins-test)) + (->> (future (run-tests 'logseq.e2e.plugins-basic-test)) (swap! *futures assoc :plugins-test))) -(defn run-all-test +(defn run-rtc-extra-test [] - (run-tests 'logseq.e2e.commands-test - 'logseq.e2e.multi-tabs-test - 'logseq.e2e.outliner-test + (->> (future (run-tests 'logseq.e2e.rtc-extra-test)) + (swap! *futures assoc :rtc-extra-test))) + +(defn run-all-basic-test + [] + (run-tests 'logseq.e2e.commands-basic-test + 'logseq.e2e.multi-tabs-basic-test + 'logseq.e2e.outliner-basic-test 'logseq.e2e.rtc-basic-test - 'logseq.e2e.plugins-test - 'logseq.e2e.reference-test)) + 'logseq.e2e.plugins-basic-test + 'logseq.e2e.reference-basic-test)) (defn start [] @@ -92,17 +98,17 @@ (w/wait-for (first (util/get-edit-block-container)) {:state :detached})) - (run-tests 'logseq.e2e.commands-test - 'logseq.e2e.multi-tabs-test - 'logseq.e2e.outliner-test + (run-tests 'logseq.e2e.commands-basic-test + 'logseq.e2e.multi-tabs-basic-test + 'logseq.e2e.outliner-basic-test 'logseq.e2e.rtc-basic-test) (do (reset! config/*headless true) (reset! config/*slow-mo 10) - (run-tests 'logseq.e2e.reference-test) + (run-tests 'logseq.e2e.reference-basic-test) (dotimes [i 10] - (run-tests 'logseq.e2e.reference-test))) + (run-tests 'logseq.e2e.reference-basic-test))) ;; ) diff --git a/clj-e2e/src/logseq/e2e/block.clj b/clj-e2e/src/logseq/e2e/block.clj index fb9e819549..1dcc5e4030 100644 --- a/clj-e2e/src/logseq/e2e/block.clj +++ b/clj-e2e/src/logseq/e2e/block.clj @@ -15,6 +15,7 @@ (defn save-block [text] + (w/click util/editor-q) (w/fill util/editor-q text) (assert/assert-is-visible (loc/filter util/editor-q :has-text text))) diff --git a/clj-e2e/src/logseq/e2e/rtc.clj b/clj-e2e/src/logseq/e2e/rtc.clj index 2c496f4adf..d12ef24839 100644 --- a/clj-e2e/src/logseq/e2e/rtc.clj +++ b/clj-e2e/src/logseq/e2e/rtc.clj @@ -33,6 +33,7 @@ (defn wait-tx-update-to [new-tx] + (assert (int? new-tx)) (loop [i 5] (when (zero? i) (throw (ex-info "wait-tx-update-to" {:update-to new-tx}))) (util/wait-timeout 1000) diff --git a/clj-e2e/test/logseq/e2e/commands_test.clj b/clj-e2e/test/logseq/e2e/commands_basic_test.clj similarity index 98% rename from clj-e2e/test/logseq/e2e/commands_test.clj rename to clj-e2e/test/logseq/e2e/commands_basic_test.clj index 9153e01bf6..486615de88 100644 --- a/clj-e2e/test/logseq/e2e/commands_test.clj +++ b/clj-e2e/test/logseq/e2e/commands_basic_test.clj @@ -1,4 +1,4 @@ -(ns logseq.e2e.commands-test +(ns logseq.e2e.commands-basic-test (:require [clj-time.core :as t] [clj-time.local :as tl] @@ -43,7 +43,7 @@ (k/enter) (is (string/includes? (util/get-edit-content) "[[")) (util/exit-edit) - (is (= "b1" (util/get-text ".block-ref")))))) + (is (= "b1" (.textContent (second (w/query "a.page-ref")))))))) (deftest link-test (testing "/link" diff --git a/clj-e2e/test/logseq/e2e/multi_tabs_test.clj b/clj-e2e/test/logseq/e2e/multi_tabs_basic_test.clj similarity index 98% rename from clj-e2e/test/logseq/e2e/multi_tabs_test.clj rename to clj-e2e/test/logseq/e2e/multi_tabs_basic_test.clj index 9f39a8e54a..471690e6bb 100644 --- a/clj-e2e/test/logseq/e2e/multi_tabs_test.clj +++ b/clj-e2e/test/logseq/e2e/multi_tabs_basic_test.clj @@ -1,4 +1,4 @@ -(ns logseq.e2e.multi-tabs-test +(ns logseq.e2e.multi-tabs-basic-test (:require [clojure.test :refer [deftest is testing use-fixtures]] [logseq.e2e.assert :as assert] [logseq.e2e.block :as b] diff --git a/clj-e2e/test/logseq/e2e/outliner_test.clj b/clj-e2e/test/logseq/e2e/outliner_basic_test.clj similarity index 98% rename from clj-e2e/test/logseq/e2e/outliner_test.clj rename to clj-e2e/test/logseq/e2e/outliner_basic_test.clj index 5009cfb90a..791d308cec 100644 --- a/clj-e2e/test/logseq/e2e/outliner_test.clj +++ b/clj-e2e/test/logseq/e2e/outliner_basic_test.clj @@ -1,4 +1,4 @@ -(ns logseq.e2e.outliner-test +(ns logseq.e2e.outliner-basic-test (:require [clojure.test :refer [deftest testing is use-fixtures]] [logseq.e2e.block :as b] diff --git a/clj-e2e/test/logseq/e2e/plugins_test.clj b/clj-e2e/test/logseq/e2e/plugins_basic_test.clj similarity index 97% rename from clj-e2e/test/logseq/e2e/plugins_test.clj rename to clj-e2e/test/logseq/e2e/plugins_basic_test.clj index cea2782168..af219db19f 100644 --- a/clj-e2e/test/logseq/e2e/plugins_test.clj +++ b/clj-e2e/test/logseq/e2e/plugins_basic_test.clj @@ -1,4 +1,4 @@ -(ns logseq.e2e.plugins-test +(ns logseq.e2e.plugins-basic-test (:require [clojure.string :as string] [clojure.test :refer [deftest testing is use-fixtures]] diff --git a/clj-e2e/test/logseq/e2e/reference_test.clj b/clj-e2e/test/logseq/e2e/reference_basic_test.clj similarity index 88% rename from clj-e2e/test/logseq/e2e/reference_test.clj rename to clj-e2e/test/logseq/e2e/reference_basic_test.clj index 9bd3a051d7..216d1f918f 100644 --- a/clj-e2e/test/logseq/e2e/reference_test.clj +++ b/clj-e2e/test/logseq/e2e/reference_basic_test.clj @@ -1,4 +1,4 @@ -(ns logseq.e2e.reference-test +(ns logseq.e2e.reference-basic-test (:require [clojure.test :refer [deftest testing is use-fixtures]] [logseq.e2e.assert :as assert] @@ -43,7 +43,7 @@ (b/wait-editor-text "b2") (b/paste) (util/exit-edit) - (b/assert-blocks-visible ["b1b2" "b2b1"]))) + (b/assert-blocks-visible ["b1[[b2]]" "b2[[b1]]"]))) (deftest parent-reference (testing "parent reference" @@ -59,7 +59,7 @@ (b/wait-editor-text "b2") (b/paste) (util/exit-edit) - (b/assert-blocks-visible ["b1b2" "b2b1"]))) + (b/assert-blocks-visible ["b1[[b2]]" "b2[[b1]]"]))) (deftest cycle-reference (testing "cycle reference" @@ -80,6 +80,6 @@ (assert/assert-editor-mode) (b/paste) (util/exit-edit) - (b/assert-blocks-visible ["b1b3b2" "b2b1b3" "b3b2b1"]))) + (b/assert-blocks-visible ["b1[[b3[[b2]]]]" "b2[[b1[[b3]]]]" "b3[[b2[[b1]]]]"]))) ;; TODO: page references diff --git a/clj-e2e/test/logseq/e2e/rtc_extra_test.clj b/clj-e2e/test/logseq/e2e/rtc_extra_test.clj new file mode 100644 index 0000000000..6373e4b70c --- /dev/null +++ b/clj-e2e/test/logseq/e2e/rtc_extra_test.clj @@ -0,0 +1,75 @@ +(ns logseq.e2e.rtc-extra-test + (:require + [clojure.test :refer [deftest testing is use-fixtures run-tests]] + [com.climate.claypoole :as cp] + [logseq.e2e.block :as b] + [logseq.e2e.fixtures :as fixtures :refer [*page1 *page2]] + [logseq.e2e.graph :as graph] + [logseq.e2e.rtc :as rtc] + [logseq.e2e.util :as util] + [wally.main :as w] + [wally.repl :as repl])) + +(def *graph-name (atom nil)) +(defn cleanup-fixture + [f] + (f) + (w/with-page @*page2 + (assert (some? @*graph-name)) + (graph/remove-remote-graph @*graph-name))) + +(use-fixtures :once + fixtures/open-2-pages + ;; cleanup-fixture + ) + +(defn- offline + [] + (.setOffline (.context (w/get-page)) true)) + +(defn- online + [] + (.setOffline (.context (w/get-page)) false)) + +(defn- insert-task-blocks + [title-prefix] + (doseq [status ["Backlog" "Todo" "Doing" "In review" "Done" "Canceled"] + priority ["No priority" "Low" "Medium" "High" "Urgent"]] + (b/new-block (str title-prefix "-" status "-" priority)) + (util/input-command status) + (util/input-command priority))) + +(deftest rtc-extra-test + (let [graph-name (str "rtc-extra-test-graph-" (.toEpochMilli (java.time.Instant/now)))] + (reset! *graph-name graph-name) + (testing "open 2 app instances, add a rtc graph, check this graph available on other instance" + (cp/prun! + 2 + #(w/with-page % + (util/login-test-account)) + [@*page1 @*page2]) + (w/with-page @*page1 + (graph/new-graph graph-name true)) + (w/with-page @*page2 + (graph/wait-for-remote-graph graph-name) + (graph/switch-graph graph-name true))) + (testing "rtc-stop app1, add some task blocks, then rtc-start on app1" + (let [*latest-remote-tx (atom nil)] + (w/with-page @*page1 + (offline)) + (w/with-page @*page2 + (let [{:keys [_local-tx remote-tx]} + (rtc/with-wait-tx-updated + (insert-task-blocks "t1"))] + (reset! *latest-remote-tx remote-tx)) + ;; TODO: more operations + (util/exit-edit)) + (w/with-page @*page1 + (online) + (rtc/wait-tx-update-to @*latest-remote-tx) + ;; TODO: check blocks exist + ))) + (testing "cleanup" + (w/with-page @*page2 + (assert (some? @*graph-name)) + (graph/remove-remote-graph @*graph-name))))) diff --git a/deps/db/src/logseq/db/common/entity_plus.cljc b/deps/db/src/logseq/db/common/entity_plus.cljc index a54e7265ea..06e4548363 100644 --- a/deps/db/src/logseq/db/common/entity_plus.cljc +++ b/deps/db/src/logseq/db/common/entity_plus.cljc @@ -21,7 +21,7 @@ ;; File graph only attributes. Can these be removed if this is only called in db graphs? :block/pre-block? :block/scheduled :block/deadline :block/type :block/name :block/marker - :block.temp/ast-title :block.temp/search? + :block.temp/ast-title :block.temp/fully-loaded? :block.temp/ast-body :db/valueType :db/cardinality :db/ident :db/index @@ -91,18 +91,16 @@ db-based? (db-based-graph? db)] (if (and db-based? (entity-util/journal? e)) (get-journal-title db e) - (let [search? (get (.-kv e) :block.temp/search?)] - (or - (when-not (and search? (keyword-identical? k :block/title)) - (get (.-kv e) k)) - (let [result (lookup-entity e k default-value) - ;; Replace title for pages only, otherwise it'll recursively - ;; replace block id refs if there're cycle references of blocks - refs (:block/refs e) - result' (if (and (string? result) refs) - (db-content/id-ref->title-ref result refs search?) - result)] - (or result' default-value))))))) + (or + (get (.-kv e) k) + (let [result (lookup-entity e k default-value) + ;; Replace title for pages only, otherwise it'll recursively + ;; replace block id refs if there're cycle references of blocks + refs (:block/refs e) + result' (if (and (string? result) refs) + (db-content/id-ref->title-ref result refs) + result)] + (or result' default-value)))))) (defn- lookup-kv-with-default-value [db ^Entity e k default-value] @@ -162,12 +160,12 @@ ;; cache :block/title :block/title - (or (when-not (get (.-kv e) :block.temp/search?) - (:block.temp/cached-title @(.-cache e))) - (let [title (get-block-title e k default-value)] - (vreset! (.-cache e) (assoc @(.-cache e) - :block.temp/cached-title title)) - title)) + (or + (:block.temp/cached-title @(.-cache e)) + (let [title (get-block-title e k default-value)] + (vreset! (.-cache e) (assoc @(.-cache e) + :block.temp/cached-title title)) + title)) :block/_parent (->> (lookup-entity e k default-value) @@ -191,7 +189,7 @@ (let [v @(.-cache this) v' (if (:block/title v) (assoc v :block/title - (db-content/id-ref->title-ref (:block/title v) (:block/refs this) (:block.temp/search? this))) + (db-content/id-ref->title-ref (:block/title v) (:block/refs this))) v)] (concat (seq v') (seq (.-kv this))))) diff --git a/deps/db/src/logseq/db/frontend/content.cljs b/deps/db/src/logseq/db/frontend/content.cljs index 0952461c18..c213bf09c0 100644 --- a/deps/db/src/logseq/db/frontend/content.cljs +++ b/deps/db/src/logseq/db/frontend/content.cljs @@ -4,8 +4,7 @@ [datascript.core :as d] [logseq.common.util :as common-util] [logseq.common.util.page-ref :as page-ref] - [logseq.db.common.entity-util :as common-entity-util] - [logseq.db.frontend.entity-util :as entity-util])) + [logseq.db.common.entity-util :as common-entity-util])) ;; [[uuid]] (def id-ref-pattern @@ -41,22 +40,25 @@ (defn id-ref->title-ref "Convert id ref backs to page name refs using refs." - [content* refs* search?] - (let [refs (filter common-entity-util/page? refs*) + [content* refs* & {:keys [replace-block-id?] + :or {replace-block-id? false}}] + (let [refs (if replace-block-id? + ;; The caller need to handle situations including + ;; mutual references and circle references. + refs* + (filter common-entity-util/page? refs*)) content (str content*)] (if (re-find id-ref-pattern content) (reduce (fn [content ref] (if (:block/title ref) - (if (or (entity-util/page? ref) search?) - (let [content' (if (not (string/includes? (:block/title ref) " ")) - (string/replace content - (str "#" (page-ref/->page-ref (:block/uuid ref))) - (str "#" (:block/title ref))) - content)] - (string/replace content' (page-ref/->page-ref (:block/uuid ref)) - (page-ref/->page-ref (:block/title ref)))) - content) + (let [content' (if (not (string/includes? (:block/title ref) " ")) + (string/replace content + (str "#" (page-ref/->page-ref (:block/uuid ref))) + (str "#" (:block/title ref))) + content)] + (string/replace content' (page-ref/->page-ref (:block/uuid ref)) + (page-ref/->page-ref (:block/title ref)))) content)) content (sort-refs refs)) @@ -125,7 +127,7 @@ [db item eid] (if-let [content (:block/title item)] (let [refs (:block/refs (d/entity db eid))] - (assoc item :block/title (id-ref->title-ref content refs false))) + (assoc item :block/title (id-ref->title-ref content refs))) item)) (defn replace-tags-with-id-refs @@ -164,3 +166,29 @@ content (sort-refs tags)) (string/trim))) + +(defn recur-replace-uuid-in-block-title + "Convert id ref (recursively) backs to page name refs, returns replaced title" + ([ent] + (recur-replace-uuid-in-block-title ent 10)) + ([ent max-depth] + (if (some->> (:block/title ent) (#(re-find id-ref-pattern %))) + (let [ref-set (loop [result-refs (:block/refs ent) + current-refs (:block/refs ent) + depth 0] + (if (or (>= depth max-depth) (empty? current-refs)) + result-refs + (let [next-refs (set (mapcat :block/refs current-refs)) + result-refs' (apply conj result-refs next-refs)] + (if (= (count result-refs') (count result-refs)) + result-refs + (recur (apply conj result-refs next-refs) next-refs (inc depth)))))) + opts {:replace-block-id? true}] + (loop [result (id-ref->title-ref (:block/title ent) ref-set opts) + last-result nil + depth 0] + (if (or (>= depth max-depth) + (= last-result result)) + result + (recur (id-ref->title-ref result ref-set opts) result (inc depth))))) + (:block/title ent)))) diff --git a/deps/db/src/logseq/db/frontend/malli_schema.cljs b/deps/db/src/logseq/db/frontend/malli_schema.cljs index d419020561..a7c15fb740 100644 --- a/deps/db/src/logseq/db/frontend/malli_schema.cljs +++ b/deps/db/src/logseq/db/frontend/malli_schema.cljs @@ -463,6 +463,7 @@ (def property-value-placeholder [:map [:db/ident [:= :logseq.property/empty-placeholder]] + [:block/uuid :uuid] [:block/tx-id {:optional true} :int]]) (defn entity-dispatch-key [db ent] @@ -489,10 +490,10 @@ :closed-value-block (and (:logseq.property/created-from-property d) (:logseq.property/value d)) :property-value-block - (:block/uuid d) - :block (= (:db/ident d) :logseq.property/empty-placeholder) :property-value-placeholder + (:block/uuid d) + :block (:db/ident d) :db-ident-key-value)] dispatch-key)) diff --git a/deps/db/src/logseq/db/frontend/schema.cljs b/deps/db/src/logseq/db/frontend/schema.cljs index 3260587a98..eec1ccbf71 100644 --- a/deps/db/src/logseq/db/frontend/schema.cljs +++ b/deps/db/src/logseq/db/frontend/schema.cljs @@ -37,7 +37,7 @@ (map (juxt :major :minor) [(parse-schema-version x) (parse-schema-version y)]))) -(def version (parse-schema-version "64.9")) +(def version (parse-schema-version "64.10")) (defn major-version "Return a number. diff --git a/deps/db/src/logseq/db/sqlite/create_graph.cljs b/deps/db/src/logseq/db/sqlite/create_graph.cljs index a633f74afd..a4819402db 100644 --- a/deps/db/src/logseq/db/sqlite/create_graph.cljs +++ b/deps/db/src/logseq/db/sqlite/create_graph.cljs @@ -201,7 +201,8 @@ (sqlite-util/kv :logseq.kv/graph-initial-schema-version db-schema/version) (sqlite-util/kv :logseq.kv/graph-created-at (common-util/time-ms)) ;; Empty property value used by db.type/ref properties - {:db/ident :logseq.property/empty-placeholder}] + {:db/ident :logseq.property/empty-placeholder + :block/uuid (common-uuid/gen-uuid :builtin-block-uuid :logseq.property/empty-placeholder)}] import-type (into (sqlite-util/import-tx import-type))) initial-files [{:block/uuid (common-uuid/gen-uuid :builtin-block-uuid "logseq/config.edn") diff --git a/deps/db/src/logseq/db/sqlite/export.cljs b/deps/db/src/logseq/db/sqlite/export.cljs index 4418463d8e..9f37dd6b26 100644 --- a/deps/db/src/logseq/db/sqlite/export.cljs +++ b/deps/db/src/logseq/db/sqlite/export.cljs @@ -814,18 +814,23 @@ (defn- patch-invalid-keywords "Fixes invalids keywords whose name start with a number e.g. :user.property/2ndsomething" [m] - (walk/postwalk - (fn [e] - (if (and (keyword? e) (some-> (namespace e) (string/starts-with? "user."))) - ;; Copied from create-db-ident-from-name since this may be shortlived - (let [sanitized-kw (keyword (namespace e) - (->> (string/replace-first (name e) #"^(\d)" "NUM-$1") - (filter #(re-find #"[0-9a-zA-Z*+!_'?<>=-]{1}" %)) - (apply str)))] - ;; (when (not= sanitized-kw e) (prn :sanitize e :-> sanitized-kw)) - (if (not= sanitized-kw e) sanitized-kw e)) - e)) - m)) + (let [initial-version (:kv/value (first (filter #(= :logseq.kv/graph-initial-schema-version (:db/ident %)) + (::kv-values m))))] + ;; Only ignore patch if initial version is > 64.8 since this fix started with 64.9 + (if (some-> initial-version (db-schema/compare-schema-version {:major 64 :minor 8}) pos?) + m + (walk/postwalk + (fn [e] + (if (and (keyword? e) (some-> (namespace e) (string/starts-with? "user."))) + ;; Copied from create-db-ident-from-name since this may be shortlived + (let [sanitized-kw (keyword (namespace e) + (->> (string/replace-first (name e) #"^(\d)" "NUM-$1") + (filter #(re-find #"[0-9a-zA-Z*+!_'?<>=-]{1}" %)) + (apply str)))] + ;; (when (not= sanitized-kw e) (prn :sanitize e :-> sanitized-kw)) + (if (not= sanitized-kw e) sanitized-kw e)) + e)) + m)))) (defn- ensure-export-is-valid "Checks that export map is usable by sqlite.build including checking that diff --git a/resources/forge.config.js b/resources/forge.config.js index 0c46d51073..2c6f974cc3 100644 --- a/resources/forge.config.js +++ b/resources/forge.config.js @@ -5,7 +5,7 @@ module.exports = { packagerConfig: { name: 'Logseq', icon: './icons/logseq_big_sur.icns', - buildVersion: "85", + buildVersion: '87', protocols: [ { "protocol": "logseq", diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index cbb6581b3c..801807f58d 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -58,7 +58,7 @@ $SED -i 's/defonce version ".*"/defonce version "'${NEW_VERSION}'"/g' src/main/f $SED -i 's/"version": ".*"/"version": "'${NEW_VERSION}'"/g' resources/package.json $SED -i 's/versionName ".*"/versionName "'${NEW_VERSION}'"/g' android/app/build.gradle $SED -i 's/versionCode .*/versionCode '${NEW_VERSION_CODE}'/g' android/app/build.gradle -$SED -i 's/buildVersion: .*/buildVersion: '${NEW_VERSION_CODE}',/g' resources/forge.config.js +$SED -i 's/buildVersion: .*/buildVersion: "'${NEW_VERSION_CODE}'",/g' resources/forge.config.js $SED -i 's/MARKETING_VERSION = .*;/MARKETING_VERSION = '${NEW_VERSION}';/g' ios/App/App.xcodeproj/project.pbxproj git --no-pager diff -U0 diff --git a/src/main/frontend/commands.cljs b/src/main/frontend/commands.cljs index 32080083a6..55aae931fd 100644 --- a/src/main/frontend/commands.cljs +++ b/src/main/frontend/commands.cljs @@ -13,12 +13,12 @@ [frontend.handler.notification :as notification] [frontend.handler.plugin :as plugin-handler] [frontend.handler.property.file :as property-file] - [frontend.util.ref :as ref] [frontend.search :as search] [frontend.state :as state] [frontend.util :as util] [frontend.util.cursor :as cursor] [frontend.util.file-based.priority :as priority] + [frontend.util.ref :as ref] [goog.dom :as gdom] [goog.object :as gobj] [logseq.common.config :as common-config] @@ -439,11 +439,10 @@ (println "draw file created, " path)) text)) "Draw a graph with Excalidraw"]) - (when (util/electron?) - ["Upload an asset" - [[:editor/click-hidden-file-input :id]] - "Upload file types like image, pdf, docx, etc.)" - :icon/upload]) + ["Upload an asset" + [[:editor/click-hidden-file-input :id]] + "Upload file types like image, pdf, docx, etc.)" + :icon/upload] ["Template" [[:editor/input command-trigger nil] [:editor/search-template]] "Insert a created template here" @@ -473,7 +472,7 @@ commands) ;; Allow user to modify or extend, should specify how to extend. - + (state/get-commands) (when-let [plugin-commands (seq (some->> (state/get-plugins-slash-commands) (mapv #(vec (concat % [nil :icon/puzzle])))))] diff --git a/src/main/frontend/common/file/core.cljs b/src/main/frontend/common/file/core.cljs index b352dc563b..26bcf2d18b 100644 --- a/src/main/frontend/common/file/core.cljs +++ b/src/main/frontend/common/file/core.cljs @@ -29,27 +29,6 @@ :else content)) -(defn- recur-replace-uuid-in-block-title - "Return block-title" - [ent max-depth] - (let [ref-set (loop [result-refs (:block/refs ent) - current-refs (:block/refs ent) - depth 0] - (if (or (>= depth max-depth) (empty? current-refs)) - result-refs - (let [next-refs (set (mapcat :block/refs current-refs)) - result-refs' (apply conj result-refs next-refs)] - (if (= (count result-refs') (count result-refs)) - result-refs - (recur (apply conj result-refs next-refs) next-refs (inc depth))))))] - (loop [result (db-content/id-ref->title-ref (:block/title ent) ref-set true) - last-result nil - depth 0] - (if (or (>= depth max-depth) - (= last-result result)) - result - (recur (db-content/id-ref->title-ref result ref-set true) result (inc depth)))))) - (defn- transform-content [repo db {:block/keys [collapsed? format pre-block? title page properties] :as b} level {:keys [heading-to-list?]} context] (let [db-based? (sqlite-util/db-based-graph? repo) @@ -60,7 +39,7 @@ markdown? (= :markdown format) title (if db-based? ;; replace [[uuid]] with block's content - (recur-replace-uuid-in-block-title (d/entity db (:db/id b)) 10) + (db-content/recur-replace-uuid-in-block-title (d/entity db (:db/id b))) title) content (or title "") page-first-child? (= (:db/id b) (ldb/get-first-child db (:db/id page))) diff --git a/src/main/frontend/components/block.cljs b/src/main/frontend/components/block.cljs index 7ab62dd52c..2b54c79cde 100644 --- a/src/main/frontend/components/block.cljs +++ b/src/main/frontend/components/block.cljs @@ -656,7 +656,7 @@ All page-names are sanitized except page-name-in-block" [state - {:keys [contents-page? whiteboard-page? html-export? other-position? show-unique-title? stop-click-event? + {:keys [contents-page? whiteboard-page? other-position? show-unique-title? stop-click-event? on-context-menu] :or {stop-click-event? true} :as config} @@ -701,12 +701,13 @@ (reset! *mouse-down? true)))) :on-pointer-up (fn [e] (when @*mouse-down? + (util/stop e) (state/clear-edit!) - (when-not (or (:disable-click? config) - (:disable-redirect? config)) + (when-not (:disable-click? config) (open-page-ref config page-entity e page-name contents-page?)) (reset! *mouse-down? false))) :on-key-up (fn [e] (when (and e (= (.-key e) "Enter") (not other-position?)) + (util/stop e) (state/clear-edit!) (open-page-ref config page-entity e page-name contents-page?)))} on-context-menu @@ -726,7 +727,7 @@ (last child) (let [{:keys [content children]} (last child) page-name (subs content 2 (- (count content) 2))] - (rum/with-key (page-reference html-export? page-name (assoc config :children children) nil) page-name)))) + (rum/with-key (page-reference (assoc config :children children) page-name nil) page-name)))) (let [page-component (cond (and label (string? label) @@ -943,26 +944,22 @@ config (assoc config :block entity)] (cond entity - (if (or (ldb/page? entity) (not (:block/page entity))) - (let [page-name (some-> (:block/title entity) util/page-name-sanity-lc) - whiteboard-page? (model/whiteboard-page? entity) - inner (page-inner (assoc config :whiteboard-page? whiteboard-page?) entity children label) - modal? (shui-dialog/has-modal?)] - (if (and (not (util/mobile?)) - (not= page-name (:id config)) - (not (false? preview?)) - (not disable-preview?) - (not modal?)) - (page-preview-trigger (assoc config :children inner) entity) - inner)) - (block-reference config (:block/uuid entity) - (if (string? label) - (gp-mldoc/inline->edn label (mldoc/get-default-config :markdown)) - label))) + (let [page-name (some-> (:block/title entity) util/page-name-sanity-lc) + whiteboard-page? (model/whiteboard-page? entity) + inner (page-inner (assoc config :whiteboard-page? whiteboard-page?) entity children label) + modal? (shui-dialog/has-modal?)] + (if (and (not (util/mobile?)) + (not= page-name (:id config)) + (not (false? preview?)) + (not disable-preview?) + (not modal?)) + (page-preview-trigger (assoc config :children inner) entity) + inner)) (and (:block/name page) show-non-exists-page?) (page-inner config (merge - {:block/title (:block/name page) + {:block/title (or (:block/title page) + (:block/name page)) :block/name (:block/name page)} page) children label) @@ -977,8 +974,9 @@ (rum/defc page-cp [config page] - (rum/with-key (page-cp-inner config page) - (or (str (:db/id page)) (str (:block/uuid page)) (:block/name page)))) + (let [id (or (:db/id page) (:block/uuid page) (:block/name page))] + (rum/with-key (page-cp-inner config page) + (str id)))) (rum/defc asset-reference [config title path] @@ -1068,58 +1066,62 @@ (declare block-positioned-properties) (rum/defc page-reference < rum/reactive db-mixins/query "Component for page reference" - [html-export? uuid-or-title* {:keys [nested-link? show-brackets? id] :as config} label] + [{:keys [html-export? nested-link? show-brackets? id] :as config*} uuid-or-title* label] (when uuid-or-title* (let [uuid-or-title (if (string? uuid-or-title*) - (string/trim uuid-or-title*) + (let [str-id (string/trim uuid-or-title*)] + (if (util/uuid-string? str-id) + (parse-uuid str-id) + str-id)) uuid-or-title*) - show-brackets? (if (some? show-brackets?) show-brackets? (state/show-brackets?)) - contents-page? (= "contents" (string/lower-case (str id))) - block* (db/get-page uuid-or-title) - block (or (some-> (:db/id block*) db/sub-block) block*) - config' (assoc config - :label (mldoc/plain->text label) - :contents-page? contents-page? - :show-icon? true?) - asset? (some? (:logseq.property.asset/type block)) - page? (ldb/page? block) - brackets? (and (or show-brackets? nested-link?) - (not html-export?) - (not contents-page?) - page?)] - (when-not (= (:db/id block) (:db/id (:block config))) - (cond - (and asset? (img-audio-video? block)) - (asset-cp config block) + self-reference? (when (set? (:ref-set config*)) + (contains? (:ref-set config*) uuid-or-title))] + (when-not self-reference? + (let [config (update config* :ref-set (fn [s] + (let [bid (:block/uuid (:block config*))] + (if (nil? s) + #{bid} + (conj s bid uuid-or-title))))) + show-brackets? (if (some? show-brackets?) show-brackets? (state/show-brackets?)) + contents-page? (= "contents" (string/lower-case (str id))) + block* (db/get-page uuid-or-title) + block (or (some-> (:db/id block*) db/sub-block) block*) + config' (assoc config + :label (mldoc/plain->text label) + :contents-page? contents-page? + :show-icon? true?) + asset? (some? (:logseq.property.asset/type block)) + brackets? (and (or show-brackets? nested-link?) + (not html-export?) + (not contents-page?))] + (when-not (= (:db/id block) (:db/id (:block config))) + (cond + (and asset? (img-audio-video? block)) + (asset-cp config block) - (or page? (:block/tags block)) - [:span.page-reference - {:data-ref (str uuid-or-title)} - (when brackets? - [:span.text-gray-500.bracket page-ref/left-brackets]) - (when (and (config/db-based-graph?) (ldb/class-instance? (db/entity :logseq.class/Task) block)) - [:div.inline-block - {:style {:margin-right 1 - :margin-top -2 - :vertical-align "middle"} - :on-pointer-down (fn [e] - (util/stop e))} - (block-positioned-properties config block :block-left)]) - (page-cp config' (if (uuid? uuid-or-title) - {:block/uuid uuid-or-title} - {:block/name uuid-or-title})) - (when brackets? - [:span.text-gray-500.bracket page-ref/right-brackets])] + (and (string? uuid-or-title) (string/ends-with? uuid-or-title ".excalidraw")) + [:div.draw {:on-click (fn [e] + (.stopPropagation e))} + (excalidraw uuid-or-title (:block/uuid config))] - (and (string? uuid-or-title) (string/ends-with? uuid-or-title ".excalidraw")) - [:div.draw {:on-click (fn [e] - (.stopPropagation e))} - (excalidraw uuid-or-title (:block/uuid config))] - - :else - (page-cp config' (if (uuid? uuid-or-title) - {:block/uuid uuid-or-title} - {:block/name uuid-or-title}))))))) + :else + [:span.page-reference + {:data-ref (str uuid-or-title)} + (when brackets? + [:span.text-gray-500.bracket page-ref/left-brackets]) + (when (and (config/db-based-graph?) (ldb/class-instance? (db/entity :logseq.class/Task) block)) + [:div.inline-block + {:style {:margin-right 1 + :margin-top -2 + :vertical-align "middle"} + :on-pointer-down (fn [e] + (util/stop e))} + (block-positioned-properties config block :block-left)]) + (page-cp config' (if (uuid? uuid-or-title) + {:block/uuid uuid-or-title} + {:block/name uuid-or-title})) + (when brackets? + [:span.text-gray-500.bracket page-ref/right-brackets])]))))))) (defn- latex-environment-content [name option content] @@ -1340,13 +1342,18 @@ (set-block! block))) []) (when-not self-reference? - (if block + (cond + (config/db-based-graph?) + (page-reference config block-id label) + + block (let [config' (update config :ref-set (fn [s] (let [bid (:block/uuid (:block config))] (if (nil? s) #{bid} (conj s bid block-id)))))] (block-reference-aux config' block label)) + :else (invalid-node-ref block-id))))) (defn- render-macro @@ -1475,7 +1482,7 @@ (block-reference config id label)) (not (string/includes? s ".")) - (page-reference (:html-export? config) s config label) + (page-reference config s label) (path/protocol-url? s) (->elem :a {:href s @@ -1507,9 +1514,9 @@ (map-inline config label))) :else - (page-reference (:html-export? config) s config label))) + (page-reference config s label))) -(defn- link-cp [config html-export? link] +(defn- link-cp [config link] (let [{:keys [url label title metadata full_text]} link] (match url ["Block_ref" id] @@ -1533,7 +1540,7 @@ (let [label* (if (seq (mldoc/plain->text label)) label nil)] (if (and (string? page) (string/blank? page)) [:span (ref/->page-ref page)] - (page-reference (:html-export? config) page config label*))))) + (page-reference config page label*))))) ["Embed_data" src] (image-link config url src nil metadata full_text) @@ -1555,7 +1562,7 @@ block (db/entity [:block/uuid id])] (if (:block/pre-block? block) (let [page (:block/page block)] - (page-reference html-export? (:block/name page) config label)) + (page-reference config (:block/name page) label)) (block-reference config (:link path) label))) (= protocol "file") @@ -1977,7 +1984,7 @@ (nested-link config html-export? link) ["Link" link] - (link-cp config html-export? link) + (link-cp config link) [(:or "Verbatim" "Code") s] [:code s] @@ -2052,7 +2059,6 @@ selected? (contains? selected block-id)] (when-not selected? (util/clear-selection!) - (state/conj-selection-block! (gdom/getElement block-id) :down) (editor-handler/highlight-block! uuid))) (editor-handler/block->data-transfer! uuid event false) @@ -2171,7 +2177,7 @@ (reset! *bullet-dragging? true) (util/stop-propagation event) (bullet-drag-start event block uuid block-id)) - :on-drag-end (fn [_] + :on-drag-end (fn [_e] (reset! *bullet-dragging? false)) :blockid (str uuid) :class (str (when collapsed? "bullet-closed") @@ -3039,7 +3045,6 @@ (block-content config block edit-input-id block-id *show-query?)))) (rum/defcs ^:large-vars/cleanup-todo block-content-or-editor < rum/reactive - (rum/local false ::hover?) [state config {:block/keys [uuid] :as block} {:keys [edit-input-id block-id edit? hide-block-refs-count? refs-count *hide-block-refs? *show-query?]}] (let [format (if (config/db-based-graph? (state/get-current-repo)) :markdown @@ -3076,7 +3081,10 @@ :format format} edit-input-id config))] - [:div.flex.flex-1.w-full.block-content-wrapper {:style {:display "flex"}} + [:div.flex.flex-1.w-full.block-content-wrapper + {:style {:display "flex"}} + (when-let [actions-cp (:page-title-actions-cp config)] + (actions-cp block)) (block-content-with-error config block edit-input-id block-id *show-query? editor-box) (when (and (not hide-block-refs-count?) @@ -3182,7 +3190,6 @@ rest) config (assoc config :breadcrumb? true - :disable-redirect? true :disable-preview? true :stop-click-event? false)] (when (seq parents) @@ -3615,8 +3622,6 @@ [:div.flex.flex-col.w-full [:div.block-main-content.flex.flex-row.gap-2 - (when-let [actions-cp (:page-title-actions-cp config)] - (actions-cp block)) (when page-icon page-icon) diff --git a/src/main/frontend/components/block.css b/src/main/frontend/components/block.css index 81ea05d9a2..a02e1bfd80 100644 --- a/src/main/frontend/components/block.css +++ b/src/main/frontend/components/block.css @@ -524,10 +524,16 @@ } } -.block-main-content { +.ls-page-title-container .block-content-wrapper { + .ls-page-title-actions { + @apply absolute -top-4 opacity-0; + left: -2px; + } + &:hover { - & > .db-page-title-actions { + & > .ls-page-title-actions { @apply delay-300 transition-opacity opacity-100; + } } } diff --git a/src/main/frontend/components/class.cljs b/src/main/frontend/components/class.cljs index 63a0f8b8b2..7f613f1607 100644 --- a/src/main/frontend/components/class.cljs +++ b/src/main/frontend/components/class.cljs @@ -13,7 +13,7 @@ (when (seq children) [:ul (for [child (sort-by :block/title children)] - (let [title [:li.ml-2 (block/page-reference false (:block/uuid child) {:show-brackets? false} nil)]] + (let [title [:li.ml-2 (block/page-reference {:show-brackets? false} (:block/uuid child) nil)]] (if (seq (:logseq.property.class/_extends child)) (ui/foldable title diff --git a/src/main/frontend/components/container.cljs b/src/main/frontend/components/container.cljs index 06e85c9f09..79418f9093 100644 --- a/src/main/frontend/components/container.cljs +++ b/src/main/frontend/components/container.cljs @@ -933,8 +933,13 @@ nil) (defn- on-mouse-up - [_e] - (editor-handler/show-action-bar!)) + [e] + (when-not (or (.closest (.-target e) ".block-control-wrap") + (.closest (.-target e) "button") + (.closest (.-target e) "input") + (.closest (.-target e) "textarea") + (.closest (.-target e) "a")) + (editor-handler/show-action-bar!))) (rum/defcs ^:large-vars/cleanup-todo root-container < rum/reactive (mixins/event-mixin diff --git a/src/main/frontend/components/editor.cljs b/src/main/frontend/components/editor.cljs index 8b9612a643..0c37a70f7a 100644 --- a/src/main/frontend/components/editor.cljs +++ b/src/main/frontend/components/editor.cljs @@ -134,6 +134,20 @@ :other-attrs {:block/link (:db/id page')}})))) (page-handler/on-chosen-handler input id pos format))) +(defn- matched-pages-with-new-page [partial-matched-pages db-tag? q] + (if (or (db/page-exists? q (if db-tag? + #{:logseq.class/Tag} + ;; Page existence here should be the same as entity-util/page?. + ;; Don't show 'New page' if a page has any of these tags + db-class/page-classes)) + (and db-tag? (some ldb/class? (:block/_alias (db/get-page q))))) + partial-matched-pages + (if db-tag? + (concat [{:block/title (str (t :new-tag) " " q)}] + partial-matched-pages) + (cons {:block/title (str (t :new-page) " " q)} + partial-matched-pages)))) + (rum/defc page-search-aux [id format embed? db-tag? q current-pos input pos] (let [db-based? (config/db-based-graph? (state/get-current-repo)) @@ -157,25 +171,11 @@ date/nlp-pages) (take 10)))) ;; reorder, shortest and starts-with first. - (let [matched-pages-with-new-page - (fn [partial-matched-pages] - (if (or (db/page-exists? q (if db-tag? - #{:logseq.class/Tag} - ;; Page existence here should be the same as entity-util/page?. - ;; Don't show 'New page' if a page has any of these tags - db-class/page-classes)) - (and db-tag? (some ldb/class? (:block/_alias (db/get-page q))))) - partial-matched-pages - (if db-tag? - (concat [{:block/title (str (t :new-tag) " " q)}] - partial-matched-pages) - (cons {:block/title (str (t :new-page) " " q)} - partial-matched-pages))))] - (if (and (seq matched-pages) - (gstring/caseInsensitiveStartsWith (:block/title (first matched-pages)) q)) - (cons (first matched-pages) - (matched-pages-with-new-page (rest matched-pages))) - (matched-pages-with-new-page matched-pages))))] + (if (and (seq matched-pages) + (gstring/caseInsensitiveStartsWith (:block/title (first matched-pages)) q)) + (cons (first matched-pages) + (matched-pages-with-new-page (rest matched-pages) db-tag? q)) + (matched-pages-with-new-page matched-pages db-tag? q)))] [:<> (ui/auto-complete matched-pages' @@ -184,7 +184,9 @@ (page-handler/page-not-exists-handler input id q current-pos)) :item-render (fn [block _chosen?] (let [block' (if-let [id (:block/uuid block)] - (or (db/entity [:block/uuid id]) block) + (if-let [e (db/entity [:block/uuid id])] + (assoc e :block/title (:block/title block)) + block) block)] [:div.flex.flex-col (when (and (:block/uuid block') (:block/parent block')) @@ -218,10 +220,11 @@ (ui/icon "letter-n" {:size 14}))]) (let [title (if db-tag? - (let [target (first (:block/_alias block'))] + (let [target (first (:block/_alias block')) + title (:block/title block)] (if (ldb/class? target) - (str (:block/title block') " -> alias: " (:block/title target)) - (:block/title block'))) + (str title " -> alias: " (:block/title target)) + title)) (block-handler/block-unique-title block'))] (search-handler/highlight-exact-query title q))]])) :empty-placeholder [:div.text-gray-500.text-sm.px-4.py-2 (if db-tag? diff --git a/src/main/frontend/components/export.cljs b/src/main/frontend/components/export.cljs index b439005cd9..f3901dd346 100644 --- a/src/main/frontend/components/export.cljs +++ b/src/main/frontend/components/export.cljs @@ -12,7 +12,6 @@ [frontend.handler.export.opml :as export-opml] [frontend.handler.export.text :as export-text] [frontend.handler.notification :as notification] - [frontend.idb :as idb] [frontend.image :as image] [frontend.mobile.util :as mobile-util] [frontend.state :as state] @@ -33,51 +32,55 @@ [:div.flex.flex-col.gap-4 [:div.font-medium.opacity-50 "Schedule backup"] - (if backup-folder - [:div.flex.flex-row.items-center.gap-1.text-sm - [:div.opacity-50 (str "Backup folder:")] - backup-folder - (shui/button - {:variant :ghost - :class "!px-1 !py-1" - :title "Change backup folder" - :on-click (fn [] - (p/do! - (db/transact! [[:db/retractEntity :logseq.kv/graph-backup-folder]]) - (reset! *backup-folder nil))) - :size :sm} - (ui/icon "edit"))] - (shui/button - {:variant :default - :on-click (fn [] - (p/let [result (utils/openDirectory #js {:mode "readwrite"}) - handle (first result) - folder-name (.-name handle)] - (idb/set-item! - (str "handle/" (js/btoa repo) "/" folder-name) handle) - (db/transact! [(ldb/kv :logseq.kv/graph-backup-folder folder-name)]) - (reset! *backup-folder folder-name)))} - "Set backup folder first")) - [:div.opacity-50.text-sm - "Backup will be created every hour."] + (if (utils/nfsSupported) + [:<> + (if backup-folder + [:div.flex.flex-row.items-center.gap-1.text-sm + [:div.opacity-50 (str "Backup folder:")] + backup-folder + (shui/button + {:variant :ghost + :class "!px-1 !py-1" + :title "Change backup folder" + :on-click (fn [] + (p/do! + (db/transact! [[:db/retractEntity :logseq.kv/graph-backup-folder]]) + (reset! *backup-folder nil))) + :size :sm} + (ui/icon "edit"))] + (shui/button + {:variant :default + :on-click (fn [] + (p/let [[folder-name _handle] (export/choose-backup-folder repo)] + (reset! *backup-folder folder-name)))} + "Set backup folder first")) + [:div.opacity-50.text-sm + "Backup will be created every hour."] - (when backup-folder - (shui/button - {:variant :default - :on-click (fn [] - (-> - (p/let [result (export/backup-db-graph repo)] - (case result - true - (notification/show! "Backup successful!" :success) - :graph-not-changed - (notification/show! "Graph has not been updated since last export." :success) - nil) - (export/auto-db-backup! repo {:backup-now? false})) - (p/catch (fn [error] - (println "Failed to backup.") - (js/console.error error)))))} - "Backup now"))])) + (when backup-folder + (shui/button + {:variant :default + :on-click (fn [] + (-> + (p/let [result (export/backup-db-graph repo :set-folder)] + (case result + true + (notification/show! "Backup successful!" :success) + :graph-not-changed + (notification/show! "Graph has not been updated since last export." :success) + nil) + (export/auto-db-backup! repo {:backup-now? false})) + (p/catch (fn [error] + (println "Failed to backup.") + (js/console.error error)))))} + "Backup now"))] + [:div + [:span "Your browser doesn't support "] + [:a + {:href "https://developer.chrome.com/docs/capabilities/web-apis/file-system-access" + :target "_blank"} + "The File System Access API"] + [:span ", please switch to a Chromium-based browser."]])])) (rum/defc export [] @@ -133,7 +136,7 @@ "Export debug transit file"] [:p.text-sm.opacity-70.mb-0 "Any sensitive data will be removed in the exported transit file, you can send it to us for debugging."]]) - (when (and db-based? util/web-platform? (utils/nfsSupported)) + (when (and db-based? util/web-platform?) [:div [:hr] (auto-backup)])]]))) diff --git a/src/main/frontend/components/file_based/hierarchy.cljs b/src/main/frontend/components/file_based/hierarchy.cljs index 6a1f92f359..d3178bb603 100644 --- a/src/main/frontend/components/file_based/hierarchy.cljs +++ b/src/main/frontend/components/file_based/hierarchy.cljs @@ -60,10 +60,7 @@ (when (and (string? page) page) (let [full-page (->> (take (inc idx) namespace) util/string-join-path)] - (block/page-reference false - full-page - {} - page)))) + (block/page-reference {} full-page page)))) (interpose [:span.mx-2.opacity-30 "/"]))])] {:default-collapsed? false :title-trigger? true})]))) diff --git a/src/main/frontend/components/header.cljs b/src/main/frontend/components/header.cljs index d5c398d968..7a9a036695 100644 --- a/src/main/frontend/components/header.cljs +++ b/src/main/frontend/components/header.cljs @@ -402,8 +402,10 @@ (str "collab-" current-repo)) (rtc-indicator/indicator)]) - (when (user-handler/logged-in?) - (rtc-indicator/downloading-detail)) + (when (user-handler/logged-in?) + (rtc-indicator/downloading-detail)) + (when (user-handler/logged-in?) + (rtc-indicator/uploading-detail)) (when (and current-repo (not (config/demo-graph? current-repo)) diff --git a/src/main/frontend/components/icon.cljs b/src/main/frontend/components/icon.cljs index ffb6e856a3..787a5f9239 100644 --- a/src/main/frontend/components/icon.cljs +++ b/src/main/frontend/components/icon.cljs @@ -29,9 +29,10 @@ opts (dissoc opts :color?) item (cond (and (= :emoji (:type icon')) (:id icon')) - [:em-emoji (merge {:id (:id icon') - :style {:line-height 1}} - opts)] + [:span.ui__icon + [:em-emoji (merge {:id (:id icon') + :style {:line-height 1}} + opts)]] (and (= :tabler-icon (:type icon')) (:id icon')) (ui/icon (:id icon') opts))] diff --git a/src/main/frontend/components/page.cljs b/src/main/frontend/components/page.cljs index 9b1cbc783f..935b867c19 100644 --- a/src/main/frontend/components/page.cljs +++ b/src/main/frontend/components/page.cljs @@ -420,11 +420,11 @@ (rum/defc db-page-title-actions [page] - [:div.absolute.-top-4.left-0.opacity-0.db-page-title-actions + [:div.ls-page-title-actions [:div.flex.flex-row.items-center.gap-2 (when-not (:logseq.property/icon (db/entity (:db/id page))) (shui/button - {:variant :outline + {:variant :ghost :size :sm :class "px-2 py-0 h-6 text-xs text-muted-foreground" :on-click (fn [e] @@ -434,7 +434,7 @@ "Add icon")) (shui/button - {:variant :outline + {:variant :ghost :size :sm :class "px-2 py-0 h-6 text-xs text-muted-foreground" :on-click (fn [e] diff --git a/src/main/frontend/components/page.css b/src/main/frontend/components/page.css index 85b65d2e80..9e19a66f58 100644 --- a/src/main/frontend/components/page.css +++ b/src/main/frontend/components/page.css @@ -92,7 +92,7 @@ } } - .db-page-title-actions { + .ls-page-title-actions { &:has(button[data-popup-active]) { @apply opacity-100; } diff --git a/src/main/frontend/components/property/value.cljs b/src/main/frontend/components/property/value.cljs index 66503aec55..40f9c66486 100644 --- a/src/main/frontend/components/property/value.cljs +++ b/src/main/frontend/components/property/value.cljs @@ -33,6 +33,7 @@ [lambdaisland.glogi :as log] [logseq.common.util.macro :as macro-util] [logseq.db :as ldb] + [logseq.db.frontend.content :as db-content] [logseq.db.frontend.entity-util :as entity-util] [logseq.db.frontend.property :as db-property] [logseq.db.frontend.property.type :as db-property-type] @@ -748,7 +749,7 @@ id (:db/id node) [header label] (if (integer? id) (let [node-title (if (seq (:logseq.property/classes property)) - (:block/title node) + (db-content/recur-replace-uuid-in-block-title node) (block-handler/block-unique-title node)) title (subs node-title 0 256) node (or (db/entity id) node) @@ -756,7 +757,7 @@ header (when-not (db/page? node) (when-let [breadcrumb (state/get-component :block/breadcrumb)] [:div.text-xs.opacity-70 - (breadcrumb {:search? true} (state/get-current-repo) (:block/uuid block) {})])) + (breadcrumb {:search? true} (state/get-current-repo) (:block/uuid node) {})])) label [:div.flex.flex-row.items-center.gap-1 (when-not (or (:logseq.property/classes property) (= (:db/ident property) :block/tags)) @@ -1092,10 +1093,7 @@ (closed-value-item value opts) (or (entity-util/page? value) - (and (seq (:block/tags value)) - ;; FIXME: page-cp should be renamed to node-cp and - ;; support this case and maybe other complex cases. - (not (string/includes? (:block/title value) "[[")))) + (seq (:block/tags value))) (when value (let [opts {:disable-preview? true :tag? tag? @@ -1158,7 +1156,7 @@ (if editing? (popup-content nil) (let [show! (fn [e] - (util/stop e) + (state/clear-selection!) (let [target (when e (.-target e))] (when-not (or config/publishing? (util/shift-key? e) @@ -1173,7 +1171,7 @@ {:ref *el :id trigger-id :tabIndex 0 - :on-click show! + :on-pointer-down show! :on-key-down (fn [e] (case (util/ekey e) ("Backspace" "Delete") diff --git a/src/main/frontend/components/right_sidebar.cljs b/src/main/frontend/components/right_sidebar.cljs index 66986c4e3d..790fcd7aae 100644 --- a/src/main/frontend/components/right_sidebar.cljs +++ b/src/main/frontend/components/right_sidebar.cljs @@ -38,10 +38,11 @@ (rum/defc block-cp < rum/reactive [repo idx block] (let [id (:block/uuid block)] - (page/page-cp {:parameters {:path {:name (str id)}} - :sidebar? true - :sidebar/idx idx - :repo repo}))) + [:div.mt-2 + (page/page-cp {:parameters {:path {:name (str id)}} + :sidebar? true + :sidebar/idx idx + :repo repo})])) (defn get-scrollable-container [] @@ -68,77 +69,89 @@ :sidebar-key sidebar-key} repo block-id {:indent? false})] (block-cp repo idx block)])) +(rum/defc search-title < rum/reactive + [*input] + (let [input (rum/react *input) + input' (if (string/blank? input) "Blank input" input)] + [:span.overflow-hidden.text-ellipsis input'])) + +(rum/defc sidebar-search + [repo block-type init-key input *input] + (rum/with-key + (cmdk/cmdk-block {:initial-input input + :sidebar? true + :on-input-change (fn [new-value] + (reset! *input new-value)) + :on-input-blur (fn [new-value] + (state/sidebar-replace-block! [repo input block-type] + [repo new-value block-type]))}) + (str init-key))) + (defn- + (p/do! + (when-not (contains? #{:contents :search} block-type) + (db-async/ 600 ;; 10min - (/ (- (t/now) created-at) 1000))))) - -(defn- uploading? - [detail-info] - (when-let [{:keys [created-at sub-type]} (first (:upload-logs detail-info))] - (and (not= :upload-completed sub-type) - (> 600 - (/ (- (t/now) created-at) 1000))))) +(rum/defc indicator + [] + (let [detail-info (hooks/use-flow-state (m/watch *detail-info)) + _ (hooks/use-flow-state flows/current-login-user-flow) + online? (hooks/use-flow-state flows/network-online-event-flow) + rtc-state (:rtc-state detail-info) + unpushed-block-update-count (:pending-local-ops detail-info) + {:keys [local-tx remote-tx]} detail-info] + [:div.cp__rtc-sync + [:div.hidden {"data-testid" "rtc-tx"} (pr-str {:local-tx local-tx :remote-tx remote-tx})] + [:div.cp__rtc-sync-indicator.flex.flex-row.items-center.gap-1 + (shui/button-ghost-icon :cloud + {:on-click #(shui/popup-show! (.-target %) + (details online?) + {:align "end"}) + :class (util/classnames [{:cloud true + :on (and online? (= :open rtc-state)) + :idle (and online? (= :open rtc-state) (zero? unpushed-block-update-count)) + :queuing (pos? unpushed-block-update-count)}])})]])) (def ^:private *accumulated-download-logs (atom [])) (c.m/run-background-task @@ -163,6 +168,17 @@ (swap! *accumulated-download-logs (fn [logs] (take 20 (conj logs log))))))) rtc-flows/rtc-download-log-flow)) +(def ^:private *accumulated-upload-logs (atom [])) +(c.m/run-background-task + ::update-accumulated-upload-logs + (m/reduce + (fn [_ log] + (when log + (if (= :upload-completed (:sub-type log)) + (reset! *accumulated-upload-logs []) + (swap! *accumulated-upload-logs (fn [logs] (take 20 (conj logs log))))))) + rtc-flows/rtc-upload-log-flow)) + (defn- accumulated-logs-flow [*acc-logs] (->> (m/watch *acc-logs) @@ -181,39 +197,14 @@ (for [log download-logs] [:div (:message log)])]))) -(rum/defc indicator +(rum/defc uploading-logs [] - (let [detail-info (hooks/use-flow-state (m/watch *detail-info)) - _ (hooks/use-flow-state flows/current-login-user-flow) - online? (hooks/use-flow-state flows/network-online-event-flow) - uploading?' (uploading? detail-info) - downloading?' (downloading? detail-info) - rtc-state (:rtc-state detail-info) - unpushed-block-update-count (:pending-local-ops detail-info) - {:keys [local-tx remote-tx]} detail-info] - [:div.cp__rtc-sync - [:div.hidden {"data-testid" "rtc-tx"} (pr-str {:local-tx local-tx :remote-tx remote-tx})] - [:div.cp__rtc-sync-indicator.flex.flex-row.items-center.gap-1 - (when downloading?' - (shui/button - {:class "opacity-50" - :variant :ghost - :size :sm} - "Downloading...")) - (when uploading?' - (shui/button - {:class "opacity-50" - :variant :ghost - :size :sm} - "Uploading...")) - (shui/button-ghost-icon :cloud - {:on-click #(shui/popup-show! (.-target %) - (details online?) - {:align "end"}) - :class (util/classnames [{:cloud true - :on (and online? (= :open rtc-state)) - :idle (and online? (= :open rtc-state) (zero? unpushed-block-update-count)) - :queuing (pos? unpushed-block-update-count)}])})]])) + (let [upload-logs-flow (accumulated-logs-flow *accumulated-upload-logs) + upload-logs (hooks/use-flow-state upload-logs-flow)] + (when (seq upload-logs) + [:div + (for [log upload-logs] + [:div (:message log)])]))) (def ^:private downloading?-flow (->> rtc-flows/rtc-download-log-flow @@ -231,3 +222,20 @@ (downloading-logs) {:align "end"})} "Downloading..."))) + +(def ^:private upload?-flow + (->> rtc-flows/rtc-upload-log-flow + (m/eduction (map (fn [log] (not= :upload-completed (:sub-type log))))) + (c.m/continue-flow false))) + +(rum/defc uploading-detail + [] + (when (true? (hooks/use-flow-state upload?-flow)) + (shui/button + {:class "opacity-50" + :variant :ghost + :size :sm + :on-click #(shui/popup-show! (.-target %) + (uploading-logs) + {:align "end"})} + "Uploading..."))) diff --git a/src/main/frontend/db/model.cljs b/src/main/frontend/db/model.cljs index dfe1b875d9..714e6cb3e4 100644 --- a/src/main/frontend/db/model.cljs +++ b/src/main/frontend/db/model.cljs @@ -99,7 +99,7 @@ independent of format as format specific heading characters are stripped" (let [block (db-utils/entity repo block-id) ref-tags (distinct (concat (:block/tags block) (:block/refs block)))] (= (-> block-content - (db-content/id-ref->title-ref ref-tags true) + (db-content/id-ref->title-ref ref-tags) (db-content/content-id-ref->page ref-tags) heading-content->route-name) (string/lower-case external-content)))) diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index 2303d0bfcc..608fd4868b 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/src/main/frontend/handler/editor.cljs @@ -1252,15 +1252,22 @@ (defonce *action-bar-timeout (atom nil)) +(defn popup-exists? + [id] + (some->> (shui-popup/get-popups) + (some #(some-> % (:id) (str) (string/includes? (str id)))))) + (defn show-action-bar! [& {:keys [delay] :or {delay 200}}] - (when (config/db-based-graph?) + (when (and (config/db-based-graph?) (not (popup-exists? :selection-action-bar))) (when-let [timeout @*action-bar-timeout] (js/clearTimeout timeout)) (state/pub-event! [:editor/hide-action-bar]) - (let [timeout (js/setTimeout #(state/pub-event! [:editor/show-action-bar]) delay)] - (reset! *action-bar-timeout timeout)))) + (when (seq (remove (fn [b] (dom/has-class? b "ls-table-cell")) + (state/get-selection-blocks))) + (let [timeout (js/setTimeout #(state/pub-event! [:editor/show-action-bar]) delay)] + (reset! *action-bar-timeout timeout))))) (defn- select-block-up-down [direction] @@ -3333,11 +3340,6 @@ (cursor/select-up-down input direction anchor cursor-rect))) (select-block-up-down direction)))) -(defn popup-exists? - [id] - (some->> (shui-popup/get-popups) - (some #(some-> % (:id) (str) (string/includes? (str id)))))) - (defn editor-commands-popup-exists? [] (popup-exists? "editor.commands")) diff --git a/src/main/frontend/handler/events.cljs b/src/main/frontend/handler/events.cljs index dea7c8bea4..816b32f645 100644 --- a/src/main/frontend/handler/events.cljs +++ b/src/main/frontend/handler/events.cljs @@ -196,14 +196,15 @@ (defmethod handle :capture-error [[_ {:keys [error payload]}]] (let [[user-uuid graph-uuid tx-id] @sync/graphs-txid - payload (assoc payload - :user-id user-uuid - :graph-id graph-uuid - :tx-id tx-id - :db-based (config/db-based-graph? (state/get-current-repo)) - :schema-version (str db-schema/version) - :db-schema-version (when-let [db (frontend.db/get-db)] - (str (:kv/value (frontend.db/entity db :logseq.kv/schema-version)))))] + payload (merge + {:schema-version (str db-schema/version) + :db-schema-version (when-let [db (frontend.db/get-db)] + (str (:kv/value (frontend.db/entity db :logseq.kv/schema-version)))) + :user-id user-uuid + :graph-id graph-uuid + :tx-id tx-id + :db-based (config/db-based-graph? (state/get-current-repo))} + payload)] (Sentry/captureException error (bean/->js {:tags payload})))) diff --git a/src/main/frontend/handler/export.cljs b/src/main/frontend/handler/export.cljs index e79c926f7e..43cf57e89e 100644 --- a/src/main/frontend/handler/export.cljs +++ b/src/main/frontend/handler/export.cljs @@ -250,11 +250,33 @@ (.remove (.-handle file)))) old-versioned-files))) -(defn backup-db-graph +(defn choose-backup-folder [repo] + (p/let [result (utils/openDirectory #js {:mode "readwrite"}) + handle (first result) + folder-name (.-name handle)] + (js/console.dir handle) + (idb/set-item! + (str "handle/" (js/btoa repo) "/" folder-name) handle) + (db/transact! [(ldb/kv :logseq.kv/graph-backup-folder folder-name)]) + [folder-name handle])) + +(defn backup-db-graph + [repo _backup-type] (when (and repo (= repo (state/get-current-repo))) (when-let [backup-folder (ldb/get-key-value (db/get-db repo) :logseq.kv/graph-backup-folder)] - (p/let [handle (idb/get-item (str "handle/" (js/btoa repo) "/" backup-folder)) + ;; ensure file handle exists + ;; ask user to choose a folder again when access expires + (p/let [handle (try + (idb/get-item (str "handle/" (js/btoa repo) "/" backup-folder)) + (catch :default _e + (throw (ex-info "Backup file handle no longer exists" {:repo repo})))) + [_folder handle] (try + (utils/verifyPermission handle true) + [backup-folder handle] + (catch :default e + (js/console.error e) + (choose-backup-folder repo))) repo-name (common-sqlite/sanitize-db-name repo)] (if handle (-> @@ -297,9 +319,9 @@ (when (and (config/db-based-graph? repo) util/web-platform? (utils/nfsSupported)) (cancel-db-backup!) - (when backup-now? (backup-db-graph repo)) + (when backup-now? (backup-db-graph repo :backup-now)) ;; run backup every hour - (let [interval (js/setInterval #(backup-db-graph repo) + (let [interval (js/setInterval #(backup-db-graph repo :auto) (* 1 60 60 1000))] (reset! *backup-interval interval))))) diff --git a/src/main/frontend/handler/repo.cljs b/src/main/frontend/handler/repo.cljs index 0ea306a6a8..90b2a50384 100644 --- a/src/main/frontend/handler/repo.cljs +++ b/src/main/frontend/handler/repo.cljs @@ -210,3 +210,7 @@ {:content (str "The graph '" graph "' already exists. Please try again with another name.") :status :error}]) (create-db full-graph-name opts))))) + +(defn fix-broken-graph! + [graph] + (state/> result - ;; remove built-in properties from content + ;; remove built-in properties from content (map #(update % :content (fn [content] diff --git a/src/main/frontend/state.cljs b/src/main/frontend/state.cljs index e8ce5cdbe5..150e120a64 100644 --- a/src/main/frontend/state.cljs +++ b/src/main/frontend/state.cljs @@ -1877,7 +1877,7 @@ Similar to re-frame subscriptions" (if (and page ;; TODO: Use config/dev? when it's not a circular dep (not goog.DEBUG) - (or (ldb/hidden? page) + (or (and (ldb/hidden? page) (not (ldb/property? page))) (and (ldb/built-in? page) (ldb/private-built-in-page? page)))) (pub-event! [:notification/show {:content "Cannot open an internal page." :status :warning}]) (when db-id diff --git a/src/main/frontend/ui.css b/src/main/frontend/ui.css index c2a102ef54..e3b5d9e3bd 100644 --- a/src/main/frontend/ui.css +++ b/src/main/frontend/ui.css @@ -275,6 +275,17 @@ html.is-mobile { display: inline-block; } +.ui__icon svg { + filter: brightness(0.8); + transition: filter .15s; + will-change: filter; +} + +.ui__icon:hover svg { + filter: brightness(1); + transition-duration: .15s; +} + .type-icon { @apply text-base text-center flex items-center justify-center rounded border mr-2 relative; diff --git a/src/main/frontend/util.cljc b/src/main/frontend/util.cljc index 7a3aef697e..868d85bb7d 100644 --- a/src/main/frontend/util.cljc +++ b/src/main/frontend/util.cljc @@ -790,6 +790,11 @@ (->> blocks (remove (fn [b] (= "true" (d/attr b "data-embed"))))))) +#?(:cljs + (defn remove-property-value-blocks [blocks] + (->> blocks + (remove (fn [b] (d/has-class? b "property-value-container")))))) + #?(:cljs (defn get-selected-text [] @@ -898,7 +903,8 @@ (defn get-prev-block-non-collapsed-non-embed [block] (when-let [blocks (->> (get-blocks-noncollapse) - remove-embedded-blocks)] + remove-embedded-blocks + remove-property-value-blocks)] (when-let [index (.indexOf blocks block)] (let [idx (dec index)] (when (>= idx 0) diff --git a/src/main/frontend/worker/db/migrate.cljs b/src/main/frontend/worker/db/migrate.cljs index 2225c09ae2..402a8833e2 100644 --- a/src/main/frontend/worker/db/migrate.cljs +++ b/src/main/frontend/worker/db/migrate.cljs @@ -906,6 +906,12 @@ move-top-parents-to-library update-children-parent-and-order))))) + +(defn- empty-placeholder-add-block-uuid + [_conn _search-db] + [{:db/ident :logseq.property/empty-placeholder + :block/uuid (common-uuid/gen-uuid :builtin-block-uuid :logseq.property/empty-placeholder)}]) + (def ^:large-vars/cleanup-todo schema-version->updates "A vec of tuples defining datascript migrations. Each tuple consists of the schema version integer and a migration map. A migration map can have keys of :properties, :classes @@ -1005,8 +1011,8 @@ [62 {:fix remove-block-schema}] [63 {:properties [:logseq.property.table/pinned-columns]}] [64 {:fix update-view-filter}] - ;;;; schema-version format: "." - ;;;; int number equals to "" (without ) +;;;; schema-version format: "." +;;;; int number equals to "" (without ) ["64.1" {:properties [:logseq.property.view/group-by-property] :fix add-view-icons}] ["64.2" {:properties [:logseq.property.view/feature-type] @@ -1018,7 +1024,8 @@ ["64.6" {:fix cardinality-one-multiple-values}] ["64.7" {:fix rename-repeated-properties}] ["64.8" {:fix rename-task-properties}] - ["64.9" {:fix fix-rename-parent-to-extends}]]) + ["64.9" {:fix empty-placeholder-add-block-uuid}] + ["64.10" {:fix fix-rename-parent-to-extends}]]) (let [[major minor] (last (sort (map (comp (juxt :major :minor) db-schema/parse-schema-version first) schema-version->updates))) diff --git a/src/main/frontend/worker/db_worker.cljs b/src/main/frontend/worker/db_worker.cljs index 884ebed626..c93f8b3d51 100644 --- a/src/main/frontend/worker/db_worker.cljs +++ b/src/main/frontend/worker/db_worker.cljs @@ -100,39 +100,43 @@ [^js pool data] (.importDb ^js pool repo-path data)) -(comment - (defn- get-all-datoms-from-sqlite-db - [db] - (some->> (.exec db #js {:sql "select * from kvs" - :rowMode "array"}) - bean/->clj - (mapcat - (fn [[_addr content _addresses]] - (let [content' (sqlite-util/transit-read content) - datoms (when (map? content') - (:keys content'))] - datoms))) - distinct - (map (fn [[e a v t]] - (d/datom e a v t))))) +(defn- get-all-datoms-from-sqlite-db + [db] + (some->> (.exec db #js {:sql "select * from kvs" + :rowMode "array"}) + bean/->clj + (mapcat + (fn [[_addr content _addresses]] + (let [content' (sqlite-util/transit-read content) + datoms (when (map? content') + (:keys content'))] + datoms))) + distinct + (map (fn [[e a v t]] + (d/datom e a v t))))) - (defn- rebuild-db-from-datoms! - "Persistent-sorted-set has been broken, used addresses can't be found" - [datascript-conn sqlite-db import-type] - (let [datoms (get-all-datoms-from-sqlite-db sqlite-db) - db (d/init-db [] db-schema/schema - {:storage (storage/storage @datascript-conn)}) - db (d/db-with db - (map (fn [d] - [:db/add (:e d) (:a d) (:v d) (:t d)]) datoms))] - (prn :debug :rebuild-db-from-datoms :datoms-count (count datoms)) - ;; export db first - (when-not import-type - (worker-util/post-message :notification ["The SQLite db will be exported to avoid any data-loss." :warning false]) - (worker-util/post-message :export-current-db [])) - (.exec sqlite-db #js {:sql "delete from kvs"}) - (d/reset-conn! datascript-conn db) - (db-migrate/fix-db! datascript-conn)))) +(defn- rebuild-db-from-datoms! + "Persistent-sorted-set has been broken, used addresses can't be found" + [datascript-conn sqlite-db] + (let [datoms (get-all-datoms-from-sqlite-db sqlite-db) + db (d/init-db [] db-schema/schema + {:storage (storage/storage @datascript-conn)}) + db (d/db-with db + (map (fn [d] + [:db/add (:e d) (:a d) (:v d) (:t d)]) datoms))] + (prn :debug :rebuild-db-from-datoms :datoms-count (count datoms)) + (worker-util/post-message :notification ["The SQLite db will be exported to avoid any data-loss." :warning false]) + (worker-util/post-message :export-current-db []) + (.exec sqlite-db #js {:sql "delete from kvs"}) + (d/reset-conn! datascript-conn db) + (db-migrate/fix-db! datascript-conn))) + +(defn- fix-broken-graph + [graph] + (let [conn (worker-state/get-datascript-conn graph) + sqlite-db (worker-state/get-sqlite-conn graph)] + (when (and conn sqlite-db) + (rebuild-db-from-datoms! conn sqlite-db)))) (comment (defn- gc-kvs-table! @@ -180,9 +184,25 @@ (when (and compare-result (not (neg? compare-result))) ; >= 64.8 (worker-util/post-message :capture-error {:error "db-missing-addresses-v2" - :payload {:missing-addresses missing-addresses}})))) + :payload {:missing-addresses (str missing-addresses) + :db-schema-version (str version-in-db)}})))) missing-addresses)) +(def get-to-delete-unused-addresses-sql + "WITH to_delete(addr) AS ( + SELECT value + FROM json_each(?) + ), + referenced(addr) AS ( + SELECT json_each.value + FROM kvs + JOIN json_each(kvs.addresses) + WHERE kvs.addr NOT IN (SELECT addr FROM to_delete) + AND json_each.value IN (SELECT addr FROM to_delete) + ) + SELECT addr FROM to_delete + WHERE addr NOT IN (SELECT addr FROM referenced)") + (defn upsert-addr-content! "Upsert addr+data-seq. Update sqlite-cli/upsert-addr-content! when making changes" [db data delete-addrs*] @@ -193,19 +213,19 @@ (.exec tx #js {:sql "INSERT INTO kvs (addr, content, addresses) values ($addr, $content, $addresses) on conflict(addr) do update set content = $content, addresses = $addresses" :bind item})))) (when (seq delete-addrs) - (.transaction db (fn [tx] - (doseq [addr delete-addrs] - (.exec tx #js {:sql "Delete from kvs WHERE addr = ? AND NOT EXISTS (SELECT 1 FROM json_each(addresses) WHERE value = ?);" - :bind #js [addr]})))) + (let [result (.exec db #js {:sql get-to-delete-unused-addresses-sql + :bind (js/JSON.stringify (clj->js delete-addrs)) + :rowMode "array"}) + non-refed-addrs (map #(aget % 0) result)] + (when (seq non-refed-addrs) + (.transaction db (fn [tx] + (doseq [addr non-refed-addrs] + (.exec tx #js {:sql "Delete from kvs where addr = ?" + :bind #js [addr]})))))) (let [missing-addrs (when worker-util/dev? (seq (find-missing-addresses nil db {:delete-addrs delete-addrs})))] - (if (seq missing-addrs) - (worker-util/post-message :notification [(str "Bug!! Missing addresses: " missing-addrs) :error false]) - (when (seq delete-addrs) - (.transaction db (fn [tx] - (doseq [addr delete-addrs] - (.exec tx #js {:sql "Delete from kvs WHERE addr = ? AND NOT EXISTS (SELECT 1 FROM json_each(addresses) WHERE value = ?);" - :bind #js [addr]})))))))))) + (when (seq missing-addrs) + (worker-util/post-message :notification [(str "Bug!! Missing addresses: " missing-addrs) :error false])))))) (defn restore-data-from-addr "Update sqlite-cli/restore-data-from-addr when making changes" @@ -771,6 +791,10 @@ [repo] (get-all-page-titles-with-cache repo)) +(def-thread-api :thread-api/fix-broken-graph + [graph] + (fix-broken-graph graph)) + (comment (def-thread-api :general/dangerousRemoveAllDbs [] diff --git a/src/main/frontend/worker/search.cljs b/src/main/frontend/worker/search.cljs index 3923badf5c..51831fed16 100644 --- a/src/main/frontend/worker/search.cljs +++ b/src/main/frontend/worker/search.cljs @@ -10,6 +10,7 @@ [logseq.common.util :as common-util] [logseq.common.util.namespace :as ns-util] [logseq.db :as ldb] + [logseq.db.frontend.content :as db-content] [logseq.db.sqlite.util :as sqlite-util] [logseq.graph-parser.text :as text])) @@ -193,7 +194,8 @@ DROP TRIGGER IF EXISTS blocks_au; (defn- page-or-object? [entity] (and (or (ldb/page? entity) (ldb/object? entity)) - (not (ldb/hidden? entity)))) + (not (ldb/hidden? entity)) + (not (ldb/hidden? (:block/page entity))))) (defn get-all-fuzzy-supported-blocks "Only pages and objects are supported now." @@ -205,7 +207,9 @@ DROP TRIGGER IF EXISTS blocks_au; (map :e))) blocks (->> (distinct (concat page-ids object-ids)) (map #(d/entity db %)))] - (remove ldb/hidden? blocks))) + (->> blocks + (remove ldb/hidden?) + (remove #(ldb/hidden? (:block/page %)))))) (defn- sanitize [content] @@ -219,13 +223,9 @@ DROP TRIGGER IF EXISTS blocks_au; (ldb/closed-value? block) (and (string? title) (> (count title) 10000)) (string/blank? title)) ; empty page or block - ;; Should properties be included in the search indice? - ;; It could slow down the search indexing, also it can be confusing - ;; if the showing properties are not useful to users. - ;; (let [content (if (and db-based? (seq (:block/properties block))) - ;; (str content (when (not= content "") "\n") (get-db-properties-str db properties)) - ;; content)]) - (let [title (ldb/get-title-with-parents (assoc block :block.temp/search? true))] + (let [title (-> block + (update :block/title ldb/get-title-with-parents) + db-content/recur-replace-uuid-in-block-title)] (when uuid {:id (str uuid) :page (str (or (:block/uuid page) uuid)) @@ -370,11 +370,11 @@ DROP TRIGGER IF EXISTS blocks_au; set) blocks-to-add-set)] {:blocks-to-remove (->> - (keep #(d/entity db-before %) blocks-to-remove-set) - (remove ldb/hidden?)) + (keep #(d/entity db-before %) blocks-to-remove-set)) :blocks-to-add (->> (keep #(d/entity db-after %) blocks-to-add-set') - (remove ldb/hidden?))}))) + (remove ldb/hidden?) + (remove #(ldb/hidden? (:block/page %))))}))) (defn- get-affected-blocks [repo tx-report] diff --git a/src/resources/dicts/en.edn b/src/resources/dicts/en.edn index 65cb62bbb1..04159e43f1 100644 --- a/src/resources/dicts/en.edn +++ b/src/resources/dicts/en.edn @@ -786,4 +786,5 @@ :dev/show-page-ast "(Dev) Show page AST" :dev/replace-graph-with-db-file "(Dev) Replace graph with its db.sqlite file" :dev/validate-db "(Dev) Validate current graph" + :dev/fix-broken-graph "(Dev) Fix current broken graph" :window/close "Close window"}}