From b5eeb826890a705ffab587569bacc79cbe2c6c2a Mon Sep 17 00:00:00 2001 From: Tienson Qin Date: Tue, 5 May 2026 20:35:11 +0800 Subject: [PATCH] fix: update markdown mirror --- .../src/logseq/cli/common/export/common.cljs | 1 - deps/cli/src/logseq/cli/common/file.cljs | 4 +- deps/common/src/logseq/common/graph.cljs | 4 +- .../common/test/logseq/common/graph_test.cljs | 4 +- docs/adr/0016-markdown-mirror.md | 30 +++--- src/main/frontend/persist_db.cljs | 10 +- src/main/frontend/worker/markdown_mirror.cljs | 7 +- src/resources/dicts/en.edn | 2 +- src/resources/dicts/zh-cn.edn | 2 +- src/test/frontend/persist_db_test.cljs | 93 ++++++++++++++++++- .../frontend/worker/markdown_mirror_test.cljs | 4 + .../frontend/worker/platform_node_test.cljs | 4 +- 12 files changed, 133 insertions(+), 32 deletions(-) diff --git a/deps/cli/src/logseq/cli/common/export/common.cljs b/deps/cli/src/logseq/cli/common/export/common.cljs index d45e32ce18..16fdea0828 100644 --- a/deps/cli/src/logseq/cli/common/export/common.cljs +++ b/deps/cli/src/logseq/cli/common/export/common.cljs @@ -2,7 +2,6 @@ "common fns for exporting. exclude some fns which produce lazy-seq, which can cause strange behaviors when use together with dynamic var." - (:refer-clojure :exclude [map filter]) (:require [cljs.core.match :refer [match]] [clojure.string :as string] [datascript.core :as d] diff --git a/deps/cli/src/logseq/cli/common/file.cljs b/deps/cli/src/logseq/cli/common/file.cljs index 78e3d2c1f9..844d176fba 100644 --- a/deps/cli/src/logseq/cli/common/file.cljs +++ b/deps/cli/src/logseq/cli/common/file.cljs @@ -82,8 +82,8 @@ [db block spaces-tabs context] (let [block (or (when-let [id (:db/id block)] (d/entity db id)) - (when-let [uuid (:block/uuid block)] - (d/entity db [:block/uuid uuid])) + (when-let [block-uuid (:block/uuid block)] + (d/entity db [:block/uuid block-uuid])) block) properties (->> (db-property/properties block) (remove (fn [[k _]] diff --git a/deps/common/src/logseq/common/graph.cljs b/deps/common/src/logseq/common/graph.cljs index dbb28b938a..49e47a9a19 100644 --- a/deps/common/src/logseq/common/graph.cljs +++ b/deps/common/src/logseq/common/graph.cljs @@ -65,7 +65,7 @@ Rules: - Contents in '/logseq/.recycle/' are ignored - Contents in '/logseq/bak/' are ignored - Contents in with '/logseq/version-files/' are ignored -- Contents in '/markdown-mirror/' are ignored +- Contents in '/mirror/markdown/' are ignored " [dir path] (let [dir (path/path-normalize dir) @@ -74,7 +74,7 @@ Rules: (when (string? path) (or (some #(string/starts-with? rpath %) - ["." "logseq/.recycle" "logseq/bak" "logseq/version-files" "markdown-mirror"]) + ["." "logseq/.recycle" "logseq/bak" "logseq/version-files" "mirror/markdown"]) (contains? #{"logseq/graphs-txid.edn" "logseq/pages-metadata.edn"} rpath) (some #(string/includes? rpath (str "/" % "/")) ["node_modules"]) diff --git a/deps/common/test/logseq/common/graph_test.cljs b/deps/common/test/logseq/common/graph_test.cljs index 63b9207665..32d2d10563 100644 --- a/deps/common/test/logseq/common/graph_test.cljs +++ b/deps/common/test/logseq/common/graph_test.cljs @@ -29,9 +29,9 @@ (fs/writeFileSync "tmp/test-graph/journals/2023_05_09.md" "") ;; Create files that are ignored (fs/mkdirSync (node-path/join "tmp/test-graph" "logseq" "bak")) - (fs/mkdirSync (node-path/join "tmp/test-graph" "markdown-mirror" "pages") #js {:recursive true}) + (fs/mkdirSync (node-path/join "tmp/test-graph" "mirror" "markdown" "pages") #js {:recursive true}) (fs/writeFileSync "tmp/test-graph/logseq/bak/baz.md" "") (fs/writeFileSync "tmp/test-graph/logseq/.gitignore" "") - (fs/writeFileSync "tmp/test-graph/markdown-mirror/pages/foo.md" "") + (fs/writeFileSync "tmp/test-graph/mirror/markdown/pages/foo.md" "") (is (= ["tmp/test-graph/journals/2023_05_09.md" "tmp/test-graph/pages/foo.md"] (common-graph/get-files "tmp/test-graph")))) diff --git a/docs/adr/0016-markdown-mirror.md b/docs/adr/0016-markdown-mirror.md index d04a19b236..608d2b9793 100644 --- a/docs/adr/0016-markdown-mirror.md +++ b/docs/adr/0016-markdown-mirror.md @@ -1,7 +1,7 @@ # ADR 0016: Electron Markdown Mirror Date: 2026-05-05 -Status: Proposed +Status: Accepted ## Context Logseq DB graphs do not expose one editable Markdown file per page in the graph @@ -22,14 +22,14 @@ builds do not have the same graph-directory filesystem guarantees. 3. When the setting is enabled for the Electron app, Logseq writes derived Markdown files under the current graph directory: - journals: - `markdown-mirror/journals/.md` + `mirror/markdown/journals/.md` - other pages: - `markdown-mirror/pages/.md` + `mirror/markdown/pages/.md` 4. For a graph at `~/logseq/graphs/graph-xxx`, mirror files are written under: - - `~/logseq/graphs/graph-xxx/markdown-mirror/journals/` - - `~/logseq/graphs/graph-xxx/markdown-mirror/pages/` + - `~/logseq/graphs/graph-xxx/mirror/markdown/journals/` + - `~/logseq/graphs/graph-xxx/mirror/markdown/pages/` 5. Markdown Mirror is derived output. The DB remains the source of truth. -6. Files under `markdown-mirror/**` must be ignored by graph import, file +6. Files under `mirror/markdown/**` must be ignored by graph import, file watchers, and graph parsing so the mirror never feeds back into the graph. 7. The feature is not available in browser or mobile builds, even if a stale setting value exists. @@ -68,8 +68,8 @@ builds do not have the same graph-directory filesystem guarantees. Electron app and CLI do not produce different file names for the same graph. ## Output Layout and Naming -1. Journal pages are written below `markdown-mirror/journals/`. -2. Non-journal pages are written below `markdown-mirror/pages/`. +1. Journal pages are written below `mirror/markdown/journals/`. +2. Non-journal pages are written below `mirror/markdown/pages/`. 3. Journal file names use the existing Logseq journal file-name rules for the graph configuration. 4. Non-journal page file names use the normalized page title: @@ -88,14 +88,14 @@ builds do not have the same graph-directory filesystem guarantees. page is renamed or deleted. Do not renumber existing duplicate-title mirror paths when another duplicate is created or removed. 9. The implementation keeps a per-graph mirror index under - `markdown-mirror/.index.edn`. + `mirror/markdown/.index.edn`. 10. The mirror index stores at least: - page uuid -> relative mirror path - relative mirror path -> page uuid - page uuid -> last known normalized title stem 11. The mirror index is implementation metadata for path stability. It is not graph content and must be ignored by graph import and watchers along with the - rest of `markdown-mirror/**`. + rest of `mirror/markdown/**`. 12. All mirror file names pass through a single cross-platform filename normalizer before joining paths. 13. Duplicate journal-day entities indicate invalid graph state for the mirror. @@ -109,7 +109,7 @@ builds do not have the same graph-directory filesystem guarantees. 16. Page deletion deletes the corresponding mirror file and removes the page uuid from the mirror index. 17. The write guard must reject any computed path outside the graph's - `markdown-mirror/` directory. + `mirror/markdown/` directory. 18. Built-in pages and property pages are excluded from path allocation and mirror writes. User Tag/Class pages are not excluded by this rule. If a previously mirrored page becomes excluded, the old mirror file is removed. @@ -209,7 +209,7 @@ builds do not have the same graph-directory filesystem guarantees. 4. Mirror files include block and page property drawers, including user properties, with rendered property values. 5. Assets are referenced as normal exported Markdown references. This ADR does - not copy assets into `markdown-mirror/`. + not copy assets into `mirror/markdown/`. 6. Page references remain in Logseq wiki-link form, for example `[[Foo]]`. 7. Ambiguous page references caused by duplicate page titles are an accepted limitation of Markdown Mirror. Do not rewrite page references to uuid-based @@ -227,7 +227,7 @@ builds do not have the same graph-directory filesystem guarantees. ## Non-goals 1. Markdown Mirror is not bidirectional sync. -2. Editing files in `markdown-mirror/` does not update the graph. +2. Editing files in `mirror/markdown/` does not update the graph. 3. The mirror is not a backup format with guaranteed import fidelity. 4. The mirror does not replace existing graph export features. 5. The mirror does not support browser or mobile runtimes in this ADR. @@ -241,7 +241,7 @@ builds do not have the same graph-directory filesystem guarantees. - The output layout is predictable for tools that watch journals and pages separately. - Page file names remain readable and practical in external Markdown tools. -- Ignoring `markdown-mirror/**` prevents mirror-generated files from becoming +- Ignoring `mirror/markdown/**` prevents mirror-generated files from becoming graph input. ### Tradeoffs @@ -276,7 +276,7 @@ bb dev:test -v frontend.worker.markdown-mirror-test/mirror-path-collision-fails- ``` Additional checks: -- `markdown-mirror/**` is excluded from graph parsing and file watchers. +- `mirror/markdown/**` is excluded from graph parsing and file watchers. - Editor save does not await mirror completion. - Browser and mobile builds do not expose the setting and do not schedule mirror jobs. diff --git a/src/main/frontend/persist_db.cljs b/src/main/frontend/persist_db.cljs index 86fbb82e33..839f9a02d9 100644 --- a/src/main/frontend/persist_db.cljs +++ b/src/main/frontend/persist_db.cljs @@ -130,6 +130,13 @@ :ws-url (config/db-sync-ws-url) :http-base (config/db-sync-http-base)}) +(defn- encoded-graph-dir-name repo) "/markdown-mirror")) + (str (graph-dir/repo->encoded-graph-dir-name repo) "/mirror/markdown")) (def ^:private invalid-file-name-chars-re #"[<>:\"|?*\\/]") @@ -159,8 +159,7 @@ (defn- supported-runtime? [platform*] (or (= :node (get-in platform* [:env :runtime])) - (and (= :browser (get-in platform* [:env :runtime])) - (= :electron (get-in platform* [:env :owner-source]))))) + (= :electron (get-in platform* [:env :owner-source])))) (defn- duplicate-journal-day? [db journal-day] @@ -296,7 +295,7 @@ (log/error :markdown-mirror/flush-failed {:repo repo :error error}))))) - (or (:debounce-ms opts) 250))] + (or (:debounce-ms opts) 1000))] (swap! *repo->flush-timeout assoc repo timeout-id)))) (defn- FakeRemote repo + (fn [qkw & args] + (swap! worker-calls conj [qkw args]) + (p/resolved nil))))) + (set! remote/stop! (fn [_] (p/resolved true))) + (-> (p/let [_ (ensure-remote! "logseq_db_graph_a")] + (is (= [:thread-api/markdown-mirror-set-enabled + ["logseq_db_graph_a" true]] + (first (filter #(= :thread-api/markdown-mirror-set-enabled (first %)) + @worker-calls))))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)))) + (p/finally (fn [] + (reset! state/state original-state) + (set! ipc/ipc original-ipc) + (set! remote/start! original-start!) + (set! remote/stop! original-stop!) + (done))))))) + +(deftest electron-ensure-remote-loads-markdown-mirror-setting-before-sync-test + (async done + (let [ipc-calls (atom []) + worker-calls (atom []) + ensure-remote! #'persist-db/FakeRemote repo + (fn [qkw & args] + (swap! worker-calls conj [qkw args]) + (p/resolved nil))))) + (set! remote/stop! (fn [_] (p/resolved true))) + (-> (p/let [_ (ensure-remote! "logseq_db_graph_a")] + (is (= [["db-worker-runtime" "logseq_db_graph_a"] + [:userAppCfgs]] + @ipc-calls)) + (is (= [:thread-api/markdown-mirror-set-enabled + ["logseq_db_graph_a" true]] + (first (filter #(= :thread-api/markdown-mirror-set-enabled (first %)) + @worker-calls))))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)))) + (p/finally (fn [] + (reset! state/state original-state) + (set! util/electron? original-electron?) + (set! ipc/ipc original-ipc) + (set! remote/start! original-start!) + (set! remote/stop! original-stop!) + (done))))))) + (deftest electron-list-db-without-current-repo-does-not-bootstrap-runtime (async done (let [ipc-calls (atom []) diff --git a/src/test/frontend/worker/markdown_mirror_test.cljs b/src/test/frontend/worker/markdown_mirror_test.cljs index c82f0e90d4..d0a0277629 100644 --- a/src/test/frontend/worker/markdown_mirror_test.cljs +++ b/src/test/frontend/worker/markdown_mirror_test.cljs @@ -41,6 +41,10 @@ (apply f args) (p/resolved ::missing-mirror-repo-fn))) +(deftest repo-mirror-dir-is-under-mirror-markdown-test + (is (= "graph-xxx/mirror/markdown" + (markdown-mirror/repo-mirror-dir test-repo)))) + (deftest normalize-file-name-is-cross-platform-and-deterministic-test (testing "invalid filesystem characters and path separators are replaced" (is (= "A_B_C_D_E_F_G_H" diff --git a/src/test/frontend/worker/platform_node_test.cljs b/src/test/frontend/worker/platform_node_test.cljs index 06ed0ba3cf..b381aadd89 100644 --- a/src/test/frontend/worker/platform_node_test.cljs +++ b/src/test/frontend/worker/platform_node_test.cljs @@ -82,7 +82,7 @@ (let [root-dir (node-helper/create-tmp-dir "platform-node-text-files")] (-> (p/let [platform (platform-node/node-platform {:root-dir root-dir}) storage (:storage platform) - path "graph-a/markdown-mirror/pages/page.md" + path "graph-a/mirror/markdown/pages/page.md" _ ((:write-text-atomic! storage) path "mirror") content ((:read-text! storage) path) _ ((:delete-file! storage) path) @@ -92,7 +92,7 @@ (is (nil? deleted-content)) (is (empty? (filter #(string/includes? % ".tmp-") (array-seq (fs/readdirSync - (node-path/join root-dir "graphs" "graph-a" "markdown-mirror" "pages"))))))) + (node-path/join root-dir "graphs" "graph-a" "mirror" "markdown" "pages"))))))) (p/catch (fn [e] (is false (str "unexpected error: " e)))) (p/finally done)))))