Merge remote-tracking branch 'origin/master' into refactor/vite-migration

This commit is contained in:
Mega Yu
2026-04-30 11:28:01 +08:00
29 changed files with 1230 additions and 91 deletions

View File

@@ -754,7 +754,9 @@
(last child)
(let [{:keys [content children]} (last child)
page-name (subs content 2 (- (count content) 2))]
(rum/with-key (page-reference (assoc config :children children) page-name nil) page-name))))
(rum/with-key (page-reference (assoc config :children children)
(or (:block/uuid page-entity) page-name)
nil) page-name))))
(cond
(and label
(string? label)
@@ -3018,7 +3020,7 @@
:disable-preview? true)]
(when (seq parents)
(let [parents-props (doall
(for [{:block/keys [uuid name title] :as block} parents]
(for [{:block/keys [uuid name] :as block} parents]
(if name
[block (page-cp (cond-> {:disable-preview? true}
disabled?
@@ -3027,7 +3029,7 @@
(let [result (block/parse-title-and-body
uuid
(get block :block/format :markdown)
title)
(:block/raw-title block))
ast-body (:block.temp/ast-body result)
ast-title (:block.temp/ast-title result)
config (assoc config :block/uuid uuid)]

View File

@@ -433,7 +433,7 @@
(let [format (util/evalue e)]
(when-not (string/blank? format)
(p/do!
(property-handler/set-block-property! :logseq.class/Journal
(property-handler/set-block-property! (:block/uuid (db/entity :logseq.class/Journal))
:logseq.property.journal/title-format
format)
(notification/show! (t :settings.general/refresh-required-feedback)))

View File

@@ -215,7 +215,7 @@
(defn backup-db-graph
[repo]
(when-not (util/capacitor?)
(when util/web-platform?
(web-backup-db-graph repo)))
(defonce *backup-interval (atom nil))

View File

@@ -25,6 +25,9 @@
(defn- ->block-id
[block-or-id]
(cond
(keyword? block-or-id)
(:block/uuid (db-utils/entity block-or-id))
(de/entity? block-or-id)
(:block/uuid block-or-id)

View File

@@ -21,7 +21,7 @@
(not (ldb/journal? entity))
(not (:logseq.property/built-in? entity))
(not (= :logseq.property/query (:db/ident (:logseq.property/created-from-property entity)))))))
(d/datom e a (str "debug " e) t)
(d/datom e a (str "debug " e " " (apply str (repeat (count v) "x"))) t)
:else
(d/datom e a v t))))))

View File

@@ -594,8 +594,6 @@ DROP TRIGGER IF EXISTS blocks_au;
(when-not (string/blank? q)
(let [option (assoc option :enable-snippet? enable-snippet?)
match-input (get-match-input q)
page-count (count (d/datoms @conn :avet :block/name))
large-graph? (> page-count 2500)
non-match-input (when (<= (count q) 2)
(str "%" (string/replace q #"\s+" "%") "%"))
limit (or limit 100)
@@ -613,11 +611,9 @@ DROP TRIGGER IF EXISTS blocks_au;
(->> (search-blocks-aux search-db non-match-sql q non-match-input page limit-p)
(map (fn [result]
(assoc result :keyword-score (fuzzy/score q (:title result)))))))
;; fuzzy is too slow for large graphs
fuzzy-result (when-not (or page large-graph?)
(->> (fuzzy-search repo @conn q option)
(map (fn [result]
(assoc result :keyword-score (fuzzy/score q (:title result)))))))
fuzzy-result (->> (fuzzy-search repo @conn q option)
(map (fn [result]
(assoc result :keyword-score (fuzzy/score q (:title result))))))
;; _ (prn :debug "Search results before combine:" enable-snippet? (map :snippet matched-result))
;; _ (doseq [item (concat fuzzy-result matched-result)]
;; (prn :debug :keyword-search-result item))

View File

@@ -17,8 +17,11 @@
[logseq.db.sqlite.util :as sqlite-util]
[promesa.core :as p]))
(def upload-kvs-batch-size 2000)
(def upload-kvs-batch-size 500)
(def upload-prepare-datoms-batch-size 100000)
(def snapshot-upload-max-bytes 1000000)
(def snapshot-frame-header-bytes 4)
(def ignored-oversized-upload-attrs #{:logseq.property.tldraw/page})
(def snapshot-content-type "application/transit+json")
(def snapshot-content-encoding "gzip")
(def snapshot-text-encoder (js/TextEncoder.))
@@ -63,6 +66,66 @@
[rows]
(.encode snapshot-text-encoder (sqlite-util/write-transit-str rows)))
(defn- datom-value-byte-length
[value]
(.-byteLength ^js (.encode snapshot-text-encoder (sqlite-util/write-transit-str value))))
(defn- drop-oversized-upload-datoms
[datoms]
(let [threshold (- snapshot-upload-max-bytes snapshot-frame-header-bytes)]
(reduce (fn [{:keys [kept dropped]} datom]
(let [attr (:a datom)
size (when (contains? ignored-oversized-upload-attrs attr)
(datom-value-byte-length (:v datom)))]
(if (and size (> size threshold))
{:kept kept
:dropped (conj dropped {:a attr
:e (:e datom)
:bytes size})}
{:kept (conj kept datom)
:dropped dropped})))
{:kept []
:dropped []}
datoms)))
(defn- snapshot-rows-byte-length
[rows]
(+ snapshot-frame-header-bytes
(.-byteLength ^js (encode-snapshot-rows rows))))
(defn- max-prefix-rows-within-bytes
[rows max-bytes]
(let [rows-count (count rows)]
(loop [low 1
high rows-count
best 0]
(if (> low high)
best
(let [mid (quot (+ low high) 2)
rows' (subvec rows 0 mid)
size (snapshot-rows-byte-length rows')]
(if (<= size max-bytes)
(recur (inc mid) high mid)
(recur low (dec mid) best)))))))
(defn- split-snapshot-rows-by-max-bytes
[rows max-bytes]
(loop [remaining rows
batches []]
(if (empty? remaining)
batches
(let [prefix-count (max-prefix-rows-within-bytes remaining max-bytes)]
(if (pos? prefix-count)
(let [batch (subvec remaining 0 prefix-count)
remaining' (subvec remaining prefix-count)]
(recur remaining' (conj batches batch)))
(let [row (first remaining)
row-size (snapshot-rows-byte-length [row])]
(fail-fast :db-sync/snapshot-row-too-large
{:max-bytes max-bytes
:row-size row-size
:addr (first row)})))))))
(defn frame-bytes
[^js data]
(let [len (.-byteLength data)
@@ -98,6 +161,30 @@
{:body buf :encoding snapshot-content-encoding})
(p/resolved {:body frame :encoding nil}))))
(defn- snapshot-upload-url
[base graph-id reset? finished? checksum]
(str base "/sync/" graph-id "/snapshot/upload?reset="
(if reset? "true" "false")
"&finished="
(if finished? "true" "false")
(when finished?
(str "&checksum=" (js/encodeURIComponent checksum)))))
(defn- <upload-snapshot-rows-batches!
[rows-batches {:keys [base graph-id first-batch? finished? checksum auth-fetch-f]}]
(p/loop [remaining rows-batches
first-request? first-batch?]
(if-let [rows-batch (first remaining)]
(let [last-request? (nil? (next remaining))
finished-request? (and finished? last-request?)
upload-url (snapshot-upload-url base graph-id first-request? finished-request? checksum)]
(p/let [{:keys [body encoding]} (<snapshot-upload-body rows-batch)
headers (cond-> {"content-type" snapshot-content-type}
(string? encoding) (assoc "content-encoding" encoding))
_ (auth-fetch-f upload-url headers body)]
(p/recur (next remaining) false)))
nil)))
(defn set-graph-sync-metadata!
[repo graph-e2ee?]
(when-let [conn (worker-state/get-datascript-conn repo)]
@@ -126,9 +213,16 @@
(fn [batch]
(p/let [datoms* (sync-large-title/offload-large-titles-in-datoms-batch
repo graph-id batch aes-key sync-apply/upload-large-title!)
{:keys [kept dropped]} (drop-oversized-upload-datoms datoms*)
_ (when (seq dropped)
(prn :db-sync/drop-oversized-upload-datoms
{:repo repo
:count (count dropped)
:attrs (vec (distinct (map :a dropped)))
:max-bytes (apply max (map :bytes dropped))}))
encrypted-datoms (if aes-key
(sync-crypt/<encrypt-datoms aes-key datoms*)
datoms*)
(sync-crypt/<encrypt-datoms aes-key kept)
kept)
tx-data (mapv sync-large-title/datom->tx encrypted-datoms)]
(d/transact! (:conn temp) tx-data {:initial-db? true})
nil))
@@ -186,25 +280,38 @@
rows* (normalize-snapshot-rows rows)
loaded' (+ loaded (count rows*))
finished? (= loaded' total-rows)
upload-url (str base "/sync/" graph-id "/snapshot/upload?reset="
(if first-batch? "true" "false")
"&finished="
(if finished? "true" "false")
(when finished?
(str "&checksum=" (js/encodeURIComponent snapshot-checksum))))]
(p/let [{:keys [body encoding]} (<snapshot-upload-body rows*)
headers (cond-> {"content-type" snapshot-content-type}
(string? encoding) (assoc "content-encoding" encoding))
_ (sync-transport/fetch-json
(fn [opts]
(sync-auth/with-auth-headers
#(sync-auth/auth-headers (worker-state/get-id-token))
opts))
upload-url
{:method "POST"
:headers headers
:body body}
{:response-schema :sync/snapshot-upload})]
row-batches (split-snapshot-rows-by-max-bytes rows* snapshot-upload-max-bytes)
batch-payloads
(mapv (fn [rows-batch]
{:rows (count rows-batch)
:payload-bytes (snapshot-rows-byte-length rows-batch)})
row-batches)]
(prn :db-sync/upload-kvs-batch
{:total-kvs-rows total-rows
:fetched-kvs-rows (count rows*)
:upload-kvs-batch-size upload-kvs-batch-size
:split-batch-count (count row-batches)
:split-batches batch-payloads
:max-request-bytes snapshot-upload-max-bytes})
(p/let [_ (<upload-snapshot-rows-batches!
row-batches
{:base base
:graph-id graph-id
:first-batch? first-batch?
:finished? finished?
:checksum snapshot-checksum
:auth-fetch-f
(fn [upload-url headers body]
(sync-transport/fetch-json
(fn [opts]
(sync-auth/with-auth-headers
#(sync-auth/auth-headers (worker-state/get-id-token))
opts))
upload-url
{:method "POST"
:headers headers
:body body}
{:response-schema :sync/snapshot-upload}))})]
(update-progress {:sub-type :upload-progress
:message (str "Uploading " loaded' "/" total-rows)})
(p/recur max-addr false loaded'))))))

View File

@@ -1663,11 +1663,11 @@
:settings.sync-server/url "Sync Server URL"
:settings.sync-server/url-desc "Set a custom HTTPS sync server URL for self-hosted sync. Your Logseq authentication tokens will be sent to this server, so only use a trusted URL. Leave empty to use the official Logseq Sync."
:settings.sync-server/url-invalid-error "URL must start with https:// or http://"
:settings-page/publish-server-url "Publish server URL"
:settings-page/publish-server-url-desc "Set a custom HTTPS publish server URL for self-hosted single-page publishing. Your Logseq authentication tokens will be sent to this server, so only use a trusted URL. Leave empty to use the official Logseq publish service."
:settings-page/publish-server-url "Publish Server URL"
:settings-page/publish-server-url-desc "Set a custom HTTPS publish server URL for self-hosted single-page publishing. Your Logseq authentication tokens will be sent to this server, so only use a trusted URL. Leave empty to use the official Logseq Publish service."
:settings-page/publish-server-url-saved "Publish server URL saved."
:settings-page/publish-server-url-cleared "Publish server URL cleared. Using official Logseq publish."
:settings-page/publish-server-url-default "Logseq publish"
:settings-page/publish-server-url-cleared "Publish server URL cleared. Using official Logseq Publish."
:settings-page/publish-server-url-default "Logseq Publish"
:settings-page/publish-server-url-reset "Reset to default"
:shell/input-command-title "Input command"

View File

@@ -2,9 +2,12 @@
(:require [cljs.test :refer [deftest is testing]]
[datascript.core :as d]
[frontend.worker.pipeline :as worker-pipeline]
[logseq.common.util :as common-util]
[logseq.common.util.date-time :as date-time-util]
[logseq.db :as ldb]
[logseq.db.common.order :as db-order]
[logseq.db.test.helper :as db-test]))
[logseq.db.test.helper :as db-test]
[logseq.outliner.page :as outliner-page]))
(deftest test-built-in-page-updates-that-should-be-reverted
(let [conn (db-test/create-conn-with-blocks
@@ -145,6 +148,21 @@
(is (= "page1-renamed"
(:block/title (d/entity (:db-after result) (:db/id page1)))))))))
(deftest create-journal-page-name-uses-default-formatter-test
(let [conn (db-test/create-conn)]
(d/transact! conn [[:db/add :logseq.class/Journal :logseq.property.journal/title-format "yyyy-MM-dd EEEE"]])
(let [[_ page-uuid] (outliner-page/create! conn "Dec 16th, 2024" {})
page (d/entity @conn [:block/uuid page-uuid])
journal-day (:block/journal-day page)
expected-title (date-time-util/int->journal-title journal-day "yyyy-MM-dd EEEE")
expected-name (-> journal-day
(date-time-util/int->journal-title date-time-util/default-journal-title-formatter)
common-util/page-name-sanity-lc)]
(is (= expected-title (:block/title page))
"Journal title follows configured title format")
(is (= expected-name (:block/name page))
"Journal block/name keeps the default formatter for stable identity"))))
(deftest built-in-tag-must-not-convert-page-child-block-to-class-test
(let [conn (db-test/create-conn-with-blocks
{:pages-and-blocks [{:page {:block/title "page1"}}]})

View File

@@ -0,0 +1,93 @@
(ns frontend.worker.sync.upload-test
(:require [cljs.test :refer [async deftest is]]
[frontend.worker.sync.upload :as sync-upload]
[promesa.core :as p]
[clojure.string :as string]))
(deftest split-snapshot-rows-by-max-bytes-splits-rows-into-byte-capped-batches-test
(let [sizes {:a 4
:b 4
:c 4
:d 4}
rows [[:a] [:b] [:c] [:d]]]
(with-redefs [sync-upload/snapshot-rows-byte-length
(fn [rows']
(reduce + (map (fn [[addr]] (get sizes addr 0)) rows')))]
(is (= [[[:a] [:b]]
[[:c] [:d]]]
(#'sync-upload/split-snapshot-rows-by-max-bytes rows 8))))))
(deftest split-snapshot-rows-by-max-bytes-fails-fast-for-oversized-single-row-test
(let [sizes {:ok 3
:too-big 11}
rows [[:ok] [:too-big]]]
(with-redefs [sync-upload/snapshot-rows-byte-length
(fn [rows']
(reduce + (map (fn [[addr]] (get sizes addr 0)) rows')))]
(try
(#'sync-upload/split-snapshot-rows-by-max-bytes rows 10)
(is false "expected snapshot row too large error")
(catch :default error
(let [data (ex-data error)]
(is (= "snapshot-row-too-large" (ex-message error)))
(is (= 10 (:max-bytes data)))
(is (= 11 (:row-size data)))
(is (= :too-big (:addr data)))))))))
(deftest upload-snapshot-rows-batches-sets-reset-and-finished-flags-correctly-test
(async done
(let [calls* (atom [])
rows-batches [[[1 "a" nil]]
[[2 "b" nil]]
[[3 "c" nil]]]]
(-> (p/with-redefs [sync-upload/<snapshot-upload-body
(fn [rows]
(p/resolved {:body rows
:encoding nil}))]
(#'sync-upload/<upload-snapshot-rows-batches!
rows-batches
{:base "https://sync.example.test"
:graph-id "graph-1"
:first-batch? true
:finished? true
:checksum "abc+123="
:auth-fetch-f
(fn [url headers body]
(swap! calls* conj {:url url
:headers headers
:body body})
(p/resolved true))}))
(p/then
(fn [_]
(is (= 3 (count @calls*)))
(is (string/includes? (:url (nth @calls* 0)) "reset=true"))
(is (string/includes? (:url (nth @calls* 0)) "finished=false"))
(is (string/includes? (:url (nth @calls* 1)) "reset=false"))
(is (string/includes? (:url (nth @calls* 1)) "finished=false"))
(is (string/includes? (:url (nth @calls* 2)) "reset=false"))
(is (string/includes? (:url (nth @calls* 2)) "finished=true"))
(is (string/includes? (:url (nth @calls* 2)) "checksum=abc%2B123%3D"))
(done)))
(p/catch
(fn [error]
(is false (str error))
(done)))))))
(deftest drop-oversized-upload-datoms-drops-large-tldraw-page-values-test
(let [datoms [{:e 1 :a :block/title :v "safe"}
{:e 2 :a :logseq.property.tldraw/page :v {:id "small"}}
{:e 3 :a :logseq.property.tldraw/page :v {:id "huge"}}]]
(with-redefs [sync-upload/datom-value-byte-length
(fn [value]
(case (:id value)
"small" 32
"huge" 1500000
0))]
(let [{:keys [kept dropped]} (#'sync-upload/drop-oversized-upload-datoms datoms)]
(is (= 2 (count kept)))
(is (= [1 2] (mapv :e kept)))
(is (= 1 (count dropped)))
(is (= {:a :logseq.property.tldraw/page
:e 3
:bytes 1500000}
(first dropped)))))))