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

This commit is contained in:
Andrey Antukh 2026-07-10 11:23:42 +02:00
commit fd8cb957d1
11 changed files with 77 additions and 29 deletions

View File

@ -38,7 +38,7 @@ Include concise sections covering:
PR descriptions follow this structure:
```markdown
**Note:** This PR was created with AI assistance as part of the Penpot MCP self-improvement initiative.
**Note:** This PR was created with AI assistance.
## What

View File

@ -197,10 +197,10 @@ services:
- penpot
environment:
<< : [*penpot-secret-key]
<< : [*penpot-secret-key, *penpot-public-uri]
# Don't touch it; this uses an internal docker network to
# communicate with the frontend.
PENPOT_PUBLIC_URI: http://penpot-frontend:8080
PENPOT_INTERNAL_URI: http://penpot-frontend:8080
## Valkey (or previously Redis) is used for the websockets notifications.
PENPOT_REDIS_URI: redis://penpot-valkey/0

View File

@ -651,6 +651,21 @@ PENPOT_EXPORTER_URI: http://your-penpot-exporter:6061
These variables are used for generate correct nginx.conf file on container startup.
### Exporter
The exporter uses this variable:
```bash
# Exporter
PENPOT_INTERNAL_URI: http://penpot-frontend:8080
```
- `PENPOT_INTERNAL_URI`: The URI used by the exporter's headless browser to
communicate with the frontend (internal Docker network). Defaults to
`PENPOT_PUBLIC_URI` if not set. The default value
`http://penpot-frontend:8080` used in the docker-compose is a good default and
it is recommended to keep it unchanged.
## Other flags
There are other flags that are useful for a more customized Penpot experience. This section has the list of the flags meant

View File

