This commit is contained in:
Tienson Qin
2025-12-30 15:52:44 +08:00
parent 60e79785f6
commit c97453d1dd
6 changed files with 741 additions and 90 deletions

View File

@@ -1,5 +1,6 @@
(ns logseq.publish.meta-store
(:require [clojure.string :as string]
(:require [cljs-bean.core :as bean]
[clojure.string :as string]
[logseq.publish.common :as publish-common])
(:require-macros [logseq.publish.async :refer [js-await]]))
@@ -71,6 +72,15 @@
"source_block_format TEXT,"
"updated_at INTEGER,"
"PRIMARY KEY (graph_uuid, tag_page_uuid, source_block_uuid)"
");"))
(publish-common/sql-exec sql
(str "CREATE TABLE IF NOT EXISTS page_blocks ("
"graph_uuid TEXT NOT NULL,"
"page_uuid TEXT NOT NULL,"
"block_uuid TEXT NOT NULL,"
"block_content TEXT,"
"updated_at INTEGER,"
"PRIMARY KEY (graph_uuid, block_uuid)"
");"))))
(defn parse-page-tags [value]
@@ -101,67 +111,71 @@
(cond
(= "POST" (.-method request))
(js-await [body (.json request)]
(publish-common/sql-exec sql
(str "INSERT INTO pages ("
"page_uuid,"
"page_title,"
"page_tags,"
"graph_uuid,"
"schema_version,"
"block_count,"
"content_hash,"
"content_length,"
"r2_key,"
"owner_sub,"
"owner_username,"
"created_at,"
"updated_at,"
"short_id,"
"password_hash"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
" ON CONFLICT(graph_uuid, page_uuid) DO UPDATE SET"
" page_uuid=excluded.page_uuid,"
" page_title=excluded.page_title,"
" page_tags=excluded.page_tags,"
" schema_version=excluded.schema_version,"
" block_count=excluded.block_count,"
" content_hash=excluded.content_hash,"
" content_length=excluded.content_length,"
" r2_key=excluded.r2_key,"
" owner_sub=excluded.owner_sub,"
" owner_username=excluded.owner_username,"
" updated_at=excluded.updated_at,"
" short_id=excluded.short_id,"
" password_hash=excluded.password_hash;")
(aget body "page_uuid")
(aget body "page_title")
(aget body "page_tags")
(aget body "graph")
(aget body "schema_version")
(aget body "block_count")
(aget body "content_hash")
(aget body "content_length")
(aget body "r2_key")
(aget body "owner_sub")
(aget body "owner_username")
(aget body "created_at")
(aget body "updated_at")
(aget body "short_id")
(aget body "password_hash"))
(let [refs (aget body "refs")
tagged-nodes (aget body "tagged_nodes")
graph-uuid (aget body "graph")
page-uuid (aget body "page_uuid")]
(when (and graph-uuid page-uuid)
(let [page-uuid (aget body "page_uuid")
graph-uuid (aget body "graph")]
(if (and (string? page-uuid) (string? graph-uuid))
(publish-common/sql-exec sql
"DELETE FROM page_refs WHERE graph_uuid = ? AND source_page_uuid = ?;"
(str "INSERT INTO pages ("
"page_uuid,"
"page_title,"
"page_tags,"
"graph_uuid,"
"schema_version,"
"block_count,"
"content_hash,"
"content_length,"
"r2_key,"
"owner_sub,"
"owner_username,"
"created_at,"
"updated_at,"
"short_id,"
"password_hash"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
" ON CONFLICT(graph_uuid, page_uuid) DO UPDATE SET"
" page_uuid=excluded.page_uuid,"
" page_title=excluded.page_title,"
" page_tags=excluded.page_tags,"
" schema_version=excluded.schema_version,"
" block_count=excluded.block_count,"
" content_hash=excluded.content_hash,"
" content_length=excluded.content_length,"
" r2_key=excluded.r2_key,"
" owner_sub=excluded.owner_sub,"
" owner_username=excluded.owner_username,"
" updated_at=excluded.updated_at,"
" short_id=excluded.short_id,"
" password_hash=excluded.password_hash;")
page-uuid
(aget body "page_title")
(aget body "page_tags")
graph-uuid
page-uuid)
(publish-common/sql-exec sql
"DELETE FROM page_tags WHERE graph_uuid = ? AND source_page_uuid = ?;"
graph-uuid
page-uuid)
(when (seq refs)
(aget body "schema_version")
(aget body "block_count")
(aget body "content_hash")
(aget body "content_length")
(aget body "r2_key")
(aget body "owner_sub")
(aget body "owner_username")
(aget body "created_at")
(aget body "updated_at")
(aget body "short_id")
(aget body "password_hash"))
(throw (js/Error. "publish: missing page_uuid or graph")))
(let [refs (aget body "refs")
tagged-nodes (aget body "tagged_nodes")
blocks (aget body "blocks")
graph-uuid (aget body "graph")
page-uuid (aget body "page_uuid")]
(when (and graph-uuid page-uuid)
(publish-common/sql-exec sql
"DELETE FROM page_refs WHERE graph_uuid = ? AND source_page_uuid = ?;"
graph-uuid
page-uuid)
(publish-common/sql-exec sql
"DELETE FROM page_tags WHERE graph_uuid = ? AND source_page_uuid = ?;"
graph-uuid
page-uuid)
(doseq [ref refs]
(publish-common/sql-exec sql
(str "INSERT OR REPLACE INTO page_refs ("
@@ -178,8 +192,8 @@
(aget ref "source_block_uuid")
(aget ref "source_block_content")
(aget ref "source_block_format")
(aget ref "updated_at"))))
(when (seq tagged-nodes)
(aget ref "updated_at")))
(doseq [tag tagged-nodes]
(publish-common/sql-exec sql
(str "INSERT OR REPLACE INTO page_tags ("
@@ -195,7 +209,21 @@
(aget tag "source_block_uuid")
(aget tag "source_block_content")
(aget tag "source_block_format")
(aget tag "updated_at")))))
(aget tag "updated_at"))))
(publish-common/sql-exec sql
"DELETE FROM page_blocks WHERE graph_uuid = ? AND page_uuid = ?;"
graph-uuid
page-uuid)
(doseq [block blocks]
(publish-common/sql-exec sql
(str "INSERT OR REPLACE INTO page_blocks ("
"graph_uuid, page_uuid, block_uuid, block_content, updated_at"
") VALUES (?, ?, ?, ?, ?);")
(aget body "graph")
(aget block "page_uuid")
(aget block "block_uuid")
(aget block "block_content")
(aget block "updated_at"))))
(publish-common/json-response {:ok true})))
(= "GET" (.-method request))
@@ -204,6 +232,45 @@
graph-uuid (nth parts 2 nil)
page-uuid (nth parts 3 nil)]
(cond
(= (nth parts 1 nil) "search")
(let [graph-uuid (nth parts 2 nil)
query (.get (.-searchParams url) "q")
query (some-> query string/trim)
query (when (and query (not (string/blank? query)))
(string/lower-case query))]
(if (or (string/blank? graph-uuid) (string/blank? query))
(publish-common/bad-request "missing graph uuid or query")
(let [like-query (str "%" query "%")
pages (publish-common/get-sql-rows
(publish-common/sql-exec sql
(str "SELECT page_uuid, page_title, short_id "
"FROM pages "
"WHERE graph_uuid = ? "
"AND password_hash IS NULL "
"AND page_title IS NOT NULL "
"AND lower(page_title) LIKE ? "
"ORDER BY updated_at DESC "
"LIMIT 20;")
graph-uuid
like-query))
blocks (publish-common/get-sql-rows
(publish-common/sql-exec sql
(str "SELECT page_blocks.page_uuid, page_blocks.block_uuid, "
"page_blocks.block_content, pages.page_title, pages.short_id "
"FROM page_blocks "
"LEFT JOIN pages "
"ON pages.graph_uuid = page_blocks.graph_uuid "
"AND pages.page_uuid = page_blocks.page_uuid "
"WHERE page_blocks.graph_uuid = ? "
"AND pages.password_hash IS NULL "
"AND page_blocks.block_content IS NOT NULL "
"AND lower(page_blocks.block_content) LIKE ? "
"ORDER BY page_blocks.updated_at DESC "
"LIMIT 50;")
graph-uuid
like-query))]
(publish-common/json-response {:pages pages :blocks blocks}))))
(= (nth parts 1 nil) "tag")
(let [tag-name (when-let [raw (nth parts 2 nil)]
(js/decodeURIComponent raw))

View File

@@ -142,6 +142,170 @@ a:hover {
margin-right: auto;
}
.publish-search {
position: relative;
display: flex;
align-items: center;
gap: 6px;
min-width: 240px;
flex-direction: row-reverse;
height: 32px;
}
.publish-search-toggle {
border: none;
background: var(--button-bg);
color: var(--ink);
width: 32px;
height: 32px;
padding: 0;
border-radius: 999px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
transition: background 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}
.publish-search-toggle:hover {
background: var(--button-bg-hover);
box-shadow: 0 6px 14px rgba(40, 39, 38, 0.12);
transform: translateY(-1px);
}
.publish-search-toggle .ti {
font-size: 16px;
}
.publish-search-input {
width: 0;
opacity: 0;
padding: 0 12px;
border: 1px solid var(--border);
background: var(--input-bg);
color: var(--ink);
font-size: 13px;
border-radius: 999px;
pointer-events: none;
transition: width 0.25s ease, opacity 0.2s ease, padding 0.25s ease;
}
.publish-search.is-expanded .publish-search-input {
width: min(320px, 70vw);
padding: 8px 12px;
opacity: 1;
pointer-events: auto;
}
.publish-search-input:focus {
outline: none;
}
.publish-search-input:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.publish-search-results {
opacity: 0;
transform: translateY(-6px);
position: absolute;
right: 0;
top: calc(100% + 24px);
z-index: 10;
width: min(480px, 90vw);
max-height: 320px;
overflow: auto;
border-radius: 16px;
background: var(--surface-strong);
box-shadow: var(--shadow);
transition: opacity 0.2s ease, transform 0.2s ease;
}
.publish-search.is-expanded .publish-search-results:not([hidden]) {
opacity: 1;
transform: translateY(0);
}
.publish-search-hint {
position: absolute;
right: 40px;
top: calc(100% + 6px);
font-size: 10px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
}
.publish-search.is-expanded .publish-search-hint {
opacity: 1;
}
.publish-search-list {
display: flex;
flex-direction: column;
gap: 2px;
}
.publish-search-section {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.18em;
color: var(--muted);
margin: 6px 6px 2px;
}
.publish-search-result {
background: transparent;
text-align: left;
display: grid;
padding: 4px 12px;
cursor: pointer;
color: var(--ink);
text-decoration: none;
}
.publish-search-result:hover {
background: var(--surface);
}
.publish-search-result:first-child {
padding-top: 12px;
}
.publish-search-result:last-child {
padding-bottom: 12px;
}
.publish-search-result.is-active {
background: var(--surface);
}
.publish-search-kind {
font-size: 10px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--muted);
}
.publish-search-title {
font-size: 13px;
font-weight: 600;
}
.publish-search-snippet {
font-size: 12px;
color: var(--muted);
}
.publish-search-empty {
font-size: 12px;
color: var(--muted);
padding: 8px;
}
.toolbar-btn {
background: transparent;
border: none;
@@ -671,6 +835,18 @@ a:hover {
justify-content: flex-end;
}
.publish-search {
min-width: 100%;
}
.publish-search-input {
width: 0;
}
.publish-search.is-expanded .publish-search-input {
width: 100%;
}
.block-children {
margin-left: 12px;
}

View File

@@ -244,6 +244,13 @@ document.addEventListener("keydown", (event) => {
return;
}
if (sequenceKey === "t" && key === "t") {
resetSequence();
window.toggleTheme();
event.preventDefault();
return;
}
if (key === "t") {
sequenceKey = "t";
if (sequenceTimer) clearTimeout(sequenceTimer);
@@ -261,6 +268,42 @@ document.addEventListener("click", (event) => {
window.toggleTheme();
});
const searchStateMap = new WeakMap();
const getSearchContainerState = () => {
const container =
document.querySelector(".publish-search.is-expanded") ||
document.querySelector(".publish-search");
if (!container) return null;
return searchStateMap.get(container) || null;
};
document.addEventListener("keydown", (event) => {
const isMod = event.metaKey || event.ctrlKey;
if (!isMod) return;
const key = (event.key || "").toLowerCase();
if (!key) return;
const typingTarget = isTypingTarget(event.target);
if (
typingTarget &&
!event.target.classList?.contains("publish-search-input")
) {
return;
}
const state = getSearchContainerState();
if (!state) return;
if (key === "k") {
event.preventDefault();
state.setExpanded(true);
state.focusInput();
return;
}
});
window.toggleTopBlocks = (btn) => {
const list = document.querySelector(".blocks");
if (!list) return;
@@ -350,6 +393,264 @@ const initTwitterEmbeds = () => {
});
};
const buildSnippet = (text, query) => {
const haystack = text.toLowerCase();
const needle = query.toLowerCase();
const idx = haystack.indexOf(needle);
if (idx < 0) return text.slice(0, 160);
const start = Math.max(0, idx - 48);
const end = Math.min(text.length, idx + needle.length + 48);
return text.slice(start, end).replace(/\s+/g, " ").trim();
};
const initSearch = () => {
const containers = Array.from(
document.querySelectorAll(".publish-search")
);
if (!containers.length) return;
containers.forEach((container) => {
const graphUuid = container.dataset.graphUuid;
const input = container.querySelector(".publish-search-input");
const toggleBtn = container.querySelector(".publish-search-toggle");
const resultsEl = container.querySelector(".publish-search-results");
if (!input || !resultsEl || !toggleBtn) return;
let debounceTimer = null;
let activeController = null;
let activeIndex = -1;
let activeItems = [];
const hideResults = () => {
resultsEl.hidden = true;
resultsEl.innerHTML = "";
activeIndex = -1;
activeItems = [];
};
const renderSection = (title) => {
const header = document.createElement("div");
header.className = "publish-search-section";
header.textContent = title;
return header;
};
const renderResults = (query, data) => {
const pages = data?.pages || [];
const blocks = data?.blocks || [];
if (!pages.length && !blocks.length) {
resultsEl.innerHTML = "";
const empty = document.createElement("div");
empty.className = "publish-search-empty";
empty.textContent = `No results for "${query}".`;
resultsEl.appendChild(empty);
resultsEl.hidden = false;
activeIndex = -1;
activeItems = [];
return;
}
const list = document.createElement("div");
list.className = "publish-search-list";
if (pages.length) {
pages.forEach((page) => {
const title = page.page_title || page.page_uuid;
const href = `/page/${graphUuid}/${page.page_uuid}`;
const item = document.createElement("a");
item.className = "publish-search-result";
item.href = href;
const kind = document.createElement("span");
kind.className = "publish-search-kind";
kind.textContent = "Page";
const titleEl = document.createElement("span");
titleEl.className = "publish-search-title";
titleEl.textContent = title;
item.appendChild(kind);
item.appendChild(titleEl);
list.appendChild(item);
});
}
if (blocks.length) {
blocks.forEach((block) => {
const title = block.page_title || block.page_uuid;
const href = `/page/${graphUuid}/${block.page_uuid}#block-${block.block_uuid}`;
const snippet = buildSnippet(block.block_content || "", query);
const item = document.createElement("a");
item.className = "publish-search-result";
item.href = href;
const titleEl = document.createElement("span");
titleEl.className = "publish-search-title";
titleEl.textContent = title;
const snippetEl = document.createElement("span");
snippetEl.className = "publish-search-snippet";
snippetEl.textContent = snippet;
item.appendChild(titleEl);
item.appendChild(snippetEl);
list.appendChild(item);
});
}
resultsEl.innerHTML = "";
resultsEl.appendChild(list);
resultsEl.hidden = false;
activeIndex = -1;
activeItems = Array.from(
resultsEl.querySelectorAll(".publish-search-result")
);
activeItems.forEach((item, index) => {
item.addEventListener("mouseenter", () => {
activeIndex = index;
updateActive();
});
});
};
const updateActive = () => {
if (!activeItems.length) return;
activeItems.forEach((item, index) => {
item.classList.toggle("is-active", index === activeIndex);
});
const activeEl = activeItems[activeIndex];
if (activeEl) {
activeEl.scrollIntoView({ block: "nearest" });
}
};
const moveActive = (direction) => {
if (!activeItems.length) {
activeItems = Array.from(
resultsEl.querySelectorAll(".publish-search-result")
);
}
if (!activeItems.length) return;
if (activeIndex === -1) {
activeIndex = direction > 0 ? 0 : activeItems.length - 1;
} else {
activeIndex =
(activeIndex + direction + activeItems.length) %
activeItems.length;
}
updateActive();
};
const activateSelection = () => {
if (!activeItems.length) {
activeItems = Array.from(
resultsEl.querySelectorAll(".publish-search-result")
);
}
if (!activeItems.length) return;
const item =
activeIndex >= 0 ? activeItems[activeIndex] : activeItems[0];
if (item?.href) {
window.location.href = item.href;
}
};
const setExpanded = (expanded) => {
container.classList.toggle("is-expanded", expanded);
toggleBtn.setAttribute("aria-expanded", String(expanded));
if (expanded) {
input.focus();
} else {
input.value = "";
hideResults();
}
};
const runSearch = async (query) => {
if (!query) {
hideResults();
return;
}
if (activeController) activeController.abort();
activeController = new AbortController();
try {
const resp = await fetch(
`/search/${encodeURIComponent(graphUuid)}?q=${encodeURIComponent(query)}`,
{ signal: activeController.signal }
);
if (!resp.ok) throw new Error("search request failed");
const data = await resp.json();
renderResults(query, data);
} catch (error) {
if (error?.name === "AbortError") return;
hideResults();
}
};
if (graphUuid) {
input.addEventListener("input", () => {
const query = input.value.trim();
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => runSearch(query), 250);
});
}
input.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
setExpanded(false);
}
if (event.key === "Enter") {
if (!resultsEl.hidden && input.value.trim()) {
activateSelection();
event.preventDefault();
}
}
if (
!resultsEl.hidden &&
input.value.trim() &&
resultsEl.querySelector(".publish-search-result")
) {
const key = event.key;
if (key === "ArrowDown" || key === "Down") {
moveActive(1);
event.preventDefault();
} else if (key === "ArrowUp" || key === "Up") {
moveActive(-1);
event.preventDefault();
} else if ((event.metaKey || event.ctrlKey) && key === "n") {
moveActive(1);
event.preventDefault();
} else if ((event.metaKey || event.ctrlKey) && key === "p") {
moveActive(-1);
event.preventDefault();
}
}
});
document.addEventListener("click", (event) => {
if (!container.contains(event.target)) setExpanded(false);
});
toggleBtn.addEventListener("click", () => {
const expanded = container.classList.contains("is-expanded");
setExpanded(!expanded);
});
searchStateMap.set(container, {
setExpanded,
focusInput: () => input.focus(),
moveActive,
activateSelection,
hasResults: () => !!resultsEl.querySelector(".publish-search-result"),
isExpanded: () => container.classList.contains("is-expanded"),
});
});
};
const initPublish = () => {
applyTheme(preferredTheme());
renderPropertyIcons();
@@ -358,6 +659,7 @@ const initPublish = () => {
}
initTwitterEmbeds();
initSearch();
document.querySelectorAll(".math-block").forEach((el) => {
const tex = el.textContent;

View File

@@ -135,6 +135,30 @@
[& nodes]
(into [:div.page-toolbar] nodes))
(defn- search-node
[graph-uuid]
(let [graph-id (some-> graph-uuid str)]
[:div.publish-search {:data-graph-uuid graph-id}
[:button.publish-search-toggle
{:type "button"
:aria-label "Search"
:aria-expanded "false"}
[:span.ti.ti-search {:aria-hidden "true"}]]
[:input.publish-search-input
(cond->
{:id "publish-search-input"
:type "search"
:placeholder "Search graph (Cmd+K)"
:autocomplete "off"
:spellcheck "false"
:aria-label "Search graph"}
(string/blank? (or graph-id ""))
(assoc :disabled true :placeholder "Search unavailable"))]
[:div.publish-search-hint "Up/Down to navigate"]
[:div.publish-search-results
{:id "publish-search-results"
:hidden true}]]))
(defn- theme-init-script
[]
[:script
@@ -967,8 +991,12 @@
positioned-left (render-positioned-properties block-left ctx (:entities ctx) :block-left)
positioned-right (render-positioned-properties block-right ctx (:entities ctx) :block-right)
positioned-below (render-positioned-properties block-below ctx (:entities ctx) :block-below)
properties (render-properties properties ctx (:entities ctx))]
properties (render-properties properties ctx (:entities ctx))
block-uuid (:block/uuid display-block)
block-uuid-str (some-> block-uuid str)]
[:li.block
(cond-> {:data-block-uuid block-uuid-str}
block-uuid-str (assoc :id (str "block-" block-uuid-str)))
[:div.block-content
(when positioned-left positioned-left)
(block-display-node display-block ctx depth)
@@ -1191,6 +1219,7 @@
(toolbar-node
(when graph-uuid
[:a.toolbar-btn {:href (str "/graph/" graph-uuid)} "Home"])
(search-node graph-uuid)
(theme-toggle-node))
(page-title-node page-title (:logseq.property/icon page-entity))
@@ -1236,6 +1265,7 @@
[:body
[:main.wrap
(toolbar-node
(search-node graph-uuid)
(theme-toggle-node))
[:h1 "Published pages"]
(if (seq rows)
@@ -1275,6 +1305,7 @@
[:body
[:main.wrap
(toolbar-node
(search-node nil)
(theme-toggle-node))
[:h1 title]
(if (seq rows)
@@ -1300,6 +1331,7 @@
(toolbar-node
(when graph-uuid
[:a.toolbar-btn {:href (str "/graph/" graph-uuid)} "Home"])
(search-node graph-uuid)
(theme-toggle-node))
[:h1 title]
[:p.tag-sub (str "Tag: " tag-uuid)]
@@ -1321,6 +1353,7 @@
[:body
[:main.wrap
(toolbar-node
(search-node nil)
(theme-toggle-node))
[:h1 title]
[:p.tag-sub (str "Tag: " tag-name)]
@@ -1353,6 +1386,7 @@
(toolbar-node
(when graph-uuid
[:a.toolbar-btn {:href (str "/graph/" graph-uuid)} "Home"])
(search-node graph-uuid)
(theme-toggle-node))
[:h1 title]
[:p.tag-sub (str "Reference: " ref-name)]
@@ -1384,6 +1418,7 @@
(toolbar-node
(when graph-uuid
[:a.toolbar-btn {:href (str "/graph/" graph-uuid)} "Home"])
(search-node graph-uuid)
(theme-toggle-node))
[:h1 title]
[:p.tag-sub "This page hasn't been published yet."]
@@ -1400,6 +1435,7 @@
(toolbar-node
(when graph-uuid
[:a.toolbar-btn {:href (str "/graph/" graph-uuid)} "Home"])
(search-node graph-uuid)
(theme-toggle-node))
[:div.password-card
[:h1 title]
@@ -1427,6 +1463,7 @@
[:body
[:main.wrap
(toolbar-node
(search-node nil)
(theme-toggle-node))
[:div.not-found
[:p.not-found-eyebrow "404"]

View File

@@ -1,5 +1,6 @@
(ns logseq.publish.routes
(:require [clojure.string :as string]
(:require [cljs-bean.core :as bean]
[clojure.string :as string]
[logseq.publish.assets :as publish-assets]
[logseq.publish.common :as publish-common]
[logseq.publish.index :as publish-index]
@@ -68,6 +69,8 @@
(get payload "page-title")
(when page-eid
(publish-model/entity->title (get payload-entities page-eid))))
blocks (or (:blocks payload)
(get payload "blocks"))
page-password-hash (or (:page-password-hash payload)
(get payload "page-password-hash"))
refs (when (and page-eid page-title)
@@ -98,28 +101,34 @@
short-id (publish-common/short-id-for-page graph-uuid page_uuid)
owner-sub (:owner_sub meta)
owner-username (:owner_username meta)
updated-at (.now js/Date)
_ (when-not (and owner-sub owner-username)
(throw (ex-info "owner sub or username is missing"
{:owner-sub owner-sub
:owner-username owner-username})))
payload (clj->js {:page_uuid page_uuid
: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
:content_hash content_hash
:content_length content_length
:r2_key r2-key
:owner_sub owner-sub
:owner_username owner-username
:created_at created_at
:updated_at (.now js/Date)
:short_id short-id
:refs refs
:tagged_nodes tagged-nodes})
payload (bean/->js
{:page_uuid page_uuid
: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
:content_hash content_hash
:content_length content_length
:r2_key r2-key
:owner_sub owner-sub
:owner_username owner-username
:created_at created_at
:updated_at updated-at
:short_id short-id
:refs refs
:tagged_nodes tagged-nodes
:blocks (when (seq blocks)
(map (fn [block]
(assoc block :updated_at updated-at))
blocks))})
meta-resp (.fetch do-stub "https://publish/pages"
#js {:method "POST"
:headers #js {"content-type" "application/json"}
@@ -301,6 +310,25 @@
(js-await [meta (.json meta-resp)]
(publish-common/json-response (js->clj meta :keywordize-keys true) 200))))))
(defn handle-graph-search [request env]
(let [url (js/URL. (.-url request))
parts (string/split (.-pathname url) #"/")
graph-uuid (nth parts 2 nil)
query (.get (.-searchParams url) "q")]
(if (or (string/blank? graph-uuid) (string/blank? query))
(publish-common/bad-request "missing graph uuid or query")
(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/search/" graph-uuid
"?q=" (js/encodeURIComponent query))
#js {:method "GET"})]
(if-not (.-ok resp)
(publish-common/not-found)
(js-await [data (.json resp)]
(publish-common/json-response (js->clj data :keywordize-keys true) 200)))))))
(defn handle-graph-html [graph-uuid env]
(if-not graph-uuid
(publish-common/bad-request "missing graph uuid")
@@ -581,6 +609,9 @@
(and (= path "/pages") (= method "GET"))
(handle-list-pages env)
(and (string/starts-with? path "/search/") (= method "GET"))
(handle-graph-search request env)
(and (string/starts-with? path "/graph/") (= method "GET"))
(let [parts (string/split path #"/")
graph-uuid (nth parts 2 nil)]

View File

@@ -1,11 +1,13 @@
(ns frontend.worker.publish
"Publish"
(:require [datascript.core :as d]
(:require [clojure.string :as string]
[datascript.core :as d]
[frontend.common.thread-api :refer [def-thread-api]]
[frontend.worker.state :as worker-state]
[logseq.common.util :as common-util]
[logseq.db :as ldb]
[logseq.db.common.entity-util :as common-entity-util]
[logseq.db.frontend.content :as db-content]
[logseq.db.frontend.property :as db-property]
[logseq.db.frontend.schema :as db-schema]))
@@ -73,9 +75,47 @@
(defn- collect-publish-blocks
[db entity]
(if (common-entity-util/page? entity)
(ldb/get-page-blocks db (:db/id entity))
(:block/_page entity)
(ldb/get-block-and-children db (:block/uuid entity))))
(def ^:private publish-search-max-length 4096)
(defn- block-page-eid
[block]
(let [page (:block/page block)]
(cond
(map? page) (:db/id page)
(number? page) page
:else nil)))
(defn- block-search-content
[block]
(let [raw-content (or (:block/content block)
(:block/title block)
(:block/name block)
"")
raw-content (string/trim raw-content)]
(when-not (string/blank? raw-content)
(let [content (db-content/recur-replace-uuid-in-block-title
(assoc block :block/title raw-content))
content (if (> (count content) publish-search-max-length)
(subs content 0 publish-search-max-length)
content)]
(string/trim content)))))
(defn- collect-search-blocks
[blocks page-eid page-uuid]
(->> blocks
(keep (fn [block]
(when (and (= (block-page-eid block) page-eid)
(not= (:db/id block) page-eid)
(not (:logseq.property/created-from-property block)))
(when-let [block-uuid (some-> (:block/uuid block) str)]
(when-let [content (block-search-content block)]
{:page_uuid (str page-uuid)
:block_uuid block-uuid
:block_content content})))))))
(defn- collect-embedded-blocks
[db blocks]
(let [linked-eids (->> blocks
@@ -109,11 +149,7 @@
(let [page-id (:db/id entity)
blocks (collect-publish-blocks db entity)
embedded-blocks (collect-embedded-blocks db blocks)
blocks (->> (concat blocks embedded-blocks)
(remove (comp nil? :db/id))
(map (juxt :db/id identity))
(into {})
vals)
blocks (concat blocks embedded-blocks)
block-eids (map :db/id blocks)
ref-eids (->> blocks
(mapcat :block/refs)
@@ -164,6 +200,7 @@
refs (when graph-uuid
(publish-refs-from-blocks db blocks entity graph-uuid))
tags (page-tags entity)
search-blocks (collect-search-blocks blocks (:db/id entity) (:block/uuid entity))
raw-datoms (->>
(mapcat (fn [eid]
(map (fn [d] [(:e d) (:a d) (:v d) (:tx d) (:added d)])
@@ -182,6 +219,7 @@
:schema-version (db-schema/schema-version->string db-schema/version)
:refs refs
:page-tags tags
:blocks search-blocks
:datoms datoms}))
(def-thread-api :thread-api/build-publish-page-payload