mirror of
https://github.com/logseq/logseq.git
synced 2026-04-24 22:25:01 +00:00
linked refs
This commit is contained in:
2
deps.edn
2
deps.edn
@@ -1,4 +1,4 @@
|
||||
{:paths ["src/publish-ssr" "src/main" "src/electron" "src/resources"]
|
||||
{:paths ["src/main" "src/electron" "src/resources"]
|
||||
:deps
|
||||
{org.clojure/clojure {:mvn/version "1.11.1"}
|
||||
rum/rum {:git/url "https://github.com/logseq/rum" ;; fork
|
||||
|
||||
5
deps/publish/package.json
vendored
5
deps/publish/package.json
vendored
@@ -1,8 +1,5 @@
|
||||
{
|
||||
"name": "@logseq/publish",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v31"
|
||||
}
|
||||
"private": true
|
||||
}
|
||||
|
||||
216
deps/publish/src/logseq/publish/worker.cljs
vendored
216
deps/publish/src/logseq/publish/worker.cljs
vendored
@@ -391,6 +391,73 @@
|
||||
(defn inline->nodes-seq [ctx items]
|
||||
(mapcat #(inline->nodes ctx %) items))
|
||||
|
||||
(defn block-content-nodes [block ctx]
|
||||
(let [raw (or (:block/content block)
|
||||
(:block/title block)
|
||||
(:block/name block)
|
||||
"")
|
||||
format (keyword (or (:block/format block) :markdown))
|
||||
ctx (assoc ctx :format format)
|
||||
ast (inline-ast raw format)
|
||||
content (if (seq ast)
|
||||
(inline->nodes-seq ctx ast)
|
||||
(content->nodes raw (:uuid->title ctx) (:graph-uuid ctx)))]
|
||||
(into [:span.block-text] content)))
|
||||
|
||||
(defn block-content-from-ref [ref ctx]
|
||||
(let [raw (or (get ref "source_block_content") "")
|
||||
format (keyword (or (get ref "source_block_format") "markdown"))
|
||||
ctx (assoc ctx :format format)
|
||||
ast (inline-ast raw format)
|
||||
content (if (seq ast)
|
||||
(inline->nodes-seq ctx ast)
|
||||
(content->nodes raw (:uuid->title ctx) (:graph-uuid ctx)))]
|
||||
(into [:span.block-text] content)))
|
||||
|
||||
(defn ref-eid [value]
|
||||
(cond
|
||||
(number? value) value
|
||||
(map? value) (:db/id value)
|
||||
:else nil))
|
||||
|
||||
(defn refs-contain? [refs target-eid]
|
||||
(when refs
|
||||
(some #(= target-eid (ref-eid %))
|
||||
(if (sequential? refs) refs [refs]))))
|
||||
|
||||
(defn page-refs-from-payload [payload page-eid page-uuid page-title graph-uuid]
|
||||
(let [entities (datoms->entities (:datoms payload))
|
||||
refs (->> entities
|
||||
(mapcat (fn [[_e entity]]
|
||||
(when (and (= (:block/page entity) page-eid)
|
||||
(not= (:block/uuid entity) page-uuid))
|
||||
(let [block-uuid (some-> (:block/uuid entity) str)
|
||||
block-content (or (:block/content entity)
|
||||
(:block/title entity)
|
||||
(:block/name entity)
|
||||
"")
|
||||
block-format (name (or (:block/format entity) :markdown))
|
||||
refs (:block/refs entity)
|
||||
refs (if (sequential? refs) refs (when refs [refs]))
|
||||
targets (->> refs
|
||||
(map ref-eid)
|
||||
(keep #(get entities %))
|
||||
(keep :block/uuid)
|
||||
(map str)
|
||||
distinct)]
|
||||
(when (seq targets)
|
||||
(map (fn [target]
|
||||
{:graph_uuid graph-uuid
|
||||
:target_page_uuid target
|
||||
:source_page_uuid (str page-uuid)
|
||||
:source_page_title page-title
|
||||
:source_block_uuid block-uuid
|
||||
:source_block_content block-content
|
||||
:source_block_format block-format
|
||||
:updated_at (.now js/Date)})
|
||||
targets)))))))]
|
||||
(vec refs)))
|
||||
|
||||
(defn render-hiccup [node]
|
||||
(cond
|
||||
(nil? node) ""
|
||||
@@ -433,32 +500,40 @@
|
||||
(when (seq children)
|
||||
[:ul.blocks
|
||||
(map (fn [block]
|
||||
(let [raw (or (:block/content block)
|
||||
(:block/title block)
|
||||
(:block/name block)
|
||||
"")
|
||||
format (keyword (or (:block/format block) :markdown))
|
||||
ctx (assoc ctx :format format)
|
||||
ast (inline-ast raw format)
|
||||
content (if (seq ast)
|
||||
(inline->nodes-seq ctx ast)
|
||||
(content->nodes raw (:uuid->title ctx) (:graph-uuid ctx)))
|
||||
child-id (:db/id block)
|
||||
(let [child-id (:db/id block)
|
||||
nested (render-block-tree children-by-parent child-id ctx)
|
||||
has-children? (boolean nested)]
|
||||
[:li.block
|
||||
[:div.block-content
|
||||
(into [:span.block-text] content)
|
||||
(when has-children?
|
||||
[:button.block-toggle
|
||||
{:type "button" :aria-expanded "true"}
|
||||
"▾"])]
|
||||
"▾"])
|
||||
(block-content-nodes block ctx)]
|
||||
(when nested
|
||||
[:div.block-children nested])]))
|
||||
(sort-blocks children))])))
|
||||
|
||||
(defn linked-references
|
||||
[ctx graph-uuid linked-by-page]
|
||||
[:section.linked-refs
|
||||
[:h2 "Linked references"]
|
||||
(for [{:keys [page_uuid page_title blocks]} linked-by-page]
|
||||
(let [ref-page-uuid page_uuid
|
||||
ref-page-title page_title
|
||||
href (when (and graph-uuid ref-page-uuid)
|
||||
(str "/p/" graph-uuid "/" ref-page-uuid))]
|
||||
[:div.ref-page
|
||||
(if href
|
||||
[:a.page-ref {:href href} ref-page-title]
|
||||
[:div.ref-title ref-page-title])
|
||||
(when (seq blocks)
|
||||
[:ul.ref-blocks
|
||||
(for [block blocks]
|
||||
[:li.ref-block [:div.block-content (block-content-from-ref block ctx)]])])]))])
|
||||
|
||||
(defn render-page-html
|
||||
[transit page_uuid-str]
|
||||
[transit page_uuid-str refs-data]
|
||||
(let [payload (read-transit-safe transit)
|
||||
meta (get-publish-meta payload)
|
||||
graph-uuid (when meta
|
||||
@@ -511,6 +586,17 @@
|
||||
:name->uuid name->uuid
|
||||
:graph-uuid graph-uuid}
|
||||
blocks (render-block-tree children-by-parent page-eid ctx)
|
||||
linked-by-page (when refs-data
|
||||
(->> (get refs-data "refs")
|
||||
(group-by #(get % "source_page_uuid"))
|
||||
(map (fn [[_ items]]
|
||||
{:page_title (get (first items) "source_page_title")
|
||||
:page_uuid (get (first items) "source_page_uuid")
|
||||
:blocks items}))
|
||||
(sort-by (fn [{:keys [page_title]}]
|
||||
(string/lower-case (or page_title ""))))))
|
||||
linked-refs (when (seq linked-by-page)
|
||||
(linked-references ctx graph-uuid linked-by-page))
|
||||
doc [:html
|
||||
[:head
|
||||
[:meta {:charset "utf-8"}]
|
||||
@@ -532,6 +618,11 @@
|
||||
".block-toggle:focus{outline:2px solid #c7b38f;outline-offset:2px;border-radius:4px;}"
|
||||
".block-children{margin-left:16px;}"
|
||||
".block.is-collapsed > .block-children { display: none; }"
|
||||
".linked-refs{margin-top:36px;}"
|
||||
".linked-refs h2{font-size:18px;margin:0 0 16px;color:#4b3b24;}"
|
||||
".ref-page{margin:0 0 16px;}"
|
||||
".ref-blocks{margin:8px 0 0 18px;padding:0;list-style:disc;}"
|
||||
".ref-block{margin:6px 0;}"
|
||||
".page-ref{color:#1a5fb4;text-decoration:none;}"
|
||||
".page-ref:hover{text-decoration:underline;}"]]
|
||||
[:body
|
||||
@@ -542,7 +633,8 @@
|
||||
{:type "button"
|
||||
:onclick "window.toggleTopBlocks(this)"}
|
||||
"Collapse all"]]
|
||||
(when blocks blocks)]
|
||||
(when blocks blocks)
|
||||
(when linked-refs linked-refs)]
|
||||
[:script
|
||||
"document.addEventListener('click',function(e){var btn=e.target.closest('.block-toggle');if(!btn)return;var li=btn.closest('li.block');if(!li)return;var collapsed=li.classList.toggle('is-collapsed');btn.setAttribute('aria-expanded',String(!collapsed));});"
|
||||
"window.toggleTopBlocks=function(btn){var list=document.querySelector('.blocks');if(!list){return;}var collapsed=list.classList.toggle('collapsed-all');list.querySelectorAll(':scope > .block').forEach(function(el){if(collapsed){el.classList.add('is-collapsed');}else{el.classList.remove('is-collapsed');}});if(btn){btn.textContent=collapsed?'Expand all':'Collapse all';}};"]]]]
|
||||
@@ -563,7 +655,17 @@
|
||||
(js-await [body (.arrayBuffer request)]
|
||||
(let [{:keys [content_hash content_length graph page_uuid schema_version block_count created_at] :as meta}
|
||||
(or (parse-meta-header request)
|
||||
(meta-from-body body))]
|
||||
(meta-from-body body))
|
||||
payload (read-transit-safe (.decode text-decoder body))
|
||||
payload-entities (datoms->entities (:datoms payload))
|
||||
page-eid (some (fn [[e entity]]
|
||||
(when (= (:block/uuid entity) (uuid page_uuid))
|
||||
e))
|
||||
payload-entities)
|
||||
page-title (when page-eid
|
||||
(entity->title (get payload-entities page-eid)))
|
||||
refs (when (and page-eid page-title)
|
||||
(page-refs-from-payload payload page-eid page_uuid page-title graph))]
|
||||
(cond
|
||||
(not (valid-meta? meta))
|
||||
(bad-request "missing publish metadata")
|
||||
@@ -592,7 +694,8 @@
|
||||
:r2_key r2-key
|
||||
:owner_sub (aget claims "sub")
|
||||
:created_at created_at
|
||||
:updated_at (.now js/Date)})
|
||||
:updated_at (.now js/Date)
|
||||
:refs refs})
|
||||
meta-resp (.fetch do-stub "https://publish/pages"
|
||||
#js {:method "POST"
|
||||
:headers #js {"content-type" "application/json"}
|
||||
@@ -664,6 +767,22 @@
|
||||
:etag etag}
|
||||
200))))))))))
|
||||
|
||||
(defn handle-get-page-refs [request env]
|
||||
(let [url (js/URL. (.-url request))
|
||||
parts (string/split (.-pathname url) #"/")
|
||||
graph-uuid (nth parts 2 nil)
|
||||
page_uuid (nth parts 3 nil)]
|
||||
(if (or (nil? graph-uuid) (nil? page_uuid))
|
||||
(bad-request "missing graph uuid or page uuid")
|
||||
(js-await [^js do-ns (aget env "PUBLISH_META_DO")
|
||||
do-id (.idFromName do-ns "index")
|
||||
do-stub (.get do-ns do-id)
|
||||
refs-resp (.fetch do-stub (str "https://publish/pages/" graph-uuid "/" page_uuid "/refs"))]
|
||||
(if-not (.-ok refs-resp)
|
||||
(not-found)
|
||||
(js-await [refs (.json refs-resp)]
|
||||
(json-response (js->clj refs :keywordize-keys true) 200)))))))
|
||||
|
||||
(defn handle-list-pages [env]
|
||||
(js-await [^js do-ns (aget env "PUBLISH_META_DO")
|
||||
do-id (.idFromName do-ns "index")
|
||||
@@ -753,13 +872,19 @@
|
||||
(if-not (.-ok meta-resp)
|
||||
(not-found)
|
||||
(js-await [meta (.json meta-resp)
|
||||
refs-resp (let [index-id (.idFromName do-ns "index")
|
||||
index-stub (.get do-ns index-id)]
|
||||
(.fetch index-stub (str "https://publish/pages/" graph-uuid "/" page_uuid "/refs")))
|
||||
refs-json (when (and refs-resp (.-ok refs-resp))
|
||||
(js-await [raw (.json refs-resp)]
|
||||
(js->clj raw :keywordize-keys false)))
|
||||
r2 (aget env "PUBLISH_R2")
|
||||
object (.get r2 (aget meta "r2_key"))]
|
||||
(if-not object
|
||||
(json-response {:error "missing transit blob"} 404)
|
||||
(js-await [buffer (.arrayBuffer object)
|
||||
transit (.decode text-decoder buffer)
|
||||
html (render-page-html transit page_uuid)]
|
||||
html (render-page-html transit page_uuid refs-json)]
|
||||
(js/Response.
|
||||
html
|
||||
#js {:headers (merge-headers
|
||||
@@ -788,6 +913,7 @@
|
||||
(cond
|
||||
(= (count parts) 3) (handle-list-graph-pages request env)
|
||||
(= (nth parts 4 nil) "transit") (handle-get-page-transit request env)
|
||||
(= (nth parts 4 nil) "refs") (handle-get-page-refs request env)
|
||||
:else (handle-get-page request env)))
|
||||
|
||||
(and (string/starts-with? path "/pages/") (= method "DELETE"))
|
||||
@@ -821,7 +947,19 @@
|
||||
"created_at INTEGER,"
|
||||
"updated_at INTEGER,"
|
||||
"PRIMARY KEY (graph_uuid, page_uuid)"
|
||||
");"))))
|
||||
");")))
|
||||
(sql-exec sql
|
||||
(str "CREATE TABLE IF NOT EXISTS page_refs ("
|
||||
"graph_uuid TEXT NOT NULL,"
|
||||
"target_page_uuid TEXT NOT NULL,"
|
||||
"source_page_uuid TEXT NOT NULL,"
|
||||
"source_page_title TEXT,"
|
||||
"source_block_uuid TEXT,"
|
||||
"source_block_content TEXT,"
|
||||
"source_block_format TEXT,"
|
||||
"updated_at INTEGER,"
|
||||
"PRIMARY KEY (graph_uuid, target_page_uuid, source_block_uuid)"
|
||||
");")))
|
||||
|
||||
(defn row->meta [row]
|
||||
(let [data (js->clj row :keywordize-keys false)]
|
||||
@@ -868,7 +1006,31 @@
|
||||
(aget body "owner_sub")
|
||||
(aget body "created_at")
|
||||
(aget body "updated_at"))
|
||||
(json-response {:ok true}))
|
||||
(let [refs (aget body "refs")
|
||||
graph-uuid (aget body "graph")
|
||||
page-uuid (aget body "page_uuid")]
|
||||
(when (and graph-uuid page-uuid)
|
||||
(sql-exec sql
|
||||
"DELETE FROM page_refs WHERE graph_uuid = ? AND source_page_uuid = ?;"
|
||||
graph-uuid
|
||||
page-uuid)
|
||||
(when (seq refs)
|
||||
(doseq [ref refs]
|
||||
(sql-exec sql
|
||||
(str "INSERT OR REPLACE INTO page_refs ("
|
||||
"graph_uuid, target_page_uuid, source_page_uuid, "
|
||||
"source_page_title, source_block_uuid, source_block_content, "
|
||||
"source_block_format, updated_at"
|
||||
") VALUES (?, ?, ?, ?, ?, ?, ?, ?);")
|
||||
(aget ref "graph_uuid")
|
||||
(aget ref "target_page_uuid")
|
||||
(aget ref "source_page_uuid")
|
||||
(aget ref "source_page_title")
|
||||
(aget ref "source_block_uuid")
|
||||
(aget ref "source_block_content")
|
||||
(aget ref "source_block_format")
|
||||
(aget ref "updated_at")))))
|
||||
(json-response {:ok true})))
|
||||
|
||||
(= "GET" (.-method request))
|
||||
(let [url (js/URL. (.-url request))
|
||||
@@ -876,6 +1038,20 @@
|
||||
graph-uuid (nth parts 2 nil)
|
||||
page_uuid (nth parts 3 nil)]
|
||||
(cond
|
||||
(= (nth parts 4 nil) "refs")
|
||||
(let [rows (get-sql-rows
|
||||
(sql-exec sql
|
||||
(str "SELECT graph_uuid, target_page_uuid, source_page_uuid, "
|
||||
"source_page_title, source_block_uuid, source_block_content, "
|
||||
"source_block_format, updated_at "
|
||||
"FROM page_refs WHERE graph_uuid = ? AND target_page_uuid = ? "
|
||||
"ORDER BY updated_at DESC;")
|
||||
graph-uuid
|
||||
page_uuid))]
|
||||
(json-response {:refs (map (fn [row]
|
||||
(js->clj row :keywordize-keys false))
|
||||
rows)}))
|
||||
|
||||
(and graph-uuid page_uuid)
|
||||
(let [rows (get-sql-rows
|
||||
(sql-exec sql
|
||||
|
||||
1
deps/publish/worker/README.md
vendored
1
deps/publish/worker/README.md
vendored
@@ -42,6 +42,5 @@ metadata in a Durable Object backed by SQLite.
|
||||
- For local testing, run `wrangler dev` and use `deps/publish/worker/scripts/dev_test.sh`.
|
||||
- If you switch schema versions, clear local DO state with
|
||||
`deps/publish/worker/scripts/clear_dev_state.sh`.
|
||||
- Build the SSR bundle with `clojure -M:cljs release publish-ssr` before running the worker.
|
||||
- Build the worker bundle with `clojure -M:cljs release publish-worker` before running the worker.
|
||||
- For dev, you can run `clojure -M:cljs watch publish-worker` in one terminal.
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
"cljs:mobile-watch": "clojure -M:cljs watch mobile db-worker --config-merge \"{:output-dir \\\"./static/mobile/js\\\" :asset-path \\\"/static/mobile/js\\\" :release {:asset-path \\\"http://localhost\\\"}}\"",
|
||||
"cljs:release-mobile": "clojure -M:cljs release mobile db-worker --config-merge \"{:output-dir \\\"./static/mobile/js\\\" :asset-path \\\"/static/mobile/js\\\" :release {:asset-path \\\"http://localhost\\\"}}\"",
|
||||
"cljs:dev-watch": "clojure -M:cljs watch app db-worker inference-worker electron mobile",
|
||||
"cljs:app-watch": "clojure -M:cljs watch app db-worker inference-worker",
|
||||
"cljs:app-watch": "clojure -M:cljs watch app db-worker inference-worker publish-worker",
|
||||
"cljs:electron-watch": "clojure -M:cljs watch app db-worker inference-worker electron --config-merge \"{:asset-path \\\"./js\\\"}\"",
|
||||
"cljs:release": "clojure -M:cljs release app db-worker inference-worker publishing electron",
|
||||
"cljs:release-electron": "clojure -M:cljs release app db-worker inference-worker electron --debug && clojure -M:cljs release publishing",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
;; shadow-cljs configuration
|
||||
{:deps true
|
||||
:nrepl {:port 8701}
|
||||
:source-paths ["src/publish-ssr" "src/main" "src/electron" "src/resources"]
|
||||
:source-paths ["src/main" "src/electron" "src/resources"]
|
||||
|
||||
;; :ssl {:password "logseq"}
|
||||
|
||||
@@ -219,11 +219,6 @@
|
||||
:devtools {:enabled true}
|
||||
:compiler-options {:optimizations :simple}}
|
||||
|
||||
:publish-ssr {:target :npm-module
|
||||
:entries [frontend.publish.ssr]
|
||||
:output-dir "deps/publish/worker/dist/ssr"
|
||||
:compiler-options {:optimizations :simple}}
|
||||
|
||||
:publish-worker {:target :esm
|
||||
:output-dir "deps/publish/worker/dist/worker"
|
||||
:modules {:main {:exports {default logseq.publish.worker/worker
|
||||
|
||||
@@ -98,7 +98,6 @@
|
||||
(if-let [db* (and repo (db/get-db repo))]
|
||||
(if (and page (:db/id page))
|
||||
(let [payload (build-page-publish-datoms db* page)]
|
||||
(notification/show! "Publishing page..." :success)
|
||||
(-> (<post-publish! payload)
|
||||
(p/then (fn [_resp]
|
||||
(let [graph-uuid (some-> (ldb/get-graph-rtc-uuid db*) str)
|
||||
|
||||
Reference in New Issue
Block a user