mirror of
https://github.com/penpot/penpot.git
synced 2026-07-31 18:36:18 +00:00
🐛 Fix invalid shape due to missing fonts (#10942)
* 🐛 Fix setting a font-related properties to nil * ✨ Repair already-corrupted text nodes * 🐛 Fix deleted fonts appearing in Recents and as the auto-selected font for new text shapes
This commit is contained in:
parent
2cb8813862
commit
be5d9f9e9c
@ -145,6 +145,29 @@
|
||||
|
||||
;; -- Content helpers
|
||||
|
||||
;; Style attrs typed as `::sm/text` in the content schema (see
|
||||
;; app.common.types.shape.text/schema:content): when the key is present its
|
||||
;; value must be a non-blank string, so an explicit nil fails backend
|
||||
;; `validate-shape`. `:key` is likewise a plain `:string`.
|
||||
(def ^:private non-nilable-style-attrs
|
||||
#{: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."
|
||||
[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))
|
||||
|
||||
(defn ensure-valid-text-content
|
||||
"Repair structurally incomplete text :content to a canonical
|
||||
root -> paragraph-set -> paragraph -> span tree. Returns the
|
||||
@ -153,7 +176,11 @@
|
||||
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."
|
||||
is the defensive normalizer used by content-commit paths.
|
||||
|
||||
It also scrubs nil-valued non-nilable style attrs (e.g. nil
|
||||
:font-family/:font-weight/:font-style left over from an unloaded font),
|
||||
so already-corrupted content self-heals on the next commit."
|
||||
[content]
|
||||
(if (and (map? content)
|
||||
(= "root" (:type content))
|
||||
@ -161,7 +188,7 @@
|
||||
(empty? (:children content))))
|
||||
(let [base (tc/v2-default-text-content)]
|
||||
(d/txt-merge base (select-keys content txt/root-attrs)))
|
||||
content))
|
||||
(remove-nil-style-attrs content)))
|
||||
|
||||
(defn- v2-content-has-text?
|
||||
[content]
|
||||
@ -294,7 +321,8 @@
|
||||
(update [_ state]
|
||||
(let [text-state (some->> content ted/import-content)
|
||||
attrs (merge (txt/get-default-text-attrs)
|
||||
(get-in state [:workspace-global :default-font]))
|
||||
(fonts/valid-default-font
|
||||
(get-in state [:workspace-global :default-font])))
|
||||
editor (cond-> (ted/create-editor-state text-state decorator)
|
||||
(and (nil? content) (some? attrs))
|
||||
(ted/update-editor-current-block-data attrs))]
|
||||
@ -1122,7 +1150,8 @@
|
||||
;; Avoid swapping the global store when the computed styles are unchanged,
|
||||
;; otherwise we can end up in store->rerender->selectionchange loops.
|
||||
(let [merged-styles (merge (txt/get-default-text-attrs)
|
||||
(get-in state [:workspace-global :default-font])
|
||||
(fonts/valid-default-font
|
||||
(get-in state [:workspace-global :default-font]))
|
||||
new-styles)
|
||||
prev (get-in state [:workspace-v2-editor-state id])]
|
||||
(if (= merged-styles prev)
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
(ns app.main.data.workspace.texts-v3
|
||||
(:require
|
||||
[app.common.types.text :as txt]
|
||||
[app.main.fonts :as fonts]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(defn v3-update-text-editor-styles
|
||||
@ -15,6 +16,7 @@
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [merged-styles (merge (txt/get-default-text-attrs)
|
||||
(get-in state [:workspace-global :default-font])
|
||||
(fonts/valid-default-font
|
||||
(get-in state [:workspace-global :default-font]))
|
||||
new-styles)]
|
||||
(update-in state [:workspace-wasm-editor-styles id] (fnil merge {}) merged-styles)))))
|
||||
|
||||
@ -73,6 +73,25 @@
|
||||
(defn get-font-data [id]
|
||||
(get @fontsdb id))
|
||||
|
||||
(defn installed?
|
||||
"True when a font with `font-id` is currently registered in `fontsdb`."
|
||||
[font-id]
|
||||
(contains? @fontsdb font-id))
|
||||
|
||||
(defn valid-default-font
|
||||
"Return the remembered default-font attrs only when its font is still
|
||||
installed in `fontsdb`; otherwise nil, so callers fall back to the built-in
|
||||
default text attrs (Source Sans Pro).
|
||||
|
||||
A new text shape inherits the last-used font (`[:workspace-global
|
||||
:default-font]`). If that font is gone (deleted, unavailable in the current
|
||||
team, or never resolved), its attrs carry a missing/nil font-family, which
|
||||
produces content that fails the backend `validate-shape` schema."
|
||||
[default-font]
|
||||
(when (and (some? default-font)
|
||||
(installed? (:font-id default-font)))
|
||||
default-font))
|
||||
|
||||
(defn find-font-data [data]
|
||||
(d/seek
|
||||
(fn [font]
|
||||
|
||||
@ -86,9 +86,11 @@
|
||||
canvas-node
|
||||
(mf/ref-val canvas-ref)
|
||||
|
||||
;; Gets the default font from the workspace refs.
|
||||
;; Gets the default font from the workspace refs. Ignore it when the
|
||||
;; remembered font is no longer installed, falling back to the built-in
|
||||
;; default so a new text shape never inherits a missing font.
|
||||
default-font
|
||||
(deref refs/default-font)
|
||||
(fonts/valid-default-font (deref refs/default-font))
|
||||
|
||||
style-defaults
|
||||
(styles/get-style-defaults
|
||||
|
||||
@ -178,15 +178,24 @@
|
||||
flist (mf/use-ref)
|
||||
input (mf/use-ref)
|
||||
|
||||
fonts (mf/deref fonts/fonts)
|
||||
fonts (mf/with-memo [state fonts]
|
||||
(filter-fonts state fonts))
|
||||
all-fonts (mf/deref fonts/fonts)
|
||||
fonts (mf/with-memo [state all-fonts]
|
||||
(filter-fonts state all-fonts))
|
||||
|
||||
;; Ids currently installed in fontsdb. Recent fonts that are no longer
|
||||
;; available (deleted, or belonging to another team) must be hidden:
|
||||
;; selecting one applies a missing/nil font-family and corrupts the
|
||||
;; text content (fails the backend `validate-shape` schema).
|
||||
installed-ids (mf/with-memo [all-fonts]
|
||||
(into #{} (map :id) all-fonts))
|
||||
|
||||
sprite-status (:status (mf/deref fonts/preview-sprite))
|
||||
|
||||
recent-fonts (mf/deref refs/recent-fonts)
|
||||
recent-fonts (mf/with-memo [state recent-fonts]
|
||||
(filter-fonts state recent-fonts))
|
||||
recent-fonts (mf/with-memo [state recent-fonts installed-ids]
|
||||
(->> recent-fonts
|
||||
(filter #(contains? installed-ids (:id %)))
|
||||
(filter-fonts state)))
|
||||
|
||||
|
||||
full-size? (boolean (and full-size show-recent))
|
||||
@ -332,12 +341,19 @@
|
||||
(fn [new-font-id]
|
||||
(let [{:keys [family] :as font} (get fonts new-font-id)
|
||||
{:keys [id name weight style]} (fonts/get-default-variant font)]
|
||||
(on-change {:font-id new-font-id
|
||||
:font-family family
|
||||
:font-variant-id (or id name)
|
||||
:font-weight weight
|
||||
:font-style style})
|
||||
(mf/set-ref-val! last-font font))))
|
||||
;; Guard against a font that is not present in fontsdb (unloaded
|
||||
;; custom font, deleted font, shared library not yet resolved).
|
||||
;; Without it `family`/`weight`/`style` come back nil and get written
|
||||
;; onto the text spans, producing content that fails the backend
|
||||
;; `validate-shape` schema (`:font-family`/`:font-weight`/`:font-style`
|
||||
;; must be non-blank strings when present).
|
||||
(when (and (some? font) (some? family))
|
||||
(on-change {:font-id new-font-id
|
||||
:font-family family
|
||||
:font-variant-id (or id name)
|
||||
:font-weight weight
|
||||
:font-style style})
|
||||
(mf/set-ref-val! last-font font)))))
|
||||
|
||||
on-font-size-change
|
||||
(mf/use-fn
|
||||
|
||||
@ -708,7 +708,13 @@
|
||||
(defn apply-styles-to-selection
|
||||
[attrs use-shape-fn set-shape-text-content-fn]
|
||||
(when (wasm/ready?)
|
||||
(let [shape-id (text-editor-get-active-shape-id)
|
||||
(let [;; Drop nil-valued attrs so they are never merged onto text spans.
|
||||
;; The DOM editor path strips these in `attrs->styles`; the WASM merge
|
||||
;; here (`apply-attrs-to-paragraph`) does not, so an unresolved attr
|
||||
;; (e.g. nil :font-family/:font-weight/:font-style from an unloaded
|
||||
;; font) would corrupt the span and fail the backend schema.
|
||||
attrs (into {} (remove (comp nil? val)) attrs)
|
||||
shape-id (text-editor-get-active-shape-id)
|
||||
selection (text-editor-get-selection)]
|
||||
|
||||
(when (and shape-id selection)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user