Bladeren bron

enhance: asset import supports multiple assets

per block and preserves text around assets.
These were both enabled by moving asset blocks to Asset page
Gabriel Horner 4 maanden geleden
bovenliggende
commit
ace71b2b25

+ 65 - 40
deps/graph-parser/src/logseq/graph_parser/exporter.cljs

@@ -8,6 +8,7 @@
             [clojure.edn :as edn]
             [clojure.set :as set]
             [clojure.string :as string]
+            [clojure.walk :as walk]
             [datascript.core :as d]
             [logseq.common.config :as common-config]
             [logseq.common.path :as path]
@@ -33,8 +34,7 @@
             [logseq.graph-parser.block :as gp-block]
             [logseq.graph-parser.extract :as extract]
             [logseq.graph-parser.property :as gp-property]
-            [promesa.core :as p]
-            [clojure.walk :as walk]))
+            [promesa.core :as p]))
 
 (defn- add-missing-timestamps
   "Add updated-at or created-at timestamps if they doesn't exist"
@@ -910,43 +910,67 @@
      ast-blocks)
     @results))
 
+(defn- update-asset-links-in-block-title [block-title asset-name-to-uuids ignored-assets]
+  (reduce (fn [acc [asset-name asset-uuid]]
+            (let [new-title (string/replace acc
+                                            (re-pattern (str "!?\\[[^\\]]*?\\]\\([^\\)]*?"
+                                                             asset-name
+                                                             "\\)(\\{[^}]*\\})?"))
+                                            (page-ref/->page-ref asset-uuid))]
+              (when (string/includes? new-title asset-name)
+                  (swap! ignored-assets conj
+                         {:reason "Some asset links were not updated to block references"
+                          :path asset-name
+                          :location {:block new-title}}))
+              new-title))
+          block-title
+          asset-name-to-uuids))
+
 (defn- handle-assets-in-block
   [block* {:keys [assets ignored-assets]}]
   (let [block (dissoc block* :block.temp/ast-blocks)
-        asset-links (find-all-asset-links (:block.temp/ast-blocks block*))
-        asset-link (first asset-links)
-        asset-name (some-> asset-link second :url second asset-path->name)]
-    (when (> (count asset-links) 1)
-      (swap! ignored-assets into
-             (map #(hash-map
-                    :reason "Multiple assets on one block. Only one asset per block is allowed"
-                    :path (-> % second :url second)
-                    :location {:block (:block/title block)})
-                  (rest asset-links))))
-    (if asset-name
-      (if-let [asset-data (get @assets asset-name)]
-        (if (:block/uuid asset-data)
-          ;; Link to existing assets instead of creating duplicates to preserve identity
-          (assoc block :block/title (page-ref/->page-ref (:block/uuid asset-data)))
-          (do
-            ;; (prn :asset-added! (node-path/basename asset-name) #_(get @assets asset-name))
-            ;; (cljs.pprint/pprint asset-link)
-            (swap! assets assoc-in [asset-name :block/uuid] (:block/uuid block))
-            (merge block
-                   {:block/tags [:logseq.class/Asset]
-                    :logseq.property.asset/type (:type asset-data)
-                    :logseq.property.asset/checksum (:checksum asset-data)
-                    :logseq.property.asset/size (:size asset-data)
-                    :block/title (db-asset/asset-name->title (node-path/basename asset-name))}
-                   (when-let [metadata (not-empty (common-util/safe-read-map-string (:metadata (second asset-link))))]
-                     {:logseq.property.asset/resize-metadata metadata}))))
-        (do
-          (swap! ignored-assets conj
-                 {:reason "Asset file was not found when reading assets"
-                  :path (-> asset-link second :url second)
-                  :location {:block (:block/title block)}})
-          block))
-      block)))
+        asset-links (find-all-asset-links (:block.temp/ast-blocks block*))]
+    (if (seq asset-links)
+      (let [asset-maps
+            (keep
+             (fn [asset-link]
+               (let [asset-name (-> asset-link second :url second asset-path->name)]
+                 (if-let [asset-data (and asset-name (get @assets asset-name))]
+                   (if (:block/uuid asset-data)
+                     {:asset-name-uuid [asset-name (:block/uuid asset-data)]}
+                     (let [new-block (sqlite-util/block-with-timestamps
+                                      {:block/uuid (d/squuid)
+                                       :block/order (db-order/gen-key)
+                                       :block/page :logseq.class/Asset
+                                       :block/parent :logseq.class/Asset})
+                           new-asset (merge new-block
+                                            {:block/tags [:logseq.class/Asset]
+                                             :logseq.property.asset/type (:type asset-data)
+                                             :logseq.property.asset/checksum (:checksum asset-data)
+                                             :logseq.property.asset/size (:size asset-data)
+                                             :block/title (db-asset/asset-name->title (node-path/basename asset-name))}
+                                            (when-let [metadata (not-empty (common-util/safe-read-map-string (:metadata (second asset-link))))]
+                                              {:logseq.property.asset/resize-metadata metadata}))]
+                      ;;  (prn :asset-added! (node-path/basename asset-name) #_(get @assets asset-name))
+                      ;;  (cljs.pprint/pprint asset-link)
+                       (swap! assets assoc-in [asset-name :block/uuid] (:block/uuid new-block))
+                       {:asset-name-uuid [asset-name (:block/uuid new-asset)]
+                        :asset new-asset}))
+                   (do
+                     (swap! ignored-assets conj
+                            {:reason "No asset data found for this asset path"
+                             :path (-> asset-link second :url second)
+                             :location {:block (:block/title block)}})
+                     nil))))
+             asset-links)
+            asset-blocks (keep :asset asset-maps)
+            asset-names-to-uuids
+            (into {} (map :asset-name-uuid asset-maps))]
+        (cond-> {:block
+                 (update block :block/title update-asset-links-in-block-title asset-names-to-uuids ignored-assets)}
+          (seq asset-blocks)
+          (assoc :asset-blocks-tx asset-blocks)))
+      {:block block})))
 
 (defn- build-block-tx
   [db block* pre-blocks {:keys [page-names-to-uuids] :as per-file-state} {:keys [import-state journal-created-ats] :as options}]
@@ -955,9 +979,11 @@
         {:keys [block properties-tx]}
         (handle-block-properties block* db page-names-to-uuids (:block/refs block*) options)
         {block-after-built-in-props :block deadline-properties-tx :properties-tx} (update-block-deadline block page-names-to-uuids options)
+        {block-after-assets :block :keys [asset-blocks-tx]}
+        (handle-assets-in-block block-after-built-in-props (select-keys import-state [:assets :ignored-assets]))
         ;; :block/page should be [:block/page NAME]
         journal-page-created-at (some-> (:block/page block*) second journal-created-ats)
-        prepared-block (cond-> block-after-built-in-props
+        prepared-block (cond-> block-after-assets
                          journal-page-created-at
                          (assoc :block/created-at journal-page-created-at))
         block' (-> prepared-block
@@ -965,7 +991,6 @@
                    (fix-block-name-lookup-ref page-names-to-uuids)
                    (update-block-refs page-names-to-uuids options)
                    (update-block-tags db (:user-options options) per-file-state (:all-idents import-state))
-                   (handle-assets-in-block (select-keys import-state [:assets :ignored-assets]))
                    (update-block-marker options)
                    (update-block-priority options)
                    add-missing-timestamps
@@ -973,8 +998,8 @@
                    (dissoc :block/left :block/format)
                    ;; ((fn [x] (prn :block-out x) x))
                    )]
