diff --git a/CHANGES.md b/CHANGES.md
index 3aa38d215c..28bb73c8bf 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -68,6 +68,8 @@
- Fix problem when changing between flex/grid layout [Taiga #11625](https://tree.taiga.io/project/penpot/issue/11625)
- 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)
## 2.8.1 (Unreleased)
@@ -75,6 +77,7 @@
- Fix unexpected exception on processing old texts [Github #6889](https://github.com/penpot/penpot/pull/6889)
- Fix UI theme selection from main menu [Taiga #11567](https://tree.taiga.io/project/penpot/issue/11567)
+- Add missing migration information to file snapshots [Github #686](https://github.com/penpot/penpot/pull/6864)
## 2.8.0
diff --git a/backend/src/app/features/logical_deletion.clj b/backend/src/app/features/logical_deletion.clj
index 8a06f3f30f..a8407cdf62 100644
--- a/backend/src/app/features/logical_deletion.clj
+++ b/backend/src/app/features/logical_deletion.clj
@@ -10,18 +10,19 @@
[app.config :as cf]
[app.util.time :as dt]))
+(def ^:private canceled-status
+ #{"canceled" "unpaid"})
+
(defn get-deletion-delay
"Calculate the next deleted-at for a resource (file, team, etc) in function
of team settings"
[team]
- (if-let [subscription (get team :subscription)]
+ (if-let [{:keys [type status]} (get team :subscription)]
(cond
- (and (= (:type subscription) "unlimited")
- (= (:status subscription) "active"))
+ (and (= "unlimited" type) (not (contains? canceled-status status)))
(dt/duration {:days 30})
- (and (= (:type subscription) "enterprise")
- (= (:status subscription) "active"))
+ (and (= "enterprise" type) (not (contains? canceled-status status)))
(dt/duration {:days 90})
:else
diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj
index 795a9bea5c..1c51365a9c 100644
--- a/backend/src/app/migrations.clj
+++ b/backend/src/app/migrations.clj
@@ -438,7 +438,10 @@
:fn (mg/resource "app/migrations/sql/0138-mod-file-data-fragment-table.sql")}
{:name "0139-mod-file-change-table.sql"
- :fn (mg/resource "app/migrations/sql/0139-mod-file-change-table.sql")}])
+ :fn (mg/resource "app/migrations/sql/0139-mod-file-change-table.sql")}
+
+ {:name "0140-mod-file-change-table.sql"
+ :fn (mg/resource "app/migrations/sql/0140-mod-file-change-table.sql")}])
(defn apply-migrations!
[pool name migrations]
diff --git a/backend/src/app/migrations/sql/0140-mod-file-change-table.sql b/backend/src/app/migrations/sql/0140-mod-file-change-table.sql
new file mode 100644
index 0000000000..6189edb140
--- /dev/null
+++ b/backend/src/app/migrations/sql/0140-mod-file-change-table.sql
@@ -0,0 +1,2 @@
+ALTER TABLE file_change
+ ADD COLUMN migrations text[];
diff --git a/backend/src/app/rpc/commands/files_snapshot.clj b/backend/src/app/rpc/commands/files_snapshot.clj
index 71689560a5..bcfbad9428 100644
--- a/backend/src/app/rpc/commands/files_snapshot.clj
+++ b/backend/src/app/rpc/commands/files_snapshot.clj
@@ -8,6 +8,7 @@
(:require
[app.binfile.common :as bfc]
[app.common.exceptions :as ex]
+ [app.common.files.migrations :as fmg]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.uuid :as uuid]
@@ -15,6 +16,7 @@
[app.db :as db]
[app.db.sql :as-alias sql]
[app.features.fdata :as feat.fdata]
+ [app.features.file-migrations :refer [reset-migrations!]]
[app.main :as-alias main]
[app.msgbus :as mbus]
[app.rpc :as-alias rpc]
@@ -27,6 +29,13 @@
[app.util.time :as dt]
[cuerdas.core :as str]))
+(defn decode-row
+ [{:keys [migrations] :as row}]
+ (when row
+ (cond-> row
+ (some? migrations)
+ (assoc :migrations (db/decode-pgarray migrations)))))
+
(def sql:get-file-snapshots
"WITH changes AS (
SELECT id, label, revn, created_at, created_by, profile_id
@@ -74,10 +83,7 @@
(assert (#{:system :user :admin} created-by)
"expected valid keyword for created-by")
- (let [conn
- (db/get-connection cfg)
-
- created-by
+ (let [created-by
(name created-by)
deleted-at
@@ -101,12 +107,15 @@
(blob/encode (:data file))
features
- (db/encode-pgarray (:features file) conn "text")]
+ (into-array (:features file))
- (l/debug :hint "creating file snapshot"
- :file-id (str (:id file))
- :id (str snapshot-id)
- :label label)
+ migrations
+ (into-array (:migrations file))]
+
+ (l/dbg :hint "creating file snapshot"
+ :file-id (str (:id file))
+ :id (str snapshot-id)
+ :label label)
(db/insert! cfg :file-change
{:id snapshot-id
@@ -114,6 +123,7 @@
:data data
:version (:version file)
:features features
+ :migrations migrations
:profile-id profile-id
:file-id (:id file)
:label label
@@ -159,7 +169,17 @@
{:file-id file-id
:id snapshot-id}
{::db/for-share true})
- (feat.fdata/resolve-file-data cfg))]
+ (feat.fdata/resolve-file-data cfg)
+ (decode-row))
+
+ ;; If snapshot has tracked applied migrations, we reuse them,
+ ;; if not we take a safest set of migrations as starting
+ ;; point. This is because, at the time of implementing
+ ;; snapshots, migrations were not taken into account so we
+ ;; need to make this backward compatible in some way.
+ file (assoc file :migrations
+ (or (:migrations snapshot)
+ (fmg/generate-migrations-from-version 67)))]
(when-not snapshot
(ex/raise :type :not-found
@@ -180,12 +200,16 @@
:label (:label snapshot)
:snapshot-id (str (:id snapshot)))
- ;; If the file was already offloaded, on restring the snapshot
- ;; we are going to replace the file data, so we need to touch
- ;; the old referenced storage object and avoid possible leaks
+ ;; If the file was already offloaded, on restoring the snapshot we
+ ;; are going to replace the file data, so we need to touch the old
+ ;; referenced storage object and avoid possible leaks
(when (feat.fdata/offloaded? file)
(sto/touch-object! storage (:data-ref-id file)))
+ ;; In the same way, on reseting the file data, we need to restore
+ ;; the applied migrations on the moment of taking the snapshot
+ (reset-migrations! conn file)
+
(db/update! conn :file
{:data (:data snapshot)
:revn (inc (:revn file))
@@ -253,7 +277,7 @@
:deleted-at nil}
{:id snapshot-id}
{::db/return-keys true})
- (dissoc :data :features)))
+ (dissoc :data :features :migrations)))
(defn- get-snapshot
"Get a minimal snapshot from database and lock for update"
diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc
index 9bff98e6d1..2af7b31f4d 100644
--- a/common/src/app/common/files/migrations.cljc
+++ b/common/src/app/common/files/migrations.cljc
@@ -80,7 +80,7 @@
(update :migrations set/union diff)
(vary-meta assoc ::migrated (not-empty diff)))))
-(defn- generate-migrations-from-version
+(defn generate-migrations-from-version
"A function that generates new format migration from the old,
version based migration system"
[version]
diff --git a/common/src/app/common/logic/shapes.cljc b/common/src/app/common/logic/shapes.cljc
index 4e3e0e3924..281d2b8e55 100644
--- a/common/src/app/common/logic/shapes.cljc
+++ b/common/src/app/common/logic/shapes.cljc
@@ -11,6 +11,7 @@
[app.common.files.helpers :as cfh]
[app.common.geom.shapes :as gsh]
[app.common.logic.variant-properties :as clvp]
+ [app.common.text :as ct]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.pages-list :as ctpl]
diff --git a/docker/images/Dockerfile.backend b/docker/images/Dockerfile.backend
index b8d59818e9..3535cffd0c 100644
--- a/docker/images/Dockerfile.backend
+++ b/docker/images/Dockerfile.backend
@@ -125,7 +125,7 @@ RUN set -ex; \
COPY --from=build /opt/jre /opt/jre
COPY --from=build /opt/node /opt/node
-COPY --from=penpotapp/imagemagick:7.1.1-47 /opt/imagick /opt/imagick
+COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick
COPY --chown=penpot:penpot ./bundle-backend/ /opt/penpot/backend/
USER penpot:penpot
diff --git a/docs/user-guide/design-tokens/index.njk b/docs/user-guide/design-tokens/index.njk
index 3de6a9dbae..186de28280 100644
--- a/docs/user-guide/design-tokens/index.njk
+++ b/docs/user-guide/design-tokens/index.njk
@@ -205,6 +205,10 @@ title: 10· Design Tokens
Y Position (dimension)
The Y property specifies the position of the element on the Y axis of the canvas.
+
Font Size
+
Font size tokens allow you to define and standardize font-size values across your design system. These tokens can be applied to the font-size property in text layers, ensuring consistent typography throughout your designs.
+
Font size token values are always computed as px (pixels).
+
Opacity
Opacity tokens allow you to define the opacity of a layer, ranging from fully opaque to fully transparent.
Opacity tokens can be applied to any design element that supports transparency. You can use any decimal value between 0 and 1 to set varying levels of opacity or you can use any value between 0 and 100 with `%` sign at the end of the value. For example, you can use 45% which would resolve to .45.
@@ -378,7 +382,15 @@ title: 10· Design Tokens
Import Options
-
Single file
+
+
ZIP file
+
You can import tokens from a .zip file. This file can either contain a single JSON file or a folder structure with multiple files. The ZIP import option provides flexibility for organizing your tokens before importing them into Penpot.
+
+
If the ZIP contains a single JSON file, it will be imported as a single set of tokens.
+
If the ZIP contains a folder structure, each file and folder will be interpreted as separate token sets, following the same rules as the multifile import.
+
+
+
Single JSON file
You can import a JSON file comprising all tokens, token sets and token themes.
When importing a single file, the first-level keys of the json file will be interpreted as the set name.
diff --git a/docs/user-guide/layer-basics/index.njk b/docs/user-guide/layer-basics/index.njk
index 317857423c..1667d8886e 100644
--- a/docs/user-guide/layer-basics/index.njk
+++ b/docs/user-guide/layer-basics/index.njk
@@ -34,7 +34,13 @@ desc: Master layer basics with Penpot's user guide! Learn to create, manipulate,
Layers are displayed from the bottom to the top of the layer stack, with layers above on the stack being shown on top in the image.
Hide and lock layers
-
Click on the eye icon to change the visibility of a layer. Click on the lock icon to lock or unlock a layer. A locked layer can not be modified.
+
+
Hide and show layers
+
You can control the visibility of any layer by clicking the eye icon next to it in the Layers panel. When a layer is hidden, it will not appear on the canvas, but you can still select it in the Layers panel, move its order, or modify its properties. The eye icon always indicates whether a layer is visible or hidden, making it easy to manage complex designs.
+
+
Lock and unlock layers
+
Locking a layer helps prevent accidental changes or movement on the canvas. When a layer is locked, it cannot be moved or edited directly in the canvas area. However, you can still select a locked layer in the Layers panel and adjust its properties, such as color, effects, or name. The lock icon next to the layer’s name shows its locked status, helping you keep your design organized and protected.
+
+
Tips for resizing
+
+
Double-click on the right side of the bounding box to set the resize setting to auto-width.
+
Double-click on the bottom side of the bounding box to set the resize setting to auto-height.
+
Edit and style text content
Press Enter with a text layer selected to start editing the text content. You can style parts of the text content as rich text.