mirror of
https://github.com/logseq/logseq.git
synced 2026-05-29 06:59:36 +00:00
fix: update markdown mirror
This commit is contained in:
@@ -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]
|
||||
|
||||
4
deps/cli/src/logseq/cli/common/file.cljs
vendored
4
deps/cli/src/logseq/cli/common/file.cljs
vendored
@@ -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 _]]
|
||||
|
||||
4
deps/common/src/logseq/common/graph.cljs
vendored
4
deps/common/src/logseq/common/graph.cljs
vendored
@@ -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"])
|
||||
|
||||
@@ -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"))))
|
||||
|
||||
@@ -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/<journal-file-name>.md`
|
||||
`mirror/markdown/journals/<journal-file-name>.md`
|
||||
- other pages:
|
||||
`markdown-mirror/pages/<page-file-name>.md`
|
||||
`mirror/markdown/pages/<page-file-name>.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.
|
||||
|
||||
@@ -130,6 +130,13 @@
|
||||
:ws-url (config/db-sync-ws-url)
|
||||
:http-base (config/db-sync-http-base)})
|
||||
|
||||
(defn- <sync-markdown-mirror-setting!
|
||||
[repo]
|
||||
(p/let [_ (state/load-app-user-cfgs)]
|
||||
(state/<invoke-db-worker :thread-api/markdown-mirror-set-enabled
|
||||
repo
|
||||
(true? (get-in @state/state [:electron/user-cfgs :feature/markdown-mirror?])))))
|
||||
|
||||
(defn- <ensure-remote!
|
||||
[repo]
|
||||
(if (or (nil? repo) (= repo @remote-repo))
|
||||
@@ -149,7 +156,8 @@
|
||||
(record-active-request-failure! repo session-id error))))]
|
||||
(set-remote-runtime! repo client session-id)
|
||||
(p/let [_ (state/<invoke-db-worker :thread-api/set-db-sync-config
|
||||
(current-db-sync-config))]
|
||||
(current-db-sync-config))
|
||||
_ (<sync-markdown-mirror-setting! repo)]
|
||||
nil)
|
||||
(ldb/register-transact-fn!
|
||||
(fn remote-transact!
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
(defn repo-mirror-dir
|
||||
[repo]
|
||||
(str (graph-dir/repo->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- <run-job!
|
||||
|
||||
@@ -1632,7 +1632,7 @@
|
||||
:settings.features/journals-enable-success "Journals enabled"
|
||||
:settings.features/login-prompt "To access new features before anyone else you must be an Open Collective Sponsor or Backer of Logseq and therefore log in first."
|
||||
:settings.features/markdown-mirror "Markdown Mirror"
|
||||
:settings.features/markdown-mirror-desc "Write a derived Markdown copy of edited pages to the graph's markdown-mirror folder. Desktop only."
|
||||
:settings.features/markdown-mirror-desc "Write a derived Markdown copy of edited pages to the graph's mirror/markdown folder. Desktop only."
|
||||
:settings.features/markdown-mirror-regenerate "Regenerate full mirror"
|
||||
:settings.features/markdown-mirror-regenerate-error "Failed to regenerate Markdown Mirror: {1}"
|
||||
:settings.features/markdown-mirror-regenerate-success "Markdown Mirror regenerated"
|
||||
|
||||
@@ -1622,7 +1622,7 @@
|
||||
:settings.features/journals-enable-success "已启用日志页"
|
||||
:settings.features/login-prompt "你必须是 Logseq 的 Open Collective Sponsor 或者 Backer 才能提前使用新功能(仍在测试中),因此需要登录。"
|
||||
:settings.features/markdown-mirror "Markdown 镜像"
|
||||
:settings.features/markdown-mirror-desc "将已编辑页面的派生 Markdown 副本写入图谱的 markdown-mirror 文件夹。仅桌面端可用。"
|
||||
:settings.features/markdown-mirror-desc "将已编辑页面的派生 Markdown 副本写入图谱的 mirror/markdown 文件夹。仅桌面端可用。"
|
||||
:settings.features/markdown-mirror-regenerate "重新生成完整镜像"
|
||||
:settings.features/markdown-mirror-regenerate-error "重新生成 Markdown 镜像失败:{1}"
|
||||
:settings.features/markdown-mirror-regenerate-success "Markdown 镜像已重新生成"
|
||||
|
||||
@@ -67,7 +67,8 @@
|
||||
(reset! persist-db/remote-repo nil)
|
||||
(reset! persist-db/remote-runtime-state nil)
|
||||
(reset! state/*db-worker nil)
|
||||
(reset! ldb/*transact-fn nil))
|
||||
(reset! ldb/*transact-fn nil)
|
||||
(swap! state/state assoc :electron/user-cfgs {}))
|
||||
|
||||
(defn- success-body
|
||||
[result]
|
||||
@@ -104,6 +105,10 @@
|
||||
(p/resolved {:status 200
|
||||
:body (success-body nil)})
|
||||
|
||||
"thread-api/markdown-mirror-set-enabled"
|
||||
(p/resolved {:status 200
|
||||
:body (success-body nil)})
|
||||
|
||||
"thread-api/list-db"
|
||||
(let [result (first @results)]
|
||||
(swap! results #(vec (rest %)))
|
||||
@@ -342,6 +347,92 @@
|
||||
(set! config/db-sync-http-base original-http)
|
||||
(done)))))))
|
||||
|
||||
(deftest electron-ensure-remote-pushes-markdown-mirror-setting-on-start-test
|
||||
(async done
|
||||
(let [worker-calls (atom [])
|
||||
ensure-remote! #'persist-db/<ensure-remote!
|
||||
original-state @state/state
|
||||
original-ipc ipc/ipc
|
||||
original-start! remote/start!
|
||||
original-stop! remote/stop!]
|
||||
(reset-runtime-state!)
|
||||
(reset! state/state (assoc-in original-state
|
||||
[:electron/user-cfgs :feature/markdown-mirror?]
|
||||
true))
|
||||
(set! ipc/ipc (fn [channel repo]
|
||||
(is (= "db-worker-runtime" channel))
|
||||
(p/resolved {:base-url "http://127.0.0.1:9101"
|
||||
:auth-token nil
|
||||
:repo repo})))
|
||||
(set! remote/start! (fn [{:keys [repo]}]
|
||||
(->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/<ensure-remote!
|
||||
original-state @state/state
|
||||
original-electron? util/electron?
|
||||
original-ipc ipc/ipc
|
||||
original-start! remote/start!
|
||||
original-stop! remote/stop!]
|
||||
(reset-runtime-state!)
|
||||
(swap! state/state assoc :electron/user-cfgs nil)
|
||||
(set! util/electron? (constantly true))
|
||||
(set! ipc/ipc (fn [channel & args]
|
||||
(swap! ipc-calls conj (into [channel] args))
|
||||
(case channel
|
||||
"db-worker-runtime"
|
||||
(p/resolved {:base-url "http://127.0.0.1:9101"
|
||||
:auth-token nil
|
||||
:repo (first args)})
|
||||
|
||||
:userAppCfgs
|
||||
(p/resolved {:feature/markdown-mirror? true})
|
||||
|
||||
(p/resolved nil))))
|
||||
(set! remote/start! (fn [{:keys [repo]}]
|
||||
(->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 [])
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)))))
|
||||
|
||||
Reference in New Issue
Block a user