Browse Source

linked refs

Tienson Qin 2 months ago
parent
commit
e9c2269e34

+ 1 - 1
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

+ 1 - 4
deps/publish/package.json

@@ -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
 }

+ 196 - 20
deps/publish/src/logseq/publish/worker.cljs

@@ -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

+ 0 - 1
deps/publish/worker/README.md

@@ -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.

+ 1 - 1
package.json

@@ -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 - 6
shadow-cljs.edn

@@ -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

+ 0 - 1
src/main/frontend/handler/publish.cljs

@@ -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)