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
uses: gsactions/commit-message-checker@v2
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'
error: 'Commit should match CONTRIBUTING.md guideline'
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)
- 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)
- 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)
- 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 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 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 tooltip position after first time [Taiga #11688](https://tree.taiga.io/project/penpot/issue/11688)
## 2.8.1 (Unreleased)

View File

@@ -7,6 +7,7 @@
(ns app.common.logic.shapes
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.geom.shapes :as gsh]
@@ -25,7 +26,7 @@
(defn- generate-unapply-tokens
"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."
[changes objects changed-sub-attr]
(let [new-objects (pcb/get-objects changes)
@@ -35,33 +36,38 @@
text-changed-attrs
(fn [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
attrs (if (set/intersection text-typography-attrs attrs)
(into attrs cto/typography-keys)
attrs)]
attrs (if (seq (set/intersection text-typography-attrs attrs))
(into attrs cto/typography-keys)
attrs)]
(apply set/union (map cto/shape-attr->token-attrs attrs))))
check-attr (fn [shape changes attr]
(let [tokens (get shape :applied-tokens {})
token-attrs (if (or (not= (:type shape) :text) (not= attr :content))
(cto/shape-attr->token-attrs attr changed-sub-attr)
(text-changed-attrs shape))]
(if (some #(contains? tokens %) token-attrs)
(pcb/update-shapes changes [(:id shape)] #(cto/unapply-token-id % token-attrs))
changes)))
check-attr
(fn [shape changes attr]
(let [shape-id (dm/get-prop shape :id)
tokens (get shape :applied-tokens {})
token-attrs (if (and (cfh/text-shape? shape) (= attr :content))
(text-changed-attrs shape)
(cto/shape-attr->token-attrs attr changed-sub-attr))]
check-shape (fn [changes mod-obj-change]
(let [shape (get objects (:id mod-obj-change))
xf (comp (filter #(= (:type %) :set))
(map :attr))
attrs (into [] xf (:operations mod-obj-change))]
(reduce (partial check-attr shape)
changes
attrs)))]
(reduce check-shape
changes
mod-obj-changes)))
(if (some #(contains? tokens %) token-attrs)
(pcb/update-shapes changes [shape-id] #(cto/unapply-token-id % token-attrs))
changes)))
check-shape
(fn [changes mod-obj-change]
(let [shape (get objects (:id mod-obj-change))
attrs (into []
(comp (filter #(= (:type %) :set))
(map :attr))
(:operations mod-obj-change))]
(reduce (partial check-attr shape)
changes
attrs)))]
(reduce check-shape changes mod-obj-changes)))
(defn generate-update-shapes
[changes ids update-fn objects {:keys [attrs changed-sub-attr ignore-tree ignore-touched with-objects?]}]

View File

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

View File

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

View File

@@ -154,12 +154,17 @@
the dom with the result."
[tooltip placement origin-brect offset]
(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)]
(when-let [[placement placement-rect] (find-matching-placement placement tooltip-brect origin-brect window-size offset)]
(let [height (if (or (= placement "right") (= placement "left"))
(- (:height placement-rect) arrow-height)
(: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 "inset-block-start" (dm/str (:top 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)}
[:& recovery-sent-page {:email @user-email}]])]]]))
(mf/defc request-dialog
(mf/defc request-dialog*
{::mf/props :obj}
[{:keys [title content button-text on-button-click cancel-text on-close]}]
(let [on-click (or on-button-click on-close)]
@@ -235,45 +235,45 @@
(cond
is-default
[:& request-dialog {:title (tr "not-found.no-permission.project")
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
[:> request-dialog* {:title (tr "not-found.no-permission.project")
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
(and (some? file-id) (:already-requested requested))
[:& request-dialog {:title (tr "not-found.no-permission.already-requested.file")
:content [(tr "not-found.no-permission.already-requested.or-others.file")]
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
[:> request-dialog* {:title (tr "not-found.no-permission.already-requested.file")
:content [(tr "not-found.no-permission.already-requested.or-others.file")]
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
(:already-requested requested)
[:& request-dialog {:title (tr "not-found.no-permission.already-requested.project")
:content [(tr "not-found.no-permission.already-requested.or-others.project")]
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
[:> request-dialog* {:title (tr "not-found.no-permission.already-requested.project")
:content [(tr "not-found.no-permission.already-requested.or-others.project")]
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
(:sent requested)
[:& request-dialog {:title (tr "not-found.no-permission.done.success")
:content [(tr "not-found.no-permission.done.remember")]
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
[:> request-dialog* {:title (tr "not-found.no-permission.done.success")
:content [(tr "not-found.no-permission.done.remember")]
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
(some? file-id)
[:& request-dialog {:title (tr "not-found.no-permission.file")
:content [(tr "not-found.no-permission.you-can-ask.file")
(tr "not-found.no-permission.if-approves")]
:button-text (tr "not-found.no-permission.ask")
:on-button-click on-request-access
:cancel-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
[:> request-dialog* {:title (tr "not-found.no-permission.file")
:content [(tr "not-found.no-permission.you-can-ask.file")
(tr "not-found.no-permission.if-approves")]
:button-text (tr "not-found.no-permission.ask")
:on-button-click on-request-access
:cancel-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
(some? team-id)
[:& request-dialog {:title (tr "not-found.no-permission.project")
:content [(tr "not-found.no-permission.you-can-ask.project")
(tr "not-found.no-permission.if-approves")]
:button-text (tr "not-found.no-permission.ask")
:on-button-click on-request-access
:cancel-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}])))
[:> request-dialog* {:title (tr "not-found.no-permission.project")
:content [(tr "not-found.no-permission.you-can-ask.project")
(tr "not-found.no-permission.if-approves")]
:button-text (tr "not-found.no-permission.ask")
:on-button-click on-request-access
:cancel-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}])))
(mf/defc not-found*
[]
@@ -535,6 +535,7 @@
[:> request-access* {:file-id (:file-id info)
:team-id (:team-id info)
:is-default (:team-default info)
:profile profile
:is-workspace workspace?}]]
[:> exception-section* props])))))

View File

@@ -10,6 +10,7 @@
["react-virtualized" :as rvt]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.types.text :as txt]
[app.main.constants :refer [max-input-length]]
[app.main.data.common :as dcm]
@@ -40,9 +41,25 @@
""
(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/wrap [mf/memo]
::mf/private true}
{::mf/wrap [mf/memo]}
[{:keys [font is-current on-click style]}]
(let [item-ref (mf/use-ref)
on-click (mf/use-fn (mf/deps font) #(on-click font))]
@@ -66,7 +83,7 @@
(declare row-renderer)
(defn- filter-fonts
(defn filter-fonts
[{:keys [term backends]} fonts]
(let [term (str/lower term)
xform (cond-> (map identity)
@@ -79,7 +96,8 @@
(mf/defc font-selector*
[{: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 #{}}))
state (deref state*)
@@ -94,77 +112,59 @@
recent-fonts (mf/with-memo [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
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))
full-size? (boolean (and full-size show-recent))
select-next
(mf/use-fn
(mf/deps combined-fonts)
(mf/deps fonts)
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
(let [next-idx (mod (inc @selected-index) (count combined-fonts))]
(reset! selected-index next-idx))))
(swap! selected get-next-font fonts)))
select-prev
(mf/use-fn
(mf/deps combined-fonts)
(mf/deps fonts)
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
(let [prev-idx (mod (dec @selected-index) (count combined-fonts))]
(reset! selected-index prev-idx))))
(swap! selected get-prev-font fonts)))
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
(mf/use-fn
(mf/deps on-select on-close)
(fn [font]
(on-select font)
(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)))]
(on-close)))]
(mf/with-effect [fonts]
(let [key (events/listen js/document "keydown" on-key-down)]
#(events/unlistenByKey key)))
(mf/with-effect [@selected-index]
(mf/with-effect [@selected]
(when-let [inst (mf/ref-val flist)]
(when (and (>= @selected-index 0) (< @selected-index (count combined-fonts)))
(.scrollToRow ^js inst @selected-index))))
(when-let [index (:index @selected)]
(.scrollToRow ^js inst index))))
(mf/with-effect [@selected]
(on-select @selected))
(mf/with-effect []
(st/emit! (dsc/push-shortcuts :typography {}))
@@ -172,12 +172,11 @@
(st/emit! (dsc/pop-shortcuts :typography))))
(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)]
(when (and index (>= index 0))
(tm/schedule
#(let [offset (.getOffsetForRow ^js inst #js {:alignment "center" :index index})]
(.scrollToPosition ^js inst offset))))))
(tm/schedule
#(let [offset (.getOffsetForRow ^js inst #js {:alignment "center" :index index})]
(.scrollToPosition ^js inst offset)))))
[:div {:class (stl/css :font-selector)}
[:div {:class (stl/css-case :font-selector-dropdown true :font-selector-dropdown-full-size full-size?)}
@@ -194,7 +193,7 @@
:font font
:style {}
: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
:fonts-list-full-size full-size?)}
@@ -202,17 +201,17 @@
(fn [props]
(let [width (unchecked-get props "width")
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
[:> rvt/List #js {:height height
:ref flist
:width width
:rowCount (count combined-fonts)
:rowCount (count fonts)
:rowHeight 36
:rowRenderer render}])))]]]]))
(defn row-renderer
[fonts selected-index on-select props]
[fonts selected on-select props]
(let [index (unchecked-get props "index")
key (unchecked-get props "key")
style (unchecked-get props "style")
@@ -222,7 +221,7 @@
:font font
:style style
:on-click on-select
:is-current (= index selected-index)}])))
:is-current (= (:id font) (:id selected))}])))
(mf/defc font-options
{::mf/wrap-props false}