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:
parent
018464aedf
commit
2da5dcb619
5 changed files with 285 additions and 14 deletions
|
@ -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]
|
||||
|
|
|
@ -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])]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Reference in a new issue