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: PR descriptions follow this structure:
```markdown ```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 ## What

View File

@ -197,10 +197,10 @@ services:
- penpot - penpot
environment: environment:
<< : [*penpot-secret-key] << : [*penpot-secret-key, *penpot-public-uri]
# Don't touch it; this uses an internal docker network to # Don't touch it; this uses an internal docker network to
# communicate with the frontend. # 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. ## Valkey (or previously Redis) is used for the websockets notifications.
PENPOT_REDIS_URI: redis://penpot-valkey/0 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. 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 ## Other flags
There are other flags that are useful for a more customized Penpot experience. This section has the list of the flags meant 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 (def ^:private defaults
{:public-uri "http://localhost:3449" {:public-uri "http://localhost:3449"
:internal-uri nil
:tenant "default" :tenant "default"
:host "localhost" :host "localhost"
:http-server-port 6061 :http-server-port 6061
@ -33,6 +34,7 @@
[:map {:title "config"} [:map {:title "config"}
[:secret-key :string] [:secret-key :string]
[:public-uri {:optional true} ::sm/uri] [:public-uri {:optional true} ::sm/uri]
[:internal-uri {:optional true} ::sm/uri]
[:exporter-shared-key {:optional true} :string] [:exporter-shared-key {:optional true} :string]
[:host {:optional true} :string] [:host {:optional true} :string]
[:tenant {:optional true} :string] [:tenant {:optional true} :string]
@ -100,6 +102,12 @@
([key default] ([key default]
(c/get config 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 (def management-key
(let [key (or (c/get config :exporter-shared-key) (let [key (or (c/get config :exporter-shared-key)
(let [secret-key (c/get config :secret-key) (let [secret-key (c/get config :secret-key)

View File

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

View File

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

View File

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

View File

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

View File

@ -108,6 +108,18 @@
{:width width {:width width
:height height})) :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 (defn render
[{:keys [page-id file-id share-id objects token scale type]} on-object] [{:keys [page-id file-id share-id objects token scale type]} on-object]
(letfn [(convert-to-ppm [pngpath] (letfn [(convert-to-ppm [pngpath]
@ -320,7 +332,9 @@
result (if (contains? cf/flags :exporter-svgo) result (if (contains? cf/flags :exporter-svgo)
(svgo/optimize result svgo/defaultOptions) (svgo/optimize result svgo/defaultOptions)
result)] result)
result (replace-internal-uris result)]
;; (println "------- ORIGIN:") ;; (println "------- ORIGIN:")
;; (cljs.pprint/pprint (xml->clj xmldata)) ;; (cljs.pprint/pprint (xml->clj xmldata))
@ -349,7 +363,7 @@
:render-embed true :render-embed true
:object-id (mapv :id objects) :object-id (mapv :id objects)
:route "objects"} :route "objects"}
uri (-> (cf/get :public-uri) uri (-> (cf/get-internal-uri)
(u/ensure-path-slash) (u/ensure-path-slash)
(u/join "render.html") (u/join "render.html")
(assoc :query (u/map->query-string params)))] (assoc :query (u/map->query-string params)))]

View File

@ -150,7 +150,7 @@
.separator { .separator {
margin: 0; margin: 0;
block-size: $sz-12; 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"] { &[data-direction="up"] {

View File

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