From 704312b4077b398ef46323eec88b900800236fb1 Mon Sep 17 00:00:00 2001 From: Tienson Qin Date: Sat, 27 Dec 2025 04:44:05 +0800 Subject: [PATCH] Add publish dep --- deps.edn | 1 + deps/publish/README.md | 18 ++++++ deps/publish/deps.edn | 6 ++ deps/publish/package.json | 8 +++ deps/publish/src/logseq/publish.cljc | 24 ++++++++ deps/publish/src/logseq/publish/snapshot.cljc | 29 ++++++++++ deps/publish/src/logseq/publish/ssr.cljc | 15 +++++ deps/publish/src/logseq/publish/storage.cljc | 9 +++ scripts/nbb.edn | 2 + scripts/src/logseq/tasks/dev/lint.clj | 2 +- src/main/frontend/components/page_menu.cljs | 5 ++ src/main/frontend/handler/publish.cljs | 55 +++++++++++++++++++ 12 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 deps/publish/README.md create mode 100644 deps/publish/deps.edn create mode 100644 deps/publish/package.json create mode 100644 deps/publish/src/logseq/publish.cljc create mode 100644 deps/publish/src/logseq/publish/snapshot.cljc create mode 100644 deps/publish/src/logseq/publish/ssr.cljc create mode 100644 deps/publish/src/logseq/publish/storage.cljc create mode 100644 src/main/frontend/handler/publish.cljs diff --git a/deps.edn b/deps.edn index 4990db9211..2c5b565204 100644 --- a/deps.edn +++ b/deps.edn @@ -38,6 +38,7 @@ logseq/common {:local/root "deps/common"} logseq/graph-parser {:local/root "deps/graph-parser"} logseq/outliner {:local/root "deps/outliner"} + logseq/publish {:local/root "deps/publish"} logseq/publishing {:local/root "deps/publishing"} logseq/cli {:local/root "deps/cli"} logseq/shui {:local/root "deps/shui"} diff --git a/deps/publish/README.md b/deps/publish/README.md new file mode 100644 index 0000000000..46012a31ca --- /dev/null +++ b/deps/publish/README.md @@ -0,0 +1,18 @@ +## Description + +Shared library for page publishing (snapshot payloads, SSR helpers, shared schemas, and storage contracts). + +The Cloudflare Durable Object implementation is expected to use SQLite with the +Logseq datascript fork layered on top. + +## API + +Namespaces live under `logseq.publish`. + +## Usage + +This module is intended to be consumed by the Logseq app and the publishing worker. + +## Dev + +Keep this module aligned with the main repo's linting and testing conventions. diff --git a/deps/publish/deps.edn b/deps/publish/deps.edn new file mode 100644 index 0000000000..d10302cd6f --- /dev/null +++ b/deps/publish/deps.edn @@ -0,0 +1,6 @@ +{:deps + {} + :aliases + {:clj-kondo + {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2024.09.27"}} + :main-opts ["-m" "clj-kondo.main"]}}} diff --git a/deps/publish/package.json b/deps/publish/package.json new file mode 100644 index 0000000000..be8d213694 --- /dev/null +++ b/deps/publish/package.json @@ -0,0 +1,8 @@ +{ + "name": "@logseq/publish", + "version": "1.0.0", + "private": true, + "devDependencies": { + "@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v31" + } +} diff --git a/deps/publish/src/logseq/publish.cljc b/deps/publish/src/logseq/publish.cljc new file mode 100644 index 0000000000..621e65bcda --- /dev/null +++ b/deps/publish/src/logseq/publish.cljc @@ -0,0 +1,24 @@ +(ns logseq.publish + "Public entrypoint for page publishing shared logic." + (:require [logseq.publish.snapshot :as snapshot] + [logseq.publish.ssr :as ssr] + [logseq.publish.storage :as storage])) + +(defn normalize-snapshot + "Public wrapper around snapshot normalization." + [snapshot-map] + (snapshot/normalize-snapshot snapshot-map)) + +(defn snapshot-valid? + "Checks required keys in the snapshot." + [snapshot-map] + (snapshot/snapshot-valid? snapshot-map)) + +(defn render-page-html + "Render HTML for a published page." + [snapshot-map opts] + (ssr/render-page-html snapshot-map opts)) + +(def PublishStore storage/PublishStore) + +;; Placeholder namespace for page publishing shared logic. diff --git a/deps/publish/src/logseq/publish/snapshot.cljc b/deps/publish/src/logseq/publish/snapshot.cljc new file mode 100644 index 0000000000..c1bd45e89f --- /dev/null +++ b/deps/publish/src/logseq/publish/snapshot.cljc @@ -0,0 +1,29 @@ +(ns logseq.publish.snapshot + "Utilities for shaping page publishing snapshot payloads.") + +(def required-keys + #{:page :blocks :linked-refs :config}) + +(defn normalize-snapshot + "Ensures the snapshot contains the minimum required keys. + + Expected shape: + {:page + :blocks + :linked-refs + :config + :assets } + " + [{:keys [page blocks linked-refs config] :as snapshot}] + (merge + {:page page + :blocks (or blocks []) + :linked-refs (or linked-refs []) + :config (or config {}) + :assets (:assets snapshot)} + (select-keys snapshot required-keys))) + +(defn snapshot-valid? + "Checks if required keys are present in the snapshot map." + [snapshot] + (every? #(contains? snapshot %) required-keys)) diff --git a/deps/publish/src/logseq/publish/ssr.cljc b/deps/publish/src/logseq/publish/ssr.cljc new file mode 100644 index 0000000000..c87da2ad4a --- /dev/null +++ b/deps/publish/src/logseq/publish/ssr.cljc @@ -0,0 +1,15 @@ +(ns logseq.publish.ssr + "SSR helpers for published pages.") + +(defn render-page-html + "Renders HTML for a published page. + + Options: + - :render-page-fn should return HTML string for the given snapshot. + - :wrap-html-fn should wrap the rendered body with document-level markup. + " + [snapshot {:keys [render-page-fn wrap-html-fn]}] + (let [body (when render-page-fn (render-page-fn snapshot))] + (if wrap-html-fn + (wrap-html-fn body) + body))) diff --git a/deps/publish/src/logseq/publish/storage.cljc b/deps/publish/src/logseq/publish/storage.cljc new file mode 100644 index 0000000000..e409aadb8b --- /dev/null +++ b/deps/publish/src/logseq/publish/storage.cljc @@ -0,0 +1,9 @@ +(ns logseq.publish.storage + "Contracts for durable storage backends.") + +(defprotocol PublishStore + "Storage for published page snapshots. Implementations should use SQLite as + the durable store and run the Logseq datascript fork on top of it." + (put-snapshot! [this page-id snapshot]) + (get-snapshot [this page-id]) + (delete-snapshot! [this page-id])) diff --git a/scripts/nbb.edn b/scripts/nbb.edn index f72a2ffe7a..f18f288735 100644 --- a/scripts/nbb.edn +++ b/scripts/nbb.edn @@ -7,5 +7,7 @@ ;; for config.edn logseq/common {:local/root "../deps/common"} + logseq/publish + {:local/root "../deps/publish"} logseq/publishing {:local/root "../deps/publishing"}}} diff --git a/scripts/src/logseq/tasks/dev/lint.clj b/scripts/src/logseq/tasks/dev/lint.clj index a97ab23ce0..d6e4a61120 100644 --- a/scripts/src/logseq/tasks/dev/lint.clj +++ b/scripts/src/logseq/tasks/dev/lint.clj @@ -24,7 +24,7 @@ (defn kondo-git-changes "Run clj-kondo across dirs and only for files that git diff detects as unstaged changes" [] - (let [kondo-dirs ["src" "deps/common" "deps/db" "deps/graph-parser" "deps/outliner" "deps/publishing" "deps/cli"] + (let [kondo-dirs ["src" "deps/common" "deps/db" "deps/graph-parser" "deps/outliner" "deps/publish" "deps/publishing" "deps/cli"] dir-regex (re-pattern (str "^(" (string/join "|" kondo-dirs) ")")) dir-to-files (->> (shell {:out :string} "git diff --name-only") :out diff --git a/src/main/frontend/components/page_menu.cljs b/src/main/frontend/components/page_menu.cljs index c1399e6227..df29485550 100644 --- a/src/main/frontend/components/page_menu.cljs +++ b/src/main/frontend/components/page_menu.cljs @@ -9,6 +9,7 @@ [frontend.handler.db-based.page :as db-page-handler] [frontend.handler.notification :as notification] [frontend.handler.page :as page-handler] + [frontend.handler.publish :as publish-handler] [frontend.mobile.util :as mobile-util] [frontend.state :as state] [frontend.util :as util] @@ -97,6 +98,10 @@ :export-type :page})) {:class "w-auto md:max-w-4xl max-h-[80vh] overflow-y-auto"})}}) + (when (and page (not config/publishing?)) + {:title "Publish page" + :options {:on-click #(publish-handler/publish-page! page)}}) + (when (util/electron?) {:title (t (if public? :page/make-private :page/make-public)) :options {:on-click diff --git a/src/main/frontend/handler/publish.cljs b/src/main/frontend/handler/publish.cljs new file mode 100644 index 0000000000..637d6a3126 --- /dev/null +++ b/src/main/frontend/handler/publish.cljs @@ -0,0 +1,55 @@ +(ns frontend.handler.publish + "Prepare publish payloads for pages." + (:require [datascript.core :as d] + [frontend.db :as db] + [frontend.handler.notification :as notification] + [frontend.state :as state] + [logseq.db :as ldb] + [logseq.db.common.entity-util :as entity-util] + [logseq.db.frontend.schema :as db-schema])) + +(defn- datom->vec + [datom] + [(:e datom) (:a datom) (:v datom) (:tx datom) (:added datom)]) + +(defn- collect-page-eids + [db page-entity] + (let [page-id (:db/id page-entity) + blocks (ldb/get-page-blocks db page-id) + block-eids (map :db/id blocks) + ref-eids (->> blocks (mapcat :block/refs) (keep :db/id)) + tag-eids (->> blocks (mapcat :block/tags) (keep :db/id)) + page-eids (->> blocks (map :block/page) (keep :db/id))] + {:blocks blocks + :eids (->> (concat [page-id] block-eids ref-eids tag-eids page-eids) + (remove nil?) + distinct)})) + +(defn build-page-publish-datoms + "Builds a datom snapshot for a single page. + + References/backlinks are intentionally ignored at this stage. + " + [db page-entity] + (let [{:keys [blocks eids]} (collect-page-eids db page-entity) + datoms (mapcat (fn [eid] + (map datom->vec (d/datoms db :eavt eid))) + eids)] + {:page (entity-util/entity->map page-entity) + :page-id (:db/id page-entity) + :block-count (count blocks) + :schema-version (db-schema/schema-version->string db-schema/version) + :datoms (vec datoms)})) + +(defn publish-page! + "Prepares the publish payload for a page. The upload step is stubbed for now." + [page] + (let [repo (state/get-current-repo)] + (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! "Publish payload prepared." :success) + (js/console.log "Publish payload" (clj->js payload)) + payload) + (notification/show! "Publish failed: invalid page." :error)) + (notification/show! "Publish failed: missing database." :error))))