-    ;; Order matters as properties are referenced in block
-    (concat properties-tx deadline-properties-tx [block'])))
+    ;; Order matters as previous txs are referenced in block
+    (concat properties-tx deadline-properties-tx asset-blocks-tx [block'])))
 
 (defn- update-page-alias
   [m page-names-to-uuids]

+ 29 - 6
deps/graph-parser/test/logseq/graph_parser/exporter_test.cljs

@@ -1,7 +1,7 @@
 (ns ^:node-only logseq.graph-parser.exporter-test
   (:require ["fs" :as fs]
             ["path" :as node-path]
-            [cljs.test :refer [testing is]]
+            [cljs.test :refer [testing is are deftest]]
             [clojure.set :as set]
             [clojure.string :as string]
             [datascript.core :as d]
@@ -10,6 +10,7 @@
             [logseq.common.util.date-time :as date-time-util]
             [logseq.db :as ldb]
             [logseq.db.common.entity-plus :as entity-plus]
+            [logseq.db.frontend.asset :as db-asset]
             [logseq.db.frontend.content :as db-content]
             [logseq.db.frontend.malli-schema :as db-malli-schema]
             [logseq.db.frontend.rules :as rules]
@@ -20,8 +21,7 @@
             [logseq.graph-parser.test.docs-graph-helper :as docs-graph-helper]
             [logseq.graph-parser.test.helper :as test-helper :include-macros true :refer [deftest-async]]
             [logseq.outliner.db-pipeline :as db-pipeline]
-            [promesa.core :as p]
-            [logseq.db.frontend.asset :as db-asset]))
+            [promesa.core :as p]))
 
 ;; Helpers
 ;; =======
@@ -145,6 +145,24 @@
 ;; Tests
 ;; =====
 
+(deftest update-asset-links-in-block-title
+  (are [x y]
+       (= y (@#'gp-exporter/update-asset-links-in-block-title (first x) {(second x) "UUID"} (atom {})))
+    ;; Standard image link with metadata
+    ["![greg-popovich-thumbs-up.png](../assets/greg-popovich-thumbs-up_1704749687791_0.png){:height 288, :width 100} says pop"
+     "assets/greg-popovich-thumbs-up_1704749687791_0.png"]
+    "[[UUID]] says pop"
+
+    ;; Image link with no metadata
+    ["![some-title](../assets/CleanShot_2022-10-12_at_15.53.20@2x_1665561216083_0.png)"
+     "assets/CleanShot_2022-10-12_at_15.53.20@2x_1665561216083_0.png"]
+    "[[UUID]]"
+
+    ;; 2nd link
+    ["[[FIRST UUID]] and ![dino!](assets/subdir/partydino.gif)"
+     "assets/subdir/partydino.gif"]
+    "[[FIRST UUID]] and [[UUID]]"))
+
 (deftest-async ^:integration export-docs-graph-with-convert-all-tags
   (p/let [file-graph-dir "test/resources/docs-0.10.12"
           start-time (cljs.core/system-time)
@@ -163,6 +181,7 @@
     (is (empty? (map :entity (:errors (db-validate/validate-db! @conn))))
         "Created graph has no validation errors")
     (is (= 0 (count @(:ignored-properties import-state))) "No ignored properties")
+    (is (= 0 (count @(:ignored-assets import-state))) "No ignored assets")
     (is (= []
            (->> (d/q '[:find (pull ?b [:block/title {:block/tags [:db/ident]}])
                        :where [?b :block/tags :logseq.class/Tag]]
@@ -189,7 +208,7 @@
       ;; Includes journals as property values e.g. :logseq.property/deadline
       (is (= 26 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Journal]] @conn))))
 
-      (is (= 2 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Asset]] @conn))))
+      (is (= 3 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Asset]] @conn))))
       (is (= 4 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Task]] @conn))))
       (is (= 4 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Query]] @conn))))
       (is (= 2 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Card]] @conn))))