@ -22,6 +22,7 @@
(def ^:private defaults
{:public-uri "http://localhost:3449"
:internal-uri nil
:tenant "default"
:host "localhost"
:http-server-port 6061
@ -33,6 +34,7 @@
[:map {:title "config"}
[:secret-key :string]
[:public-uri {:optional true} ::sm/uri]
[:internal-uri {:optional true} ::sm/uri]
[:exporter-shared-key {:optional true} :string]
[:host {:optional true} :string]
[:tenant {:optional true} :string]
@ -100,6 +102,12 @@
([key default]
(c/get config key default)))
(defn get-internal-uri
"Returns internal-uri if set, otherwise falls back to public-uri."
[]
(or (c/get config :internal-uri)
(c/get config :public-uri)))
(def management-key
(let [key (or (c/get config :exporter-shared-key)
(let [secret-key (c/get config :secret-key)

View File

@ -21,6 +21,7 @@
[& _]
(l/info :msg "initializing"
:public-uri (str (cf/get :public-uri))
:internal-uri (str (cf/get-internal-uri))
:version (:full cf/version))
(p/do!
(bwr/init)

View File

@ -79,7 +79,7 @@
:method "POST"
:body fdata
:dispatcher agent}
uri (-> (cf/get :public-uri)
uri (-> (cf/get-internal-uri)
(u/ensure-path-slash)
(u/join "api/management/methods/upload-tempfile")
(str))]

View File

@ -62,7 +62,7 @@
:skip-children skip-children
:wasm (when is-wasm "true")
:scale scale}
uri (-> (cf/get :public-uri)
uri (-> (cf/get-internal-uri)
(u/ensure-path-slash)
(u/join "render.html")
(assoc :query (u/map->query-string params)))]

View File

@ -77,7 +77,7 @@
(on-object (assoc object :path path))
(p/recur (rest objects))))))]
(let [base-uri (-> (cf/get :public-uri)
(let [base-uri (-> (cf/get-internal-uri)
(u/ensure-path-slash))]
(bw/exec! (prepare-options base-uri)
(partial render base-uri)))))

View File

@ -108,6 +108,18 @@
{:width width
:height height}))
(defn- replace-internal-uris
"Replaces internal-uri references with public-uri in SVG output.
This ensures that font URLs and other resource references in the
exported SVG use the public-facing URI accessible to end users."
[svg-content]
(let [internal-uri (str (cf/get-internal-uri))
public-uri (str (cf/get :public-uri))]
(if (and (not= internal-uri public-uri)
(str/includes? svg-content internal-uri))
(str/replace svg-content internal-uri public-uri)
svg-content)))
(defn render
[{:keys [page-id file-id share-id objects token scale type]} on-object]
(letfn [(convert-to-ppm [pngpath]
@ -320,7 +332,9 @@
result (if (contains? cf/flags :exporter-svgo)
(svgo/optimize result svgo/defaultOptions)
result)]
result)
result (replace-internal-uris result)]
;; (println "------- ORIGIN:")
;; (cljs.pprint/pprint (xml->clj xmldata))
@ -349,7 +363,7 @@
:render-embed true
:object-id (mapv :id objects)
:route "objects"}
uri (-> (cf/get :public-uri)
uri (-> (cf/get-internal-uri)
(u/ensure-path-slash)
(u/join "render.html")
(assoc :query (u/map->query-string params)))]

View File

@ -150,7 +150,7 @@
.separator {
margin: 0;
block-size: $sz-12;
border-block-start: $b-1 solid var(--color-background-primary);
border-block-start: $b-1 solid var(--color-background-quaternary);
}
&[data-direction="up"] {

View File

@ -171,12 +171,20 @@
on-caps-start-change
(mf/use-fn
(mf/deps index on-stroke-cap-start-change)
#(on-stroke-cap-start-change index (keyword %)))
(fn [cap]
(let [cap (if (= cap "none")
nil
(keyword cap))]
(on-stroke-cap-start-change index cap))))
on-caps-end-change
(mf/use-fn
(mf/deps index on-stroke-cap-end-change)
#(on-stroke-cap-end-change index (keyword %)))
(fn [cap]
(let [cap (if (= cap "none")
nil
(keyword cap))]
(on-stroke-cap-end-change index cap))))
on-detach-token-color
(mf/use-fn
@ -191,16 +199,16 @@
(on-detach-token token #{:stroke-width})))
stroke-caps-options
[{:value nil :label (tr "workspace.options.stroke-cap.none")}
:separator
{:value :line-arrow :label (tr "workspace.options.stroke-cap.line-arrow-short") :icon :stroke-arrow}
{:value :triangle-arrow :label (tr "workspace.options.stroke-cap.triangle-arrow-short") :icon :stroke-triangle}
{:value :square-marker :label (tr "workspace.options.stroke-cap.square-marker-short") :icon :stroke-rectangle}
{:value :circle-marker :label (tr "workspace.options.stroke-cap.circle-marker-short") :icon :stroke-circle}
{:value :diamond-marker :label (tr "workspace.options.stroke-cap.diamond-marker-short") :icon :stroke-diamond}
:separator
{:value :round :label (tr "workspace.options.stroke-cap.round") :icon :stroke-rounded}
{:value :square :label (tr "workspace.options.stroke-cap.square") :icon :stroke-squared}]
[{:id "none" :value "none" :label (tr "workspace.options.stroke-cap.none")}
{:label "" :type :separator :id "separator"}
{:id "line-arrow" :value :line-arrow :label (tr "workspace.options.stroke-cap.line-arrow-short") :icon i/stroke-arrow}
{:id "triangle-arrow" :value :triangle-arrow :label (tr "workspace.options.stroke-cap.triangle-arrow-short") :icon i/stroke-triangle}
{:id "square-marker" :value :square-marker :label (tr "workspace.options.stroke-cap.square-marker-short") :icon i/stroke-rectangle}
{:id "circle-marker" :value :circle-marker :label (tr "workspace.options.stroke-cap.circle-marker-short") :icon i/stroke-circle}
{:id "diamond-marker" :value :diamond-marker :label (tr "workspace.options.stroke-cap.diamond-marker-short") :icon i/stroke-diamond}
{:label "" :type :separator :id "separator"}
{:id "round" :value :round :label (tr "workspace.options.stroke-cap.round") :icon i/stroke-rounded}
{:id "square" :value :square :label (tr "workspace.options.stroke-cap.square") :icon i/stroke-squared}]
on-cap-switch
(mf/use-fn
@ -340,16 +348,18 @@
;; Stroke Caps
(when show-caps
[:div {:class (stl/css :stroke-caps-options)}
[:& select {:default-value (:stroke-cap-start stroke)
:options stroke-caps-options
:disabled hidden?
:on-change on-caps-start-change}]
[:> select* {:default-selected (or (d/name (:stroke-cap-start stroke)) "none")
:options stroke-caps-options
:data-testid "stroke.cap-start"
:disabled hidden?
:on-change on-caps-start-change}]
[:> icon-button* {:variant "secondary"
:aria-label (tr "labels.switch")
:disabled hidden?
:on-click on-cap-switch
:icon i/switch}]
[:& select {:default-value (:stroke-cap-end stroke)
:options stroke-caps-options
:disabled hidden?
:on-change on-caps-end-change}]])]))
[:> select* {:default-selected (or (d/name (:stroke-cap-end stroke)) "none")
:options stroke-caps-options
:data-testid "stroke.cap-end"
:disabled hidden?
:on-change on-caps-end-change}]])]))