global ref

This commit is contained in:
Tienson Qin
2025-12-28 18:57:12 +08:00
parent a682b94991
commit 0d38c982e7
3 changed files with 111 additions and 200 deletions

View File

@@ -596,19 +596,19 @@
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)})
(map (fn [target-entity]
(let [target-uuid (some-> (:block/uuid target-entity) str)]
{:graph_uuid graph-uuid
:target_page_uuid target-uuid
:target_page_title (entity->title target-entity)
: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)))
@@ -1020,6 +1020,40 @@
[:p "No published nodes use this tag yet."])]]]]
(str "<!doctype html>" (render-hiccup doc))))
(defn render-ref-html
[graph-uuid ref-name ref-title ref-items]
(let [rows ref-items
title (or ref-title ref-name)
doc [:html
[:head
[:meta {:charset "utf-8"}]
[:meta {:name "viewport" :content "width=device-width,initial-scale=1"}]
[:title (str "Ref - " title)]
[:style
"body{margin:0;background:#fbf8f3;color:#1b1b1b;font-family:Georgia,serif;}"
".wrap{max-width:880px;margin:0 auto;padding:40px 24px;}"
"h1{font-size:26px;margin:0 0 8px;font-weight:600;}"
".graph-meta{color:#6b4f2b;font-size:13px;margin:0 0 24px;}"
".tag-sub{color:#6b4f2b;font-size:13px;margin:0 0 18px;}"
".page-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:10px;}"
".page-item{padding:12px 14px;border:1px solid #e6dccb;border-radius:12px;background:#fffdf8;display:flex;justify-content:space-between;gap:12px;align-items:flex-start;}"
".tagged-main{display:flex;flex-direction:column;gap:4px;min-width:0;}"
".tagged-block{font-size:13px;color:#2e2a23;white-space:pre-wrap;}"
".tagged-sub{font-size:12px;color:#6b4f2b;}"
".page-ref{color:#1a5fb4;text-decoration:none;overflow-wrap:anywhere;}"
".page-ref:hover{text-decoration:underline;}"
".page-meta{color:#6b4f2b;font-size:12px;white-space:nowrap;}"]]
[:body
[:main.wrap
[:h1 title]
[:p.tag-sub (str "Reference: " ref-name)]
(if (seq rows)
[:ul.page-list
(for [item rows]
(render-tagged-item graph-uuid item))]
[:p "No published nodes reference this yet."])]]]]
(str "<!doctype html>" (render-hiccup doc))))
(defn handle-post-pages [request env]
(js-await [auth-header (.get (.-headers request) "authorization")
token (when (and auth-header (string/starts-with? auth-header "Bearer "))
@@ -1265,6 +1299,38 @@
#js {"content-type" "text/html; charset=utf-8"}
(cors-headers))}))))))
(defn handle-ref-name-json [ref-name env]
(if-not ref-name
(bad-request "missing ref name")
(js-await [^js do-ns (aget env "PUBLISH_META_DO")
do-id (.idFromName do-ns "index")
do-stub (.get do-ns do-id)
resp (.fetch do-stub (str "https://publish/ref/" (js/encodeURIComponent ref-name))
#js {:method "GET"})]
(if-not (.-ok resp)
(not-found)
(js-await [data (.json resp)]
(json-response (js->clj data :keywordize-keys true) 200))))))
(defn handle-ref-name-html [ref-name env]
(if-not ref-name
(bad-request "missing ref name")
(js-await [^js do-ns (aget env "PUBLISH_META_DO")
do-id (.idFromName do-ns "index")
do-stub (.get do-ns do-id)
resp (.fetch do-stub (str "https://publish/ref/" (js/encodeURIComponent ref-name))
#js {:method "GET"})]
(if-not (.-ok resp)
(not-found)
(js-await [data (.json resp)
rows (or (aget data "pages") #js [])
title (or ref-name "Reference")]
(js/Response.
(render-ref-html "all" ref-name title rows)
#js {:headers (merge-headers
#js {"content-type" "text/html; charset=utf-8"}
(cors-headers))}))))))
(defn handle-tag-page-html [graph-uuid tag-uuid env]
(if (or (nil? graph-uuid) (nil? tag-uuid))
(bad-request "missing graph uuid or tag uuid")
@@ -1437,6 +1503,15 @@
(handle-tag-name-json tag-name env)
(handle-tag-name-html tag-name env)))
(and (string/starts-with? path "/ref/") (= method "GET"))
(let [parts (string/split path #"/")
raw-name (nth parts 2 nil)
ref-name (when raw-name
(js/decodeURIComponent raw-name))]
(if (= (nth parts 3 nil) "json")
(handle-ref-name-json ref-name env)
(handle-ref-name-html ref-name env)))
(and (string/starts-with? path "/pages/") (= method "GET"))
(let [parts (string/split path #"/")]
(cond
@@ -1486,10 +1561,16 @@
(sql-exec sql "ALTER TABLE pages ADD COLUMN page_title TEXT;"))
(when-not (contains? col-names "page_tags")
(sql-exec sql "ALTER TABLE pages ADD COLUMN page_tags TEXT;")))
(let [cols (get-sql-rows (sql-exec sql "PRAGMA table_info(page_refs);"))
col-names (set (map #(aget % "name") cols))]
(when (seq col-names)
(when-not (contains? col-names "target_page_title")
(sql-exec sql "ALTER TABLE page_refs ADD COLUMN target_page_title TEXT;"))))
(sql-exec sql
(str "CREATE TABLE IF NOT EXISTS page_refs ("
"graph_uuid TEXT NOT NULL,"
"target_page_uuid TEXT NOT NULL,"
"target_page_title TEXT,"
"source_page_uuid TEXT NOT NULL,"
"source_page_title TEXT,"
"source_block_uuid TEXT,"
@@ -1592,12 +1673,13 @@
(doseq [ref refs]
(sql-exec sql
(str "INSERT OR REPLACE INTO page_refs ("
"graph_uuid, target_page_uuid, source_page_uuid, "
"graph_uuid, target_page_uuid, target_page_title, source_page_uuid, "
"source_page_title, source_block_uuid, source_block_content, "
"source_block_format, updated_at"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?);")
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);")
(aget ref "graph_uuid")
(aget ref "target_page_uuid")
(aget ref "target_page_title")
(aget ref "source_page_uuid")
(aget ref "source_page_title")
(aget ref "source_block_uuid")
@@ -1644,11 +1726,26 @@
(js->clj row :keywordize-keys false))
rows)}))
(= (nth parts 1 nil) "ref")
(let [ref-name (when-let [raw (nth parts 2 nil)]
(js/decodeURIComponent raw))
rows (get-sql-rows
(sql-exec sql
(str "SELECT graph_uuid, source_page_uuid, source_page_title, "
"MAX(updated_at) AS updated_at "
"FROM page_refs WHERE target_page_title = ? "
"GROUP BY graph_uuid, source_page_uuid, source_page_title "
"ORDER BY updated_at DESC;")
ref-name))]
(json-response {:pages (map (fn [row]
(js->clj row :keywordize-keys false))
rows)}))
(= (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, "
"target_page_title, 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;")

View File

@@ -1,106 +0,0 @@
(ns frontend.publish.client
"Client helpers for published page snapshots."
(:require [frontend.config :as config]
[frontend.state :as state]
[logseq.db :as ldb]
[promesa.core :as p]))
(defn- <fetch-json
[url headers]
(p/let [resp (js/fetch url (clj->js {:headers headers}))]
(cond
(= 304 (.-status resp)) {:status 304}
(.-ok resp) (p/let [data (.json resp)] {:status 200 :data data})
:else (p/let [body (.text resp)]
(throw (ex-info "Publish fetch failed" {:status (.-status resp) :body body}))))))
(defn- cache-key
[graph-uuid page-uuid]
(str "publish/" graph-uuid "/" page-uuid))
(defn- get-cache
[graph-uuid page-uuid]
(when-let [raw (js/localStorage.getItem (cache-key graph-uuid page-uuid))]
(try
(js/JSON.parse raw)
(catch :default _e nil))))
(defn- set-cache!
[graph-uuid page-uuid value]
(js/localStorage.setItem (cache-key graph-uuid page-uuid)
(js/JSON.stringify (clj->js value))))
(defn <get-page-meta
"Fetch metadata for a published page, honoring ETag if cached.
Returns {:status 200 :data <meta>} or {:status 304}.
"
([page-uuid]
(<get-page-meta page-uuid (get-graph-uuid)))
([page-uuid graph-uuid]
(when-not graph-uuid
(throw (ex-info "Missing graph UUID" {:page-uuid page-uuid})))
(let [cached (get-cache graph-uuid page-uuid)
headers (cond-> {}
(and cached (.-etag cached))
(assoc "if-none-match" (.-etag cached)))]
(p/let [resp (<fetch-json (str config/PUBLISH-API-BASE "/pages/" graph-uuid "/" page-uuid)
headers)]
(if (= 304 (:status resp))
resp
(let [meta (js->clj (:data resp) :keywordize-keys true)
etag (get meta :publish/content-hash)]
(set-cache! graph-uuid page-uuid {:etag etag :meta meta})
{:status 200 :data meta}))))))
(defn <get-transit-url
"Fetch a signed transit URL. Uses meta ETag caching if provided.
Returns {:status 200 :data {:url ... :etag ...}} or {:status 304}.
"
([page-uuid]
(<get-transit-url page-uuid (get-graph-uuid)))
([page-uuid graph-uuid]
(when-not graph-uuid
(throw (ex-info "Missing graph UUID" {:page-uuid page-uuid})))
(let [cached (get-cache graph-uuid page-uuid)
headers (cond-> {}
(and cached (.-etag cached))
(assoc "if-none-match" (.-etag cached)))]
(p/let [resp (<fetch-json (str config/PUBLISH-API-BASE "/pages/" graph-uuid "/" page-uuid
"/transit")
headers)]
(if (= 304 (:status resp))
resp
(let [data (js->clj (:data resp) :keywordize-keys true)]
{:status 200 :data data}))))))
(defn <get-published-transit
"Fetch the published transit blob and return its text body.
If the metadata is unchanged, returns {:status 304}.
"
([page-uuid]
(<get-published-transit page-uuid (get-graph-uuid)))
([page-uuid graph-uuid]
(p/let [meta-resp (<get-page-meta page-uuid graph-uuid)]
(if (= 304 (:status meta-resp))
meta-resp
(p/let [url-resp (<get-transit-url page-uuid graph-uuid)]
(if (= 304 (:status url-resp))
url-resp
(let [url (get-in url-resp [:data :url])]
(p/let [resp (js/fetch url)]
(if (.-ok resp)
(p/let [text (.text resp)]
{:status 200
:etag (get-in url-resp [:data :etag])
:body text})
(p/let [body (.text resp)]
(throw (ex-info "Publish transit fetch failed"
{:status (.-status resp) :body body}))))))))))))
(defn get-graph-uuid
"Returns the RTC graph UUID if available."
[]
(some-> (ldb/get-graph-rtc-uuid (state/get-current-repo)) str))

View File

@@ -1,80 +0,0 @@
(ns frontend.publish.ssr
"SSR entry for published pages."
(:require ["react" :as react]
["react-dom/server" :as react-dom-server]
[datascript.core :as d]
[frontend.components.page :as page]
[frontend.db.conn-state :as conn-state]
[frontend.state :as state]
[logseq.db :as ldb]
[logseq.db.frontend.schema :as db-schema]
[rum.core :as rum]))
(def ^:private minimal-css
(str
"html,body{margin:0;padding:0;background:#fff;color:#111;}",
"body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;}",
".cp__page-inner-wrap{max-width:900px;margin:0 auto;padding:32px 24px;}",
".page-inner{gap:24px;}",
".page-title{font-size:28px;font-weight:600;margin:0;}",
".ls-page-blocks{margin-top:16px;}",
".ls-block{margin:6px 0;}",
".block-content{line-height:1.6;}",
"a{color:#2563eb;text-decoration:none;}",
"a:hover{text-decoration:underline;}"))
(defn- ensure-global-stubs!
[]
(let [g js/globalThis]
(when-not (.-React g)
(set! (.-React g) react))
(when-not (.-ReactDOMServer g)
(set! (.-ReactDOMServer g) react-dom-server))
(when-not (.-window g)
(set! (.-window g) g))
(when-not (.-document g)
(set! (.-document g)
#js {:getElementById (fn [_] nil)
:getElementsByClassName (fn [_] #js [])
:querySelector (fn [_] nil)}))
(when-not (.-navigator g)
(set! (.-navigator g) #js {:userAgent ""}))))
(defn- prepare-state!
[repo conn]
(swap! conn-state/conns assoc repo conn)
(state/set-current-repo! repo)
(swap! state/state merge
{:git/current-repo repo
:route-match {:data {:name :page}}
:config {repo {}}}))
(defn- render-page-html
[db page-uuid]
(let [entity (d/entity db [:block/uuid page-uuid])
title (or (:block/title entity) "Logseq Publish")
body (rum/render-static-markup
[:div#root
(page/page-inner {:page entity :repo (state/get-current-repo)})])]
(str "<!doctype html>"
"<html><head><meta charset=\"utf-8\"/>"
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>"
"<title>" title "</title>"
"<style>" minimal-css "</style>"
"</head><body>"
body
"</body></html>")))
(defn ^:export render-page
"Render a published page HTML string from transit payload and page uuid string."
[transit-str page-uuid-str]
(ensure-global-stubs!)
(let [payload (ldb/read-transit-str transit-str)
datoms (:datoms payload)
repo (:publish/graph payload)
conn (d/conn-from-datoms datoms db-schema/schema)
page-uuid (uuid page-uuid-str)]
(prepare-state! repo conn)
(render-page-html @conn page-uuid)))
(set! (.-logseqPublishRender js/globalThis) render-page)