@@ -215,7 +234,7 @@
       (is (= 0 (count @(:ignored-properties import-state))) "No ignored properties")
       (is (= 0 (count @(:ignored-assets import-state))) "No ignored assets")
       (is (= 1 (count @(:ignored-files import-state))) "Ignore .edn for now")
-      (is (= 2 (count @assets))))
+      (is (= 3 (count @assets))))
 
     (testing "logseq files"
       (is (= ".foo {}\n"
@@ -392,7 +411,11 @@
               :logseq.property.asset/checksum "3d5e620cac62159d8196c118574bfea7a16e86fa86efd1c3fa15a00a0a08792d"
               :logseq.property.asset/size 753471
               :logseq.property.asset/resize-metadata {:height 288, :width 252}}
-             (db-test/readable-properties (db-test/find-block-by-content @conn "greg-popovich-thumbs-up_1704749687791_0")))))
+             (db-test/readable-properties (db-test/find-block-by-content @conn "greg-popovich-thumbs-up_1704749687791_0")))
+          "Asset has correct properties")
+      (is (= (d/entity @conn :logseq.class/Asset)
+             (:block/page (db-test/find-block-by-content @conn "greg-popovich-thumbs-up_1704749687791_0")))
+          "Imported into Asset page"))
 
     (testing "tags convert to classes"
       (is (= :user.class/Quotes___life

BIN
deps/graph-parser/test/resources/exporter-test-graph/assets/HEART_Teams.png


+ 1 - 1
deps/graph-parser/test/resources/exporter-test-graph/journals/2025_06_12.md

@@ -2,4 +2,4 @@
   
   
   ![dino!](assets/subdir/partydino.gif){:width 105} tests an asset with a manual link, custom title and in a subdirectory
-- ![greg-popovich-thumbs-up.png](../assets/greg-popovich-thumbs-up_1704749687791_0.png){:height 288, :width 252}
+- ![greg-popovich-thumbs-up.png](../assets/greg-popovich-thumbs-up_1704749687791_0.png){:height 288, :width 252} and ![HEART Teams](../assets/HEART_Teams.png){:width 335.99774169921875}