diff --git a/CHANGES.md b/CHANGES.md
index 828d5af8b..7c7a1a6db 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -70,6 +70,7 @@ is a number of cores)
- Fix missing state refresh on notifications update [Taiga #10253](https://tree.taiga.io/project/penpot/issue/10253)
- Fix icon visualization on select component [Taiga #8889](https://tree.taiga.io/project/penpot/issue/8889)
- Fix typo on integration tests docs [Taiga #10112](https://tree.taiga.io/project/penpot/issue/10112)
+- Fix menu shadow color [Taiga #10102](https://tree.taiga.io/project/penpot/issue/10102)
- Fix problem with alt key measures being stuck [Taiga #9348](https://tree.taiga.io/project/penpot/issue/9348)
- Fix error when reseting stroke cap
- Fix problem with strokes not refreshing in Safari [Taiga #9040](https://tree.taiga.io/project/penpot/issue/9040)
@@ -86,6 +87,7 @@ is a number of cores)
- Fix rename locked boards [Taiga #10174](https://tree.taiga.io/project/penpot/issue/10174)
- Fix update-libraries dialog disappear when clicking outside [Taiga #10238](https://tree.taiga.io/project/penpot/issue/10238)
- Fix incorrect handling of team access requests with deleted/recreated users
+- Fix incorect handling of profile settings related to invitation notifications [Taiga #10252](https://tree.taiga.io/project/penpot/issue/10252)
## 2.4.3
diff --git a/backend/src/app/binfile/common.clj b/backend/src/app/binfile/common.clj
index 2ae8d2171..95fae4dfe 100644
--- a/backend/src/app/binfile/common.clj
+++ b/backend/src/app/binfile/common.clj
@@ -20,7 +20,6 @@
[app.config :as cf]
[app.db :as db]
[app.db.sql :as sql]
- [app.features.components-v2 :as feat.compv2]
[app.features.fdata :as feat.fdata]
[app.features.file-migrations :as feat.fmigr]
[app.loggers.audit :as-alias audit]
@@ -307,7 +306,7 @@
update-shapes
(fn [data {:keys [page-id shape-id]}]
- (d/update-in-when data [:pages-index page-id :objects shape-id] cfh/relink-media-refs lookup-index))
+ (d/update-in-when data [:pages-index page-id :objects shape-id] cfh/relink-refs lookup-index))
file
(update file :data #(reduce update-shapes % media-refs))]
@@ -375,7 +374,7 @@
replace the old :component-file reference with the new
ones, using the provided file-index."
[data]
- (cfh/relink-media-refs data lookup-index))
+ (cfh/relink-refs data lookup-index))
(defn- relink-media
"A function responsible of process the :media attr of file data and
@@ -523,38 +522,3 @@
(l/error :hint "file schema validation error" :cause result))))
(insert-file! cfg file opts)))
-
-(defn register-pending-migrations!
- "All features that are enabled and requires explicit migration are
- added to the state for a posterior migration step."
- [cfg {:keys [id features] :as file}]
- (doseq [feature (-> (::features cfg)
- (set/difference cfeat/no-migration-features)
- (set/difference cfeat/backend-only-features)
- (set/difference features))]
- (vswap! *state* update :pending-to-migrate (fnil conj []) [feature id]))
-
- file)
-
-(defn apply-pending-migrations!
- "Apply alredy registered pending migrations to files"
- [cfg]
- (doseq [[feature file-id] (-> *state* deref :pending-to-migrate)]
- (case feature
- "components/v2"
- (feat.compv2/migrate-file! cfg file-id
- :validate? (::validate cfg true)
- :skip-on-graphic-error? true)
-
- "fdata/shape-data-type"
- nil
-
- ;; There is no migration needed, but we don't want to allow
- ;; copy paste nor import of variant files into no-variant teams
- "variants/v1"
- nil
-
- (ex/raise :type :internal
- :code :no-migration-defined
- :hint (str/ffmt "no migation for feature '%' on file importation" feature)
- :feature feature))))
diff --git a/backend/src/app/binfile/migrations.clj b/backend/src/app/binfile/migrations.clj
new file mode 100644
index 000000000..62d180b49
--- /dev/null
+++ b/backend/src/app/binfile/migrations.clj
@@ -0,0 +1,50 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns app.binfile.migrations
+ "A binfile related migrations handling"
+ (:require
+ [app.binfile.common :as bfc]
+ [app.common.exceptions :as ex]
+ [app.common.features :as cfeat]
+ [app.features.components-v2 :as feat.compv2]
+ [clojure.set :as set]
+ [cuerdas.core :as str]))
+
+(defn register-pending-migrations!
+ "All features that are enabled and requires explicit migration are
+ added to the state for a posterior migration step."
+ [cfg {:keys [id features] :as file}]
+ (doseq [feature (-> (::features cfg)
+ (set/difference cfeat/no-migration-features)
+ (set/difference cfeat/backend-only-features)
+ (set/difference features))]
+ (vswap! bfc/*state* update :pending-to-migrate (fnil conj []) [feature id]))
+
+ file)
+
+(defn apply-pending-migrations!
+ "Apply alredy registered pending migrations to files"
+ [cfg]
+ (doseq [[feature file-id] (-> bfc/*state* deref :pending-to-migrate)]
+ (case feature
+ "components/v2"
+ (feat.compv2/migrate-file! cfg file-id
+ :validate? (::validate cfg true)
+ :skip-on-graphic-error? true)
+
+ "fdata/shape-data-type"
+ nil
+
+ ;; There is no migration needed, but we don't want to allow
+ ;; copy paste nor import of variant files into no-variant teams
+ "variants/v1"
+ nil
+
+ (ex/raise :type :internal
+ :code :no-migration-defined
+ :hint (str/ffmt "no migation for feature '%' on file importation" feature)
+ :feature feature))))
diff --git a/backend/src/app/binfile/v1.clj b/backend/src/app/binfile/v1.clj
index 0f94df969..2f70ed50d 100644
--- a/backend/src/app/binfile/v1.clj
+++ b/backend/src/app/binfile/v1.clj
@@ -9,6 +9,7 @@
(:refer-clojure :exclude [assert])
(:require
[app.binfile.common :as bfc]
+ [app.binfile.migrations :as bfm]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
@@ -473,7 +474,7 @@
(read-section options))))
[:v1/metadata :v1/files :v1/rels :v1/sobjects])
- (bfc/apply-pending-migrations! cfg)
+ (bfm/apply-pending-migrations! cfg)
;; Knowing that the ids of the created files are in index,
;; just lookup them and return it as a set
diff --git a/backend/src/app/binfile/v3.clj b/backend/src/app/binfile/v3.clj
index dcda35455..dc6bf7b80 100644
--- a/backend/src/app/binfile/v3.clj
+++ b/backend/src/app/binfile/v3.clj
@@ -9,6 +9,7 @@
(:refer-clojure :exclude [read])
(:require
[app.binfile.common :as bfc]
+ [app.binfile.migrations :as bfm]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
@@ -735,7 +736,7 @@
(bfc/process-file))]
- (bfc/register-pending-migrations! cfg file)
+ (bfm/register-pending-migrations! cfg file)
(bfc/save-file! cfg file ::db/return-keys false)
file-id')))
@@ -915,7 +916,7 @@
(import-file-media cfg)
(import-file-thumbnails cfg)
- (bfc/apply-pending-migrations! cfg)
+ (bfm/apply-pending-migrations! cfg)
ids)))))))
diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj
index f30075f94..795a9bea5 100644
--- a/backend/src/app/migrations.clj
+++ b/backend/src/app/migrations.clj
@@ -435,7 +435,10 @@
:fn (mg/resource "app/migrations/sql/0137-add-file-migration-table.sql")}
{:name "0138-mod-file-data-fragment-table.sql"
- :fn (mg/resource "app/migrations/sql/0138-mod-file-data-fragment-table.sql")}])
+ :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")}])
(defn apply-migrations!
[pool name migrations]
diff --git a/backend/src/app/migrations/sql/0139-mod-file-change-table.sql b/backend/src/app/migrations/sql/0139-mod-file-change-table.sql
new file mode 100644
index 000000000..0b8171325
--- /dev/null
+++ b/backend/src/app/migrations/sql/0139-mod-file-change-table.sql
@@ -0,0 +1,5 @@
+ALTER TABLE file_change
+ DROP CONSTRAINT file_change_file_id_fkey,
+ DROP CONSTRAINT file_change_profile_id_fkey,
+ ADD FOREIGN KEY (file_id) REFERENCES file(id) DEFERRABLE,
+ ADD FOREIGN KEY (profile_id) REFERENCES profile(id) ON DELETE SET NULL DEFERRABLE;
diff --git a/backend/src/app/rpc/commands/files_snapshot.clj b/backend/src/app/rpc/commands/files_snapshot.clj
index 43e3f1c95..71689560a 100644
--- a/backend/src/app/rpc/commands/files_snapshot.clj
+++ b/backend/src/app/rpc/commands/files_snapshot.clj
@@ -6,6 +6,7 @@
(ns app.rpc.commands.files-snapshot
(:require
+ [app.binfile.common :as bfc]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.schema :as sm]
@@ -22,7 +23,6 @@
[app.rpc.quotes :as quotes]
[app.storage :as sto]
[app.util.blob :as blob]
- [app.util.pointer-map :as pmap]
[app.util.services :as sv]
[app.util.time :as dt]
[cuerdas.core :as str]))
@@ -58,26 +58,6 @@
(files/check-read-permissions! conn profile-id file-id)
(get-file-snapshots conn file-id))))
-(def ^:private sql:get-file
- "SELECT f.*,
- p.id AS project_id,
- p.team_id AS team_id
- FROM file AS f
- INNER JOIN project AS p ON (p.id = f.project_id)
- WHERE f.id = ?")
-
-(defn- get-file
- [cfg file-id]
- (let [file (->> (db/exec-one! cfg [sql:get-file file-id])
- (feat.fdata/resolve-file-data cfg))]
- (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)]
- (-> file
- (update :data blob/decode)
- (update :data feat.fdata/process-pointers deref)
- (update :data feat.fdata/process-objects (partial into {}))
- (update :data assoc ::id file-id)
- (update :data blob/encode)))))
-
(defn- generate-snapshot-label
[]
(let [ts (-> (dt/now)
@@ -87,49 +67,53 @@
(str "snapshot-" ts)))
(defn create-file-snapshot!
- [cfg profile-id file-id label]
- (let [file (get-file cfg file-id)
+ [cfg file & {:keys [label created-by deleted-at profile-id]
+ :or {deleted-at :default
+ created-by :system}}]
+
+ (assert (#{:system :user :admin} created-by)
+ "expected valid keyword for created-by")
+
+ (let [conn
+ (db/get-connection cfg)
- ;; NOTE: final user never can provide label as `:system`
- ;; keyword because the validator implies label always as
- ;; string; keyword is used for signal a special case
created-by
- (if (= label :system)
- "system"
- "user")
+ (name created-by)
deleted-at
- (if (= label :system)
+ (cond
+ (= deleted-at :default)
(dt/plus (dt/now) (cf/get-deletion-delay))
+
+ (dt/instant? deleted-at)
+ deleted-at
+
+ :else
nil)
label
- (if (= label :system)
- (str "internal/snapshot/" (:revn file))
- (or label (generate-snapshot-label)))
+ (or label (generate-snapshot-label))
snapshot-id
- (uuid/next)]
+ (uuid/next)
- (-> cfg
- (assoc ::quotes/profile-id profile-id)
- (assoc ::quotes/project-id (:project-id file))
- (assoc ::quotes/team-id (:team-id file))
- (assoc ::quotes/file-id (:id file))
- (quotes/check! {::quotes/id ::quotes/snapshots-per-file}
- {::quotes/id ::quotes/snapshots-per-team}))
+ data
+ (blob/encode (:data file))
+
+ features
+ (db/encode-pgarray (:features file) conn "text")]
(l/debug :hint "creating file snapshot"
- :file-id (str file-id)
+ :file-id (str (:id file))
:id (str snapshot-id)
:label label)
(db/insert! cfg :file-change
{:id snapshot-id
:revn (:revn file)
- :data (:data file)
+ :data data
:version (:version file)
- :features (:features file)
+ :features features
:profile-id profile-id
:file-id (:id file)
:label label
@@ -146,12 +130,25 @@
(sv/defmethod ::create-file-snapshot
{::doc/added "1.20"
- ::sm/params schema:create-file-snapshot}
- [cfg {:keys [::rpc/profile-id file-id label]}]
- (db/tx-run! cfg
- (fn [{:keys [::db/conn] :as cfg}]
- (files/check-edition-permissions! conn profile-id file-id)
- (create-file-snapshot! cfg profile-id file-id label))))
+ ::sm/params schema:create-file-snapshot
+ ::db/transaction true}
+ [{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id file-id label]}]
+ (files/check-edition-permissions! conn profile-id file-id)
+ (let [file (bfc/get-file cfg file-id)
+ project (db/get-by-id cfg :project (:project-id file))]
+
+ (-> cfg
+ (assoc ::quotes/profile-id profile-id)
+ (assoc ::quotes/project-id (:project-id file))
+ (assoc ::quotes/team-id (:team-id project))
+ (assoc ::quotes/file-id (:id file))
+ (quotes/check! {::quotes/id ::quotes/snapshots-per-file}
+ {::quotes/id ::quotes/snapshots-per-team}))
+
+ (create-file-snapshot! cfg file
+ {:label label
+ :profile-id profile-id
+ :created-by :user})))
(defn restore-file-snapshot!
[{:keys [::db/conn ::mbus/msgbus] :as cfg} file-id snapshot-id]
@@ -237,8 +234,11 @@
(db/tx-run! cfg
(fn [{:keys [::db/conn] :as cfg}]
(files/check-edition-permissions! conn profile-id file-id)
- (create-file-snapshot! cfg profile-id file-id :system)
- (restore-file-snapshot! cfg file-id id))))
+ (let [file (bfc/get-file cfg file-id)]
+ (create-file-snapshot! cfg file
+ {:profile-id profile-id
+ :created-by :system})
+ (restore-file-snapshot! cfg file-id id)))))
(def ^:private schema:update-file-snapshot
[:map {:title "update-file-snapshot"}
diff --git a/backend/src/app/rpc/commands/teams_invitations.clj b/backend/src/app/rpc/commands/teams_invitations.clj
index c376b6fae..0fd0d9127 100644
--- a/backend/src/app/rpc/commands/teams_invitations.clj
+++ b/backend/src/app/rpc/commands/teams_invitations.clj
@@ -34,7 +34,6 @@
;; --- Mutation: Create Team Invitation
-
(def sql:upsert-team-invitation
"insert into team_invitation(id, team_id, email_to, created_by, role, valid_until)
values (?, ?, ?, ?, ?, ?)
@@ -79,27 +78,23 @@
[:role ::types.team/role]
[:email ::sm/email]])
-(def ^:private check-create-invitation-params!
+(def ^:private check-create-invitation-params
(sm/check-fn schema:create-invitation))
+(defn- allow-invitation-emails?
+ [member]
+ (let [notifications (dm/get-in member [:props :notifications])]
+ (not= :none (:email-invites notifications))))
+
(defn- create-invitation
[{:keys [::db/conn] :as cfg} {:keys [team profile role email] :as params}]
- (dm/assert!
- "expected valid connection on cfg parameter"
- (db/connection? conn))
-
- (dm/assert!
- "expected valid params for `create-invitation` fn"
- (check-create-invitation-params! params))
+ (assert (db/connection? conn) "expected valid connection on cfg parameter")
+ (assert (check-create-invitation-params params))
(let [email (profile/clean-email email)
member (profile/get-profile-by-email conn email)]
- (teams/check-profile-muted conn member)
- (teams/check-email-bounce conn email true)
- (teams/check-email-spam conn email true)
-
;; When we have email verification disabled and invitation user is
;; already present in the database, we proceed to add it to the
;; team as-is, without email roundtrip.
@@ -125,48 +120,54 @@
nil)
- (let [id (uuid/next)
- expire (dt/in-future "168h") ;; 7 days
- invitation (db/exec-one! conn [sql:upsert-team-invitation id
- (:id team) (str/lower email)
- (:id profile)
- (name role) expire
- (name role) expire])
- updated? (not= id (:id invitation))
- profile-id (:id profile)
- tprops {:profile-id profile-id
- :invitation-id (:id invitation)
- :valid-until expire
- :team-id (:id team)
- :member-email (:email-to invitation)
- :member-id (:id member)
- :role role}
- itoken (create-invitation-token cfg tprops)
- ptoken (create-profile-identity-token cfg profile-id)]
+ (do
+ (some->> member (teams/check-profile-muted conn))
+ (teams/check-email-bounce conn email true)
+ (teams/check-email-spam conn email true)
- (when (contains? cf/flags :log-invitation-tokens)
- (l/info :hint "invitation token" :token itoken))
+ (let [id (uuid/next)
+ expire (dt/in-future "168h") ;; 7 days
+ invitation (db/exec-one! conn [sql:upsert-team-invitation id
+ (:id team) (str/lower email)
+ (:id profile)
+ (name role) expire
+ (name role) expire])
+ updated? (not= id (:id invitation))
+ profile-id (:id profile)
+ tprops {:profile-id profile-id
+ :invitation-id (:id invitation)
+ :valid-until expire
+ :team-id (:id team)
+ :member-email (:email-to invitation)
+ :member-id (:id member)
+ :role role}
+ itoken (create-invitation-token cfg tprops)
+ ptoken (create-profile-identity-token cfg profile-id)]
- (let [props (-> (dissoc tprops :profile-id)
- (audit/clean-props))
- evname (if updated?
- "update-team-invitation"
- "create-team-invitation")
- event (-> (audit/event-from-rpc-params params)
- (assoc ::audit/name evname)
- (assoc ::audit/props props))]
- (audit/submit! cfg event))
+ (when (contains? cf/flags :log-invitation-tokens)
+ (l/info :hint "invitation token" :token itoken))
- (eml/send! {::eml/conn conn
- ::eml/factory eml/invite-to-team
- :public-uri (cf/get :public-uri)
- :to email
- :invited-by (:fullname profile)
- :team (:name team)
- :token itoken
- :extra-data ptoken})
+ (let [props (-> (dissoc tprops :profile-id)
+ (audit/clean-props))
+ evname (if updated?
+ "update-team-invitation"
+ "create-team-invitation")
+ event (-> (audit/event-from-rpc-params params)
+ (assoc ::audit/name evname)
+ (assoc ::audit/props props))]
+ (audit/submit! cfg event))
- itoken))))
+ (when (allow-invitation-emails? member)
+ (eml/send! {::eml/conn conn
+ ::eml/factory eml/invite-to-team
+ :public-uri (cf/get :public-uri)
+ :to email
+ :invited-by (:fullname profile)
+ :team (:name team)
+ :token itoken
+ :extra-data ptoken}))
+
+ itoken)))))
(defn- add-member-to-team
[conn profile team role member]
diff --git a/backend/src/app/rpc/commands/viewer.clj b/backend/src/app/rpc/commands/viewer.clj
index 1eeb13818..7e471e4fc 100644
--- a/backend/src/app/rpc/commands/viewer.clj
+++ b/backend/src/app/rpc/commands/viewer.clj
@@ -16,7 +16,8 @@
[app.rpc.commands.teams :as teams]
[app.rpc.cond :as-alias cond]
[app.rpc.doc :as-alias doc]
- [app.util.services :as sv]))
+ [app.util.services :as sv]
+ [cuerdas.core :as str]))
;; --- QUERY: View Only Bundle
@@ -26,6 +27,27 @@
(update :pages (fn [pages] (filterv #(contains? allowed %) pages)))
(update :pages-index select-keys allowed)))
+(defn obfuscate-email
+ [email]
+ (let [[name domain]
+ (str/split email "@" 2)
+
+ [_ rest]
+ (str/split domain "." 2)
+
+ name
+ (if (> (count name) 3)
+ (str (subs name 0 1) (apply str (take (dec (count name)) (repeat "*"))))
+ "****")]
+
+ (str name "@****." rest)))
+
+(defn anonymize-member
+ [member]
+ (-> (select-keys member [:id :email :name :fullname :photo-id])
+ (update :email obfuscate-email)
+ (assoc :can-read true)))
+
(defn- get-view-only-bundle
[{:keys [::db/conn] :as cfg} {:keys [profile-id file-id ::perms] :as params}]
(let [file (files/get-file cfg file-id)
@@ -37,7 +59,10 @@
team (-> (db/get conn :team {:id (:team-id project)})
(teams/decode-row))
- members (teams/get-team-members conn (:team-id project))
+ members (cond->> (teams/get-team-members conn (:team-id project))
+ (= :share-link (:type perms))
+ (mapv anonymize-member))
+
member-ids (into #{} (map :id) members)
perms (assoc perms :in-team (contains? member-ids profile-id))
diff --git a/backend/src/app/srepl/helpers.clj b/backend/src/app/srepl/helpers.clj
index 2ea26e3bb..d8293253e 100644
--- a/backend/src/app/srepl/helpers.clj
+++ b/backend/src/app/srepl/helpers.clj
@@ -15,7 +15,8 @@
[app.features.components-v2 :as feat.comp-v2]
[app.main :as main]
[app.rpc.commands.files :as files]
- [app.rpc.commands.files-snapshot :as fsnap]))
+ [app.rpc.commands.files-snapshot :as fsnap]
+ [app.util.time :as dt]))
(def ^:dynamic *system* nil)
@@ -96,8 +97,11 @@
(let [conn (db/get-connection system)]
(->> (feat.comp-v2/get-and-lock-team-files conn team-id)
(reduce (fn [result file-id]
- (fsnap/create-file-snapshot! system nil file-id label)
- (inc result))
+ (let [file (fsnap/get-file-snapshots system file-id)]
+ (fsnap/create-file-snapshot! system file
+ {:label label
+ :created-by :admin})
+ (inc result)))
0))))
(defn restore-team-snapshot!
@@ -143,7 +147,10 @@
(cfv/validate-file-schema! file'))
(when (string? label)
- (fsnap/create-file-snapshot! system nil file-id label))
+ (fsnap/create-file-snapshot! system file
+ {:label label
+ :deleted-at (dt/in-future {:days 30})
+ :created-by :admin}))
(let [file' (update file' :revn inc)]
(bfc/update-file! system file')
diff --git a/backend/src/app/tasks/delete_object.clj b/backend/src/app/tasks/delete_object.clj
index b9939c8be..29370e61e 100644
--- a/backend/src/app/tasks/delete_object.clj
+++ b/backend/src/app/tasks/delete_object.clj
@@ -40,6 +40,11 @@
:file-id id
:cause cause))))
+ ;; Mark file change to be deleted
+ (db/update! conn :file-change
+ {:deleted-at deleted-at}
+ {:file-id id})
+
;; Mark file media objects to be deleted
(db/update! conn :file-media-object
{:deleted-at deleted-at}
diff --git a/backend/src/app/tasks/objects_gc.clj b/backend/src/app/tasks/objects_gc.clj
index e08bdce44..843feb1c0 100644
--- a/backend/src/app/tasks/objects_gc.clj
+++ b/backend/src/app/tasks/objects_gc.clj
@@ -26,7 +26,7 @@
(defn- delete-profiles!
[{:keys [::db/conn ::min-age ::chunk-size ::sto/storage] :as cfg}]
- (->> (db/cursor conn [sql:get-profiles min-age chunk-size] {:chunk-size 5})
+ (->> (db/plan conn [sql:get-profiles min-age chunk-size] {:fetch-size 5})
(reduce (fn [total {:keys [id photo-id]}]
(l/trc :hint "permanently delete" :rel "profile" :id (str id))
@@ -49,7 +49,7 @@
(defn- delete-teams!
[{:keys [::db/conn ::min-age ::chunk-size ::sto/storage] :as cfg}]
- (->> (db/cursor conn [sql:get-teams min-age chunk-size] {:chunk-size 5})
+ (->> (db/plan conn [sql:get-teams min-age chunk-size] {:fetch-size 5})
(reduce (fn [total {:keys [id photo-id deleted-at]}]
(l/trc :hint "permanently delete"
:rel "team"
@@ -77,7 +77,7 @@
(defn- delete-fonts!
[{:keys [::db/conn ::min-age ::chunk-size ::sto/storage] :as cfg}]
- (->> (db/cursor conn [sql:get-fonts min-age chunk-size] {:chunk-size 5})
+ (->> (db/plan conn [sql:get-fonts min-age chunk-size] {:fetch-size 5})
(reduce (fn [total {:keys [id team-id deleted-at] :as font}]
(l/trc :hint "permanently delete"
:rel "team-font-variant"
@@ -109,7 +109,7 @@
(defn- delete-projects!
[{:keys [::db/conn ::min-age ::chunk-size] :as cfg}]
- (->> (db/cursor conn [sql:get-projects min-age chunk-size] {:chunk-size 5})
+ (->> (db/plan conn [sql:get-projects min-age chunk-size] {:fetch-size 5})
(reduce (fn [total {:keys [id team-id deleted-at]}]
(l/trc :hint "permanently delete"
:rel "project"
@@ -135,7 +135,7 @@
(defn- delete-files!
[{:keys [::db/conn ::sto/storage ::min-age ::chunk-size] :as cfg}]
- (->> (db/cursor conn [sql:get-files min-age chunk-size] {:chunk-size 5})
+ (->> (db/plan conn [sql:get-files min-age chunk-size] {:fetch-size 5})
(reduce (fn [total {:keys [id deleted-at project-id] :as file}]
(l/trc :hint "permanently delete"
:rel "file"
@@ -164,7 +164,7 @@
(defn delete-file-thumbnails!
[{:keys [::db/conn ::min-age ::chunk-size ::sto/storage] :as cfg}]
- (->> (db/cursor conn [sql:get-file-thumbnails min-age chunk-size] {:chunk-size 5})
+ (->> (db/plan conn [sql:get-file-thumbnails min-age chunk-size] {:fetch-size 5})
(reduce (fn [total {:keys [file-id revn media-id deleted-at]}]
(l/trc :hint "permanently delete"
:rel "file-thumbnail"
@@ -193,7 +193,7 @@
(defn delete-file-object-thumbnails!
[{:keys [::db/conn ::min-age ::chunk-size ::sto/storage] :as cfg}]
- (->> (db/cursor conn [sql:get-file-object-thumbnails min-age chunk-size] {:chunk-size 5})
+ (->> (db/plan conn [sql:get-file-object-thumbnails min-age chunk-size] {:fetch-size 5})
(reduce (fn [total {:keys [file-id object-id media-id deleted-at]}]
(l/trc :hint "permanently delete"
:rel "file-tagged-object-thumbnail"
@@ -222,7 +222,7 @@
(defn- delete-file-data-fragments!
[{:keys [::db/conn ::sto/storage ::min-age ::chunk-size] :as cfg}]
- (->> (db/cursor conn [sql:get-file-data-fragments min-age chunk-size] {:chunk-size 5})
+ (->> (db/plan conn [sql:get-file-data-fragments min-age chunk-size] {:fetch-size 5})
(reduce (fn [total {:keys [file-id id deleted-at data-ref-id]}]
(l/trc :hint "permanently delete"
:rel "file-data-fragment"
@@ -248,7 +248,7 @@
(defn- delete-file-media-objects!
[{:keys [::db/conn ::min-age ::chunk-size ::sto/storage] :as cfg}]
- (->> (db/cursor conn [sql:get-file-media-objects min-age chunk-size] {:chunk-size 5})
+ (->> (db/plan conn [sql:get-file-media-objects min-age chunk-size] {:fetch-size 5})
(reduce (fn [total {:keys [id file-id deleted-at] :as fmo}]
(l/trc :hint "permanently delete"
:rel "file-media-object"
@@ -275,9 +275,9 @@
FOR UPDATE
SKIP LOCKED")
-(defn- delete-file-change!
+(defn- delete-file-changes!
[{:keys [::db/conn ::min-age ::chunk-size ::sto/storage] :as cfg}]
- (->> (db/cursor conn [sql:get-file-change min-age chunk-size] {:chunk-size 5})
+ (->> (db/plan conn [sql:get-file-change min-age chunk-size] {:fetch-size 5})
(reduce (fn [total {:keys [id file-id deleted-at] :as xlog}]
(l/trc :hint "permanently delete"
:rel "file-change"
@@ -299,11 +299,11 @@
#'delete-file-data-fragments!
#'delete-file-object-thumbnails!
#'delete-file-thumbnails!
+ #'delete-file-changes!
#'delete-files!
#'delete-projects!
#'delete-fonts!
- #'delete-teams!
- #'delete-file-change!])
+ #'delete-teams!])
(defn- execute-proc!
"A generic function that executes the specified proc iterativelly
@@ -326,7 +326,7 @@
[k v]
{k (assoc v
::min-age (cf/get-deletion-delay)
- ::chunk-size 50)})
+ ::chunk-size 100)})
(defmethod ig/init-key ::handler
[_ cfg]
diff --git a/backend/test/backend_tests/rpc_file_snapshot_test.clj b/backend/test/backend_tests/rpc_file_snapshot_test.clj
index 90e7366ee..1da63c4f3 100644
--- a/backend/test/backend_tests/rpc_file_snapshot_test.clj
+++ b/backend/test/backend_tests/rpc_file_snapshot_test.clj
@@ -97,6 +97,7 @@
(th/db-query :file-change
{:file-id (:id file)}
{:order-by [:created-at]})]
+
(t/is (= 2 (count rows)))
(t/is (= "user" (:created-by row1)))
(t/is (= "system" (:created-by row2)))))
diff --git a/common/src/app/common/files/helpers.cljc b/common/src/app/common/files/helpers.cljc
index f167601b6..e73aed5ea 100644
--- a/common/src/app/common/files/helpers.cljc
+++ b/common/src/app/common/files/helpers.cljc
@@ -589,10 +589,9 @@
(into xform:collect-media-refs (vals (:components data)))
(into (keys (:media data)))))
-(defn relink-media-refs
- "A function responsible to analyze all file data and replace the
- old :component-file reference with the new ones, using the provided
- file-index."
+(defn relink-refs
+ "A function responsible to analyze the file data or shape for references
+ and apply lookup-index on it."
[data lookup-index]
(letfn [(process-map-form [form]
(cond-> form
diff --git a/docs/img/dev-tools-1.png b/docs/img/dev-tools-1.png
new file mode 100644
index 000000000..11496cf77
Binary files /dev/null and b/docs/img/dev-tools-1.png differ
diff --git a/docs/img/dev-tools-2.png b/docs/img/dev-tools-2.png
new file mode 100644
index 000000000..125c0a7d7
Binary files /dev/null and b/docs/img/dev-tools-2.png differ
diff --git a/docs/img/penpot-report.png b/docs/img/penpot-report.png
new file mode 100644
index 000000000..3240691e4
Binary files /dev/null and b/docs/img/penpot-report.png differ
diff --git a/docs/technical-guide/getting-started.md b/docs/technical-guide/getting-started.md
index f0cff3c80..70248a62e 100644
--- a/docs/technical-guide/getting-started.md
+++ b/docs/technical-guide/getting-started.md
@@ -280,6 +280,65 @@ Postgres database and another one for the assets uploaded by your users (images
clips). There may be more volumes if you enable other features, as explained in the file
itself.
+### Troubleshooting
+
+Knowing how to do Penpot troubleshooting can be very useful; on the one hand, it helps to create issues easier to resolve, since they include relevant information from the beginning which also makes them get solved faster; on the other hand, many times troubleshooting gives the necessary information to resolve a problem autonomously, without even creating an issue.
+
+Troubleshooting requires patience and practice; you have to read the stacktrace carefully, even if it looks like a mess at first. It takes some practice to learn how to read the traces properly and extract important information.
+
+If your Penpot installation is not working as intended, there are several places to look up searching for hints:
+
+**Docker logs**
+
+Check if all containers are up and running:
+```bash
+docker compose -p penpot -f docker-compose.yaml ps
+```
+
+Check logs of all Penpot:
+```bash
+docker compose -p penpot -f docker-compose.yaml logs -f
+```
+
+If there is too much information and you'd like to check just one service at a time:
+```bash
+docker compose -p penpot -f docker-compose.yaml logs penpot-frontend -f
+```
+
+You can always check the logs form a specific container:
+```bash
+docker logs -f penpot-penpot-postgres-1
+```
+
+**Browser logs**
+
+The browser provides as well useful information to corner the issue.
+
+First, use the devtools to ensure which version and flags you're using. Go to your Penpot instance in the browser and press F12; you'll see the devtools. In the Console
, you can see the exact version that's being used.
+
+
+
+
+
+
+
+Other interesting tab in the devtools is the Network
tab, to check if there is a request that throws errors.
+
+
+
+
+
+
+
+**Penpot Report**
+
+When Penpot crashes, it provides a report with very useful information. Don't miss it!
+
+
+
+
+
+
## Install with Kubernetes
diff --git a/frontend/playwright/ui/specs/dashboard.spec.js b/frontend/playwright/ui/specs/dashboard.spec.js
index 58f2f07d0..768106634 100644
--- a/frontend/playwright/ui/specs/dashboard.spec.js
+++ b/frontend/playwright/ui/specs/dashboard.spec.js
@@ -105,10 +105,10 @@ test("Multiple elements in context", async ({ page }) => {
await button.click({ button: "right" });
- await expect(button.getByTestId("duplicate-multi")).toBeVisible();
- await expect(button.getByTestId("file-move-multi")).toBeVisible();
- await expect(button.getByTestId("file-binary-export-multi")).toBeVisible();
- await expect(button.getByTestId("file-delete-multi")).toBeVisible();
+ await expect(page.getByTestId("duplicate-multi")).toBeVisible();
+ await expect(page.getByTestId("file-move-multi")).toBeVisible();
+ await expect(page.getByTestId("file-binary-export-multi")).toBeVisible();
+ await expect(page.getByTestId("file-delete-multi")).toBeVisible();
});
test("User has create file button", async ({ page }) => {
diff --git a/frontend/src/app/main/ui/components/portal.cljs b/frontend/src/app/main/ui/components/portal.cljs
new file mode 100644
index 000000000..5fadf20e7
--- /dev/null
+++ b/frontend/src/app/main/ui/components/portal.cljs
@@ -0,0 +1,15 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns app.main.ui.components.portal
+ (:require
+ [rumext.v2 :as mf]))
+
+(mf/defc portal-on-document*
+ [{:keys [children]}]
+ (mf/portal
+ (mf/html [:* children])
+ (.-body js/document)))
diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs
index 7649a264d..d0d7bafde 100644
--- a/frontend/src/app/main/ui/dashboard.cljs
+++ b/frontend/src/app/main/ui/dashboard.cljs
@@ -59,9 +59,6 @@
permissions (:permissions team)
- dashboard-local (mf/deref refs/dashboard-local)
- file-menu-open? (:menu-open dashboard-local)
-
default-project-id
(get default-project :id)
@@ -87,7 +84,6 @@
(mf/use-effect on-resize)
[:div {:class (stl/css :dashboard-content)
- :style {:pointer-events (when file-menu-open? "none")}
:on-click clear-selected-fn
:ref container}
(case section
diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs
index f3ef69ca4..f882c2179 100644
--- a/frontend/src/app/main/ui/dashboard/file_menu.cljs
+++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs
@@ -55,8 +55,8 @@
projects))
(mf/defc file-menu*
- {::mf/props :obj}
[{:keys [files on-edit on-menu-close top left navigate origin parent-id can-edit]}]
+
(assert (seq files) "missing `files` prop")
(assert (fn? on-edit) "missing `on-edit` prop")
(assert (fn? on-menu-close) "missing `on-menu-close` prop")
diff --git a/frontend/src/app/main/ui/dashboard/files.cljs b/frontend/src/app/main/ui/dashboard/files.cljs
index 251a7bbd2..08d2230c6 100644
--- a/frontend/src/app/main/ui/dashboard/files.cljs
+++ b/frontend/src/app/main/ui/dashboard/files.cljs
@@ -13,7 +13,7 @@
[app.main.data.project :as dpj]
[app.main.refs :as refs]
[app.main.store :as st]
- [app.main.ui.dashboard.grid :refer [grid]]
+ [app.main.ui.dashboard.grid :refer [grid*]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.pin-button :refer [pin-button*]]
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
@@ -198,11 +198,11 @@
:subtitle (if is-draft-proyect
(tr "dashboard.empty-placeholder-drafts-subtitle")
(tr "dashboard.empty-placeholder-files-subtitle"))}]
- [:& grid {:project project
- :files files
- :selected-files selected-files
- :can-edit can-edit?
- :origin :files
- :create-fn create-file
- :limit limit}])]]))
+ [:> grid* {:project project
+ :files files
+ :selected-files selected-files
+ :can-edit can-edit?
+ :origin :files
+ :create-fn create-file
+ :limit limit}])]]))
diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs
index bc5963634..4a3112ca2 100644
--- a/frontend/src/app/main/ui/dashboard/grid.cljs
+++ b/frontend/src/app/main/ui/dashboard/grid.cljs
@@ -25,6 +25,7 @@
[app.main.repo :as rp]
[app.main.store :as st]
[app.main.ui.components.color-bullet :as bc]
+ [app.main.ui.components.portal :refer [portal-on-document*]]
[app.main.ui.dashboard.file-menu :refer [file-menu*]]
[app.main.ui.dashboard.import :refer [use-import-file]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
@@ -241,29 +242,37 @@
counter-el))
(mf/defc grid-item*
- {::mf/props :obj}
[{:keys [file origin can-edit selected-files]}]
- (let [file-id (:id file)
+ (let [file-id (get file :id)
+ state (mf/deref refs/dashboard-local)
- is-library-view (= origin :libraries)
+ menu-pos
+ (get state :menu-pos)
- dashboard-local (mf/deref refs/dashboard-local)
- file-menu-open? (:menu-open dashboard-local)
+ menu-open?
+ (and (get state :menu-open)
+ (= file-id (:file-id state)))
- selected? (contains? selected-files file-id)
+ selected?
+ (contains? selected-files file-id)
- node-ref (mf/use-ref)
- menu-ref (mf/use-ref)
+ selected-num
+ (count selected-files)
+
+ node-ref (mf/use-ref)
+ menu-ref (mf/use-ref)
+
+ is-library-view?
+ (= origin :libraries)
on-menu-close
- (mf/use-fn
- (fn [_]
- (st/emit! (dd/hide-file-menu))))
+ (mf/use-fn #(st/emit! (dd/hide-file-menu)))
on-select
(mf/use-fn
+ (mf/deps selected? selected-num)
(fn [event]
- (when (or (not selected?) (> (count selected-files) 1))
+ (when (or (not selected?) (> selected-num 1))
(dom/stop-propagation event)
(let [shift? (kbd/shift? event)]
(when-not shift?
@@ -281,41 +290,36 @@
on-drag-start
(mf/use-fn
- (mf/deps selected-files can-edit)
+ (mf/deps selected? selected-num)
(fn [event]
(st/emit! (dd/hide-file-menu))
(when can-edit
- (let [offset (dom/get-offset-position (dom/event->native-event event))
-
- select-current? (not (contains? selected-files (:id file)))
-
- item-el (mf/ref-val node-ref)
- counter-el (create-counter-element
- item-el
- (if select-current?
- 1
- (count selected-files)))]
- (when select-current?
+ (let [offset (dom/get-offset-position (dom/event->native-event event))
+ item-el (mf/ref-val node-ref)
+ counter-el (create-counter-element item-el
+ (if (not selected?)
+ 1
+ selected-num))]
+ (when (not selected?)
(st/emit! (dd/clear-selected-files))
(st/emit! (dd/toggle-file-select file)))
(dnd/set-data! event "penpot/files" "dummy")
(dnd/set-allowed-effect! event "move")
- ;; set-drag-image requires that the element is rendered and
- ;; visible to the user at the moment of creating the ghost
- ;; image (to make a snapshot), but you may remove it right
- ;; afterwards, in the next render cycle.
+ ;; set-drag-image requires that the element is rendered
+ ;; and visible to the user at the moment of creating the
+ ;; ghost image (to make a snapshot), but you may remove
+ ;; it right afterwards, in the next render cycle.
(dom/append-child! item-el counter-el)
(dnd/set-drag-image! event item-el (:x offset) (:y offset))
- (ts/raf #(.removeChild ^js item-el counter-el))))))
+ (ts/raf #(dom/remove-child! item-el counter-el))))))
on-menu-click
(mf/use-fn
(mf/deps file selected?)
(fn [event]
(dom/stop-propagation event)
- (dom/prevent-default event)
(when-not selected?
(when-not (kbd/shift? event)
@@ -339,12 +343,10 @@
on-context-menu
(mf/use-fn
- (mf/deps on-menu-click is-library-view)
+ (mf/deps on-menu-click)
(fn [event]
- (dom/stop-propagation event)
(dom/prevent-default event)
- (when-not is-library-view
- (on-menu-click event))))
+ (on-menu-click event)))
edit
(mf/use-fn
@@ -362,8 +364,8 @@
(dom/stop-propagation event)
(st/emit! (dd/start-edit-file-name file-id))))
- handle-key-down
- (mf/use-callback
+ on-key-down
+ (mf/use-fn
(mf/deps on-navigate on-select)
(fn [event]
(dom/stop-propagation event)
@@ -371,63 +373,70 @@
(on-navigate event))
(when (kbd/shift? event)
(when (or (kbd/down-arrow? event) (kbd/left-arrow? event) (kbd/up-arrow? event) (kbd/right-arrow? event))
- (on-select event)) ;; TODO Fix this
- )))]
+ ;; TODO Fix this
+ (on-select event)))))
+
+ on-menu-key-down
+ (mf/use-fn
+ (mf/deps on-menu-click)
+ (fn [event]
+ (when (kbd/enter? event)
+ (dom/stop-propagation event)
+ (dom/prevent-default event)
+ (on-menu-click event))))]
[:li {:class (stl/css-case :grid-item true
:project-th true
- :library is-library-view)}
+ :library is-library-view?)}
[:div
{:class (stl/css-case :selected selected?
- :library is-library-view)
+ :library is-library-view?)
:ref node-ref
:role "button"
:title (:name file)
:draggable (dm/str can-edit)
:on-click on-select
- :on-key-down handle-key-down
+ :on-key-down on-key-down
:on-double-click on-navigate
:on-drag-start on-drag-start
:on-context-menu on-context-menu}
[:div {:class (stl/css :overlay)}]
- (if ^boolean is-library-view
+ (if ^boolean is-library-view?
[:> grid-item-library* {:file file}]
[:> grid-item-thumbnail* {:file file :can-edit can-edit}])
- (when (and (:is-shared file) (not is-library-view))
+ (when (and (:is-shared file) (not is-library-view?))
[:div {:class (stl/css :item-badge)} i/library])
[:div {:class (stl/css :info-wrapper)}
[:div {:class (stl/css :item-info)}
- (if (and (= file-id (:file-id dashboard-local)) (:edition dashboard-local))
+ (if (and (= file-id (:file-id state)) (:edition state))
[:& inline-edition {:content (:name file)
:on-end edit}]
[:h3 (:name file)])
[:& grid-item-metadata {:modified-at (:modified-at file)}]]
- [:div {:class (stl/css-case :project-th-actions true :force-display (:menu-open dashboard-local))}
+ [:div {:class (stl/css-case :project-th-actions true :force-display menu-open?)}
[:div
{:class (stl/css :project-th-icon :menu)
:tab-index "0"
:role "button"
:aria-label (tr "dashboard.options")
:ref menu-ref
- :id (str file-id "-action-menu")
+ :id (dm/str file-id "-action-menu")
:on-click on-menu-click
- :on-key-down (fn [event]
- (when (kbd/enter? event)
- (dom/stop-propagation event)
- (on-menu-click event)))}
+ :on-key-down on-menu-key-down}
+
menu-icon
- (when (and selected? file-menu-open?)
+ (when (and selected? menu-open?)
;; When the menu is open we disable events in the dashboard. We need to force pointer events
;; so the menu can be handled
- [:div {:style {:pointer-events "all"}}
+ [:> portal-on-document* {}
[:> file-menu* {:files (vals selected-files)
- :left (+ 24 (:x (:menu-pos dashboard-local)))
- :top (:y (:menu-pos dashboard-local))
+ :left (+ 24 (:x menu-pos))
+ :top (:y menu-pos)
:can-edit can-edit
:navigate true
:on-edit on-edit
@@ -435,7 +444,7 @@
:origin origin
:parent-id (dm/str file-id "-action-menu")}]])]]]]]))
-(mf/defc grid
+(mf/defc grid*
{::mf/props :obj}
[{:keys [files project origin limit create-fn can-edit selected-files]}]
(let [dragging? (mf/use-state false)
@@ -455,6 +464,9 @@
import-files
(use-import-file project-id on-finish-import)
+ on-scroll
+ (mf/use-fn #(st/emit! (dd/hide-file-menu)))
+
on-drag-enter
(mf/use-fn
(fn [e]
@@ -496,6 +508,7 @@
:on-drag-over on-drag-over
:on-drag-leave on-drag-leave
:on-drop on-drop
+ :on-scroll on-scroll
:ref node-ref}
(cond
(nil? files)
diff --git a/frontend/src/app/main/ui/dashboard/libraries.cljs b/frontend/src/app/main/ui/dashboard/libraries.cljs
index dd9464c60..3f0dbee34 100644
--- a/frontend/src/app/main/ui/dashboard/libraries.cljs
+++ b/frontend/src/app/main/ui/dashboard/libraries.cljs
@@ -11,7 +11,7 @@
[app.main.data.team :as dtm]
[app.main.refs :as refs]
[app.main.store :as st]
- [app.main.ui.dashboard.grid :refer [grid]]
+ [app.main.ui.dashboard.grid :refer [grid*]]
[app.main.ui.hooks :as hooks]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
@@ -67,10 +67,10 @@
[:section {:class (stl/css :dashboard-container :no-bg :dashboard-shared)
:ref rowref}
- [:& grid {:files files
- :selected-files selected-files
- :project default-project
- :origin :libraries
- :limit limit
- :can-edit can-edit}]]]))
+ [:> grid* {:files files
+ :selected-files selected-files
+ :project default-project
+ :origin :libraries
+ :limit limit
+ :can-edit can-edit}]]]))
diff --git a/frontend/src/app/main/ui/dashboard/search.cljs b/frontend/src/app/main/ui/dashboard/search.cljs
index 0ad67d06a..76ec19777 100644
--- a/frontend/src/app/main/ui/dashboard/search.cljs
+++ b/frontend/src/app/main/ui/dashboard/search.cljs
@@ -11,7 +11,7 @@
[app.main.data.dashboard :as dd]
[app.main.refs :as refs]
[app.main.store :as st]
- [app.main.ui.dashboard.grid :refer [grid]]
+ [app.main.ui.dashboard.grid :refer [grid*]]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
@@ -77,7 +77,7 @@
[:div {:class (stl/css :text)} (tr "dashboard.no-matches-for" search-term)]]
:else
- [:& grid {:files result
- :selected-files selected
- :origin :search
- :limit limit}])]]))
+ [:> grid* {:files result
+ :selected-files selected
+ :origin :search
+ :limit limit}])]]))
diff --git a/frontend/src/app/main/ui/releases/v2_5.cljs b/frontend/src/app/main/ui/releases/v2_5.cljs
index fcbddc92c..c39c4aebb 100644
--- a/frontend/src/app/main/ui/releases/v2_5.cljs
+++ b/frontend/src/app/main/ui/releases/v2_5.cljs
@@ -38,10 +38,10 @@
"We’re thrilled to introduce Penpot 2.5"]
[:p {:class (stl/css :feature-content)}
- "This release brings multi-step gradients, along with comment notifications, making it easier than ever to communicate with your team members. Now you also can easily copy/paste groups of styles between layers and share direct links to specific boards, among other new capabilities considered true gems for designers and team collaboration."]
+ "Packed with powerful new features and little big details. This release brings multi-step gradients, along with comment notifications, making it easier than ever to communicate with your team members. Now you also can easily copy/paste groups of styles between layers and share direct links to specific boards, among other new capabilities considered true gems for designers and team collaboration."]
[:p {:class (stl/css :feature-content)}
- "But that’s not all—we’ve also tackled numerous bug fixes and optimizations that will improve performance when working with long texts."]
+ "But that’s not all—we’ve also tackled numerous bug fixes and optimizations."]
[:p {:class (stl/css :feature-content)}
"Let’s dive in!"]]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
index d0d8ca8be..066e1d4ce 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
@@ -92,7 +92,7 @@
reverse-sort? (= :desc ordering)
libs (mf/deref refs/libraries)
num-libs (count libs)
- file (get libs (:id file-id))
+ file (get libs file-id)
components (mf/with-memo [file] (ctkl/components (:data file)))
toggle-ordering
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
index c4ef3a93d..3bce96473 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
@@ -108,6 +108,9 @@
(filter-fonts state fonts))
recent-fonts (mf/deref refs/recent-fonts)
+ recent-fonts (mf/with-memo [state recent-fonts]
+ (filter-fonts state recent-fonts))
+
full-size? (boolean (and full-size show-recent))