Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh
2025-07-31 12:22:14 +02:00
8 changed files with 132 additions and 121 deletions

View File

@@ -26,7 +26,7 @@ jobs:
- name: Check Commit Type - name: Check Commit Type
uses: gsactions/commit-message-checker@v2 uses: gsactions/commit-message-checker@v2
with: with:
pattern: '^(Merge|:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle):)\s[A-Z].*[^.]$' pattern: '^(Merge|Revert|:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle):)\s[A-Z].*[^.]$'
flags: 'gm' flags: 'gm'
error: 'Commit should match CONTRIBUTING.md guideline' error: 'Commit should match CONTRIBUTING.md guideline'
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request

View File

@@ -45,7 +45,6 @@
- Hide bounding box while editing visual effects [Taiga #11576](https://tree.taiga.io/project/penpot/issue/11576) - Hide bounding box while editing visual effects [Taiga #11576](https://tree.taiga.io/project/penpot/issue/11576)
- Improved text layer resizing: Allow double-click on text bounding box to set auto-width/auto-height [Taiga #11577](https://tree.taiga.io/project/penpot/issue/11577) - Improved text layer resizing: Allow double-click on text bounding box to set auto-width/auto-height [Taiga #11577](https://tree.taiga.io/project/penpot/issue/11577)
- Improve text layer auto-resize: auto-width switches to auto-height on horizontal resize, and only switches to fixed on vertical resize [Taiga #11578](https://tree.taiga.io/project/penpot/issue/11578) - Improve text layer auto-resize: auto-width switches to auto-height on horizontal resize, and only switches to fixed on vertical resize [Taiga #11578](https://tree.taiga.io/project/penpot/issue/11578)
- Highlight first font in font selector search. Apply only on Enter or click. [Taiga #11579](https://tree.taiga.io/project/penpot/issue/11579)
- Add the ability to show login dialog on profile settings [Github #6871](https://github.com/penpot/penpot/pull/6871) - Add the ability to show login dialog on profile settings [Github #6871](https://github.com/penpot/penpot/pull/6871)
- Improve the application of tokens with object specific tokens [Taiga #10209](https://tree.taiga.io/project/penpot/us/10209) - Improve the application of tokens with object specific tokens [Taiga #10209](https://tree.taiga.io/project/penpot/us/10209)
@@ -71,8 +70,8 @@
- Fix opacity on stroke gradients [Taiga #11646](https://tree.taiga.io/project/penpot/issue/11646) - Fix opacity on stroke gradients [Taiga #11646](https://tree.taiga.io/project/penpot/issue/11646)
- Fix change from gradient to solid color [Taiga #11648](https://tree.taiga.io/project/penpot/issue/11648) - Fix change from gradient to solid color [Taiga #11648](https://tree.taiga.io/project/penpot/issue/11648)
- Fix the context menu always closes after any action [Taiga #11624](https://tree.taiga.io/project/penpot/issue/11624) - Fix the context menu always closes after any action [Taiga #11624](https://tree.taiga.io/project/penpot/issue/11624)
- Fix font selector highlight inconsistency when using keyboard navigation [Taiga #11668](https://tree.taiga.io/project/penpot/issue/11668)
- Fix X & Y position do not sincronize with tokens [Taiga #11617](https://tree.taiga.io/project/penpot/issue/11617) - Fix X & Y position do not sincronize with tokens [Taiga #11617](https://tree.taiga.io/project/penpot/issue/11617)
- Fix tooltip position after first time [Taiga #11688](https://tree.taiga.io/project/penpot/issue/11688)
## 2.8.1 (Unreleased) ## 2.8.1 (Unreleased)

View File

@@ -7,6 +7,7 @@
(ns app.common.logic.shapes (ns app.common.logic.shapes
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
@@ -25,7 +26,7 @@
(defn- generate-unapply-tokens (defn- generate-unapply-tokens
"When updating attributes that have a token applied, we must unapply it, because the value "When updating attributes that have a token applied, we must unapply it, because the value
of the attribute now has been given directly, and does not come from the token. of the attribute now has been given directly, and does not come from the token.
When applying a typography asset style we also unapply any typographic tokens." When applying a typography asset style we also unapply any typographic tokens."
[changes objects changed-sub-attr] [changes objects changed-sub-attr]
(let [new-objects (pcb/get-objects changes) (let [new-objects (pcb/get-objects changes)
@@ -35,33 +36,38 @@
text-changed-attrs text-changed-attrs
(fn [shape] (fn [shape]
(let [new-shape (get new-objects (:id shape)) (let [new-shape (get new-objects (:id shape))
attrs (ctt/get-diff-attrs (:content shape) (:content new-shape)) attrs (ctt/get-diff-attrs (:content shape) (:content new-shape))
;; Unapply token when applying typography asset style ;; Unapply token when applying typography asset style
attrs (if (set/intersection text-typography-attrs attrs) attrs (if (seq (set/intersection text-typography-attrs attrs))
(into attrs cto/typography-keys) (into attrs cto/typography-keys)
attrs)] attrs)]
(apply set/union (map cto/shape-attr->token-attrs attrs)))) (apply set/union (map cto/shape-attr->token-attrs attrs))))
check-attr (fn [shape changes attr] check-attr
(let [tokens (get shape :applied-tokens {}) (fn [shape changes attr]
token-attrs (if (or (not= (:type shape) :text) (not= attr :content)) (let [shape-id (dm/get-prop shape :id)
(cto/shape-attr->token-attrs attr changed-sub-attr) tokens (get shape :applied-tokens {})
(text-changed-attrs shape))] token-attrs (if (and (cfh/text-shape? shape) (= attr :content))
(if (some #(contains? tokens %) token-attrs) (text-changed-attrs shape)
(pcb/update-shapes changes [(:id shape)] #(cto/unapply-token-id % token-attrs)) (cto/shape-attr->token-attrs attr changed-sub-attr))]
changes)))
check-shape (fn [changes mod-obj-change] (if (some #(contains? tokens %) token-attrs)
(let [shape (get objects (:id mod-obj-change)) (pcb/update-shapes changes [shape-id] #(cto/unapply-token-id % token-attrs))
xf (comp (filter #(= (:type %) :set)) changes)))
(map :attr))
attrs (into [] xf (:operations mod-obj-change))] check-shape
(reduce (partial check-attr shape) (fn [changes mod-obj-change]
changes (let [shape (get objects (:id mod-obj-change))
attrs)))] attrs (into []
(reduce check-shape (comp (filter #(= (:type %) :set))
changes (map :attr))
mod-obj-changes))) (:operations mod-obj-change))]
(reduce (partial check-attr shape)
changes
attrs)))]
(reduce check-shape changes mod-obj-changes)))
(defn generate-update-shapes (defn generate-update-shapes
[changes ids update-fn objects {:keys [attrs changed-sub-attr ignore-tree ignore-touched with-objects?]}] [changes ids update-fn objects {:keys [attrs changed-sub-attr ignore-tree ignore-touched with-objects?]}]

View File

@@ -122,7 +122,7 @@
(fix-gradients) (fix-gradients)
(assoc :text text)))) (assoc :text text))))
(split-texts [text styles] (split-texts [text styles data]
(let [cpoints (text->code-points text) (let [cpoints (text->code-points text)
children (->> (parse-draft-styles styles) children (->> (parse-draft-styles styles)
(build-style-index (count cpoints)) (build-style-index (count cpoints))
@@ -131,7 +131,7 @@
(mapv #(extract-text cpoints %)))] (mapv #(extract-text cpoints %)))]
(cond-> children (cond-> children
(empty? children) (empty? children)
(conj {:text ""})))) (conj (assoc data :text "")))))
(build-paragraph [block] (build-paragraph [block]
(let [key (get block :key) (let [key (get block :key)
@@ -142,7 +142,7 @@
(-> data (-> data
(assoc :key key) (assoc :key key)
(assoc :type "paragraph") (assoc :type "paragraph")
(assoc :children (split-texts text styles)))))] (assoc :children (split-texts text styles data)))))]
{:type "root" {:type "root"
:children :children

View File

@@ -501,6 +501,7 @@
ctt/spacing-keys ctt/spacing-keys
ctt/sizing-keys ctt/sizing-keys
ctt/border-radius-keys ctt/border-radius-keys
ctt/axis-keys
ctt/stroke-width-keys) ctt/stroke-width-keys)
:on-update-shape update-shape-dimensions :on-update-shape update-shape-dimensions
:modal {:key :tokens/dimensions :modal {:key :tokens/dimensions

View File

@@ -154,12 +154,17 @@
the dom with the result." the dom with the result."
[tooltip placement origin-brect offset] [tooltip placement origin-brect offset]
(show-popover tooltip) (show-popover tooltip)
(let [tooltip-brect (dom/get-bounding-rect tooltip) (let [saved-height (dom/get-data tooltip "height")
saved-width (dom/get-data tooltip "width")
tooltip-brect (dom/get-bounding-rect tooltip)
tooltip-brect (assoc tooltip-brect :height (or saved-height (:height tooltip-brect)) :width (or saved-width (:width tooltip-brect)))
window-size (dom/get-window-size)] window-size (dom/get-window-size)]
(when-let [[placement placement-rect] (find-matching-placement placement tooltip-brect origin-brect window-size offset)] (when-let [[placement placement-rect] (find-matching-placement placement tooltip-brect origin-brect window-size offset)]
(let [height (if (or (= placement "right") (= placement "left")) (let [height (if (or (= placement "right") (= placement "left"))
(- (:height placement-rect) arrow-height) (- (:height placement-rect) arrow-height)
(:height placement-rect))] (:height placement-rect))]
(dom/set-data! tooltip "height" (:height tooltip-brect))
(dom/set-data! tooltip "width" (:width tooltip-brect))
(dom/set-css-property! tooltip "block-size" (dm/str height "px")) (dom/set-css-property! tooltip "block-size" (dm/str height "px"))
(dom/set-css-property! tooltip "inset-block-start" (dm/str (:top placement-rect) "px")) (dom/set-css-property! tooltip "inset-block-start" (dm/str (:top placement-rect) "px"))
(dom/set-css-property! tooltip "inset-inline-start" (dm/str (:left placement-rect) "px"))) (dom/set-css-property! tooltip "inset-inline-start" (dm/str (:left placement-rect) "px")))

View File

@@ -181,7 +181,7 @@
[:div {:class (stl/css :form-container)} [:div {:class (stl/css :form-container)}
[:& recovery-sent-page {:email @user-email}]])]]])) [:& recovery-sent-page {:email @user-email}]])]]]))
(mf/defc request-dialog (mf/defc request-dialog*
{::mf/props :obj} {::mf/props :obj}
[{:keys [title content button-text on-button-click cancel-text on-close]}] [{:keys [title content button-text on-button-click cancel-text on-close]}]
(let [on-click (or on-button-click on-close)] (let [on-click (or on-button-click on-close)]
@@ -235,45 +235,45 @@
(cond (cond
is-default is-default
[:& request-dialog {:title (tr "not-found.no-permission.project") [:> request-dialog* {:title (tr "not-found.no-permission.project")
:button-text (tr "not-found.no-permission.go-dashboard") :button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}] :on-close on-close}]
(and (some? file-id) (:already-requested requested)) (and (some? file-id) (:already-requested requested))
[:& request-dialog {:title (tr "not-found.no-permission.already-requested.file") [:> request-dialog* {:title (tr "not-found.no-permission.already-requested.file")
:content [(tr "not-found.no-permission.already-requested.or-others.file")] :content [(tr "not-found.no-permission.already-requested.or-others.file")]
:button-text (tr "not-found.no-permission.go-dashboard") :button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}] :on-close on-close}]
(:already-requested requested) (:already-requested requested)
[:& request-dialog {:title (tr "not-found.no-permission.already-requested.project") [:> request-dialog* {:title (tr "not-found.no-permission.already-requested.project")
:content [(tr "not-found.no-permission.already-requested.or-others.project")] :content [(tr "not-found.no-permission.already-requested.or-others.project")]
:button-text (tr "not-found.no-permission.go-dashboard") :button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}] :on-close on-close}]
(:sent requested) (:sent requested)
[:& request-dialog {:title (tr "not-found.no-permission.done.success") [:> request-dialog* {:title (tr "not-found.no-permission.done.success")
:content [(tr "not-found.no-permission.done.remember")] :content [(tr "not-found.no-permission.done.remember")]
:button-text (tr "not-found.no-permission.go-dashboard") :button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}] :on-close on-close}]
(some? file-id) (some? file-id)
[:& request-dialog {:title (tr "not-found.no-permission.file") [:> request-dialog* {:title (tr "not-found.no-permission.file")
:content [(tr "not-found.no-permission.you-can-ask.file") :content [(tr "not-found.no-permission.you-can-ask.file")
(tr "not-found.no-permission.if-approves")] (tr "not-found.no-permission.if-approves")]
:button-text (tr "not-found.no-permission.ask") :button-text (tr "not-found.no-permission.ask")
:on-button-click on-request-access :on-button-click on-request-access
:cancel-text (tr "not-found.no-permission.go-dashboard") :cancel-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}] :on-close on-close}]
(some? team-id) (some? team-id)
[:& request-dialog {:title (tr "not-found.no-permission.project") [:> request-dialog* {:title (tr "not-found.no-permission.project")
:content [(tr "not-found.no-permission.you-can-ask.project") :content [(tr "not-found.no-permission.you-can-ask.project")
(tr "not-found.no-permission.if-approves")] (tr "not-found.no-permission.if-approves")]
:button-text (tr "not-found.no-permission.ask") :button-text (tr "not-found.no-permission.ask")
:on-button-click on-request-access :on-button-click on-request-access
:cancel-text (tr "not-found.no-permission.go-dashboard") :cancel-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]))) :on-close on-close}])))
(mf/defc not-found* (mf/defc not-found*
[] []
@@ -535,6 +535,7 @@
[:> request-access* {:file-id (:file-id info) [:> request-access* {:file-id (:file-id info)
:team-id (:team-id info) :team-id (:team-id info)
:is-default (:team-default info) :is-default (:team-default info)
:profile profile
:is-workspace workspace?}]] :is-workspace workspace?}]]
[:> exception-section* props]))))) [:> exception-section* props])))))

View File

@@ -10,6 +10,7 @@
["react-virtualized" :as rvt] ["react-virtualized" :as rvt]
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.types.text :as txt] [app.common.types.text :as txt]
[app.main.constants :refer [max-input-length]] [app.main.constants :refer [max-input-length]]
[app.main.data.common :as dcm] [app.main.data.common :as dcm]
@@ -40,9 +41,25 @@
"" ""
(ust/format-precision value 2))) (ust/format-precision value 2)))
(defn- get-next-font
[{:keys [id] :as current} fonts]
(if (seq fonts)
(let [index (d/index-of-pred fonts #(= (:id %) id))
index (or index -1)
next (ex/ignoring (nth fonts (inc index)))]
(or next (first fonts)))
current))
(defn- get-prev-font
[{:keys [id] :as current} fonts]
(if (seq fonts)
(let [index (d/index-of-pred fonts #(= (:id %) id))
next (ex/ignoring (nth fonts (dec index)))]
(or next (peek fonts)))
current))
(mf/defc font-item* (mf/defc font-item*
{::mf/wrap [mf/memo] {::mf/wrap [mf/memo]}
::mf/private true}
[{:keys [font is-current on-click style]}] [{:keys [font is-current on-click style]}]
(let [item-ref (mf/use-ref) (let [item-ref (mf/use-ref)
on-click (mf/use-fn (mf/deps font) #(on-click font))] on-click (mf/use-fn (mf/deps font) #(on-click font))]
@@ -66,7 +83,7 @@
(declare row-renderer) (declare row-renderer)
(defn- filter-fonts (defn filter-fonts
[{:keys [term backends]} fonts] [{:keys [term backends]} fonts]
(let [term (str/lower term) (let [term (str/lower term)
xform (cond-> (map identity) xform (cond-> (map identity)
@@ -79,7 +96,8 @@
(mf/defc font-selector* (mf/defc font-selector*
[{:keys [on-select on-close current-font show-recent full-size]}] [{:keys [on-select on-close current-font show-recent full-size]}]
(let [state* (mf/use-state (let [selected (mf/use-state current-font)
state* (mf/use-state
#(do {:term "" :backends #{}})) #(do {:term "" :backends #{}}))
state (deref state*) state (deref state*)
@@ -94,77 +112,59 @@
recent-fonts (mf/with-memo [state recent-fonts] recent-fonts (mf/with-memo [state recent-fonts]
(filter-fonts state recent-fonts)) (filter-fonts state recent-fonts))
;; Combine recent fonts with filtered fonts, avoiding duplicates
combined-fonts
(mf/with-memo [recent-fonts fonts]
(let [recent-ids (into #{} d/xf:map-id recent-fonts)]
(into recent-fonts (remove #(contains? recent-ids (:id %))) fonts)))
;; Initialize selected with current font index full-size? (boolean (and full-size show-recent))
selected-index
(mf/use-state
(fn []
(or (some (fn [[idx font]]
(when (= (:id current-font) (:id font)) idx))
(map-indexed vector combined-fonts))
0)))
full-size?
(boolean (and full-size show-recent))
select-next select-next
(mf/use-fn (mf/use-fn
(mf/deps combined-fonts) (mf/deps fonts)
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(dom/prevent-default event) (dom/prevent-default event)
(let [next-idx (mod (inc @selected-index) (count combined-fonts))] (swap! selected get-next-font fonts)))
(reset! selected-index next-idx))))
select-prev select-prev
(mf/use-fn (mf/use-fn
(mf/deps combined-fonts) (mf/deps fonts)
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(dom/prevent-default event) (dom/prevent-default event)
(let [prev-idx (mod (dec @selected-index) (count combined-fonts))] (swap! selected get-prev-font fonts)))
(reset! selected-index prev-idx))))
on-key-down
(mf/use-fn
(mf/deps fonts)
(fn [event]
(cond
(kbd/up-arrow? event) (select-prev event)
(kbd/down-arrow? event) (select-next event)
(kbd/esc? event) (on-close)
(kbd/enter? event) (on-close)
:else (dom/focus! (mf/ref-val input)))))
on-filter-change
(mf/use-fn
(fn [event]
(swap! state* assoc :term event)))
on-select-and-close on-select-and-close
(mf/use-fn (mf/use-fn
(mf/deps on-select on-close) (mf/deps on-select on-close)
(fn [font] (fn [font]
(on-select font) (on-select font)
(on-close))) (on-close)))]
on-key-down
(mf/use-fn
(mf/deps combined-fonts)
(fn [event]
(cond
(kbd/up-arrow? event) (select-prev event)
(kbd/down-arrow? event) (select-next event)
(kbd/esc? event) (on-close)
(kbd/enter? event) (do
(let [selected-font (nth combined-fonts @selected-index)]
(on-select-and-close selected-font)))
:else (dom/focus! (mf/ref-val input)))))
on-filter-change
(mf/use-fn
(fn [event]
(swap! state* assoc :term event)
;; Reset selection to first item when filter changes
(reset! selected-index 0)))]
(mf/with-effect [fonts] (mf/with-effect [fonts]
(let [key (events/listen js/document "keydown" on-key-down)] (let [key (events/listen js/document "keydown" on-key-down)]
#(events/unlistenByKey key))) #(events/unlistenByKey key)))
(mf/with-effect [@selected-index] (mf/with-effect [@selected]
(when-let [inst (mf/ref-val flist)] (when-let [inst (mf/ref-val flist)]
(when (and (>= @selected-index 0) (< @selected-index (count combined-fonts))) (when-let [index (:index @selected)]
(.scrollToRow ^js inst @selected-index)))) (.scrollToRow ^js inst index))))
(mf/with-effect [@selected]
(on-select @selected))
(mf/with-effect [] (mf/with-effect []
(st/emit! (dsc/push-shortcuts :typography {})) (st/emit! (dsc/push-shortcuts :typography {}))
@@ -172,12 +172,11 @@
(st/emit! (dsc/pop-shortcuts :typography)))) (st/emit! (dsc/pop-shortcuts :typography))))
(mf/with-effect [] (mf/with-effect []
(let [index (d/index-of-pred combined-fonts #(= (:id %) (:id current-font))) (let [index (d/index-of-pred fonts #(= (:id %) (:id current-font)))
inst (mf/ref-val flist)] inst (mf/ref-val flist)]
(when (and index (>= index 0)) (tm/schedule
(tm/schedule #(let [offset (.getOffsetForRow ^js inst #js {:alignment "center" :index index})]
#(let [offset (.getOffsetForRow ^js inst #js {:alignment "center" :index index})] (.scrollToPosition ^js inst offset)))))
(.scrollToPosition ^js inst offset))))))
[:div {:class (stl/css :font-selector)} [:div {:class (stl/css :font-selector)}
[:div {:class (stl/css-case :font-selector-dropdown true :font-selector-dropdown-full-size full-size?)} [:div {:class (stl/css-case :font-selector-dropdown true :font-selector-dropdown-full-size full-size?)}
@@ -194,7 +193,7 @@
:font font :font font
:style {} :style {}
:on-click on-select-and-close :on-click on-select-and-close
:is-current (= idx @selected-index)}])])] :is-current (= (:id font) (:id @selected))}])])]
[:div {:class (stl/css-case :fonts-list true [:div {:class (stl/css-case :fonts-list true
:fonts-list-full-size full-size?)} :fonts-list-full-size full-size?)}
@@ -202,17 +201,17 @@
(fn [props] (fn [props]
(let [width (unchecked-get props "width") (let [width (unchecked-get props "width")
height (unchecked-get props "height") height (unchecked-get props "height")
render #(row-renderer combined-fonts @selected-index on-select-and-close %)] render #(row-renderer fonts @selected on-select-and-close %)]
(mf/html (mf/html
[:> rvt/List #js {:height height [:> rvt/List #js {:height height
:ref flist :ref flist
:width width :width width
:rowCount (count combined-fonts) :rowCount (count fonts)
:rowHeight 36 :rowHeight 36
:rowRenderer render}])))]]]])) :rowRenderer render}])))]]]]))
(defn row-renderer (defn row-renderer
[fonts selected-index on-select props] [fonts selected on-select props]
(let [index (unchecked-get props "index") (let [index (unchecked-get props "index")
key (unchecked-get props "key") key (unchecked-get props "key")
style (unchecked-get props "style") style (unchecked-get props "style")
@@ -222,7 +221,7 @@
:font font :font font
:style style :style style
:on-click on-select :on-click on-select
:is-current (= index selected-index)}]))) :is-current (= (:id font) (:id selected))}])))
(mf/defc font-options (mf/defc font-options
{::mf/wrap-props false} {::mf/wrap-props false}