Merge tag '2.7.2-RC1'

This commit is contained in:
Andrey Antukh 2025-06-03 10:43:14 +02:00
commit f4ef4a705c
33 changed files with 305 additions and 189 deletions

View File

@ -1,6 +1,22 @@
# CHANGELOG # CHANGELOG
## 2.7.0 (Unreleased)
## 2.7.2 (Unreleased)
### :bug: Bugs fixed
- Update plugins runtime [Github #6604](https://github.com/penpot/penpot/pull/6604)
## 2.7.1
### :bug: Bugs fixed
- Fix incorrect handling of strokes with images on importing files
- Fix tokens disappearing after manual additions [Taiga #11063](https://tree.taiga.io/project/penpot/issue/11063)
## 2.7.0
### :rocket: Epics and highlights ### :rocket: Epics and highlights
@ -21,6 +37,7 @@
### :bug: Bugs fixed ### :bug: Bugs fixed
- Fix "at" icon to match all icons on app [Taiga #11136](https://tree.taiga.io/project/penpot/issue/11136)
- Fix problem in viewer with the back button [Taiga #10907](https://tree.taiga.io/project/penpot/issue/10907) - Fix problem in viewer with the back button [Taiga #10907](https://tree.taiga.io/project/penpot/issue/10907)
- Fix resize bar background on tokens panel [Taiga #10811](https://tree.taiga.io/project/penpot/issue/10811) - Fix resize bar background on tokens panel [Taiga #10811](https://tree.taiga.io/project/penpot/issue/10811)
- Fix shortcut for history version panel [Taiga #11006](https://tree.taiga.io/project/penpot/issue/11006) - Fix shortcut for history version panel [Taiga #11006](https://tree.taiga.io/project/penpot/issue/11006)
@ -47,6 +64,13 @@
- Fix cannot rename Design Token Sets when group of same name exists [Taiga Issue #10773](https://tree.taiga.io/project/penpot/issue/10773) - Fix cannot rename Design Token Sets when group of same name exists [Taiga Issue #10773](https://tree.taiga.io/project/penpot/issue/10773)
- Fix problem when duplicating grid layout [Github #6391](https://github.com/penpot/penpot/issues/6391) - Fix problem when duplicating grid layout [Github #6391](https://github.com/penpot/penpot/issues/6391)
- Fix issue that makes workspace shortcuts stop working [Taiga #11062](https://tree.taiga.io/project/penpot/issue/11062) - Fix issue that makes workspace shortcuts stop working [Taiga #11062](https://tree.taiga.io/project/penpot/issue/11062)
- Fix problem while syncing library colors and typographies [Taiga #11068](https://tree.taiga.io/project/penpot/issue/11068)
- Fix problem with path edition of shapes [Taiga #9496](https://tree.taiga.io/project/penpot/issue/9496)
- Fix exception on paste invalid html [Taiga #11047](https://tree.taiga.io/project/penpot/issue/11047)
- Fix share button being displayed with no permissions [Taiga #11086](https://tree.taiga.io/project/penpot/issue/11086)
- Fix inline styles in code tab [Taiga Issue #7583](https://tree.taiga.io/project/penpot/issue/7583)
- Fix exception on returning openapi.json
- Fix json encoding of TokensLib [Taiga #10994](https://tree.taiga.io/project/penpot/issue/10994)
## 2.6.2 ## 2.6.2

View File

@ -9,7 +9,6 @@
(:refer-clojure :exclude [tap]) (:refer-clojure :exclude [tap])
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.logging :as l] [app.common.logging :as l]
[app.common.transit :as t] [app.common.transit :as t]
[app.http.errors :as errors] [app.http.errors :as errors]
@ -54,18 +53,20 @@
::yres/status 200 ::yres/status 200
::yres/body (yres/stream-body ::yres/body (yres/stream-body
(fn [_ output] (fn [_ output]
(binding [events/*channel* (sp/chan :buf buf :xf (keep encode))] (let [channel (sp/chan :buf buf :xf (keep encode))
(let [listener (events/start-listener listener (events/start-listener
(partial write! output) channel
(partial pu/close! output))] (partial write! output)
(try (partial pu/close! output))]
(try
(binding [events/*channel* channel]
(let [result (handler)] (let [result (handler)]
(events/tap :end result)) (events/tap :end result)))
(catch Throwable cause
(events/tap :error (errors/handle' cause request)) (catch Throwable cause
(when-not (ex/instance? java.io.EOFException cause) (let [result (errors/handle' cause request)]
(binding [l/*context* (errors/request->context request)] (events/tap channel :error result)))
(l/err :hint "unexpected error on processing sse response" :cause cause))))
(finally (finally
(sp/close! events/*channel*) (sp/close! channel)
(px/await! listener)))))))})) (px/await! listener))))))}))

View File

@ -92,9 +92,9 @@
[:string {:max 250}] [:string {:max 250}]
[::sm/one-of {:format "string"} valid-event-types]]] [::sm/one-of {:format "string"} valid-event-types]]]
[:props [:props
[:map-of :keyword :any]] [:map-of :keyword ::sm/any]]
[:context {:optional true} [:context {:optional true}
[:map-of :keyword :any]]]) [:map-of :keyword ::sm/any]]])
(def schema:push-audit-events (def schema:push-audit-events
[:map {:title "push-audit-events"} [:map {:title "push-audit-events"}

View File

@ -115,7 +115,8 @@
(db/update! pool :project (db/update! pool :project
{:modified-at (dt/now)} {:modified-at (dt/now)}
{:id project-id}) {:id project-id}
{::db/return-keys false})
result)) result))

View File

@ -189,7 +189,7 @@
[:is-shared ::sm/boolean] [:is-shared ::sm/boolean]
[:project-id ::sm/uuid] [:project-id ::sm/uuid]
[:created-at ::dt/instant] [:created-at ::dt/instant]
[:data {:optional true} :any]]) [:data {:optional true} ::sm/any]])
(def schema:permissions-mixin (def schema:permissions-mixin
[:map {:title "PermissionsMixin"} [:map {:title "PermissionsMixin"}

View File

@ -80,9 +80,9 @@
(def ^:private schema:create-font-variant (def ^:private schema:create-font-variant
[:map {:title "create-font-variant"} [:map {:title "create-font-variant"}
[:team-id ::sm/uuid] [:team-id ::sm/uuid]
[:data [:map-of :string :any]] [:data [:map-of ::sm/text ::sm/any]]
[:font-id ::sm/uuid] [:font-id ::sm/uuid]
[:font-family :string] [:font-family ::sm/text]
[:font-weight [::sm/one-of {:format "number"} valid-weight]] [:font-weight [::sm/one-of {:format "number"} valid-weight]]
[:font-style [::sm/one-of {:format "string"} valid-style]]]) [:font-style [::sm/one-of {:format "string"} valid-style]]])

View File

@ -9,6 +9,7 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.json :as json]
[app.common.pprint :as pp] [app.common.pprint :as pp]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.schema.desc-js-like :as smdj] [app.common.schema.desc-js-like :as smdj]
@ -19,7 +20,6 @@
[app.http.sse :as-alias sse] [app.http.sse :as-alias sse]
[app.loggers.webhooks :as-alias webhooks] [app.loggers.webhooks :as-alias webhooks]
[app.rpc :as-alias rpc] [app.rpc :as-alias rpc]
[app.util.json :as json]
[app.util.services :as sv] [app.util.services :as sv]
[app.util.template :as tmpl] [app.util.template :as tmpl]
[clojure.java.io :as io] [clojure.java.io :as io]
@ -86,7 +86,7 @@
(fn [request] (fn [request]
(let [params (:query-params request) (let [params (:query-params request)
pstyle (:type params "js") pstyle (:type params "js")
context (assoc context :param-style pstyle)] context (assoc @context :param-style pstyle)]
{::yres/status 200 {::yres/status 200
::yres/body (-> (io/resource "app/templates/api-doc.tmpl") ::yres/body (-> (io/resource "app/templates/api-doc.tmpl")
@ -178,8 +178,7 @@
(fn [_] (fn [_]
{::yres/status 200 {::yres/status 200
::yres/headers {"content-type" "application/json; charset=utf-8"} ::yres/headers {"content-type" "application/json; charset=utf-8"}
::yres/body (json/encode context)}) ::yres/body (json/encode @context)})
(fn [_] (fn [_]
{::yres/status 404}))) {::yres/status 404})))
@ -209,7 +208,7 @@
(defmethod ig/init-key ::routes (defmethod ig/init-key ::routes
[_ {:keys [::rpc/methods] :as cfg}] [_ {:keys [::rpc/methods] :as cfg}]
[(let [context (prepare-doc-context methods)] [(let [context (delay (prepare-doc-context methods))]
[["/_doc" [["/_doc"
{:handler (doc-handler context) {:handler (doc-handler context)
:allowed-methods #{:get}}] :allowed-methods #{:get}}]
@ -217,7 +216,7 @@
{:handler (doc-handler context) {:handler (doc-handler context)
:allowed-methods #{:get}}]]) :allowed-methods #{:get}}]])
(let [context (prepare-openapi-context methods)] (let [context (delay (prepare-openapi-context methods))]
[["/openapi" [["/openapi"
{:handler (openapi-handler) {:handler (openapi-handler)
:allowed-methods #{:get}}] :allowed-methods #{:get}}]

View File

@ -10,7 +10,6 @@
to them. Mainly used in http.sse for progress reporting." to them. Mainly used in http.sse for progress reporting."
(:refer-clojure :exclude [tap run!]) (:refer-clojure :exclude [tap run!])
(:require (:require
[app.common.data.macros :as dm]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.logging :as l] [app.common.logging :as l]
[promesa.exec :as px] [promesa.exec :as px]
@ -18,33 +17,30 @@
(def ^:dynamic *channel* nil) (def ^:dynamic *channel* nil)
(defn channel
[]
(sp/chan :buf 32))
(defn tap (defn tap
[type data] ([type data]
(when-let [channel *channel*] (when-let [channel *channel*]
(sp/put! channel [type data]) (sp/put! channel [type data])
nil)) nil))
([channel type data]
(when channel
(sp/put! channel [type data])
nil)))
(defn start-listener (defn start-listener
[on-event on-close] [channel on-event on-close]
(assert (sp/chan? channel) "expected active events channel")
(dm/assert!
"expected active events channel"
(sp/chan? *channel*))
(px/thread (px/thread
{:virtual true} {:virtual true}
(try (try
(loop [] (loop []
(when-let [event (sp/take! *channel*)] (when-let [event (sp/take! channel)]
(let [result (ex/try! (on-event event))] (let [result (ex/try! (on-event event))]
(if (ex/exception? result) (if (ex/exception? result)
(do (do
(l/wrn :hint "unexpected exception" :cause result) (l/wrn :hint "unexpected exception" :cause result)
(sp/close! *channel*)) (sp/close! channel))
(recur))))) (recur)))))
(finally (finally
(on-close))))) (on-close)))))
@ -55,7 +51,7 @@
[f on-event] [f on-event]
(binding [*channel* (sp/chan :buf 32)] (binding [*channel* (sp/chan :buf 32)]
(let [listener (start-listener on-event (constantly nil))] (let [listener (start-listener *channel* on-event (constantly nil))]
(try (try
(f) (f)
(finally (finally

View File

@ -28,7 +28,7 @@
integrant/integrant {:mvn/version "0.13.1"} integrant/integrant {:mvn/version "0.13.1"}
funcool/tubax {:mvn/version "2021.05.20-0"} funcool/tubax {:mvn/version "2021.05.20-0"}
funcool/cuerdas {:mvn/version "2023.11.09-407"} funcool/cuerdas {:mvn/version "2025.05.26-411"}
funcool/promesa funcool/promesa
{:git/sha "0c5ed6ad033515a2df4b55addea044f60e9653d0" {:git/sha "0c5ed6ad033515a2df4b55addea044f60e9653d0"
:git/url "https://github.com/funcool/promesa"} :git/url "https://github.com/funcool/promesa"}

View File

@ -47,14 +47,14 @@
[:type [:= :assign]] [:type [:= :assign]]
;; NOTE: the full decoding is happening on the handler because it ;; NOTE: the full decoding is happening on the handler because it
;; needs a proper context of the current shape and its type ;; needs a proper context of the current shape and its type
[:value [:map-of :keyword :any]] [:value [:map-of :keyword ::sm/any]]
[:ignore-touched {:optional true} :boolean] [:ignore-touched {:optional true} :boolean]
[:ignore-geometry {:optional true} :boolean]]] [:ignore-geometry {:optional true} :boolean]]]
[:set [:set
[:map {:title "SetOperation"} [:map {:title "SetOperation"}
[:type [:= :set]] [:type [:= :set]]
[:attr :keyword] [:attr :keyword]
[:val :any] [:val ::sm/any]
[:ignore-touched {:optional true} :boolean] [:ignore-touched {:optional true} :boolean]
[:ignore-geometry {:optional true} :boolean]]] [:ignore-geometry {:optional true} :boolean]]]
[:set-touched [:set-touched
@ -238,9 +238,9 @@
[:component-id {:optional true} ::sm/uuid] [:component-id {:optional true} ::sm/uuid]
[:ignore-touched {:optional true} :boolean] [:ignore-touched {:optional true} :boolean]
[:parent-id ::sm/uuid] [:parent-id ::sm/uuid]
[:shapes :any] [:shapes ::sm/any]
[:index {:optional true} [:maybe :int]] [:index {:optional true} [:maybe :int]]
[:after-shape {:optional true} :any] [:after-shape {:optional true} ::sm/any]
[:component-swap {:optional true} :boolean]]] [:component-swap {:optional true} :boolean]]]
[:reorder-children [:reorder-children
@ -250,14 +250,14 @@
[:component-id {:optional true} ::sm/uuid] [:component-id {:optional true} ::sm/uuid]
[:ignore-touched {:optional true} :boolean] [:ignore-touched {:optional true} :boolean]
[:parent-id ::sm/uuid] [:parent-id ::sm/uuid]
[:shapes :any]]] [:shapes ::sm/any]]]
[:add-page [:add-page
[:map {:title "AddPageChange"} [:map {:title "AddPageChange"}
[:type [:= :add-page]] [:type [:= :add-page]]
[:id {:optional true} ::sm/uuid] [:id {:optional true} ::sm/uuid]
[:name {:optional true} :string] [:name {:optional true} :string]
[:page {:optional true} :any]]] [:page {:optional true} ::sm/any]]]
[:mod-page [:mod-page
[:map {:title "ModPageChange"} [:map {:title "ModPageChange"}
@ -327,14 +327,14 @@
[:type [:= :add-component]] [:type [:= :add-component]]
[:id ::sm/uuid] [:id ::sm/uuid]
[:name :string] [:name :string]
[:shapes {:optional true} [:vector {:gen/max 3} :any]] [:shapes {:optional true} [:vector {:gen/max 3} ::sm/any]]
[:path {:optional true} :string]]] [:path {:optional true} :string]]]
[:mod-component [:mod-component
[:map {:title "ModCompoenentChange"} [:map {:title "ModCompoenentChange"}
[:type [:= :mod-component]] [:type [:= :mod-component]]
[:id ::sm/uuid] [:id ::sm/uuid]
[:shapes {:optional true} [:vector {:gen/max 3} :any]] [:shapes {:optional true} [:vector {:gen/max 3} ::sm/any]]
[:name {:optional true} :string] [:name {:optional true} :string]
[:variant-id {:optional true} ::sm/uuid] [:variant-id {:optional true} ::sm/uuid]
[:variant-properties {:optional true} [:vector ::ctv/variant-property]]]] [:variant-properties {:optional true} [:vector ::ctv/variant-property]]]]
@ -411,7 +411,7 @@
[:set-tokens-lib [:set-tokens-lib
[:map {:title "SetTokensLib"} [:map {:title "SetTokensLib"}
[:type [:= :set-tokens-lib]] [:type [:= :set-tokens-lib]]
[:tokens-lib :any]]] [:tokens-lib ::sm/any]]]
[:set-token-set [:set-token-set
[:map {:title "SetTokenSetChange"} [:map {:title "SetTokenSetChange"}

View File

@ -25,18 +25,19 @@
;; Auxiliary functions to help create a set of changes (undo + redo) ;; Auxiliary functions to help create a set of changes (undo + redo)
(sm/register! (def schema:changes
^{::sm/type ::changes} (sm/register!
[:map {:title "changes"} ^{::sm/type ::changes}
[:redo-changes vector?] [:map {:title "changes"}
[:undo-changes seq?] [:redo-changes vector?]
[:origin {:optional true} any?] [:undo-changes seq?]
[:save-undo? {:optional true} boolean?] [:origin {:optional true} ::sm/any]
[:stack-undo? {:optional true} boolean?] [:save-undo? {:optional true} boolean?]
[:undo-group {:optional true} any?]]) [:stack-undo? {:optional true} boolean?]
[:undo-group {:optional true} ::sm/any]]))
(def check-changes! (def check-changes!
(sm/check-fn ::changes)) (sm/check-fn schema:changes))
(defn empty-changes (defn empty-changes
([origin page-id] ([origin page-id]

View File

@ -626,6 +626,9 @@
(map? (:fill-image form)) (map? (:fill-image form))
(update-in [:fill-image :id] lookup-index) (update-in [:fill-image :id] lookup-index)
(map? (:stroke-image form))
(update-in [:stroke-image :id] lookup-index)
;; This covers old shapes and the new :fills. ;; This covers old shapes and the new :fills.
(uuid? (:fill-color-ref-file form)) (uuid? (:fill-color-ref-file form))
(update :fill-color-ref-file lookup-index) (update :fill-color-ref-file lookup-index)

View File

@ -584,7 +584,7 @@
(generate-sync-shape-direct changes file libraries container shape-id false))) (generate-sync-shape-direct changes file libraries container shape-id false)))
(defmethod generate-sync-shape :colors (defmethod generate-sync-shape :colors
[_ changes library-id _ shape _ libraries _] [_ changes library-id _ shape libraries _]
(shape-log :debug (:id shape) nil :msg "Sync colors of shape" :shape (:name shape)) (shape-log :debug (:id shape) nil :msg "Sync colors of shape" :shape (:name shape))
;; Synchronize a shape that uses some colors of the library. The value of the ;; Synchronize a shape that uses some colors of the library. The value of the
@ -595,7 +595,7 @@
#(ctc/sync-shape-colors % library-id library-colors)))) #(ctc/sync-shape-colors % library-id library-colors))))
(defmethod generate-sync-shape :typographies (defmethod generate-sync-shape :typographies
[_ changes library-id container shape _ libraries _] [_ changes library-id container shape libraries _]
(shape-log :debug (:id shape) nil :msg "Sync typographies of shape" :shape (:name shape)) (shape-log :debug (:id shape) nil :msg "Sync typographies of shape" :shape (:name shape))
;; Synchronize a shape that uses some typographies of the library. The attributes ;; Synchronize a shape that uses some typographies of the library. The attributes

View File

@ -214,8 +214,7 @@
(defn lazy-validator (defn lazy-validator
[s] [s]
(let [s (schema s) (let [vfn (delay (validator s))]
vfn (delay (validator s))]
(fn [v] (@vfn v)))) (fn [v] (@vfn v))))
(defn lazy-explainer (defn lazy-explainer
@ -318,11 +317,14 @@
([params] ([params]
(cond (cond
(map? params) (map? params)
(let [type (get params :type)] (let [mdata (meta params)
type (or (get mdata ::id)
(get mdata ::type)
(get params :type))]
(assert (qualified-keyword? type) "expected qualified keyword for `type`") (assert (qualified-keyword? type) "expected qualified keyword for `type`")
(let [s (m/-simple-schema params)] (let [s (m/-simple-schema params)]
(swap! sr/registry assoc type s) (swap! sr/registry assoc type s)
nil)) s))
(vector? params) (vector? params)
(let [mdata (meta params) (let [mdata (meta params)
@ -330,11 +332,12 @@
(get mdata ::type))] (get mdata ::type))]
(assert (qualified-keyword? type) "expected qualified keyword to be on metadata") (assert (qualified-keyword? type) "expected qualified keyword to be on metadata")
(swap! sr/registry assoc type params) (swap! sr/registry assoc type params)
nil) params)
(m/into-schema? params) (m/into-schema? params)
(let [type (m/-type params)] (let [type (m/-type params)]
(swap! sr/registry assoc type params)) (swap! sr/registry assoc type params)
params)
:else :else
(throw (ex-info "Invalid Arguments" {})))) (throw (ex-info "Invalid Arguments" {}))))
@ -1042,6 +1045,8 @@
{:title "agent" {:title "agent"
:description "instance of clojure agent"}})) :description "instance of clojure agent"}}))
(register! ::any (mu/update-properties :any assoc :gen/gen sg/any))
;; ---- PREDICATES ;; ---- PREDICATES
(def valid-safe-number? (def valid-safe-number?

View File

@ -7,6 +7,7 @@
(ns app.common.schema.desc-js-like (ns app.common.schema.desc-js-like
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.schema :as-alias sm]
[cuerdas.core :as str] [cuerdas.core :as str]
[malli.core :as m] [malli.core :as m]
[malli.util :as mu])) [malli.util :as mu]))
@ -90,7 +91,7 @@
(defmethod visit :int [_ schema _ _] (str "integer" (-titled schema) (-min-max-suffix-number schema))) (defmethod visit :int [_ schema _ _] (str "integer" (-titled schema) (-min-max-suffix-number schema)))
(defmethod visit :double [_ schema _ _] (str "double" (-titled schema) (-min-max-suffix-number schema))) (defmethod visit :double [_ schema _ _] (str "double" (-titled schema) (-min-max-suffix-number schema)))
(defmethod visit :select-keys [_ schema _ options] (describe* (m/deref schema) options)) (defmethod visit :select-keys [_ schema _ options] (describe* (m/deref schema) options))
(defmethod visit :and [_ s children _] (str (str/join ", and " children) (-titled s))) (defmethod visit :and [_ s children _] (str (str/join " && " children) (-titled s)))
(defmethod visit :enum [_ s children _options] (str "enum" (-titled s) " of " (str/join ", " children))) (defmethod visit :enum [_ s children _options] (str "enum" (-titled s) " of " (str/join ", " children)))
(defmethod visit :maybe [_ _ children _] (str (first children) " nullable")) (defmethod visit :maybe [_ _ children _] (str (first children) " nullable"))
(defmethod visit :tuple [_ _ children _] (str "(" (str/join ", " children) ")")) (defmethod visit :tuple [_ _ children _] (str "(" (str/join ", " children) ")"))
@ -106,7 +107,8 @@
(defmethod visit :qualified-symbol [_ _ _ _] "qualified symbol") (defmethod visit :qualified-symbol [_ _ _ _] "qualified symbol")
(defmethod visit :uuid [_ _ _ _] "uuid") (defmethod visit :uuid [_ _ _ _] "uuid")
(defmethod visit :boolean [_ _ _ _] "boolean") (defmethod visit :boolean [_ _ _ _] "boolean")
(defmethod visit :keyword [_ _ _ _] "keyword") (defmethod visit :keyword [_ _ _ _] "string")
(defmethod visit :fn [_ _ _ _] "FN")
(defmethod visit :vector [_ _ children _] (defmethod visit :vector [_ _ children _]
(str "[" (last children) "]")) (str "[" (last children) "]"))
@ -123,10 +125,12 @@
(defmethod visit :repeat [_ schema children _] (defmethod visit :repeat [_ schema children _]
(str "repeat " (-diamond (first children)) (-repeat-suffix schema))) (str "repeat " (-diamond (first children)) (-repeat-suffix schema)))
(defmethod visit :set [_ schema children _] (defmethod visit :set [_ schema children _]
(str "set[" (first children) "]" (minmax-suffix schema))) (str "set[" (first children) "]" (minmax-suffix schema)))
(defmethod visit ::sm/set [_ schema children _]
(str "set[" (first children) "]" (minmax-suffix schema)))
(defmethod visit ::m/val [_ schema children _] (defmethod visit ::m/val [_ schema children _]
(let [suffix (minmax-suffix schema)] (let [suffix (minmax-suffix schema)]
(cond-> (first children) (cond-> (first children)
@ -152,7 +156,6 @@
(or (:title props) (or (:title props)
"*"))) "*")))
(defmethod visit :map (defmethod visit :map
[_ schema children {:keys [::level ::max-level] :as options}] [_ schema children {:keys [::level ::max-level] :as options}]
(let [props (m/properties schema) (let [props (m/properties schema)
@ -172,13 +175,11 @@
": " s))) ": " s)))
(str/join ",\n")) (str/join ",\n"))
header (cond-> (if (zero? level) header (cond-> (str "type " title)
(str "type " title)
(str title))
closed? (str "!") closed? (str "!")
(some? title) (str " "))] (some? title) (str " "))]
(str header "{\n" entries "\n" (pad "}" level)))))) (str (pad header level) "{\n" entries "\n" (pad "}\n" level))))))
(defmethod visit :multi (defmethod visit :multi
[_ s children {:keys [::level ::max-level] :as options}] [_ s children {:keys [::level ::max-level] :as options}]
@ -205,18 +206,18 @@
(defmethod visit :merge (defmethod visit :merge
[_ schema children _] [_ schema children _]
(let [entries (str/join " , " children) (let [entries (str/join ",\n" children)
props (m/properties schema) props (m/properties schema)
title (or (some-> (:title props) str/camel str/capital) title (or (some-> (:title props) str/camel str/capital)
"<untitled>")] "<untitled>")]
(str "merge object " title " { " entries " }"))) (str "merge type " title " { \n" entries "\n}\n")))
(defmethod visit :app.common.schema/one-of (defmethod visit ::sm/one-of
[_ _ children _] [_ _ children _]
(let [elems (last children)] (let [elems (last children)]
(str "OneOf[" (->> elems (str "string oneOf (" (->> elems
(map d/name) (map d/name)
(str/join ",")) "]"))) (str/join "|")) ")")))
(defmethod visit :schema [_ schema children options] (defmethod visit :schema [_ schema children options]
(visit ::m/schema schema children options)) (visit ::m/schema schema children options))

View File

@ -5,7 +5,7 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.common.schema.generators (ns app.common.schema.generators
(:refer-clojure :exclude [set subseq uuid filter map let boolean]) (:refer-clojure :exclude [set subseq uuid filter map let boolean vector keyword int double])
#?(:cljs (:require-macros [app.common.schema.generators])) #?(:cljs (:require-macros [app.common.schema.generators]))
(:require (:require
[app.common.schema.registry :as sr] [app.common.schema.registry :as sr]
@ -38,10 +38,6 @@
([s opts] ([s opts]
(mg/generator s (assoc opts :registry sr/default-registry)))) (mg/generator s (assoc opts :registry sr/default-registry))))
(defn filter
[pred gen]
(tg/such-that pred gen 100))
(defn small-double (defn small-double
[& {:keys [min max] :or {min -100 max 100}}] [& {:keys [min max] :or {min -100 max 100}}]
(tg/double* {:min min, :max max, :infinite? false, :NaN? false})) (tg/double* {:min min, :max max, :infinite? false, :NaN? false}))
@ -61,7 +57,7 @@
(defn word-keyword (defn word-keyword
[] []
(->> (word-string) (->> (word-string)
(tg/fmap keyword))) (tg/fmap c/keyword)))
(defn email (defn email
[] []
@ -100,12 +96,11 @@
(c/map second)) (c/map second))
(c/map list bools elements))))))) (c/map list bools elements)))))))
(def any tg/any) (defn map-of
(def boolean tg/boolean) ([kg vg]
(tg/map kg vg {:min-elements 1 :max-elements 3}))
(defn set ([kg vg opts]
[g] (tg/map kg vg opts)))
(tg/set g))
(defn elements (defn elements
[s] [s]
@ -119,6 +114,10 @@
[f g] [f g]
(tg/fmap f g)) (tg/fmap f g))
(defn filter
[pred gen]
(tg/such-that pred gen 100))
(defn mcat (defn mcat
[f g] [f g]
(tg/bind g f)) (tg/bind g f))
@ -126,3 +125,22 @@
(defn tuple (defn tuple
[& opts] [& opts]
(apply tg/tuple opts)) (apply tg/tuple opts))
(defn vector
[& opts]
(apply tg/vector opts))
(defn set
[g]
(tg/set g))
;; Static Generators
(def boolean tg/boolean)
(def text (word-string))
(def double (small-double))
(def int (small-int))
(def keyword (word-keyword))
(def any
(tg/one-of [text boolean double int keyword]))

View File

@ -97,7 +97,8 @@
(defmethod visit :enum [_ _ children options] (merge (some-> (m/-infer children) (transform* options)) {:enum children})) (defmethod visit :enum [_ _ children options] (merge (some-> (m/-infer children) (transform* options)) {:enum children}))
(defmethod visit :maybe [_ _ children _] {:oneOf (conj children {:type "null"})}) (defmethod visit :maybe [_ _ children _] {:oneOf (conj children {:type "null"})})
(defmethod visit :tuple [_ _ children _] {:type "array", :items children, :additionalItems false}) (defmethod visit :tuple [_ _ children _] {:type "array", :items children, :additionalItems false})
(defmethod visit :re [_ schema _ options] {:type "string", :pattern (first (m/children schema options))}) (defmethod visit :re [_ schema _ options]
{:type "string", :pattern (str (first (m/children schema options)))})
(defmethod visit :nil [_ _ _ _] {:type "null"}) (defmethod visit :nil [_ _ _ _] {:type "null"})
(defmethod visit :string [_ schema _ _] (defmethod visit :string [_ schema _ _]

View File

@ -35,7 +35,7 @@
(.. r (toString 16) (padStart 2 "0")) (.. r (toString 16) (padStart 2 "0"))
(.. g (toString 16) (padStart 2 "0")) (.. g (toString 16) (padStart 2 "0"))
(.. b (toString 16) (padStart 2 "0")))))) (.. b (toString 16) (padStart 2 "0"))))))
sg/any)) sg/int))
(defn rgb-color-string? (defn rgb-color-string?
[o] [o]

View File

@ -41,7 +41,7 @@
[:map-of {:gen/max 10} ::sm/uuid :map]] [:map-of {:gen/max 10} ::sm/uuid :map]]
[:plugin-data {:optional true} ::ctpg/plugin-data]]) [:plugin-data {:optional true} ::ctpg/plugin-data]])
(def check-container! (def check-container
(sm/check-fn ::container)) (sm/check-fn ::container))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -74,13 +74,9 @@
(defn get-shape (defn get-shape
[container shape-id] [container shape-id]
(dm/assert! (assert (check-container container))
"expected valid container" (assert (uuid? shape-id)
(check-container! container)) "expected valid uuid for `shape-id`")
(dm/assert!
"expected valid uuid for `shape-id`"
(uuid? shape-id))
(-> container (-> container
(get :objects) (get :objects)

View File

@ -16,54 +16,56 @@
(def node-types #{"root" "paragraph-set" "paragraph"}) (def node-types #{"root" "paragraph-set" "paragraph"})
(sm/register! (def schema:content
^{::sm/type ::content} [:map
[:map [:type [:= "root"]]
[:type [:= "root"]] [:key {:optional true} :string]
[:key {:optional true} :string] [:children
[:children {:optional true}
{:optional true} [:maybe
[:maybe [:vector {:min 1 :gen/max 2 :gen/min 1}
[:vector {:min 1 :gen/max 2 :gen/min 1} [:map
[:map [:type [:= "paragraph-set"]]
[:type [:= "paragraph-set"]] [:key {:optional true} :string]
[:key {:optional true} :string] [:children
[:children [:vector {:min 1 :gen/max 2 :gen/min 1}
[:vector {:min 1 :gen/max 2 :gen/min 1} [:map
[:map [:type [:= "paragraph"]]
[:type [:= "paragraph"]] [:key {:optional true} :string]
[:key {:optional true} :string] [:fills {:optional true}
[:fills {:optional true} [:maybe
[:maybe [:vector {:gen/max 2} ::shape/fill]]]
[:vector {:gen/max 2} ::shape/fill]]] [:font-family {:optional true} :string]
[:font-family {:optional true} :string] [:font-size {:optional true} :string]
[:font-size {:optional true} :string] [:font-style {:optional true} :string]
[:font-style {:optional true} :string] [:font-weight {:optional true} :string]
[:font-weight {:optional true} :string] [:direction {:optional true} :string]
[:direction {:optional true} :string] [:text-decoration {:optional true} :string]
[:text-decoration {:optional true} :string] [:text-transform {:optional true} :string]
[:text-transform {:optional true} :string] [:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]] [:typography-ref-file {:optional true} [:maybe ::sm/uuid]]
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]] [:children
[:children [:vector {:min 1 :gen/max 2 :gen/min 1}
[:vector {:min 1 :gen/max 2 :gen/min 1} [:map
[:map [:text :string]
[:text :string] [:key {:optional true} :string]
[:key {:optional true} :string] [:fills {:optional true}
[:fills {:optional true} [:maybe
[:maybe [:vector {:gen/max 2} ::shape/fill]]]
[:vector {:gen/max 2} ::shape/fill]]] [:font-family {:optional true} :string]
[:font-family {:optional true} :string] [:font-size {:optional true} :string]
[:font-size {:optional true} :string] [:font-style {:optional true} :string]
[:font-style {:optional true} :string] [:font-weight {:optional true} :string]
[:font-weight {:optional true} :string] [:direction {:optional true} :string]
[:direction {:optional true} :string] [:text-decoration {:optional true} :string]
[:text-decoration {:optional true} :string] [:text-transform {:optional true} :string]
[:text-transform {:optional true} :string] [:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]] [:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]]])
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]]])
(sm/register! ::content schema:content)
(def valid-content?
(sm/lazy-validator schema:content))
(sm/register! (sm/register!
^{::sm/type ::position-data} ^{::sm/type ::position-data}

View File

@ -7,6 +7,7 @@
(ns app.common.types.tokens-lib (ns app.common.types.tokens-lib
(:require (:require
#?(:clj [app.common.fressian :as fres]) #?(:clj [app.common.fressian :as fres])
#?(:clj [clojure.data.json :as json])
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
@ -118,7 +119,7 @@
[:map {:title "Token"} [:map {:title "Token"}
[:name cto/token-name-ref] [:name cto/token-name-ref]
[:type [::sm/one-of cto/token-types]] [:type [::sm/one-of cto/token-types]]
[:value :any] [:value ::sm/any]
[:description {:optional true} :string] [:description {:optional true} :string]
[:modified-at {:optional true} ::sm/inst]]) [:modified-at {:optional true} ::sm/inst]])
@ -389,7 +390,8 @@
[:description {:optional true} :string] [:description {:optional true} :string]
[:modified-at {:optional true} ::sm/inst] [:modified-at {:optional true} ::sm/inst]
[:tokens {:optional true [:tokens {:optional true
:gen/gen (->> (sg/generator [:map-of ::sm/text schema:token]) :gen/gen (->> (sg/map-of (sg/generator ::sm/text)
(sg/generator schema:token))
(sg/fmap #(into (d/ordered-map) %)))} (sg/fmap #(into (d/ordered-map) %)))}
[:and [:and
[:map-of {:gen/max 5 [:map-of {:gen/max 5
@ -910,6 +912,12 @@ Will return a value that matches this schema:
"themes" (clj->js themes) "themes" (clj->js themes)
"active-themes" (clj->js active-themes)))]) "active-themes" (clj->js active-themes)))])
#?@(:clj
[json/JSONWriter
(-write [this writter options] (json/-write (encode-dtcg this) writter options))])
ITokenSets ITokenSets
(add-set [_ token-set] (add-set [_ token-set]
(let [path (get-token-set-prefixed-path token-set) (let [path (get-token-set-prefixed-path token-set)

View File

@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14.499" height="14.507" viewBox="700.734 827.762 14.499 14.507"><path d="M707.948 827.762c-.193 0-.386.007-.578.022-2.311.181-4.574 1.452-5.839 3.982-1.686 3.372-.52 6.821 1.835 8.787 2.354 1.966 5.955 2.497 8.974.233a.566.566 0 1 0-.68-.906c-2.611 1.959-5.563 1.478-7.568-.196-2.004-1.675-3.005-4.495-1.546-7.412 1.458-2.917 4.314-3.808 6.856-3.208 2.543.599 4.698 2.672 4.698 5.936v.667c0 .525-.176.847-.435 1.076-.258.23-.624.357-.998.357s-.741-.127-.999-.357c-.258-.229-.435-.551-.435-1.076v-3.334a.567.567 0 0 0-1.133 0v.215a3.215 3.215 0 0 0-2.1-.781 3.241 3.241 0 0 0-3.233 3.233 3.241 3.241 0 0 0 3.233 3.233 3.23 3.23 0 0 0 2.482-1.168c.122.199.267.377.433.525a2.63 2.63 0 0 0 1.752.643c.626 0 1.259-.206 1.751-.643.492-.437.815-1.115.815-1.923V835c0-3.773-2.586-6.336-5.572-7.04a7.405 7.405 0 0 0-1.713-.198ZM708 832.9c1.167 0 2.1.933 2.1 2.1a2.09 2.09 0 0 1-2.1 2.1 2.09 2.09 0 0 1-2.1-2.1c0-1.167.933-2.1 2.1-2.1Z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M10.667 5.333v3.334a2 2 0 1 0 4 0V8a6.667 6.667 0 1 0-2.614 5.293M10.667 8a2.667 2.667 0 1 1-5.334 0 2.667 2.667 0 0 1 5.334 0Z" />
</svg>

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 282 B

View File

@ -5086,7 +5086,8 @@ class zc extends HTMLElement {
"allow-modals", "allow-modals",
"allow-popups", "allow-popups",
"allow-popups-to-escape-sandbox", "allow-popups-to-escape-sandbox",
"allow-storage-access-by-user-activation" "allow-storage-access-by-user-activation",
"allow-same-origin"
), o && l.sandbox.add("allow-downloads"), l.addEventListener("load", () => { ), o && l.sandbox.add("allow-downloads"), l.addEventListener("load", () => {
var d; var d;
(d = this.shadowRoot) == null || d.dispatchEvent( (d = this.shadowRoot) == null || d.dispatchEvent(

View File

@ -5,6 +5,7 @@
set -ex set -ex
export INCLUDE_STORYBOOK=${BUILD_STORYBOOK:-no}; export INCLUDE_STORYBOOK=${BUILD_STORYBOOK:-no};
export INCLUDE_WASM=${BUILD_WASM:-yes};
export CURRENT_VERSION=$1; export CURRENT_VERSION=$1;
export BUILD_DATE=$(date -R); export BUILD_DATE=$(date -R);
@ -17,14 +18,18 @@ export TS=$(date +%s);
export NODE_ENV=production; export NODE_ENV=production;
corepack enable; corepack enable;
corepack up || exit 1; corepack install || exit 1;
yarn install || exit 1; yarn install || exit 1;
rm -rf resources/public; rm -rf resources/public;
rm -rf target/dist; rm -rf target/dist;
yarn run build:app:main --config-merge "{:release-version \"${CURRENT_HASH}-${TS}\"}" $EXTRA_PARAMS || exit 1 yarn run build:app:main --config-merge "{:release-version \"${CURRENT_HASH}-${TS}\"}" $EXTRA_PARAMS || exit 1
yarn run build:wasm || exit 1;
if [ "$INCLUDE_WASM" = "yes" ]; then
yarn run build:wasm || exit 1;
fi
yarn run build:app:libs || exit 1; yarn run build:app:libs || exit 1;
yarn run build:app:assets || exit 1; yarn run build:app:assets || exit 1;
@ -36,7 +41,10 @@ sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./target/dist/render.html;
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./target/dist/rasterizer.html; sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./target/dist/rasterizer.html;
sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./target/dist/index.html; sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./target/dist/index.html;
sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./target/dist/rasterizer.html; sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./target/dist/rasterizer.html;
sed -i "s/version=develop/version=$CURRENT_VERSION/g" ./target/dist/js/render_wasm.js;
if [ "$INCLUDE_WASM" = "yes" ]; then
sed -i "s/version=develop/version=$CURRENT_VERSION/g" ./target/dist/js/render_wasm.js;
fi
if [ "$INCLUDE_STORYBOOK" = "yes" ]; then if [ "$INCLUDE_STORYBOOK" = "yes" ]; then
# build storybook # build storybook

View File

@ -34,6 +34,7 @@
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.types.shape.text :as types.text]
[app.common.types.typography :as ctt] [app.common.types.typography :as ctt]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
@ -2192,27 +2193,27 @@
(ptk/reify ::paste-html-text (ptk/reify ::paste-html-text
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [root (dwtxt/create-root-from-html html) (let [root (dwtxt/create-root-from-html html)
content (tc/dom->cljs root) content (tc/dom->cljs root)]
(when (types.text/valid-content? content)
(let [id (uuid/next)
width (max 8 (min (* 7 (count text)) 700))
height 16
{:keys [x y]} (calculate-paste-position state)
id (uuid/next) shape {:id id
width (max 8 (min (* 7 (count text)) 700)) :type :text
height 16 :name (txt/generate-shape-name text)
{:keys [x y]} (calculate-paste-position state) :x x
:y y
shape {:id id :width width
:type :text :height height
:name (txt/generate-shape-name text) :grow-type (if (> (count text) 100) :auto-height :auto-width)
:x x :content content}
:y y undo-id (js/Symbol)]
:width width (rx/of (dwu/start-undo-transaction undo-id)
:height height (dwsh/create-and-add-shape :text x y shape)
:grow-type (if (> (count text) 100) :auto-height :auto-width) (dwu/commit-undo-transaction undo-id))))))))
:content content}
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dwsh/create-and-add-shape :text x y shape)
(dwu/commit-undo-transaction undo-id))))))
(defn- paste-text (defn- paste-text
[text] [text]

View File

@ -48,7 +48,7 @@
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(-> state (-> state
(update :workspace-local dissoc :edition) (update :workspace-local dissoc :edition :edit-path)
(update :workspace-drawing dissoc :tool :object :lock) (update :workspace-drawing dissoc :tool :object :lock)
(dissoc :workspace-grid-edition))) (dissoc :workspace-grid-edition)))

View File

@ -22,6 +22,7 @@
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.data.modal :as md] [app.main.data.modal :as md]
[app.main.data.workspace.collapse :as dwc] [app.main.data.workspace.collapse :as dwc]
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.specialized-panel :as-alias dwsp] [app.main.data.workspace.specialized-panel :as-alias dwsp]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.zoom :as dwz] [app.main.data.workspace.zoom :as dwz]
@ -305,8 +306,9 @@
(watch [_ state _] (watch [_ state _]
(let [params-without-board (-> (rt/get-params state) (let [params-without-board (-> (rt/get-params state)
(dissoc :board-id))] (dissoc :board-id))]
(rx/of ::dwsp/interrupt) (rx/of ::dwsp/interrupt
(rx/of (rt/nav :workspace params-without-board {::rt/replace true})))) (dwe/clear-edition-mode)
(rt/nav :workspace params-without-board {::rt/replace true}))))
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]

View File

@ -147,6 +147,7 @@
(swap! state* assoc :width width))) (swap! state* assoc :width width)))
[:div {:class (stl/css :palette-wrapper) [:div {:class (stl/css :palette-wrapper)
:id "palette-wrapper"
:style (calculate-palette-padding rulers?) :style (calculate-palette-padding rulers?)
:data-testid "palette"} :data-testid "palette"}
(when-not workspace-read-only? (when-not workspace-read-only?

View File

@ -4,6 +4,10 @@
// //
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "../ds/spacing.scss" as *;
@use "../ds/z-index.scss" as *;
@use "../ds/_sizes.scss" as *;
@import "refactor/common-refactor.scss"; @import "refactor/common-refactor.scss";
.palette-wrapper { .palette-wrapper {
@ -12,6 +16,12 @@
left: 0; left: 0;
bottom: 0; bottom: 0;
padding-bottom: $s-4; padding-bottom: $s-4;
/** Aligns AI Chat button **/
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--sp-s);
} }
.palettes { .palettes {
@ -164,3 +174,27 @@
padding-bottom: $s-8; padding-bottom: $s-8;
} }
} }
/** AI Chat button styles **/
.help-btn {
z-index: var(--z-index-panels);
flex-shrink: 0;
@extend .button-secondary;
inline-size: $sz-40;
block-size: $sz-40;
border-radius: $br-circle;
border: none;
&.selected {
@extend .button-icon-selected;
}
&:hover {
border: none;
}
}
.icon-help {
@extend .button-icon;
stroke: var(--icon-foreground);
inline-size: var(--sp-xxl);
block-size: var(--sp-xxl);
}

View File

@ -129,6 +129,12 @@
input-ref (mf/use-ref nil) input-ref (mf/use-ref nil)
team (mf/deref refs/team) team (mf/deref refs/team)
permissions (get team :permissions)
display-share-button?
(and (not (:is-default team))
(or (:is-admin permissions)
(:is-owner permissions)))
nav-to-viewer nav-to-viewer
(mf/use-fn (mf/use-fn
@ -216,7 +222,7 @@
:on-click toggle-history} :on-click toggle-history}
i/history]]) i/history]])
(when (not (:is-default team)) (when display-share-button?
[:a {:class (stl/css :viewer-btn) [:a {:class (stl/css :viewer-btn)
:title (tr "workspace.header.share") :title (tr "workspace.header.share")
:on-click open-share-dialog} :on-click open-share-dialog}

View File

@ -38,7 +38,8 @@
indent)) indent))
(cfh/text-shape? shape) (cfh/text-shape? shape)
(let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape #js {:shape shape :code? true}))] (let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape #js {:shape shape :code? true}))
text-shape-html (str/replace text-shape-html #"style\s*=\s*[\"'][^\"']*[\"']" "")]
(dm/fmt "%<div class=\"%\">\n%\n%</div>" (dm/fmt "%<div class=\"%\">\n%\n%</div>"
indent indent
(dm/str "shape " (d/name (:type shape)) " " (dm/str "shape " (d/name (:type shape)) " "

View File

@ -83,7 +83,10 @@ export function mapContentFragmentFromDocument(document, root, styleDefaults) {
currentNode = nodeIterator.nextNode(); currentNode = nodeIterator.nextNode();
} }
fragment.appendChild(currentParagraph); if (currentParagraph) {
fragment.appendChild(currentParagraph);
}
if (fragment.children.length === 1) { if (fragment.children.length === 1) {
const isContentInline = isContentFragmentFromDocumentInline(document); const isContentInline = isContentFragmentFromDocumentInline(document);
if (isContentInline) { if (isContentInline) {

View File

@ -115,6 +115,7 @@ function build {
--mount source=`pwd`,type=bind,target=/home/penpot/penpot \ --mount source=`pwd`,type=bind,target=/home/penpot/penpot \
-e EXTERNAL_UID=$CURRENT_USER_ID \ -e EXTERNAL_UID=$CURRENT_USER_ID \
-e BUILD_STORYBOOK=$BUILD_STORYBOOK \ -e BUILD_STORYBOOK=$BUILD_STORYBOOK \
-e BUILD_WASM=$BUILD_WASM \
-e SHADOWCLJS_EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS \ -e SHADOWCLJS_EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS \
-e JAVA_OPTS="$JAVA_OPTS" \ -e JAVA_OPTS="$JAVA_OPTS" \
-w /home/penpot/penpot/$1 \ -w /home/penpot/penpot/$1 \