penpot/frontend/src/app/util/code_gen/markup_html.cljs
FairyPiggyDev fa06efa84d
♻️ Migrate fo-text and html-text renderers to modern component syntax (#9385)
Step toward issue #9260 (incremental migration of legacy UI
components to the modern `*`-suffixed syntax, removing the per-render
JS-to-Clojure props conversion overhead).

Twin namespaces with parallel structure: each defines six components
that drive a recursive text rendering pass over the editor's content
tree (root -> paragraph-set -> paragraph -> node -> text). Both files
were uniformly legacy: every component carried `::mf/wrap-props
false` and read its props with `(obj/get props "key")`. None had
`::mf/register`, `unchecked-get` or `obj/merge!`, so they qualify as
clean Case-A migrations.

frontend/src/app/main/ui/shapes/text/fo_text.cljs (6 components)
----------------------------------------------------------------

- `render-text`           -> `render-text*`
- `render-root`           -> `render-root*`
- `render-paragraph-set`  -> `render-paragraph-set*`
- `render-paragraph`      -> `render-paragraph*`
- `render-node`           -> `render-node*`     (forward-props case,
                                                 see below)
- `text-shape`            -> `text-shape*`      (`::mf/forward-ref`
                                                 preserved)

The four leaf components switch from `[props]` + per-key
`(obj/get props "key")` to standard destructuring. `text-shape`
already used destructuring under `::mf/props :obj`; that legacy
metadata is dropped because the modern `*` form handles props
automatically. Its single `::mf/forward-ref true` is kept per the
prompt's "preserve forward-ref" rule.

`render-node` is the recursive driver. It needs to forward all of
its incoming props to the matched paragraph-* / text component and
then to a child `render-node*` after overriding `:node`, `:index`
and `:key`. The migrated form uses `::mf/props :obj` together with
`{:keys [node] :as props}` to keep the JS-object props symbol
available, and `(mf/spread-props props {…})` replaces the previous
`obj/clone` + `obj/set!` chain.

`app.util.object` is no longer required by this namespace and the
`(:require ... [app.util.object :as obj] ...)` line is removed.

frontend/src/app/main/ui/shapes/text/html_text.cljs (6 components)
-----------------------------------------------------------------

Identical six-component shape as `fo_text.cljs`, plus a `code?`
flag threaded through every component to switch the rendering path
between regular shapes and code-style shapes.

- `render-text`           -> `render-text*`
- `render-root`           -> `render-root*`
- `render-paragraph-set`  -> `render-paragraph-set*`
- `render-paragraph`      -> `render-paragraph*`
- `render-node`           -> `render-node*`     (same forward-props
                                                 treatment as above,
                                                 plus `is-code` in
                                                 the spread)
- `text-shape`            -> `text-shape*`      (`::mf/forward-ref`
                                                 preserved)

The `code?` boolean prop is renamed to `is-code` per the migration
prompt's "?-suffixed boolean -> `is-` prefix" rule. The rename is
applied at every read site (5 components) and at the `text-shape*`
internal call to `render-node*`, so the prop is consistent inside
the namespace.

`app.util.object` is no longer required by this namespace either
and the corresponding `:require` line is dropped.

External call sites (3 files, 4 sites)
--------------------------------------

- `frontend/src/app/main/ui/shapes/text.cljs` - the legacy
  text-shape wrapper (intentionally kept legacy in this PR because
  it dispatches to `svg/text-shape`, which is still being touched by
  the in-flight PR #9016) now calls `[:> fo/text-shape* props]`.
  The `props` symbol is the wrapper's incoming JS-object; modern
  destructured components accept JS-object props at the call site
  via `[:>` so this works unchanged.

- `frontend/src/app/util/code_gen/markup_html.cljs` -
  `(mf/element text/text-shape #js {:shape shape :code? true})`
  becomes
  `(mf/element text/text-shape* #js {:shape shape :is-code true})`
  (component renamed and the `code?` JS key updated to match the
  renamed prop).

- `frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs`
  - `[:& html/text-shape {…}]` -> `[:> html/text-shape* {…}]`.

Behavior preserved verbatim
---------------------------

Same render output, same forward-ref forwarding semantics, same
recursive children-by-index keying, same default `:dir "auto"` on
`render-paragraph*`. The visible-prop changes are only the `code?`
-> `is-code` rename, all driven from this namespace and its single
caller in `markup_html.cljs`.

Github #9260

Signed-off-by: FairyPigDev <luislee3108@gmail.com>
2026-05-07 15:03:51 +02:00

95 lines
3.6 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.util.code-gen.markup-html
(:require
["react-dom/server" :as rds]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.types.shape.layout :as ctl]
[app.config :as cfg]
[app.main.ui.shapes.text.html-text :as text]
[app.util.code-gen.common :as cgc]
[app.util.code-gen.markup-svg :refer [generate-svg]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn generate-html
([objects shape]
(generate-html objects shape 0))
([objects shape level]
(when (and (some? shape) (some? (:selrect shape)))
(let [indent (str/repeat " " level)
shape-html
(cond
(cgc/svg-markup? shape)
(let [svg-markup (generate-svg objects shape)]
(dm/fmt "%<div class=\"%\">\n%\n%</div>"
indent
(dm/str "shape " (d/name (:type shape)) " "
(cgc/shape->selector shape))
svg-markup
indent))
(cfh/text-shape? shape)
(let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape* #js {:shape shape :is-code true}))
text-shape-html (str/replace text-shape-html #"style\s*=\s*[\"'][^\"']*[\"']" "")]
(dm/fmt "%<div class=\"%\">\n%\n%</div>"
indent
(dm/str "shape " (d/name (:type shape)) " "
(cgc/shape->selector shape))
text-shape-html
indent))
(cfh/image-shape? shape)
(let [data (or (:metadata shape) (:fill-image shape))
image-url (cfg/resolve-file-media data)]
(dm/fmt "%<img src=\"%\" class=\"%\">\n%</img>"
indent
image-url
(dm/str "shape " (d/name (:type shape)) " "
(cgc/shape->selector shape))
indent))
(empty? (:shapes shape))
(dm/fmt "%<div class=\"%\">\n%</div>"
indent
(dm/str "shape " (d/name (:type shape)) " "
(cgc/shape->selector shape))
indent)
:else
(let [children (->> shape :shapes (map #(get objects %)))
reverse? (ctl/any-layout? shape)
;; The order for layout elements is the reverse of SVG order
children (cond-> children reverse? reverse)]
(dm/fmt "%<div class=\"%\">\n%\n%</div>"
indent
(dm/str (d/name (:type shape)) " "
(cgc/shape->selector shape))
(->> children
(map #(generate-html objects % (inc level)))
(str/join "\n"))
indent)))
shape-html
(if (cgc/has-wrapper? objects shape)
(dm/fmt "<div class=\"%\">%</div>"
(dm/str (cgc/shape->selector shape) "-wrapper")
shape-html)
shape-html)]
(dm/fmt "%<!-- % -->\n%" indent (dm/str (d/name (:type shape)) ": " (:name shape)) shape-html)))))
(defn generate-markup
[objects shapes]
(->> shapes
(keep #(generate-html objects %))
(str/join "\n")))