0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-09 16:48:16 -05:00

Add text ranges support in plugins

This commit is contained in:
alonso.torres 2024-06-13 12:29:17 +02:00
parent 018464aedf
commit 2da5dcb619
5 changed files with 285 additions and 14 deletions

View file

@ -361,7 +361,7 @@
new-acc
(cond
(:children node)
(not (is-text-node? node))
(reduce #(rec-style-text-map %1 %2 node-style) acc (:children node))
(not= head-style node-style)
@ -381,6 +381,28 @@
(-> (rec-style-text-map [] node {})
reverse)))
(defn content-range->text+styles
"Given a root node of a text content extracts the texts with its associated styles"
[node start end]
(let [sss (content->text+styles node)]
(loop [styles (seq sss)
taking? false
acc 0
result []]
(if styles
(let [[node-style text] (first styles)
from acc
to (+ acc (count text))
taking? (or taking? (and (<= from start) (< start to)))
text (subs text (max 0 (- start acc)) (- end acc))
result (cond-> result
(and taking? (d/not-empty? text))
(conj (assoc node-style :text text)))
continue? (or (> from end) (>= end to))]
(recur (when continue? (rest styles)) taking? to result))
result))))
(defn content->text
"Given a root node of a text content extracts the texts with its associated styles"
[content]

View file

@ -205,6 +205,102 @@
;; --- TEXT EDITION IMPL
(defn count-node-chars
([node]
(count-node-chars node false))
([node last?]
(case (:type node)
("root" "paragraph-set")
(apply + (concat (map count-node-chars (drop-last (:children node)))
(map #(count-node-chars % true) (take-last 1 (:children node)))))
"paragraph"
(+ (apply + (map count-node-chars (:children node))) (if last? 0 1))
(count (:text node)))))
(defn decorate-range-info
"Adds information about ranges inside the metadata of the text nodes"
[content]
(->> (with-meta content {:start 0 :end (count-node-chars content)})
(txt/transform-nodes
(fn [node]
(d/update-when
node
:children
(fn [children]
(let [start (-> node meta (:start 0))]
(->> children
(reduce (fn [[result start] node]
(let [end (+ start (count-node-chars node))]
[(-> result
(conj (with-meta node {:start start :end end})))
end]))
[[] start])
(first)))))))))
(defn split-content-at
[content position]
(->> content
(txt/transform-nodes
(fn [node]
(and (txt/is-paragraph-node? node)
(< (-> node meta :start) position (-> node meta :end))))
(fn [node]
(letfn
[(process-node [child]
(let [start (-> child meta :start)
end (-> child meta :end)]
(if (< start position end)
[(-> child
(vary-meta assoc :end position)
(update :text subs 0 (- position start)))
(-> child
(vary-meta assoc :start position)
(update :text subs (- position start)))]
[child])))]
(-> node
(d/update-when :children #(into [] (mapcat process-node) %))))))))
(defn update-content-range
[content start end attrs]
(->> content
(txt/transform-nodes
(fn [node]
(and (txt/is-text-node? node)
(and (>= (-> node meta :start) start)
(<= (-> node meta :end) end))))
#(d/patch-object % attrs))))
(defn- update-text-range-attrs
[shape start end attrs]
(let [new-content (-> (:content shape)
(decorate-range-info)
(split-content-at start)
(split-content-at end)
(update-content-range start end attrs))]
(assoc shape :content new-content)))
(defn update-text-range
[id start end attrs]
(ptk/reify ::update-text-range
ptk/WatchEvent
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
shape (get objects id)
update-fn
(fn [shape]
(cond-> shape
(cfh/text-shape? shape)
(update-text-range-attrs start end attrs)))
shape-ids (cond (cfh/text-shape? shape) [id]
(cfh/group-shape? shape) (cfh/get-children-ids objects id))]
(rx/of (dwsh/update-shapes shape-ids update-fn))))))
(defn- update-text-content
[shape pred-fn update-fn attrs]
(let [update-attrs-fn #(update-fn % attrs)
@ -278,7 +374,6 @@
(cfh/group-shape? shape) (cfh/get-children-ids objects id))]
(rx/of (dwsh/update-shapes shape-ids #(update-text-content % update-node? d/txt-merge attrs))))))))
(defn migrate-node
[node]
(let [color-attrs (select-keys node [:fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file :fill-color-gradient])]

View file

@ -233,9 +233,16 @@
(st/emit! (dwt/apply-typography #{shape-id} typography $file))))
(applyToTextRange
[_ _shape _from _to]
;; TODO
)
[self range]
(let [shape-id (obj/get range "$id")
start (obj/get range "start")
end (obj/get range "end")
typography (u/proxy->library-typography self)
attrs (-> typography
(assoc :typography-ref-file $file)
(assoc :typography-ref-id (:id typography))
(dissoc :id :name))]
(st/emit! (dwt/update-text-range shape-id start end attrs))))
;; PLUGIN DATA
(getPluginData

View file

@ -36,6 +36,139 @@
[app.util.text-editor :as ted]
[cuerdas.core :as str]))
(deftype TextRange [$plugin $file $page $id start end]
Object
(applyTypography [_ typography]
(let [typography (u/proxy->library-typography typography)
attrs (-> typography
(assoc :typography-ref-file $file)
(assoc :typography-ref-id (:id typography))
(dissoc :id :name))]
(st/emit! (dwt/update-text-range $id start end attrs)))))
(defn mixed-value
[values]
(let [s (set values)]
(if (= (count s) 1) (first s) "mixed")))
;; TODO Validate inputs
(defn text-range
[plugin-id file-id page-id id start end]
(-> (TextRange. plugin-id file-id page-id id start end)
(crc/add-properties!
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$page" :enumerable false :get (constantly page-id)}
{:name "shape"
:get #(-> % u/proxy->shape)}
{:name "characters"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text) (str/join "")))}
{:name "fontId"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-id) mixed-value))
:set
(fn [_ value]
(st/emit! (dwt/update-text-range id start end {:font-id value})))}
{:name "fontFamily"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-family) mixed-value))
:set
(fn [_ value]
(st/emit! (dwt/update-text-range id start end {:font-family value})))}
{:name "fontVariantId"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-variant-id) mixed-value))
:set
(fn [_ value]
(st/emit! (dwt/update-text-range id start end {:font-variant-id value})))}
{:name "fontSize"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-size) mixed-value))
:set
(fn [_ value]
(st/emit! (dwt/update-text-range id start end {:font-size value})))}
{:name "fontWeight"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-weight) mixed-value))
:set
(fn [_ value]
(st/emit! (dwt/update-text-range id start end {:font-weight value})))}
{:name "fontStyle"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-style) mixed-value))
:set
(fn [_ value]
(st/emit! (dwt/update-text-range id start end {:font-style value})))}
{:name "lineHeight"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :line-height) mixed-value))
:set
(fn [_ value]
(st/emit! (dwt/update-text-range id start end {:line-height value})))}
{:name "letterSpacing"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :letter-spacing) mixed-value))
:set
(fn [_ value]
(st/emit! (dwt/update-text-range id start end {:letter-spacing value})))}
{:name "textTransform"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-transform) mixed-value))
:set
(fn [_ value]
(st/emit! (dwt/update-text-range id start end {:text-transform value})))}
{:name "textDecoration"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-decoration) mixed-value))
:set
(fn [_ value]
(st/emit! (dwt/update-text-range id start end {:text-decoration value})))}
{:name "direction"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :direction) mixed-value))
:set
(fn [_ value]
(st/emit! (dwt/update-text-range id start end {:direction value})))}
{:name "fills"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :fills) mixed-value u/array-to-js))
:set
(fn [_ value]
(let [value (mapv #(u/from-js %) value)]
(st/emit! (dwt/update-text-range id start end {:fills value}))))})))
(declare shape-proxy)
(defn parse-command
@ -214,11 +347,19 @@
;; Text shapes
(getRange
[_ _from _to]
[_ start end]
(let [shape (u/locate-shape $file $page $id)]
(if (cfh/text-shape? shape)
nil ;; TODO
(u/display-not-valid :makeMask (:type shape))))))
(text-range $plugin $file $page $id start end)
(u/display-not-valid :makeMask (:type shape)))))
(applyTypography
[_ typography]
(let [shape (u/locate-shape $file $page $id)]
(if (cfh/text-shape? shape)
(let [typography (u/proxy->library-typography typography)]
(st/emit! (dwt/apply-typography #{$id} typography $file)))
(u/display-not-valid :applyTypography (:type shape))))))
(crc/define-properties!
ShapeProxy
@ -490,14 +631,18 @@
:get #(-> % u/proxy->shape :flip-y)}
;; Strokes and fills
;; TODO: Validate fills input
{:name "fills"
:get #(if (cfh/text-shape? data)
(-> % u/proxy->shape text-props :fills u/array-to-js)
(-> % u/proxy->shape :fills u/array-to-js))
:set (fn [self value]
(let [id (obj/get self "$id")
(let [shape (u/proxy->shape self)
id (:id shape)
value (mapv #(u/from-js %) value)]
(st/emit! (dwsh/update-shapes [id] #(assoc % :fills value)))))}
(if (cfh/text-shape? shape)
(st/emit! (dwt/update-attrs id {:fills value}))
(st/emit! (dwsh/update-shapes [id] #(assoc % :fills value))))))}
{:name "strokes"
:get #(-> % u/proxy->shape :strokes u/array-to-js)
@ -634,7 +779,7 @@
:set
(fn [self value]
(let [id (obj/get self "$id")]
(st/emit! (dwt/update-attrs id {:font-id value}))))}
(st/emit! (dwt/update-attrs id {:font-size value}))))}
{:name "fontWeight"
:get #(-> % u/proxy->shape text-props :font-weight)

View file

@ -177,9 +177,11 @@
(defn array-to-js
[value]
(.freeze
js/Object
(apply array (->> value (map to-js)))))
(if (coll? value)
(.freeze
js/Object
(apply array (->> value (map to-js))))
value))
(defn result-p
"Creates a pair of atom+promise. The promise will be resolved when the atom gets a value.