mirror of
https://github.com/logseq/logseq.git
synced 2026-05-23 20:24:15 +00:00
Fix external asset rendering and journal import namespace handling (#12673)
* fix render external asset fail * fix: handle nil stat in exteranal asset size calculation * fix: normalize journal UUIDs and prevent namespace creation for slash-formatted journals * fix: update journal UUID generation and prevent namespace creation for slash-formatted journals * fix(import): avoid namespace pages for slash journal refs * fix: clarify journal uuid option docs Agent-Logs-Url: https://github.com/logseq/logseq/sessions/d8292bbe-fc6f-4c74-91cd-5705571a89b2 Co-authored-by: tiensonqin <479169+tiensonqin@users.noreply.github.com> --------- Co-authored-by: Tienson Qin <tiensonqin@gmail.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tiensonqin <479169+tiensonqin@users.noreply.github.com>
This commit is contained in:
@@ -328,6 +328,7 @@
|
||||
original-page-name (common-util/remove-boundary-slashes original-page-name)
|
||||
[original-page-name' page-name journal-day] (convert-page-if-journal original-page-name date-formatter {:export-to-db-graph? @*export-to-db-graph?})
|
||||
namespace? (and (or (not db-based?) @*export-to-db-graph?)
|
||||
(not journal-day)
|
||||
(not (boolean (text/get-nested-page-name original-page-name')))
|
||||
(text/namespace-page? original-page-name'))
|
||||
page-entity (when (and db (not skip-existing-page-check?))
|
||||
@@ -442,7 +443,9 @@
|
||||
p)]
|
||||
(when (string? p)
|
||||
(let [p (or (text/get-nested-page-name p) p)]
|
||||
(if (and (text/namespace-page? p) (not tag?))
|
||||
(if (and (text/namespace-page? p)
|
||||
(not (common-date/valid-journal-title-with-slash? p))
|
||||
(not tag?))
|
||||
(common-util/split-namespace-pages p)
|
||||
[p])))))
|
||||
col)
|
||||
|
||||
@@ -177,7 +177,8 @@
|
||||
:page-names (sort (keys @page-names-to-uuids))})))))
|
||||
|
||||
(defn- replace-namespace-with-parent [block page-names-to-uuids parent-k]
|
||||
(if (:block/namespace block)
|
||||
(if (and (:block/namespace block)
|
||||
(not (:block/journal-day block)))
|
||||
(-> (dissoc block :block/namespace)
|
||||
(assoc parent-k
|
||||
{:block/uuid (get-page-uuid page-names-to-uuids
|
||||
@@ -1571,14 +1572,15 @@
|
||||
(not (get @assets asset-link-or-name))
|
||||
(string/ends-with? path ".pdf")
|
||||
(fn? <get-file-stat))
|
||||
(-> (p/let [^js stat (<get-file-stat path)]
|
||||
(-> (p/let [stat (<get-file-stat path)]
|
||||
(swap! assets assoc asset-link-or-name
|
||||
{:asset-id (d/squuid)
|
||||
:type "pdf"
|
||||
;; avoid using the real checksum since it could be the same with in-graph asset
|
||||
:checksum "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
;; gracefully create stat-less assets so that references to them are still valid
|
||||
:size (if stat (.-size stat) 0)
|
||||
;; Electron IPC returns a CLJS map, while Node import scripts return fs.Stats.
|
||||
:size (or (:size stat) (some-> stat .-size) 0)
|
||||
:external-url (or asset-link-or-name path)
|
||||
:external-file-name asset-path}))
|
||||
(p/catch (fn [error]
|
||||
@@ -2017,7 +2019,7 @@
|
||||
(cond-> page'
|
||||
true
|
||||
(dissoc :block/format)
|
||||
(:block/namespace page)
|
||||
(and (:block/namespace page) (not (:block/journal-day page)))
|
||||
((fn [block']
|
||||
(merge (build-new-namespace-page block')
|
||||
{;; save original name b/c it's still used for a few name lookups
|
||||
@@ -2523,6 +2525,68 @@
|
||||
(when (seq tx)
|
||||
(d/transact! conn tx))))
|
||||
|
||||
(defn- journal-uuid-normalizations
|
||||
[db]
|
||||
(keep (fn [datom]
|
||||
(let [entity (d/entity db (:e datom))
|
||||
old-uuid (:block/uuid entity)
|
||||
journal-day (:block/journal-day entity)
|
||||
standard-uuid (common-uuid/gen-uuid :journal-page-uuid journal-day)]
|
||||
(when (and old-uuid (not= old-uuid standard-uuid))
|
||||
(when-let [target (d/entity db [:block/uuid standard-uuid])]
|
||||
(when (not= (:db/id target) (:db/id entity))
|
||||
(throw (ex-info "Cannot normalize journal uuid because the standard uuid is already used"
|
||||
{:journal-day journal-day
|
||||
:old-uuid old-uuid
|
||||
:standard-uuid standard-uuid
|
||||
:target-id (:db/id target)}))))
|
||||
{:eid (:db/id entity)
|
||||
:old-uuid old-uuid
|
||||
:standard-uuid standard-uuid})))
|
||||
(d/datoms db :avet :block/journal-day)))
|
||||
|
||||
(defn- replace-journal-uuid-refs
|
||||
[value uuid-replacements]
|
||||
(if (seq uuid-replacements)
|
||||
(walk/postwalk
|
||||
(fn [x]
|
||||
(if (string? x)
|
||||
(reduce (fn [s [old-uuid standard-uuid]]
|
||||
(-> s
|
||||
(string/replace (page-ref/->page-ref old-uuid)
|
||||
(page-ref/->page-ref standard-uuid))
|
||||
(string/replace (block-ref/->block-ref old-uuid)
|
||||
(block-ref/->block-ref standard-uuid))))
|
||||
x
|
||||
uuid-replacements)
|
||||
x))
|
||||
value)
|
||||
value))
|
||||
|
||||
(defn- normalize-journal-uuids-tx
|
||||
[db]
|
||||
(let [normalizations (vec (journal-uuid-normalizations db))
|
||||
uuid-replacements (map (juxt :old-uuid :standard-uuid) normalizations)
|
||||
uuid-tx (mapcat (fn [{:keys [eid old-uuid standard-uuid]}]
|
||||
[[:db/retract eid :block/uuid old-uuid]
|
||||
[:db/add eid :block/uuid standard-uuid]])
|
||||
normalizations)
|
||||
text-tx (when (seq uuid-replacements)
|
||||
(keep (fn [datom]
|
||||
(let [value (:v datom)
|
||||
value' (when (or (string? value) (coll? value))
|
||||
(replace-journal-uuid-refs value uuid-replacements))]
|
||||
(when (and (some? value') (not= value value'))
|
||||
[:db/add (:e datom) (:a datom) value'])))
|
||||
(d/datoms db :eavt)))]
|
||||
(vec (concat uuid-tx text-tx))))
|
||||
|
||||
(defn- normalize-journal-uuids!
|
||||
[conn]
|
||||
(let [tx (normalize-journal-uuids-tx @conn)]
|
||||
(when (seq tx)
|
||||
(d/transact! conn tx))))
|
||||
|
||||
(defn export-doc-files
|
||||
"Exports all user created files i.e. under journals/ and pages/.
|
||||
Recommended to use build-doc-options and pass that as options"
|
||||
@@ -2544,9 +2608,11 @@
|
||||
(p/recur (export-doc-file (get doc-files (inc i)) conn <read-file options)
|
||||
(inc i))))
|
||||
(p/then (fn [_]
|
||||
(p/let [tx-report (cleanup-missing-block-refs! conn)
|
||||
_ (when tx-report (on-tx-report tx-report))]
|
||||
tx-report)))
|
||||
(p/let [normalize-tx-report (normalize-journal-uuids! conn)
|
||||
_ (when normalize-tx-report (on-tx-report normalize-tx-report))
|
||||
cleanup-tx-report (cleanup-missing-block-refs! conn)
|
||||
_ (when cleanup-tx-report (on-tx-report cleanup-tx-report))]
|
||||
cleanup-tx-report)))
|
||||
(p/catch (fn [e]
|
||||
(notify-user {:msg (str "Import has unexpected error:\n" (.-message e))
|
||||
:level :error
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
[clojure.walk :as walk]
|
||||
[datascript.core :as d]
|
||||
[logseq.common.util :as common-util]
|
||||
[logseq.common.uuid :as common-uuid]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.graph-parser.block :as gp-block]
|
||||
[logseq.graph-parser.mldoc :as gp-mldoc]
|
||||
@@ -188,7 +189,8 @@
|
||||
(defn- build-pages-aux
|
||||
[db page-map ref-pages date-formatter format]
|
||||
(let [namespace-pages (let [page (:block/title page-map)]
|
||||
(when (text/namespace-page? page)
|
||||
(when (and (not (:block/journal-day page-map))
|
||||
(text/namespace-page? page))
|
||||
(->> (common-util/split-namespace-pages page)
|
||||
(map (fn [page]
|
||||
(-> (gp-block/page-name->map page db true date-formatter)
|
||||
@@ -204,9 +206,11 @@
|
||||
pages (common-util/distinct-by :block/name pages)
|
||||
pages (remove nil? pages)]
|
||||
(map (fn [page]
|
||||
(let [page-id (or (when db
|
||||
(:block/uuid (ldb/get-page db (:block/name page))))
|
||||
(d/squuid))]
|
||||
(let [page-id (if-let [journal-day (:block/journal-day page)]
|
||||
(common-uuid/gen-uuid :journal-page-uuid journal-day)
|
||||
(or (when db
|
||||
(:block/uuid (ldb/get-page db (:block/name page))))
|
||||
(d/squuid)))]
|
||||
(assoc page :block/uuid page-id)))
|
||||
pages)))
|
||||
|
||||
@@ -299,9 +303,11 @@
|
||||
[pages]
|
||||
(->> (common-util/distinct-by :block/name pages)
|
||||
(map (fn [page]
|
||||
(if (:block/uuid page)
|
||||
page
|
||||
(assoc page :block/uuid (d/squuid)))))))
|
||||
(if-let [journal-day (:block/journal-day page)]
|
||||
(assoc page :block/uuid (common-uuid/gen-uuid :journal-page-uuid journal-day))
|
||||
(cond-> page
|
||||
(nil? (:block/uuid page))
|
||||
(assoc :block/uuid (d/squuid))))))))
|
||||
|
||||
(defn with-ref-pages
|
||||
[pages blocks]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns logseq.graph-parser.block-test
|
||||
(:require [cljs.test :refer [deftest are testing is]]
|
||||
[datascript.core :as d]
|
||||
[logseq.common.uuid :as common-uuid]
|
||||
[logseq.graph-parser.block :as gp-block]
|
||||
[logseq.graph-parser.mldoc :as gp-mldoc]))
|
||||
|
||||
@@ -106,6 +107,17 @@
|
||||
{:property-pages/enabled? true})))
|
||||
"Only editable linkable built-in properties have page-refs in property values")))
|
||||
|
||||
(deftest test-page-name-map-namespace-for-slash-journals
|
||||
(testing "slash-formatted journals do not keep namespace metadata"
|
||||
(let [journal (gp-block/page-name->map "2026/05/18" nil false "yyyy/MM/dd")]
|
||||
(is (= 20260518 (:block/journal-day journal)))
|
||||
(is (= (common-uuid/gen-uuid :journal-page-uuid 20260518)
|
||||
(:block/uuid journal)))
|
||||
(is (nil? (:block/namespace journal)))))
|
||||
(testing "non-journal slash pages keep namespace metadata"
|
||||
(is (= {:block/name "project"}
|
||||
(:block/namespace (gp-block/page-name->map "project/child" nil false "yyyy/MM/dd"))))))
|
||||
|
||||
(defn find-block-for-content
|
||||
[db content]
|
||||
(->> (d/q '[:find (pull ?b [* {:block/refs [:block/uuid]}])
|
||||
@@ -114,4 +126,4 @@
|
||||
db
|
||||
content)
|
||||
(map first)
|
||||
first))
|
||||
first))
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
[logseq.common.config :as common-config]
|
||||
[logseq.common.graph :as common-graph]
|
||||
[logseq.common.path :as path]
|
||||
[logseq.common.util.block-ref :as block-ref]
|
||||
[logseq.common.util.date-time :as date-time-util]
|
||||
[logseq.common.util.page-ref :as page-ref]
|
||||
[logseq.common.uuid :as common-uuid]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.db.common.entity-plus :as entity-plus]
|
||||
[logseq.db.frontend.asset :as db-asset]
|
||||
@@ -1296,6 +1299,91 @@
|
||||
(:logseq.property/page-tags (db-test/readable-properties (db-test/find-page-by-title @conn "chat-gpt"))))
|
||||
"tagged page has new page and other pages marked with '#' and '[[]]` imported as tags to page-tags")))))
|
||||
|
||||
(deftest-async import-journals-use-standard-uuids-and-keep-uuid-refs
|
||||
(p/let [file-graph-dir "test/resources/exporter-test-graph"
|
||||
files (mapv #(path/path-join file-graph-dir %) ["journals/2026_01_27.md"])
|
||||
conn (db-test/create-conn)
|
||||
_ (import-files-to-db files conn {})]
|
||||
(let [journal (db-test/find-journal-by-journal-day @conn 20260127)
|
||||
ref-journal (db-test/find-journal-by-journal-day @conn 20260101)
|
||||
ref-block (some->> (d/q '[:find [?b ...]
|
||||
:in $ ?page ?ref-page
|
||||
:where
|
||||
[?b :block/page ?page]
|
||||
[?b :block/refs ?ref-page]]
|
||||
@conn (:db/id journal) (:db/id ref-journal))
|
||||
first
|
||||
(d/entity @conn))]
|
||||
(is (= (common-uuid/gen-uuid :journal-page-uuid 20260127)
|
||||
(:block/uuid journal))
|
||||
"Imported journal page keeps the standard journal uuid")
|
||||
(is (= (common-uuid/gen-uuid :journal-page-uuid 20260101)
|
||||
(:block/uuid ref-journal))
|
||||
"Referenced journal page keeps the standard journal uuid")
|
||||
(is (= #{(:block/uuid ref-journal)}
|
||||
(set (map :block/uuid (:block/refs ref-block))))
|
||||
"Journal refs point at the standard journal uuid"))))
|
||||
|
||||
(deftest-async import-journal-with-slash-title-format-does-not-create-namespace-pages
|
||||
(p/let [file-graph-dir "test/resources/exporter-test-graph"
|
||||
files (mapv #(path/path-join file-graph-dir %) ["journals/2026_01_27.md"])
|
||||
conn (db-test/create-conn)
|
||||
_ (import-files-to-db files conn {:user-config {:journal/page-title-format "yyyy/MM/dd"}})
|
||||
journal (db-test/find-journal-by-journal-day @conn 20260127)]
|
||||
(is (= "2026/01/27" (:block/title journal))
|
||||
"Journal title follows slash title format")
|
||||
(is (= (common-uuid/gen-uuid :journal-page-uuid 20260127)
|
||||
(:block/uuid journal))
|
||||
"Slash-formatted journal keeps the standard journal uuid")
|
||||
(is (nil? (:block/namespace journal))
|
||||
"Slash-formatted journal does not keep a namespace attribute")
|
||||
(is (nil? (db-test/find-page-by-title @conn "2026"))
|
||||
"Journal title is not split into a year namespace page")
|
||||
(is (nil? (db-test/find-page-by-title @conn "01"))
|
||||
"Journal title is not split into a month namespace page")
|
||||
(is (nil? (db-test/find-page-by-title @conn "27"))
|
||||
"Journal title is not split into a day namespace page")))
|
||||
|
||||
(deftest-async import-slash-journal-ref-does-not-create-namespace-pages
|
||||
(p/let [file (write-temp-graph-file "journals/2026_05_18.md" "- yes\n- [[Sun, 2026/05/17]]\n")
|
||||
conn (db-test/create-conn)
|
||||
_ (import-files-to-db [file] conn {:user-config {:journal/page-title-format "EEE, yyyy/MM/dd"}})
|
||||
ref-journal (db-test/find-journal-by-journal-day @conn 20260517)]
|
||||
(is (= "Sun, 2026/05/17" (:block/title ref-journal))
|
||||
"Journal reference is imported as a journal page")
|
||||
(is (nil? (:block/namespace ref-journal))
|
||||
"Referenced slash-formatted journal does not keep a namespace attribute")
|
||||
(is (nil? (db-test/find-page-by-title @conn "Sun, 2026"))
|
||||
"Journal reference is not split into a parent namespace page")
|
||||
(is (nil? (db-test/find-page-by-title @conn "05"))
|
||||
"Journal reference is not split into a child namespace page")))
|
||||
|
||||
(deftest-async import-normalizes-existing-random-journal-uuid-and-text-refs
|
||||
(let [old-journal-uuid (random-uuid)
|
||||
standard-journal-uuid (common-uuid/gen-uuid :journal-page-uuid 20260127)
|
||||
title (str "refs " (page-ref/->page-ref old-journal-uuid)
|
||||
" and " (block-ref/->block-ref old-journal-uuid))
|
||||
conn (db-test/create-conn-with-blocks
|
||||
{:pages-and-blocks
|
||||
[{:page {:build/journal 20260127
|
||||
:block/uuid old-journal-uuid
|
||||
:build/keep-uuid? true}
|
||||
:blocks [{:block/title title}]}]})
|
||||
file (write-temp-graph-file "pages/trigger-normalize.md" "- trigger normalize\n")]
|
||||
(p/let [_ (import-files-to-db [file] conn {})
|
||||
journal (db-test/find-journal-by-journal-day @conn 20260127)
|
||||
ref-block (db-test/find-block-by-content @conn #"refs")]
|
||||
(is (= standard-journal-uuid (:block/uuid journal))
|
||||
"Existing random journal uuid is normalized to the standard journal uuid")
|
||||
(is (nil? (d/entity @conn [:block/uuid old-journal-uuid]))
|
||||
"Old journal uuid no longer resolves after normalization")
|
||||
(is (= (str "refs " (page-ref/->page-ref standard-journal-uuid)
|
||||
" and " (block-ref/->block-ref standard-journal-uuid))
|
||||
(:block/title ref-block))
|
||||
"Text references are rewritten to the standard journal uuid")
|
||||
(is (= (:db/id journal) (get-in ref-block [:block/page :db/id]))
|
||||
"Structured block page reference still points to the same journal entity"))))
|
||||
|
||||
(deftest-async export-files-with-tag-classes-option
|
||||
(p/let [file-graph-dir "test/resources/exporter-test-graph"
|
||||
files (mapv #(path/path-join file-graph-dir %) ["journals/2024_02_07.md" "pages/Interstellar.md"])
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
- 2nd instance of block w/ longer ns tag #n1/n2/n3
|
||||
- property pages with '/' should be valid
|
||||
key/value:: 1
|
||||
- block with multi word tag #[[another test]] doesn't get removed by page-ref replacement
|
||||
key/value:: 1
|
||||
- block with multi word tag #[[another test]] doesn't get removed by page-ref replacement
|
||||
- [[Jan 1st, 2026]]
|
||||
14
deps/outliner/src/logseq/outliner/page.cljs
vendored
14
deps/outliner/src/logseq/outliner/page.cljs
vendored
@@ -7,6 +7,7 @@
|
||||
[logseq.common.util :as common-util]
|
||||
[logseq.common.util.date-time :as date-time-util]
|
||||
[logseq.common.util.namespace :as ns-util]
|
||||
[logseq.common.uuid :as common-uuid]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.db.common.entity-plus :as entity-plus]
|
||||
[logseq.db.common.order :as db-order]
|
||||
@@ -297,7 +298,7 @@
|
||||
[db title*
|
||||
{uuid' :uuid
|
||||
:keys [tags properties persist-op?
|
||||
class? today-journal? split-namespace? class-ident-namespace]
|
||||
class? journal? today-journal? split-namespace? class-ident-namespace]
|
||||
:or {properties nil
|
||||
persist-op? true}
|
||||
:as options}]
|
||||
@@ -311,7 +312,7 @@
|
||||
_ (outliner-validate/validate-page-title-no-hashtag title {:node {:block/title title}})
|
||||
types (cond class?
|
||||
#{:logseq.class/Tag}
|
||||
today-journal?
|
||||
(or journal? today-journal?)
|
||||
#{:logseq.class/Journal}
|
||||
(seq tags)
|
||||
(set (map :db/ident tags))
|
||||
@@ -375,7 +376,9 @@
|
||||
{:class? class?
|
||||
:page-uuid (when (uuid? uuid') uuid')
|
||||
:skip-existing-page-check? true})
|
||||
[page parents'] (if (and (text/namespace-page? title) split-namespace?)
|
||||
[page parents'] (if (and (not (:block/journal-day page))
|
||||
(text/namespace-page? title)
|
||||
split-namespace?)
|
||||
(let [pages (split-namespace-pages db page date-formatter class?)]
|
||||
[(last pages) (butlast pages)])
|
||||
[page nil])]
|
||||
@@ -389,7 +392,10 @@
|
||||
(doseq [parent parents']
|
||||
(outliner-validate/validate-page-title-characters (str (:block/title parent)) {:node parent})))
|
||||
|
||||
(let [page-uuid (:block/uuid page)
|
||||
(let [page-uuid (if-let [journal-day (:block/journal-day page)]
|
||||
(common-uuid/gen-uuid :journal-page-uuid journal-day)
|
||||
(:block/uuid page))
|
||||
page (assoc page :block/uuid page-uuid)
|
||||
page-txs (build-page-tx db properties page (select-keys options [:class? :tags :class-ident-namespace]))
|
||||
txs (concat
|
||||
;; transact doesn't support entities
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
[logseq.common.util :as common-util]
|
||||
[logseq.common.util.date-time :as date-time-util]
|
||||
[logseq.common.util.page-ref :as page-ref]
|
||||
[logseq.common.uuid :as common-uuid]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.db.common.order :as db-order]
|
||||
[logseq.db.frontend.db :as db-db]
|
||||
@@ -186,3 +187,20 @@
|
||||
"Journal title follows configured formatter")
|
||||
(is (= default-name (:block/name page))
|
||||
"Journal block/name remains the default formatter, independent of title format")))
|
||||
|
||||
(deftest create-slash-formatted-journal-does-not-create-namespace-pages
|
||||
(let [conn (db-test/create-conn)
|
||||
_ (d/transact! conn [[:db/add :logseq.class/Journal :logseq.property.journal/title-format "yyyy/MM/dd"]])
|
||||
[_ page-uuid] (outliner-page/create! conn "May 18th, 2026" {:split-namespace? true
|
||||
:journal? true})
|
||||
page (d/entity @conn [:block/uuid page-uuid])]
|
||||
(is (= "2026/05/18" (:block/title page))
|
||||
"Journal title follows slash title format")
|
||||
(is (= (common-uuid/gen-uuid :journal-page-uuid 20260518) (:block/uuid page))
|
||||
"Journal page has the standard journal uuid")
|
||||
(is (nil? (ldb/get-page @conn "2026"))
|
||||
"Journal title is not split into a year namespace page")
|
||||
(is (nil? (ldb/get-page @conn "05"))
|
||||
"Journal title is not split into a month namespace page")
|
||||
(is (nil? (ldb/get-page @conn "18"))
|
||||
"Journal title is not split into a day namespace page")))
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
[electron.ipc :as ipc]
|
||||
[frontend.components.avatar :as avatar]
|
||||
[frontend.components.block.breadcrumb-model :as breadcrumb-model]
|
||||
[frontend.components.block.asset :as block-asset]
|
||||
[frontend.components.block.comments :as block-comments]
|
||||
[frontend.components.block.comments-model :as comments-model]
|
||||
[frontend.components.block.drop :as block-drop]
|
||||
@@ -518,8 +519,8 @@
|
||||
(:image-placeholder config)
|
||||
(if (and (:image-placeholder config) (nil? @src))
|
||||
(:image-placeholder config)
|
||||
(let [ext (keyword (or (util/get-file-ext @src)
|
||||
(util/get-file-ext href)))
|
||||
(let [asset-block (:asset-block config)
|
||||
ext (block-asset/link-ext @src href asset-block)
|
||||
repo (state/get-current-repo)
|
||||
repo-dir (config/get-repo-dir repo)
|
||||
share-fn (fn [event]
|
||||
@@ -560,15 +561,14 @@
|
||||
title]
|
||||
|
||||
util/web-platform?
|
||||
(let [file-name (str (:block/title (:asset-block config)) "." (name ext))]
|
||||
(let [file-name (block-asset/link-file-name asset-block ext)]
|
||||
[:a.asset-ref
|
||||
{:href @src
|
||||
:download file-name}
|
||||
file-name])
|
||||
|
||||
(and (util/electron?) (:asset-block config))
|
||||
(let [asset-block (:asset-block config)
|
||||
file-name (str (:block/title asset-block) "." (name ext))]
|
||||
(and (util/electron?) asset-block)
|
||||
(let [file-name (block-asset/link-file-name asset-block ext)]
|
||||
[:a.asset-ref
|
||||
{:on-click (fn [e]
|
||||
(util/stop e)
|
||||
@@ -582,7 +582,7 @@
|
||||
file-fpath (if local-ext-url?
|
||||
;; Plugin-sourced asset stored under assets/storages/<plugin-id>/...
|
||||
(path/path-join repo-dir (string/replace ext-url #"^[./]+" ""))
|
||||
(path/path-join repo-dir (str "assets/" (:block/uuid asset-block) "." (name ext))))]
|
||||
(path/path-join repo-dir (str "assets/" (:block/uuid asset-block) (when ext (str "." (name ext))))))]
|
||||
(if remote-ext-url?
|
||||
(js/window.apis.openExternal ext-url)
|
||||
(js/window.apis.openPath file-fpath))))}
|
||||
|
||||
34
src/main/frontend/components/block/asset.cljs
Normal file
34
src/main/frontend/components/block/asset.cljs
Normal file
@@ -0,0 +1,34 @@
|
||||
(ns frontend.components.block.asset
|
||||
"Helpers for rendering asset links in block content.
|
||||
|
||||
Asset links can point to local files, graph-relative files, remote URLs, or
|
||||
protocol URLs. These helpers normalize the display-facing parts of an asset
|
||||
without assuming that the URL itself contains a file extension."
|
||||
(:require [clojure.string :as string]
|
||||
[frontend.util :as util]))
|
||||
|
||||
(defn- asset-type->keyword
|
||||
"Coerces `asset-type` from an asset entity into a lowercase keyword.
|
||||
|
||||
Returns `nil` when `asset-type` is absent or has an unsupported type."
|
||||
[asset-type]
|
||||
(cond
|
||||
(keyword? asset-type) asset-type
|
||||
(string? asset-type) (keyword (string/lower-case asset-type))))
|
||||
|
||||
(defn link-ext
|
||||
"Resolves the extension keyword for an asset link.
|
||||
|
||||
`src` is the resolved render URL, `href` is the original asset link, and
|
||||
`asset-block` is the asset entity. The URL-derived extension has priority;
|
||||
`:logseq.property.asset/type` is used when neither URL exposes an extension."
|
||||
[src href asset-block]
|
||||
(or (some-> (util/get-file-ext src) keyword)
|
||||
(some-> (util/get-file-ext href) keyword)
|
||||
(asset-type->keyword (:logseq.property.asset/type asset-block))))
|
||||
|
||||
(defn link-file-name
|
||||
"Builds the display file name for `asset-block` using resolved extension `ext`."
|
||||
[asset-block ext]
|
||||
(cond-> (str (:block/title asset-block))
|
||||
ext (str "." (name ext))))
|
||||
@@ -3,19 +3,36 @@
|
||||
(:require [logseq.outliner.page :as outliner-page]))
|
||||
|
||||
(defn create!
|
||||
"Create page. Has the following options:
|
||||
"Creates a page through the outliner page service.
|
||||
|
||||
* :uuid - when set, use this uuid instead of generating a new one.
|
||||
* :class? - when true, adds a :block/tags ':logseq.class/Tag'
|
||||
* :tags - tag uuids that are added to :block/tags
|
||||
* :persist-op? - when true, add an update-page op
|
||||
* :properties - properties to add to the page
|
||||
TODO: Add other options"
|
||||
Supported options:
|
||||
|
||||
* :uuid - when set, use this uuid instead of generating a new one; ignored
|
||||
when :journal? or :today-journal? uses a deterministic journal
|
||||
uuid from :block/journal-day.
|
||||
* :class? - create the page as a Tag class page.
|
||||
* :journal? - create the page as a Journal page.
|
||||
* :today-journal? - mark the create-page tx as today's journal creation.
|
||||
* :tags - tag uuids or tag entities added to :block/tags.
|
||||
* :properties - properties to add to the page.
|
||||
* :split-namespace? - create namespace parent pages for non-journal slash pages.
|
||||
* :class-ident-namespace - namespace used when creating a class ident.
|
||||
* :persist-op? - when true, persist the create-page outliner op."
|
||||
[conn title & {:as options}]
|
||||
(outliner-page/create! conn title options))
|
||||
|
||||
(defn delete!
|
||||
"Deletes a page. Returns true if able to delete page. If unable to delete,
|
||||
calls error-handler fn and returns false"
|
||||
"Deletes a page through the outliner page service.
|
||||
|
||||
Returns true when the page can be deleted. If deletion is rejected, calls
|
||||
:error-handler and returns false.
|
||||
|
||||
Supported options:
|
||||
|
||||
* :persist-op? - when true, persist the delete-page outliner op.
|
||||
* :rename? - mark the tx as part of a rename flow.
|
||||
* :error-handler - callback invoked with {:msg string} on rejection.
|
||||
* :deleted-by-uuid - user uuid recorded in the delete op metadata.
|
||||
* :now-ms - timestamp recorded in the delete op metadata."
|
||||
[conn page-uuid & {:as options}]
|
||||
(outliner-page/delete! conn page-uuid options))
|
||||
|
||||
18
src/test/frontend/components/block/asset_test.cljs
Normal file
18
src/test/frontend/components/block/asset_test.cljs
Normal file
@@ -0,0 +1,18 @@
|
||||
(ns frontend.components.block.asset-test
|
||||
(:require [cljs.test :refer [deftest is testing]]
|
||||
[frontend.components.block.asset :as block-asset]))
|
||||
|
||||
(deftest link-ext-test
|
||||
(testing "falls back to asset type when the URL has no extension"
|
||||
(is (= :pdf
|
||||
(block-asset/link-ext
|
||||
"zotero://select/library/items/QLUSY2JL"
|
||||
"zotero://select/library/items/QLUSY2JL"
|
||||
{:logseq.property.asset/type "pdf"})))))
|
||||
|
||||
(deftest link-file-name-test
|
||||
(testing "uses the resolved extension in the displayed file name"
|
||||
(is (= "test.pdf"
|
||||
(block-asset/link-file-name
|
||||
{:block/title "test"}
|
||||
:pdf)))))
|
||||
@@ -189,7 +189,7 @@
|
||||
(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" {})
|
||||
(let [[_ page-uuid] (outliner-page/create! conn "Dec 16th, 2024" {:journal? true})
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user