mirror of
https://github.com/logseq/logseq.git
synced 2026-02-01 22:47:36 +00:00
feat: password protected page
This commit is contained in:
28
deps/publish/src/logseq/publish/meta_store.cljs
vendored
28
deps/publish/src/logseq/publish/meta_store.cljs
vendored
@@ -22,6 +22,7 @@
|
||||
"owner_sub TEXT,"
|
||||
"created_at INTEGER,"
|
||||
"updated_at INTEGER,"
|
||||
"password_hash TEXT,"
|
||||
"PRIMARY KEY (graph_uuid, page_uuid)"
|
||||
");"))
|
||||
(let [cols (publish-common/get-sql-rows (publish-common/sql-exec sql "PRAGMA table_info(pages);"))
|
||||
@@ -31,7 +32,9 @@
|
||||
(when-not (contains? col-names "page_tags")
|
||||
(publish-common/sql-exec sql "ALTER TABLE pages ADD COLUMN page_tags TEXT;"))
|
||||
(when-not (contains? col-names "short_id")
|
||||
(publish-common/sql-exec sql "ALTER TABLE pages ADD COLUMN short_id TEXT;")))
|
||||
(publish-common/sql-exec sql "ALTER TABLE pages ADD COLUMN short_id TEXT;"))
|
||||
(when-not (contains? col-names "password_hash")
|
||||
(publish-common/sql-exec sql "ALTER TABLE pages ADD COLUMN password_hash TEXT;")))
|
||||
(let [cols (publish-common/get-sql-rows (publish-common/sql-exec sql "PRAGMA table_info(page_refs);"))
|
||||
col-names (set (map #(aget % "name") cols))]
|
||||
(when (seq col-names)
|
||||
@@ -109,8 +112,9 @@
|
||||
"owner_sub,"
|
||||
"created_at,"
|
||||
"updated_at,"
|
||||
"short_id"
|
||||
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
"short_id,"
|
||||
"password_hash"
|
||||
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
" ON CONFLICT(graph_uuid, page_uuid) DO UPDATE SET"
|
||||
" page_uuid=excluded.page_uuid,"
|
||||
" page_title=excluded.page_title,"
|
||||
@@ -122,7 +126,8 @@
|
||||
" r2_key=excluded.r2_key,"
|
||||
" owner_sub=excluded.owner_sub,"
|
||||
" updated_at=excluded.updated_at,"
|
||||
" short_id=excluded.short_id;")
|
||||
" short_id=excluded.short_id,"
|
||||
" password_hash=excluded.password_hash;")
|
||||
(aget body "page_uuid")
|
||||
(aget body "page_title")
|
||||
(aget body "page_tags")
|
||||
@@ -135,7 +140,8 @@
|
||||
(aget body "owner_sub")
|
||||
(aget body "created_at")
|
||||
(aget body "updated_at")
|
||||
(aget body "short_id"))
|
||||
(aget body "short_id")
|
||||
(aget body "password_hash"))
|
||||
(let [refs (aget body "refs")
|
||||
tagged-nodes (aget body "tagged_nodes")
|
||||
graph-uuid (aget body "graph")
|
||||
@@ -244,6 +250,18 @@
|
||||
row (first rows)]
|
||||
(publish-common/json-response {:page (when row (js->clj row :keywordize-keys false))}))
|
||||
|
||||
(= (nth parts 4 nil) "password")
|
||||
(let [rows (publish-common/get-sql-rows
|
||||
(publish-common/sql-exec sql
|
||||
(str "SELECT password_hash "
|
||||
"FROM pages WHERE graph_uuid = ? AND page_uuid = ? LIMIT 1;")
|
||||
graph-uuid
|
||||
page-uuid))
|
||||
row (first rows)]
|
||||
(if-not row
|
||||
(publish-common/not-found)
|
||||
(publish-common/json-response {:password_hash (aget row "password_hash")})))
|
||||
|
||||
(= (nth parts 4 nil) "refs")
|
||||
(let [rows (publish-common/get-sql-rows
|
||||
(publish-common/sql-exec sql
|
||||
|
||||
45
deps/publish/src/logseq/publish/publish.css
vendored
45
deps/publish/src/logseq/publish/publish.css
vendored
@@ -339,6 +339,51 @@ a:hover {
|
||||
letter-spacing: 0.12em;
|
||||
}
|
||||
|
||||
.password-card {
|
||||
margin: 20px auto 0;
|
||||
max-width: 460px;
|
||||
padding: 24px;
|
||||
border-radius: 18px;
|
||||
border: 1px solid var(--border);
|
||||
background: #fff7ee;
|
||||
box-shadow: 0 14px 24px rgba(217, 125, 63, 0.12);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.password-form {
|
||||
margin-top: 18px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.password-label {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.14em;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.password-input {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(31, 26, 20, 0.2);
|
||||
font-size: 15px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.password-input:focus {
|
||||
outline: 2px solid rgba(217, 125, 63, 0.4);
|
||||
border-color: rgba(217, 125, 63, 0.6);
|
||||
}
|
||||
|
||||
.password-error {
|
||||
margin: 8px 0 0;
|
||||
color: #b42318;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.not-found {
|
||||
text-align: center;
|
||||
padding: 32px 16px 8px;
|
||||
|
||||
41
deps/publish/src/logseq/publish/publish.js
vendored
41
deps/publish/src/logseq/publish/publish.js
vendored
@@ -1,19 +1,27 @@
|
||||
import katexPkg from "https://esm.sh/katex@0.16.10?bundle";
|
||||
|
||||
// Core CodeMirror pieces
|
||||
import { EditorState } from "https://esm.sh/@codemirror/state@6";
|
||||
import {
|
||||
EditorState,
|
||||
EditorView,
|
||||
basicSetup,
|
||||
defaultHighlightStyle,
|
||||
lineNumbers,
|
||||
} from "https://esm.sh/@codemirror/view@6";
|
||||
|
||||
// Highlighting
|
||||
import {
|
||||
syntaxHighlighting,
|
||||
javascript,
|
||||
python,
|
||||
html,
|
||||
css,
|
||||
json,
|
||||
markdown,
|
||||
sql,
|
||||
clojure,
|
||||
} from "https://esm.sh/@codemirror/basic-setup@0.20.0?bundle";
|
||||
defaultHighlightStyle,
|
||||
} from "https://esm.sh/@codemirror/language@6";
|
||||
|
||||
// Languages
|
||||
import { javascript } from "https://esm.sh/@codemirror/lang-javascript@6";
|
||||
import { python } from "https://esm.sh/@codemirror/lang-python@6";
|
||||
import { html } from "https://esm.sh/@codemirror/lang-html@6";
|
||||
import { json } from "https://esm.sh/@codemirror/lang-json@6";
|
||||
import { markdown } from "https://esm.sh/@codemirror/lang-markdown@6";
|
||||
import { sql } from "https://esm.sh/@codemirror/lang-sql@6";
|
||||
import { css } from "https://esm.sh/@codemirror/lang-css@6";
|
||||
import { clojure } from "https://esm.sh/@nextjournal/lang-clojure";
|
||||
|
||||
const katex = katexPkg.default || katexPkg;
|
||||
|
||||
@@ -54,6 +62,7 @@ const initPublish = () => {
|
||||
const codeEl = block.querySelector("code");
|
||||
const doc = codeEl ? codeEl.textContent : "";
|
||||
block.textContent = "";
|
||||
|
||||
const lang = (block.dataset.lang || "").toLowerCase();
|
||||
const langExt = (() => {
|
||||
if (!lang) return null;
|
||||
@@ -62,24 +71,28 @@ const initPublish = () => {
|
||||
}
|
||||
if (["py", "python"].includes(lang)) return python();
|
||||
if (["html", "htm"].includes(lang)) return html();
|
||||
if (["css", "scss"].includes(lang)) return css();
|
||||
if (["json"].includes(lang)) return json();
|
||||
if (["md", "markdown"].includes(lang)) return markdown();
|
||||
if (["sql"].includes(lang)) return sql();
|
||||
if (["css", "scss"].includes(lang)) return css();
|
||||
if (["clj", "cljc", "cljs", "clojure"].includes(lang)) return clojure();
|
||||
return null;
|
||||
})();
|
||||
|
||||
const extensions = [
|
||||
basicSetup,
|
||||
lineNumbers(),
|
||||
syntaxHighlighting(defaultHighlightStyle),
|
||||
EditorView.editable.of(false),
|
||||
EditorView.lineWrapping,
|
||||
];
|
||||
|
||||
if (langExt) extensions.push(langExt);
|
||||
|
||||
const state = EditorState.create({
|
||||
doc,
|
||||
extensions,
|
||||
});
|
||||
|
||||
new EditorView({ state, parent: block });
|
||||
});
|
||||
};
|
||||
|
||||
31
deps/publish/src/logseq/publish/render.cljs
vendored
31
deps/publish/src/logseq/publish/render.cljs
vendored
@@ -696,6 +696,37 @@
|
||||
[:p.tag-sub "This page hasn't been published yet."]]]]]
|
||||
(str "<!doctype html>" (render-hiccup doc))))
|
||||
|
||||
(defn render-password-html
|
||||
[graph-uuid page-uuid wrong?]
|
||||
(let [title "Protected page"
|
||||
doc [:html
|
||||
[:head
|
||||
[:meta {:charset "utf-8"}]
|
||||
[:meta {:name "viewport" :content "width=device-width,initial-scale=1"}]
|
||||
[:title title]
|
||||
[:link {:rel "stylesheet" :href "/static/publish.css"}]]
|
||||
[:body
|
||||
[:main.wrap
|
||||
[:div.page-toolbar
|
||||
(when graph-uuid
|
||||
[:a.toolbar-btn {:href (str "/graph/" graph-uuid)} "Home"])]
|
||||
[:div.password-card
|
||||
[:h1 title]
|
||||
[:p.tag-sub "This page is password protected."]
|
||||
(when wrong?
|
||||
[:p.password-error "Incorrect password."])
|
||||
[:form.password-form {:method "GET"}
|
||||
(when page-uuid
|
||||
[:input {:type "hidden" :name "page" :value page-uuid}])
|
||||
[:label.password-label {:for "publish-password"} "Enter password"]
|
||||
[:input.password-input {:id "publish-password"
|
||||
:name "password"
|
||||
:type "password"
|
||||
:placeholder "Password"
|
||||
:required true}]
|
||||
[:button.toolbar-btn {:type "submit"} "Unlock"]]]]]]]
|
||||
(str "<!doctype html>" (render-hiccup doc))))
|
||||
|
||||
(defn render-404-html
|
||||
[]
|
||||
(let [title "Page not found"
|
||||
|
||||
192
deps/publish/src/logseq/publish/routes.cljs
vendored
192
deps/publish/src/logseq/publish/routes.cljs
vendored
@@ -11,6 +11,35 @@
|
||||
(def publish-css (resource/inline "logseq/publish/publish.css"))
|
||||
(def publish-js (resource/inline "logseq/publish/publish.js"))
|
||||
|
||||
(defn- request-password
|
||||
[request]
|
||||
(let [url (js/URL. (.-url request))
|
||||
query (.get (.-searchParams url) "password")
|
||||
header (.get (.-headers request) "x-publish-password")]
|
||||
(or header query)))
|
||||
|
||||
(defn- fetch-page-password-hash
|
||||
[graph-uuid page-uuid env]
|
||||
(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/pages/" graph-uuid "/" page-uuid "/password")
|
||||
#js {:method "GET"})]
|
||||
(when (.-ok resp)
|
||||
(js-await [data (.json resp)]
|
||||
(aget data "password_hash")))))
|
||||
|
||||
(defn- check-page-password
|
||||
[request graph-uuid page-uuid env]
|
||||
(js-await [stored-hash (fetch-page-password-hash graph-uuid page-uuid env)]
|
||||
(if (string/blank? stored-hash)
|
||||
{:allowed? true :provided? false}
|
||||
(let [provided (request-password request)]
|
||||
(if (string? provided)
|
||||
(js-await [hashed (publish-common/sha256-hex provided)]
|
||||
{:allowed? (= hashed stored-hash) :provided? true})
|
||||
{:allowed? false :provided? false})))))
|
||||
|
||||
(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 "))
|
||||
@@ -37,6 +66,8 @@
|
||||
(get payload "page-title")
|
||||
(when page-eid
|
||||
(publish-model/entity->title (get payload-entities page-eid))))
|
||||
page-password-hash (or (:page-password-hash payload)
|
||||
(get payload "page-password-hash"))
|
||||
refs (when (and page-eid page-title)
|
||||
(publish-index/page-refs-from-payload payload page-eid page_uuid page-title graph))
|
||||
tagged-nodes (when (and page-eid page-title)
|
||||
@@ -67,6 +98,7 @@
|
||||
:page_title page-title
|
||||
:page_tags (when page-tags
|
||||
(js/JSON.stringify (clj->js page-tags)))
|
||||
:password_hash page-password-hash
|
||||
:graph graph-uuid
|
||||
:schema_version schema_version
|
||||
:block_count block_count
|
||||
@@ -136,15 +168,18 @@
|
||||
meta-resp (.fetch do-stub (str "https://publish/pages/" graph-uuid "/" page-uuid))]
|
||||
(if-not (.-ok meta-resp)
|
||||
(handle-tag-page-html graph-uuid page-uuid env)
|
||||
(js-await [meta (.json meta-resp)
|
||||
etag (aget meta "content_hash")
|
||||
if-none-match (publish-common/normalize-etag (.get (.-headers request) "if-none-match"))]
|
||||
(if (and etag if-none-match (= etag if-none-match))
|
||||
(js/Response. nil #js {:status 304
|
||||
:headers (publish-common/merge-headers
|
||||
#js {:etag etag}
|
||||
(publish-common/cors-headers))})
|
||||
(publish-common/json-response (js->clj meta :keywordize-keys true) 200))))))))
|
||||
(js-await [{:keys [allowed?]} (check-page-password request graph-uuid page-uuid env)]
|
||||
(if-not allowed?
|
||||
(publish-common/json-response {:error "password required"} 401)
|
||||
(js-await [meta (.json meta-resp)
|
||||
etag (aget meta "content_hash")
|
||||
if-none-match (publish-common/normalize-etag (.get (.-headers request) "if-none-match"))]
|
||||
(if (and etag if-none-match (= etag if-none-match))
|
||||
(js/Response. nil #js {:status 304
|
||||
:headers (publish-common/merge-headers
|
||||
#js {:etag etag}
|
||||
(publish-common/cors-headers))})
|
||||
(publish-common/json-response (js->clj meta :keywordize-keys true) 200))))))))))
|
||||
|
||||
(defn handle-get-page-transit [request env]
|
||||
(let [url (js/URL. (.-url request))
|
||||
@@ -163,23 +198,26 @@
|
||||
#js {:headers (publish-common/merge-headers
|
||||
#js {"content-type" "text/html; charset=utf-8"}
|
||||
(publish-common/cors-headers))})
|
||||
(js-await [meta (.json meta-resp)
|
||||
r2-key (aget meta "r2_key")]
|
||||
(if-not r2-key
|
||||
(publish-common/json-response {:error "missing transit"} 404)
|
||||
(js-await [etag (aget meta "content_hash")
|
||||
if-none-match (publish-common/normalize-etag (.get (.-headers request) "if-none-match"))
|
||||
signed-url (when-not (and etag if-none-match (= etag if-none-match))
|
||||
(publish-common/presign-r2-url r2-key env))]
|
||||
(if (and etag if-none-match (= etag if-none-match))
|
||||
(js/Response. nil #js {:status 304
|
||||
:headers (publish-common/merge-headers
|
||||
#js {:etag etag}
|
||||
(publish-common/cors-headers))})
|
||||
(publish-common/json-response {:url signed-url
|
||||
:expires_in 300
|
||||
:etag etag}
|
||||
200))))))))))
|
||||
(js-await [{:keys [allowed?]} (check-page-password request graph-uuid page-uuid env)]
|
||||
(if-not allowed?
|
||||
(publish-common/json-response {:error "password required"} 401)
|
||||
(js-await [meta (.json meta-resp)
|
||||
r2-key (aget meta "r2_key")]
|
||||
(if-not r2-key
|
||||
(publish-common/json-response {:error "missing transit"} 404)
|
||||
(js-await [etag (aget meta "content_hash")
|
||||
if-none-match (publish-common/normalize-etag (.get (.-headers request) "if-none-match"))
|
||||
signed-url (when-not (and etag if-none-match (= etag if-none-match))
|
||||
(publish-common/presign-r2-url r2-key env))]
|
||||
(if (and etag if-none-match (= etag if-none-match))
|
||||
(js/Response. nil #js {:status 304
|
||||
:headers (publish-common/merge-headers
|
||||
#js {:etag etag}
|
||||
(publish-common/cors-headers))})
|
||||
(publish-common/json-response {:url signed-url
|
||||
:expires_in 300
|
||||
:etag etag}
|
||||
200))))))))))))
|
||||
|
||||
(defn handle-get-page-refs [request env]
|
||||
(let [url (js/URL. (.-url request))
|
||||
@@ -188,18 +226,21 @@
|
||||
page-uuid (nth parts 3 nil)]
|
||||
(if (or (nil? graph-uuid) (nil? page-uuid))
|
||||
(publish-common/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)
|
||||
(js/Response.
|
||||
(publish-render/render-404-html)
|
||||
#js {:headers (publish-common/merge-headers
|
||||
#js {"content-type" "text/html; charset=utf-8"}
|
||||
(publish-common/cors-headers))})
|
||||
(js-await [refs (.json refs-resp)]
|
||||
(publish-common/json-response (js->clj refs :keywordize-keys true) 200)))))))
|
||||
(js-await [{:keys [allowed?]} (check-page-password request graph-uuid page-uuid env)]
|
||||
(if-not allowed?
|
||||
(publish-common/json-response {:error "password required"} 401)
|
||||
(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)
|
||||
(js/Response.
|
||||
(publish-render/render-404-html)
|
||||
#js {:headers (publish-common/merge-headers
|
||||
#js {"content-type" "text/html; charset=utf-8"}
|
||||
(publish-common/cors-headers))})
|
||||
(js-await [refs (.json refs-resp)]
|
||||
(publish-common/json-response (js->clj refs :keywordize-keys true) 200)))))))))
|
||||
|
||||
(defn handle-get-page-tagged-nodes [request env]
|
||||
(let [url (js/URL. (.-url request))
|
||||
@@ -208,14 +249,17 @@
|
||||
page-uuid (nth parts 3 nil)]
|
||||
(if (or (nil? graph-uuid) (nil? page-uuid))
|
||||
(publish-common/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)
|
||||
tags-resp (.fetch do-stub (str "https://publish/pages/" graph-uuid "/" page-uuid "/tagged_nodes"))]
|
||||
(if-not (.-ok tags-resp)
|
||||
(publish-common/not-found)
|
||||
(js-await [tags (.json tags-resp)]
|
||||
(publish-common/json-response (js->clj tags :keywordize-keys true) 200)))))))
|
||||
(js-await [{:keys [allowed?]} (check-page-password request graph-uuid page-uuid env)]
|
||||
(if-not allowed?
|
||||
(publish-common/json-response {:error "password required"} 401)
|
||||
(js-await [^js do-ns (aget env "PUBLISH_META_DO")
|
||||
do-id (.idFromName do-ns "index")
|
||||
do-stub (.get do-ns do-id)
|
||||
tags-resp (.fetch do-stub (str "https://publish/pages/" graph-uuid "/" page-uuid "/tagged_nodes"))]
|
||||
(if-not (.-ok tags-resp)
|
||||
(publish-common/not-found)
|
||||
(js-await [tags (.json tags-resp)]
|
||||
(publish-common/json-response (js->clj tags :keywordize-keys true) 200)))))))))
|
||||
|
||||
(defn handle-list-pages [env]
|
||||
(js-await [^js do-ns (aget env "PUBLISH_META_DO")
|
||||
@@ -439,30 +483,38 @@
|
||||
#js {:headers (publish-common/merge-headers
|
||||
#js {"content-type" "text/html; charset=utf-8"}
|
||||
(publish-common/cors-headers))}))
|
||||
(js-await [meta (.json meta-resp)
|
||||
index-id (.idFromName do-ns "index")
|
||||
index-stub (.get do-ns index-id)
|
||||
refs-resp (.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)))
|
||||
tags-resp (.fetch index-stub (str "https://publish/pages/" graph-uuid "/" page-uuid "/tagged_nodes")
|
||||
#js {:method "GET"})
|
||||
tagged-nodes (when (and tags-resp (.-ok tags-resp))
|
||||
(js-await [raw (.json tags-resp)]
|
||||
(js->clj (or (aget raw "tagged_nodes") #js [])
|
||||
:keywordize-keys true)))
|
||||
r2 (aget env "PUBLISH_R2")
|
||||
object (.get r2 (aget meta "r2_key"))]
|
||||
(if-not object
|
||||
(publish-common/json-response {:error "missing transit blob"} 404)
|
||||
(js-await [buffer (.arrayBuffer object)
|
||||
transit (.decode publish-common/text-decoder buffer)]
|
||||
(js/Response.
|
||||
(publish-render/render-page-html transit page-uuid refs-json tagged-nodes)
|
||||
#js {:headers (publish-common/merge-headers
|
||||
#js {"content-type" "text/html; charset=utf-8"}
|
||||
(publish-common/cors-headers))})))))))))
|
||||
(js-await [{:keys [allowed? provided?]} (check-page-password request graph-uuid page-uuid env)]
|
||||
(if-not allowed?
|
||||
(js/Response.
|
||||
(publish-render/render-password-html graph-uuid page-uuid provided?)
|
||||
#js {:status 401
|
||||
:headers (publish-common/merge-headers
|
||||
#js {"content-type" "text/html; charset=utf-8"}
|
||||
(publish-common/cors-headers))})
|
||||
(js-await [meta (.json meta-resp)
|
||||
index-id (.idFromName do-ns "index")
|
||||
index-stub (.get do-ns index-id)
|
||||
refs-resp (.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)))
|
||||
tags-resp (.fetch index-stub (str "https://publish/pages/" graph-uuid "/" page-uuid "/tagged_nodes")
|
||||
#js {:method "GET"})
|
||||
tagged-nodes (when (and tags-resp (.-ok tags-resp))
|
||||
(js-await [raw (.json tags-resp)]
|
||||
(js->clj (or (aget raw "tagged_nodes") #js [])
|
||||
:keywordize-keys true)))
|
||||
r2 (aget env "PUBLISH_R2")
|
||||
object (.get r2 (aget meta "r2_key"))]
|
||||
(if-not object
|
||||
(publish-common/json-response {:error "missing transit blob"} 404)
|
||||
(js-await [buffer (.arrayBuffer object)
|
||||
transit (.decode publish-common/text-decoder buffer)]
|
||||
(js/Response.
|
||||
(publish-render/render-page-html transit page-uuid refs-json tagged-nodes)
|
||||
#js {:headers (publish-common/merge-headers
|
||||
#js {"content-type" "text/html; charset=utf-8"}
|
||||
(publish-common/cors-headers))})))))))))))
|
||||
|
||||
(defn handle-fetch [request env]
|
||||
(let [url (js/URL. (.-url request))
|
||||
|
||||
@@ -16,8 +16,42 @@
|
||||
[frontend.util.page :as page-util]
|
||||
[logseq.common.path :as path]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.shui.hooks :as hooks]
|
||||
[logseq.shui.ui :as shui]
|
||||
[promesa.core :as p]))
|
||||
[promesa.core :as p]
|
||||
[rum.core :as rum]))
|
||||
|
||||
(rum/defc publish-page-dialog
|
||||
[page]
|
||||
(let [[password set-password!] (hooks/use-state "")
|
||||
[publishing? set-publishing!] (hooks/use-state false)
|
||||
submit! (fn []
|
||||
(when-not publishing?
|
||||
(set-publishing! true)
|
||||
(-> (publish-handler/publish-page! page {:password password})
|
||||
(p/finally (fn []
|
||||
(set-publishing! false)
|
||||
(shui/dialog-close!))))))]
|
||||
[:div.flex.flex-col.gap-4.p-2
|
||||
[:div.text-lg.font-medium "Publish page"]
|
||||
[:div.text-sm.opacity-70
|
||||
"Optionally protect this page with a password. Leave empty for public access."]
|
||||
(shui/toggle-password
|
||||
{:placeholder "Optional password"
|
||||
:value password
|
||||
:on-change (fn [e]
|
||||
(set-password! (util/evalue e)))})
|
||||
[:div.flex.justify-end.gap-2
|
||||
(shui/button
|
||||
{:variant "ghost"
|
||||
:on-click #(shui/dialog-close!)}
|
||||
"Cancel")
|
||||
(shui/button
|
||||
{:on-click submit!
|
||||
:disabled publishing?}
|
||||
(if publishing?
|
||||
"Publishing..."
|
||||
"Publish"))]]))
|
||||
|
||||
(defn- delete-page!
|
||||
[page]
|
||||
@@ -100,7 +134,8 @@
|
||||
|
||||
(when (and page (not config/publishing?))
|
||||
{:title "Publish page"
|
||||
:options {:on-click #(publish-handler/publish-page! page)}})
|
||||
:options {:on-click #(shui/dialog-open! (fn [] (publish-page-dialog page))
|
||||
{:class "w-auto max-w-md"})}})
|
||||
|
||||
(when (util/electron?)
|
||||
{:title (t (if public? :page/make-private :page/make-public))
|
||||
|
||||
@@ -119,11 +119,18 @@
|
||||
assets)))))
|
||||
|
||||
(defn- <post-publish!
|
||||
[payload]
|
||||
[payload {:keys [password]}]
|
||||
(let [token (state/get-auth-id-token)
|
||||
headers (cond-> {"content-type" "application/transit+json"}
|
||||
token (assoc "authorization" (str "Bearer " token)))]
|
||||
(p/let [body (ldb/write-transit-str payload)
|
||||
(p/let [page-password (some-> password string/trim)
|
||||
page-password (when (and (string? page-password)
|
||||
(not (string/blank? page-password)))
|
||||
page-password)
|
||||
page-password-hash (when page-password (<sha256-hex page-password))
|
||||
payload (cond-> payload
|
||||
page-password-hash (assoc :page-password-hash page-password-hash))
|
||||
body (ldb/write-transit-str payload)
|
||||
content-hash (<sha256-hex body)
|
||||
graph-uuid (or (:graph-uuid payload)
|
||||
(some-> (ldb/get-graph-rtc-uuid (db/get-db)) str))
|
||||
@@ -153,7 +160,7 @@
|
||||
|
||||
(defn publish-page!
|
||||
"Prepares and uploads the publish payload for a page."
|
||||
[page]
|
||||
[page & [{:keys [password]}]]
|
||||
(let [repo (state/get-current-repo)]
|
||||
(when-let [db* (and repo (db/get-db repo))]
|
||||
(if (and page (:db/id page))
|
||||
@@ -164,7 +171,7 @@
|
||||
graph-uuid)]
|
||||
(if payload
|
||||
(-> (p/let [_ (<upload-assets! repo graph-uuid payload)]
|
||||
(<post-publish! payload))
|
||||
(<post-publish! payload {:password password}))
|
||||
(p/then (fn [resp]
|
||||
(p/let [json (.json resp)
|
||||
data (bean/->clj json)]
|
||||
|
||||
Reference in New Issue
Block a user