Compare commits

...
Author SHA1 Message Date
Elena Torro a8b457a1d3 🐛 Fix empty text content breaking file persistence 2026-09-07 16:51:58 +02:00
3 changed files with 80 additions and 59 deletions

No files matched your search

+38 -18
View File
@@ -192,30 +192,50 @@
#{:font-family :font-size :font-style :font-weight
:direction :text-direction :text-decoration :text-transform :key})
(defn- remove-nil-style-attrs
"Strip nil-valued non-nilable style attrs from every node in a content tree.
Repairs content already corrupted with e.g. nil :font-family/:font-weight/
:font-style (from an unloaded font) so it can pass the backend schema again."
(defn- repair-nodes
"Walk a content tree once, repairing every node so it can pass the backend
`validate-shape` schema: nil-valued non-nilable style attrs are stripped
(e.g. nil :font-family/:font-weight/:font-style from an unloaded font) and a
level with no children is seeded with the default paragraph/span."
[content]
(txt/transform-nodes
(fn [node]
(reduce (fn [node k]
(if (and (contains? node k) (nil? (get node k)))
(dissoc node k)
node))
node
non-nilable-style-attrs))
content))
(let [;; Seeding only makes sense inside a root tree: a bare node handed over
;; as content is not ours to complete.
seed? (= "root" (:type content))
default-para (delay (-> (tc/v2-default-text-content) :children first :children first))]
(txt/transform-nodes
(fn [node]
(let [node (reduce (fn [node k]
(if (and (contains? node k) (nil? (get node k)))
(dissoc node k)
node))
node
non-nilable-style-attrs)]
(if seed?
(case (:type node)
"paragraph-set"
(cond-> node
(empty? (:children node))
(assoc :children [@default-para]))
"paragraph"
(cond-> node
(empty? (:children node))
(assoc :children (:children @default-para)))
node)
node)))
content)))
(defn ensure-valid-text-content
"Repair structurally incomplete text :content to a canonical
root -> paragraph-set -> paragraph -> span tree. Returns the
content unchanged when it is already well-formed.
A `nil` content, a root with no :children, or a root with an empty
:children vector all fail the backend `validate-shape` schema
(children must contain at least one paragraph-set). This helper
is the defensive normalizer used by content-commit paths.
A `nil` content, a root with no :children, a paragraph-set with no
paragraphs, or a paragraph with no spans all fail the backend
`validate-shape` schema (every level requires at least one child).
This helper is the defensive normalizer used by content-commit paths;
it mirrors the `0025-repair-empty-text-content` migration.
It also scrubs nil-valued non-nilable style attrs (e.g. nil
:font-family/:font-weight/:font-style left over from an unloaded font),
@@ -227,7 +247,7 @@
(empty? (:children content))))
(let [base (tc/v2-default-text-content)]
(d/txt-merge base (select-keys content txt/root-attrs)))
(remove-nil-style-attrs content)))
(repair-nodes content)))
(defn- v2-content-has-text?
[content]
+10 -14
View File
@@ -1278,22 +1278,18 @@
(if (< index total)
(let [paragraph (nth paragraphs index)
spans (get paragraph :children)]
(if (empty? (seq spans))
(recur (inc index)
emoji?
langs)
spans (or (seq (get paragraph :children))
[(assoc (select-keys paragraph txt/text-node-attrs) :text "")])
text (apply str (map :text spans))
emoji? (if emoji? emoji? (t/contains-emoji? text))
langs (t/collect-used-languages langs text)]
(let [text (apply str (map :text spans))
emoji? (if emoji? emoji? (t/contains-emoji? text))
langs (t/collect-used-languages langs text)]
;; FIXME: this should probably be somewhere else
(when fallback-fonts-only? (t/write-shape-text spans paragraph text))
;; FIXME: this should probably be somewhere else
(when fallback-fonts-only? (t/write-shape-text spans paragraph text))
(recur (inc index)
emoji?
langs))))
(recur (inc index)
emoji?
langs))
(let [updated-fonts
(-> #{}
+32 -27
View File
@@ -550,32 +550,37 @@
exported-texts vector of vectors [[\"span1\" \"span2\"] [\"p2s1\"]]
content existing Penpot content map (root -> paragraph-set -> …)"
[content exported-texts]
(let [para-set (first (get content :children))
orig-paras (get para-set :children)
num-orig (count orig-paras)
last-orig-para (when (seq orig-paras) (last orig-paras))
template-span (when last-orig-para
(-> last-orig-para :children last))
new-paras
(mapv (fn [para-idx exported-span-texts]
(let [orig-para (if (< para-idx num-orig)
(nth orig-paras para-idx)
(dissoc last-orig-para :children))
orig-spans (get orig-para :children)
num-orig-spans (count orig-spans)
last-orig-span (when (seq orig-spans) (last orig-spans))]
(assoc orig-para :children
(mapv (fn [span-idx new-text]
(let [orig-span (if (< span-idx num-orig-spans)
(nth orig-spans span-idx)
(or last-orig-span template-span))]
(assoc orig-span :text new-text)))
(range (count exported-span-texts))
exported-span-texts))))
(range (count exported-texts))
exported-texts)
new-para-set (assoc para-set :children new-paras)]
(assoc content :children [new-para-set])))
(if (empty? exported-texts)
content
(let [para-set (first (get content :children))
orig-paras (get para-set :children)
num-orig (count orig-paras)
last-orig-para (when (seq orig-paras) (last orig-paras))
template-span (when last-orig-para
(-> last-orig-para :children last))
exported-texts (mapv (fn [span-texts]
(if (empty? span-texts) [""] span-texts))
exported-texts)
new-paras
(mapv (fn [para-idx exported-span-texts]
(let [orig-para (if (< para-idx num-orig)
(nth orig-paras para-idx)
(dissoc last-orig-para :children))
orig-spans (get orig-para :children)
num-orig-spans (count orig-spans)
last-orig-span (when (seq orig-spans) (last orig-spans))]
(assoc orig-para :children
(mapv (fn [span-idx new-text]
(let [orig-span (if (< span-idx num-orig-spans)
(nth orig-spans span-idx)
(or last-orig-span template-span))]
(assoc orig-span :text new-text)))
(range (count exported-span-texts))
exported-span-texts))))
(range (count exported-texts))
exported-texts)
new-para-set (assoc para-set :children new-paras)]
(assoc content :children [new-para-set]))))
(defn- default-empty-text-content
"Build a default, empty text content tree used as a merge template.
@@ -687,7 +692,7 @@
paragraphs (:children paragraph-set)
new-paragraphs
(when (not collapsed?)
(when (and (not collapsed?) (seq paragraphs))
(mapv (fn [idx para]
(cond
;; paragraph outside the range of paragraphs.