Compare commits

..

172 Commits

Author SHA1 Message Date
Alejandro Alonso
214a89e20d 📎 Update CHANGES.md file 2025-03-03 07:13:23 +01:00
Yamila Moreno
e64cf9f283 Merge pull request #5908 from penpot/yms-proxy-documentation
📚 Document how to use a proxy
2025-02-28 17:18:46 +01:00
Marina López
3a34c51e43 Add pricing page event 2025-02-28 13:04:15 +01:00
Yamila Moreno
0ff9c44246 🐳 Improve nginx resolvers (#5967) 2025-02-28 09:02:40 +01:00
Yamila Moreno
5bfab454f5 📚 Document how to use a proxy - caddy 2025-02-28 08:43:40 +01:00
Yamila Moreno
5ebde405ea 📚 Document how to use a proxy - nginx 2025-02-28 08:43:40 +01:00
Alejandro
531b002a5c Merge pull request #5976 from penpot/hotfix-texts
🐛 Fix problems with empty position-data
2025-02-27 15:33:25 +01:00
alonso.torres
3eae3178a2 🐛 Fix problems with empty position-data 2025-02-27 14:31:49 +01:00
luisδμ
2cf3e37b7a 🐛 Fix comment update fails in viewer (#5958)
* 🐛 Fix comment update fails in viewer

* 🐛 Reload team members in workspace but not in viewer
2025-02-26 14:29:51 +01:00
luisδμ
e0b9751b16 Merge pull request #5947 from penpot/luis-fix-design-panel-draft-comment-open
🐛 Fix design panel does not reappear if comment draft is open
2025-02-26 11:33:06 +01:00
Alejandro
ccea9b1564 Merge pull request #5962 from penpot/elenatorro-9339-fix-typo-libraries-es
🐛 Fix typo at libraries modal
2025-02-26 09:32:23 +01:00
luisδμ
5fcf889d3c Merge pull request #5960 from penpot/luis-fix-avoid-post-blank-comment
🐛 Fix avoid enabling post button if blank comment
2025-02-26 09:28:59 +01:00
luisδμ
7247db14b2 Merge pull request #5957 from penpot/luis-fix-mention-users-in-viewer
🐛 Fix mention users in viewer
2025-02-26 09:28:29 +01:00
elenatorro
658e5dce22 🐛 Fix ES typo in libraries modal 2025-02-26 09:22:45 +01:00
Elena Torró
f27cbfa0ec Merge pull request #5953 from penpot/marina-fix-typo-libraries
🐛 Fix typo at libraries modal
2025-02-26 09:12:21 +01:00
Luis de Dios
5754c393b9 🐛 Fix avoid enabling post button if blank comment 2025-02-25 18:09:22 +01:00
Luis de Dios
c618efc29e 🐛 Fix mention users in viewer 2025-02-25 15:43:29 +01:00
Marina López
3685f7b32b 🐛 Fix typo at libraries modal 2025-02-25 13:41:26 +01:00
andrés gonzález
06b5304926 📚 New section for Your account (#5941)
* 📚 New section for Your account

* Update docs/user-guide/the-interface/index.njk

Co-authored-by: Madalena Melo <madalena.melo@kaleidos.net>

* Update docs/user-guide/the-interface/index.njk

Co-authored-by: Madalena Melo <madalena.melo@kaleidos.net>

* Update docs/user-guide/the-interface/index.njk

Co-authored-by: Madalena Melo <madalena.melo@kaleidos.net>

---------

Co-authored-by: Madalena Melo <madalena.melo@kaleidos.net>
2025-02-25 13:05:49 +01:00
Marina López
8f06fa1026 🎉 Add AB test descriptive board tooltip (#5936)
* 🎉 Add AB test descriptive board tooltip

* 📎 Add changes from feedback
2025-02-25 12:50:16 +01:00
Andrey Antukh
a549d783ba Downgrade s3 sdk for compatibility with minio (#5946)
* 📚 Add `⬇️` emoji to the contributing guide

For cases where we need to downgrade dependencies

*  Downgrade S3 SDK version

Mainly for minio and other S3-compatible services
2025-02-25 12:49:33 +01:00
Luis de Dios
91efcd17a2 🐛 Fix design panel does not reappear if comment draft is open 2025-02-25 10:30:11 +01:00
Andrey Antukh
6c1e8c3fe8 ⬆️ Update deps (fix compat issues with minio) 2025-02-25 09:39:13 +01:00
luisδμ
7f9a9ad774 🐛 Fix visual glitches in the comment dropdown at the dashboard (#5938) 2025-02-25 09:17:42 +01:00
luisδμ
2219d91e4d 🐛 Fix all comments appear in every page (#5943) 2025-02-25 09:16:46 +01:00
Yamila Moreno
fac2314d62 🔧 Relax secure cookies requirement when non-https public uri is set (#5939) 2025-02-25 09:10:53 +01:00
Yamila Moreno
aac61ff229 📚 Document how to troubleshoot Penpot (#5937) 2025-02-24 12:07:47 +01:00
Andrey Antukh
15d09eb0d4 🐛 Fix incorrect data id assignation on creating a snapshot (#5934)
* 📎 Set proper name to relink-refs mechanism function

* 🐛 Fix incorrect id assignation on snapshot file resolution

* ♻️ Use uniform api for file retrieval on file snapshot code
2025-02-24 11:05:16 +01:00
Alejandro
786383c25d Merge pull request #5935 from penpot/niwinz-viewer-bugfix
🐛 Fix incorrect data returned on viewer subapp bundle
2025-02-24 10:41:17 +01:00
elhombretecla
662c3c64a9 Fix 2.5 slides wording (#5925) 2025-02-24 09:39:43 +01:00
Juanfran
9084c184e7 🐛 Filter out recent fonts from search results (#5927) 2025-02-24 09:12:12 +01:00
Andrey Antukh
ae718c3328 🐛 Fix incorrect data returned on viewer subapp bundle 2025-02-24 09:03:57 +01:00
Marina López
702bd41047 🐛 Fix error when getting file from libs 2025-02-24 08:26:27 +01:00
Aitor Moreno
9896275fa8 Merge pull request #5932 from penpot/niwinz-objects-gc-bugfix
 Remove automatic cascade on file_change table fk constraint
2025-02-21 14:32:24 +01:00
Andrey Antukh
d2c800fc0f 🐛 Add missing handling of file_change on delete-object task 2025-02-21 14:24:18 +01:00
Andrey Antukh
893f19fa5e Remove automatic cascade on file_change table fk constraint 2025-02-21 14:24:07 +01:00
Andrey Antukh
624750ad16 🐛 Fix incorrect order of execution of internal procs on objects-gc (#5929)
*  Improve efficiency on objects-gc tasks

Replacing db/cursor with db/plan

* 🐛 Use correct order of subprocs on objects-gc

Mainly affects the file deletion

* 📎 Increase chunk-size to 100 on objects-gc
2025-02-21 13:01:20 +01:00
Andrey Antukh
24cb1728b0 🐛 Fix context menu event handling issues (#5917)
* 💄 Change call convention for dashboard grid component

* 🎉 Add helper component for easy portal to document

* 🐛 Fix context menu event handling issues

With this commit, the behavior of context menu and scroll is changed
to: close menu on scroll instead of disabling all pointer events while
menu is open. The previous behavior causes a second event of context
menu open a native browser context menu instead of penpot menu.
2025-02-21 07:57:56 +01:00
Eva Marco
dda9f62504 🐛 Fix menu shadow color 2025-02-20 17:23:28 +01:00
Andrey Antukh
479f39338b 🐛 Don't send invitation email to profiles that explicitly disallow that
* 🐛 Don't send invitation email to profiles that explicitly disallow that

* 📎 Add changes post feedback

* 📎 Fix typo on changelog
2025-02-20 09:20:28 +01:00
Alejandro
befa5f4c7f Merge pull request #5906 from penpot/niwinz-libraries-fix
🐛 Fix incorrect libraries filtering on workspace
2025-02-20 07:40:24 +01:00
Andrey Antukh
6e92e3b765 🐛 Fix inconsistency on naming
This also a fix of passing incorrect prop :shared-libs
to a component that already expectes :libraries.

It also removes unnecesary use of refs/libraries ref
2025-02-20 07:32:11 +01:00
Andrey Antukh
0e73de17ec 🐛 Fix incorrect libraries filtering on workspace 2025-02-20 07:32:10 +01:00
Alejandro
2dcf692853 Merge pull request #5911 from penpot/niwinz-notifications-post-save-refresh
🐛 Fix incorrect notification assignation after update operation
2025-02-20 07:06:49 +01:00
Alejandro
66f2e0aa5e Merge pull request #5912 from penpot/niwinz-binfile-v3-fixes
🐛 Add proper feature handling for binfile imports
2025-02-20 07:03:59 +01:00
Andrey Antukh
dd6ae81e83 🐛 Add correct feature handling on dbg binfile import 2025-02-19 22:47:55 +01:00
Andrey Antukh
cb8e31e7f8 🐛 Add correct handling of features on clone-template 2025-02-19 22:47:54 +01:00
Andrey Antukh
ca9b5b1b8a 📎 Use standard asserts on binfile common ns 2025-02-19 22:47:54 +01:00
Andrey Antukh
a391d71b60 🐛 Add correct feature handling on binfile import 2025-02-19 22:47:40 +01:00
Andrey Antukh
7d0c19fcc7 🐛 Add correct feature check on manifest reading
Instead on the file save operation so we can raise
exception if something does not match without processing
the whole file
2025-02-19 22:45:08 +01:00
Andrey Antukh
e4ee585704 🐛 Fix incorrect notification assignation after update operation 2025-02-19 17:21:52 +01:00
Marina López
5f61254a75 🐛 Fix library button condition and copy (#5889)
* 🐛 Fix library button condition and copy

* 📎 Add changes from feedback

* 📎 Add changes from feedback
2025-02-19 16:52:35 +01:00
luisδμ
0784d6b62a 🐛 Fix reposition comment bubbles under viewer role (#5905) 2025-02-19 16:47:53 +01:00
Alejandro
7a7fa44f6b 🐛 Fix click prototype flow (#5896) 2025-02-19 16:00:17 +01:00
Yamila Moreno
4b5d304a40 📚 Improve technical guide
* 📚 Improve technical guide

* 📚 Improve technical guide

* 📚 Improve technical guide

* 📚 Improve technical guide

* 📚 Improve technical guide

* 📚 Improve technical guide

* 📚 Improve technical guide

* 📚 Improve technical guide

* 📚 Improve technical guide

* 📚 Improve technical guide
2025-02-19 14:35:40 +01:00
alonso.torres
e7b9ae6415 🐛 Remove fit content shortcut 2025-02-19 13:02:53 +01:00
luisδμ
4ac52c138c Merge pull request #5895 from penpot/niwinz-bugfix-comments
🐛 Fix unexpected exception on clicking empty area on creating comment
2025-02-19 11:29:21 +01:00
Andrey Antukh
4744085426 🐛 Fix incorrect handling request access with deleted profiles
* 📎 Add minor improvements to team tests

* 🐛 Fix incorrect handling request access with deleted profiles

* 🐛 Fix redirect loop on empty route

Happens when the current profile is deleted from team

* 🐛 Fix urls on request access emails

* 📎 Revert url changes on emails
2025-02-19 11:04:19 +01:00
Alejandro
19bae05f41 Merge pull request #5884 from penpot/niwinz-bugfix-4
🐛 Fix update-libraries dialog disappear when clicking outside
2025-02-19 07:53:35 +01:00
Alejandro
02f78d80d7 Merge pull request #5883 from penpot/niwinz-bugfix-3
🐛 Fix incorrect navigation on show-main-component menu option
2025-02-19 07:42:02 +01:00
Alejandro
51202df105 Merge pull request #5885 from penpot/niwinz-bugfix-5
🐛 Fix regression on request-access dialog
2025-02-19 07:15:30 +01:00
Andrey Antukh
cd1eefb214 Add safety checks for node on comment-input* component 2025-02-18 18:33:02 +01:00
Andrey Antukh
869a412c74 🐛 Fix unexpected exception on clicking empty area on creating comment 2025-02-18 18:19:08 +01:00
luisδμ
d019afe667 🐛 Fix incorrect number of replies in comments (#5893) 2025-02-18 17:25:43 +01:00
Andrey Antukh
c41aa56a60 Merge pull request #5869 from penpot/marina-empty-workspace-create-board-tool-default
🎉 Add AB test for empty workspace set board tool by default
2025-02-18 17:24:52 +01:00
Andrey Antukh
7d840722c4 Add abstraction for page emptiness checking 2025-02-18 16:59:00 +01:00
Marina López
272bbdd54a 🎉 Add AB test for empty workspace set board tool by default 2025-02-18 16:57:59 +01:00
Andrey Antukh
fe3fec7a50 🐛 Fix workspace hot reload race condtion
This reverts commit 8139ee3ef9.
2025-02-18 16:49:50 +01:00
Andrey Antukh
63524dce8d 🐛 Fix regression on request-access dialog 2025-02-18 12:37:04 +01:00
Yamila Moreno
807b8d82e3 🔧 Improve flags documentation (#5863)
* 📎 Fix typo

* 🔧 Enable certain flags by default

* 🔧 Compile all flags in a single source of truth

* 📎 Move all default flags to common

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-02-18 12:36:16 +01:00
Andrey Antukh
3f45863823 🐛 Fix update-libraries dialog disappear when clicking outside 2025-02-18 12:04:10 +01:00
Andrey Antukh
f9f5f0af7d ♻️ Refactor code style on actionable ds component 2025-02-18 12:04:09 +01:00
Andrey Antukh
f98dbef228 Simplify code and use lookup helpers 2025-02-18 12:04:09 +01:00
Andrey Antukh
713d6a31df Add better way to delay a stream for library notification 2025-02-18 12:04:09 +01:00
Andrey Antukh
77f906ae37 Improve notification show params validation assertion 2025-02-18 12:04:09 +01:00
María Valderrama
6a5538bb15 🐛 Fix unreachable Save color style button (#5879)
* 🐛 Fix unreachable Save color style button

* 📎 Fix unreachable Save color style button code review
2025-02-18 11:32:00 +01:00
Andrey Antukh
0ce99968b3 🐛 Fix incorrect navigation on show-main-component menu option 2025-02-18 10:53:32 +01:00
Alonso Torres
0900b7a572 🐛 Fix problem with grid layout duplicate positioning (#5877) 2025-02-18 10:17:42 +01:00
Andrey Antukh
3412a0a18a Merge pull request #5868 from penpot/niwinz-terms-link-fix
🐛 Set correct default for terms link on fonts hero
2025-02-18 09:55:52 +01:00
Eva Marco
5e3b47e455 🎉 Add integration test for bug (#5875) 2025-02-17 16:29:28 +01:00
Andrey Antukh
83423a9509 Merge pull request #5864 from penpot/eva-fix-colorpicker-dnd
🐛 Fix add recent color while drag and drop
2025-02-17 15:22:49 +01:00
Alonso Torres
ccabaf4552 🐛 Fix style problem with update lib notifications (#5871) 2025-02-17 13:31:29 +01:00
Alonso Torres
ad15ac6c1e 🐛 Fix several problems with navigation in viewer (#5872) 2025-02-17 13:24:15 +01:00
luisδμ
a9340709c8 🐛 Fix open comment in workspace from dashboard notification (#5865) 2025-02-17 12:29:19 +01:00
Alonso Torres
faa3451da9 🐛 Fix problem with board name input style (#5870) 2025-02-17 12:26:04 +01:00
Alonso Torres
0aa95ea058 🐛 Fix problem with copy/paste props (#5867) 2025-02-17 12:25:42 +01:00
Andrey Antukh
66182152cb 🐛 Set correct default for terms link 2025-02-17 11:51:21 +01:00
Andrey Antukh
b9629b7be6 🔥 Remove unused default flags on frontend 2025-02-17 11:23:46 +01:00
Eva Marco
6c9875e4f9 🐛 Fix add recent color while drag and drop 2025-02-17 10:20:40 +01:00
Andrey Antukh
f90c63b5f0 Merge pull request #5834 from penpot/juan-slides-2.5
🎉 Add slides for 2.5 release
2025-02-14 15:52:57 +01:00
Elhombretecla
680e611266 🎉 Add slides for 2.5 version 2025-02-14 15:41:27 +01:00
Belén Albeza
cad7d75590 🐛 Fix libraries context menu (#5854)
*  Add integration test for Bug #10421

* 🐛 Fix dashboard library item menu

*  Fixup integration test
2025-02-14 14:34:54 +01:00
luisδμ
8c81d48858 Merge pull request #5844 from penpot/luis-refactor-zero-width-space
♻️ Use constant for zero width space
2025-02-14 14:06:28 +01:00
Eva Marco
a7ed5228d3 🐛 Fix lost translation strings (#5846)
* 🐛 Fix lost translation strings

* 🐛 Fix form error management internal issues and inconsistencies

* 📎 Add better validation conditons for ::sm/text schema

* 🐛 Add better touched detection mechanism for input and textarea

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-02-14 12:51:14 +01:00
Eva Marco
6bb7fa26f4 🐛 Fix rename blocked boards (#5845) 2025-02-14 11:47:16 +01:00
Andrey Antukh
8b6a9b373d Improve efficiency and logging on process-files! srepl helper 2025-02-14 10:02:34 +01:00
Andrey Antukh
8139ee3ef9 Revert "🐛 Fix workspace hot reload race condtion (#5851)"
This reverts commit 5d56d28cb6.
2025-02-14 10:02:05 +01:00
Marina López
af93325fd9 🎉 Consolidate suggested libraries and add library button (#5828) 2025-02-13 20:06:28 +01:00
Pablo Alba
d836cc66da 🐛 Fix unable to drag & drop assets into/outside component groups (#5849) 2025-02-13 17:30:33 +01:00
Andrey Antukh
5d56d28cb6 🐛 Fix workspace hot reload race condtion (#5851)
Mainly ensure that all required paramers for workspace
file and page bootstrap are always available from parameters
and not taken from context
2025-02-13 17:04:34 +01:00
Eva Marco
46d2359107 🐛 Fix empty translation strings (#5847) 2025-02-13 15:48:44 +01:00
Alejandro
f8820695cc 🐛 Fix incorrect numbering files when new team (#5835) 2025-02-13 11:55:59 +01:00
andrés gonzález
2d1d1fee1c 📚 Info about gradients (#5843) 2025-02-13 10:42:56 +01:00
Luis de Dios
4c6f086f82 ♻️ Use constant for zero width space 2025-02-13 09:17:09 +01:00
Alonso Torres
688b9f2194 🐛 Fix focus to main component (#5842) 2025-02-12 17:16:14 +01:00
luisδμ
8992eb98ec Merge pull request #5841 from penpot/luis-fix-comments-mentions-keyboard
🐛 Fix keyboard interactions with mentions
2025-02-12 17:03:44 +01:00
Luis de Dios
638a8a8d3f 🐛 Fix keyboard interactions with mentions 2025-02-12 16:37:51 +01:00
Belén Albeza
fb6cd3d9d4 🐛 Fix "Publish empty library" modal appearing for non-empty libraries (#5838)
* 🐛 Fix 'Publish empty library' modal appearing for non-empty libraries

*  Add integration test for bug 10113
2025-02-12 15:36:01 +01:00
Alonso Torres
fb0e22c16b 🐛 Fix problem with team permissions redirection (#5839) 2025-02-12 15:35:28 +01:00
Alejandro
6b26adb187 🐛 Fix team doesn't disappear after deletion (#5832) 2025-02-12 14:36:46 +01:00
Alonso Torres
8fe1271690 🐛 Fix problem opening url when page-id didn't exist (#5833)
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-02-12 14:36:05 +01:00
Alonso Torres
ceb90cd9e0 🐛 Fix problem with dashboard multiple selection (#5836) 2025-02-12 14:34:05 +01:00
Alonso Torres
51f924a5e1 🐛 Fix problem with onboarding team invite (#5829)
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-02-12 13:09:37 +01:00
Alonso Torres
fb24a37e83 🐛 Fix problem with grid layout crashing (#5831) 2025-02-12 13:07:41 +01:00
Andrey Antukh
4f38d258b5 Merge pull request #5822 from penpot/niwinz-add-features-check-report
 Report restriction errors to logger
2025-02-11 16:00:32 +01:00
Andrey Antukh
6feae7f359 🐛 Register media-refs on mod-obj 2025-02-11 15:44:12 +01:00
Andrey Antukh
a3bc4ff9f3 🐛 Prevent exception when no file is found on process file srepl helper 2025-02-11 15:43:35 +01:00
Andrey Antukh
df7dd15705 🐛 Make the absorb library aware that file can be nil 2025-02-11 15:42:55 +01:00
Andrey Antukh
8dbf6adfda 🐛 Pass correct media-ref object on fixing media ref script 2025-02-11 15:42:21 +01:00
Andrey Antukh
35f44a6eb4 🐛 Remove duplicate decoding on srepl helper get-file 2025-02-11 14:44:37 +01:00
Andrey Antukh
895450c9d5 Report restriction errors to logger 2025-02-11 13:52:12 +01:00
Andrey Antukh
36518e0e6e Fix linter issues 2025-02-11 13:52:12 +01:00
andrés gonzález
898e66fd18 📚 Add info about direct links to boards (#5815) 2025-02-11 10:12:17 +01:00
Aitor Moreno
db55c06c67 🐛 Fix pasted text loses font-family style (#5808) 2025-02-11 10:07:59 +01:00
luisδμ
7eefbc5979 💄 Improve readability of some messages in the browser console (#5814) 2025-02-10 20:05:48 +01:00
luisδμ
bed79d7c27 Merge pull request #5817 from penpot/elenatorro-10135-change-copy-link-text
📎 Make 'Copy link' the default text when copying to clipboard
2025-02-10 18:07:28 +01:00
elenatorro
830be55ee8 📎 Make 'Copy link' the default text when copying to clipboard 2025-02-10 17:47:46 +01:00
andrés gonzález
7d52b55d21 📚 Add info for copy/paste options (#5803) 2025-02-10 16:18:34 +01:00
luisδμ
7fd0ca2243 🐛 Fix allow a comment length of 750 chars and show error text if exceeded (#5810) 2025-02-10 15:03:07 +01:00
Andrey Antukh
f173e15bb3 Merge branch 'main' into staging 2025-02-10 12:00:57 +01:00
Andrey Antukh
ccce3bcb8f 🐛 Fix exporter build script 2025-02-10 11:40:14 +01:00
Andrey Antukh
6ca1a84557 🐛 Backport build issue from staging
Related to the devenv update
2025-02-10 10:59:29 +01:00
andrés gonzález
debad85d6d 📚 Add info about fit board to content (#5800) 2025-02-07 16:41:04 +01:00
luisδμ
92aa615da4 🐛 Fix users from another teams appear in mentions (#5789) 2025-02-07 12:33:29 +01:00
Eva Marco
b991a94685 🐛 Fix icon visualization on select component (#5801) 2025-02-07 12:32:44 +01:00
Marina López
a98111f68d 🔥 Remove unnecessary prevent and stop (#5799) 2025-02-07 11:07:47 +01:00
Marina López
5fa96315e3 Add event for A/B test first state project (#5791) 2025-02-07 09:51:25 +01:00
andrés gonzález
a4e35ec713 📚 Update FAQ about sharing plugins (#5794) 2025-02-07 09:05:29 +01:00
andrés gonzález
87295eab76 📚 Add info about boards renaming (#5797) 2025-02-07 09:05:12 +01:00
andrés gonzález
028a5ba596 📚 Remove the permissions table (#5795) 2025-02-07 09:04:56 +01:00
Eva Marco
ef9ead8072 🐛 Fix typo on integration test docs (#5796) 2025-02-06 17:42:15 +01:00
luisδμ
00b0a59868 🐛 Fix user unread comments appear in every workspace (#5786) 2025-02-06 14:24:30 +01:00
Andres Gonzalez
518ba0ebe7 📚 Add info about import size limit at the user guide 2025-02-06 14:23:05 +01:00
elhombretecla
8a13ce5f91 💄 Change email button styles (#5785) 2025-02-06 14:17:32 +01:00
Eva Marco
09b3868b0e 🐛 Fix flakiness of playwright test for token (#5790) 2025-02-06 12:49:58 +01:00
Pablo Alba
95f727033e Merge pull request #5771 from penpot/superalex-fix-create-new-layers-component-copy
🐛 Fix create new layers in component copy
2025-02-06 12:45:32 +01:00
Belén Albeza
e78100a776 🐛 Fix flakiness of playwright test for bug 10090 (#5787) 2025-02-06 12:12:29 +01:00
Alonso Torres
cdfc0fd988 🐛 Fix problem when changing colorpicker alpha (#5770) 2025-02-06 10:49:42 +01:00
Alejandro Alonso
8aae928796 🐛 Fix create new layers in component copy 2025-02-06 10:43:26 +01:00
Marina López
0b90722d5a 🐛 Fix change flex direction using plugins API 2025-02-06 09:55:25 +01:00
Belén Albeza
2cbc09a0e2 🐛 Fix local library being collapsed by default (#5775)
* 🐛 Fix local library being collapsed by default

* ♻️ Move layers tab spec code to its own file
2025-02-06 09:28:51 +01:00
Pablo Alba
c774592b9e 🐛 Fix several bugs for the combination of status and share on workspace (#5773)
*  Add status notification bubble

*  Add file persistance functionality

*  Add new colors

*  Add status tooltips

*  Fix z-index share modal

* 🐛 Fix share modal doesn't register on workspace

* 💄 Fix files formatting

*  Add revision fixes

*  Add revision fixes CI

* 🔥 Remove unused require

---------

Co-authored-by: Elhombretecla <delacruzgarciajuan@gmail.com>
2025-02-05 19:11:44 +01:00
Alonso Torres
3012ccf90a 🐛 Fix problem with email notifications on deleted comments (#5772) 2025-02-05 17:48:41 +01:00
alonso.torres
508531cfc2 ⬆️ Update plugins runtime 2025-02-05 17:43:01 +01:00
alonso.torres
7aa1237833 🐛 Fix problem with root frame parent reference 2025-02-05 17:43:01 +01:00
alonso.torres
66076f1332 🐛 Added upload svg with images method 2025-02-05 17:43:01 +01:00
alonso.torres
9c626d22c7 🐛 Fix problem in plugins with renaming components 2025-02-05 17:43:01 +01:00
alonso.torres
8217bbc7a0 🐛 Fix problems with zoomIntoView 2025-02-05 17:43:01 +01:00
Aitor Moreno
b0d0abb0ad 🐛 Fix cursor not preserving white space (#5764) 2025-02-04 15:54:46 +01:00
Aitor Moreno
1f2e36774e 🐛 Fix pasting adds a newline (#5763) 2025-02-04 15:54:17 +01:00
Andrey Antukh
b913c75c41 🔥 Remove unused parameters from backend run template script 2025-02-04 15:36:22 +01:00
Marina López
9b64a6034b 🐛 Fix wrong text and styles from AB test (#5762) 2025-02-04 15:20:22 +01:00
Andrey Antukh
33da02421e 🐛 Fix unexepected exception on importing binfile
Happens when importing binfile with files that has deleted_at
property.
2025-02-04 14:07:23 +01:00
Andrey Antukh
b88ec13448 📎 Fix linter issues 2025-02-04 14:07:23 +01:00
Andrey Antukh
6106e56052 📎 Import from develop github workflows 2025-02-04 14:07:23 +01:00
Andrey Antukh
4bfe4ca230 🐛 Fix incorrect error handling on legacy workspace redirect 2025-02-04 13:35:24 +01:00
elhombretecla
1ac86aced0 Update CHANGES.md 2025-02-04 13:05:05 +01:00
Aitor Moreno
774cf81fc8 🐛 Fix wrong blinking position after paste (#5756) 2025-02-04 11:29:25 +01:00
BDVGitHub
f8ba029b62 📚 Update Penpot Desktop link (#5521)
Change Penpot Desktop link
2025-02-04 11:25:16 +01:00
Aitor Moreno
dbf7b94651 🐛 Fix zoom to fit all didn't fit all (#5673) 2025-02-04 11:00:46 +01:00
elhombretecla
a5dc9eb458 Update CHANGES.md 2025-02-03 15:45:40 +01:00
Pablo Alba
43cde4e5e4 🎉 Add A/B test for first state of a project (#5691) 2025-02-03 12:52:51 +01:00
254 changed files with 210012 additions and 2173 deletions

View File

@@ -26,7 +26,7 @@ jobs:
- name: Check Commit Type
uses: gsactions/commit-message-checker@v2
with:
pattern: '^:(lipstick|globe_with_meridians|wrench|books|arrow_up|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle):\s[A-Z].*[^.]$'
pattern: '^:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle):\s[A-Z].*[^.]$'
flags: 'gm'
error: 'Commit should match CONTRIBUTING.md guideline'
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request

View File

@@ -1,6 +1,20 @@
# CHANGELOG
## 2.5.0 (Unreleased)
## 2.5.1
### :rocket: Epics and highlights
### :boom: Breaking changes & Deprecations
### :heart: Community contributions (Thank you!)
### :sparkles: New features
- Improve Nginx entryponit to get the resolvers dinamically by default
### :bug: Bugs fixed
## 2.5.0
### :rocket: Epics and highlights
@@ -37,21 +51,41 @@ is a number of cores)
### :sparkles: New features
- New gradients UI with multi-stop support. [Taiga #3418](https://tree.taiga.io/project/penpot/epic/3418)
- [GRADIENTS] New gradients UI with multi-stop support. [Taiga #3418](https://tree.taiga.io/project/penpot/epic/3418)
- [GRADIENTS] Radial Gradient [Taiga #8768](https://tree.taiga.io/project/penpot/us/8768)
- Shareable link pointing to an specific board. [Taiga #3219](https://tree.taiga.io/project/penpot/us/3219)
- Copy styles in CSS [Taiga #9401](https://tree.taiga.io/project/penpot/us/9401)
- Copy/paste shape styles (fills, strokes, shadows, etc..) [Taiga #8937](https://tree.taiga.io/project/penpot/us/8937)
- Copy text content to clipboard [Taiga #9970](https://tree.taiga.io/project/penpot/us/9970?milestone=424203)
- Resize board to fit content option [Taiga #4707](https://tree.taiga.io/project/penpot/us/4707)
- Rename selected layer via Board name [Taiga #9430](https://tree.taiga.io/project/penpot/us/9430)
- [COMMENTS] Mention Functionality with and Sidebar Filters [Taiga #9237](https://tree.taiga.io/project/penpot/us/9237)
- [COMMENTS] Visual Changes in Comments [Taiga #9234](https://tree.taiga.io/project/penpot/us/9234)
- [COMMENTS] Notifications in Backend, Profile Section, and Mention Email Notification [Taiga #9233](https://tree.taiga.io/project/penpot/us/9233)
### :bug: Bugs fixed
- 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)
- Fix problem with multiple color changes [Taiga #9631](https://tree.taiga.io/project/penpot/issue/9631)
- Fix create new layers in a component copy [Taiga #10037](https://tree.taiga.io/project/penpot/issue/10037)
- Fix problem in plugins with zoomIntoView [Plugins #189](https://github.com/penpot/penpot-plugins/issues/189)
- Fix problem in plugins with renaming components [Taiga #10060](https://tree.taiga.io/project/penpot/issue/10060)
- Added upload svg with images method [#5489](https://github.com/penpot/penpot/issues/5489)
- Fix problem with root frame parent reference [Taiga #9437](https://tree.taiga.io/project/penpot/issue/9437)
- Fix change flex direction using plugins API [Taiga #9407](https://tree.taiga.io/project/penpot/issue/9407)
- Fix problem opening url when page-id didn't exist [Taiga #10157](https://tree.taiga.io/project/penpot/issue/10157)
- Fix problem with onboarding to a team [Taiga #10143](https://tree.taiga.io/project/penpot/issue/10143)
- Fix problem with grid layout crashing [Taiga #10127](https://tree.taiga.io/project/penpot/issue/10127)
- 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

View File

@@ -84,6 +84,7 @@ Where type is:
- :whale: `:whale:` a commit for docker related stuff
- :paperclip: `:paperclip:` a commit with other not relevant changes
- :arrow_up: `:arrow_up:` a commit with dependencies updates
- :arrow_down: `:arrow_down:` a commit with dependencies downgrades
- :fire: `:fire:` a commit that removes files or code
More info:

View File

@@ -35,7 +35,7 @@
org.postgresql/postgresql {:mvn/version "42.7.5"}
org.xerial/sqlite-jdbc {:mvn/version "3.48.0.0"}
com.zaxxer/HikariCP {:mvn/version "6.0.0"}
com.zaxxer/HikariCP {:mvn/version "6.2.1"}
io.whitfin/siphash {:mvn/version "2.0.0"}
@@ -59,8 +59,7 @@
;; Pretty Print specs
pretty-spec/pretty-spec {:mvn/version "0.1.4"}
software.amazon.awssdk/s3 {:mvn/version "2.30.7"}
}
software.amazon.awssdk/s3 {:mvn/version "2.28.26"}}
:paths ["src" "resources" "target/classes"]
:aliases

View File

@@ -203,7 +203,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="#6911d4#31EFB8" role="presentation"
<td align="center" bgcolor="#6911d4" role="presentation"
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
valign="middle">
<a href="{{ comment-url }}"

View File

@@ -195,12 +195,12 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="#31EFB8" role="presentation"
style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#31EFB8;"
<td align="center" bgcolor="#6911d4" role="presentation"
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
valign="middle">
<a href="{{ public-uri }}/#/auth/verify-token?token={{token}}"
style="display:inline-block;background:#31EFB8;color:#1F1F1F;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"
target="_blank"> Accept invite </a>
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
target="_blank"> ACCEPT INVITE </a>
</td>
</tr>
</table>

View File

@@ -196,12 +196,12 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="#31EFB8" role="presentation"
style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#31EFB8;"
<td align="center" bgcolor="#6911d4" role="presentation"
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
valign="middle">
<a href="{{ public-uri }}/#/dashboard/team/{{team-id}}/projects"
style="display:inline-block;background:#31EFB8;color:#1F1F1F;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"
target="_blank"> Go to the Team </a>
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
target="_blank"> GO TO THE TEAM </a>
</td>
</tr>
</table>

View File

@@ -196,12 +196,12 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="#31EFB8" role="presentation"
style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#31EFB8;"
<td align="center" bgcolor="#6911d4" role="presentation"
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
valign="middle">
<a href="{{ public-uri }}/#/auth/recovery?token={{token}}"
style="display:inline-block;background:#31EFB8;color:#1F1F1F;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"
target="_blank"> Reset password </a>
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
target="_blank"> RESET PASSWORD </a>
</td>
</tr>
</table>

View File

@@ -196,12 +196,12 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="#31EFB8" role="presentation"
style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#31EFB8;"
<td align="center" bgcolor="#6911d4" role="presentation"
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
valign="middle">
<a href="{{ public-uri }}/#/auth/verify-token?token={{token}}"
style="display:inline-block;background:#31EFB8;color:#1F1F1F;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"
target="_blank"> Verify email </a>
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
target="_blank"> VERIFY EMAIL </a>
</td>
</tr>
</table>

View File

@@ -204,12 +204,12 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
style="border-collapse:separate;line-height:100%">
<tr>
<td align="center" bgcolor="#31EFB8" role="presentation"
style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#31EFB8;"
<td align="center" bgcolor="#6911d4" role="presentation"
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
valign="middle">
<a href="{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}&section=interactions&index=0&share=true"
style="display:inline-block;background:#31EFB8;color:#1F1F1F;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"
target="_blank"> Send a View-Only link </a>
<a href="{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}&section=interactions&index=0&share=true"
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
target="_blank"> SEND A VIEW-ONLY LINK </a>
</td>
</tr>
</table>
@@ -251,4 +251,4 @@
</div>
</body>
</html>
</html>

View File

@@ -6,7 +6,7 @@ Since this file is in your Penpot team, you can provide access by sending a view
To proceed, please click the link below to generate and send the view-only link:
{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}&section=interactions&index=0&share=true
{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}&section=interactions&index=0&share=true

View File

@@ -227,12 +227,12 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
style="border-collapse:separate;line-height:100%">
<tr>
<td align="center" bgcolor="#31EFB8" role="presentation"
style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#31EFB8;"
<td align="center" bgcolor="#6911d4" role="presentation"
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
valign="middle">
<a href="{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}&section=interactions&index=0&share=true"
style="display:inline-block;background:#31EFB8;color:#1F1F1F;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"
target="_blank"> Send a View-Only link </a>
<a href="{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}&section=interactions&index=0&share=true"
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
target="_blank"> SEND A VIEW-ONLY LINK </a>
</td>
</tr>
</table>
@@ -274,4 +274,4 @@
</div>
</body>
</html>
</html>

View File

@@ -19,7 +19,7 @@ Alternatively, you can create and share a view-only link to the file. This will
Click the link below to generate and send the link:
{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}&section=interactions&index=0&share=true
{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}&section=interactions&index=0&share=true

View File

@@ -211,12 +211,12 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
style="border-collapse:separate;line-height:100%">
<tr>
<td align="center" bgcolor="#31EFB8" role="presentation"
style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#31EFB8;"
<td align="center" bgcolor="#6911d4" role="presentation"
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
valign="middle">
<a href="{{ public-uri }}/#/dashboard/team/{{team-id}}/members?invite-email={{requested-by-email|urlescape }}"
style="display:inline-block;background:#31EFB8;color:#1F1F1F;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"
target="_blank"> Give access to “{{team-name|abbreviate:25}}” Team </a>
<a href="{{ public-uri }}/#/dashboard/members?team-id={{team-id}}&invite-email={{requested-by-email|urlescape }}"
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
target="_blank"> GIVE ACCESS TO “{{team-name|abbreviate:25}}” TEAM </a>
</td>
</tr>
</table>
@@ -244,12 +244,12 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
style="border-collapse:separate;line-height:100%">
<tr>
<td align="center" bgcolor="#31EFB8" role="presentation"
style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#31EFB8;"
<td align="center" bgcolor="#6911d4" role="presentation"
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
valign="middle">
<a href="{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}&section=interactions&index=0&share=true"
style="display:inline-block;background:#31EFB8;color:#1F1F1F;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"
target="_blank"> Send a View-Only link </a>
<a href="{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}&section=interactions&index=0&share=true"
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
target="_blank"> SEND A VIEW-ONLY LINK </a>
</td>
</tr>
</table>
@@ -292,4 +292,4 @@
</div>
</body>
</html>
</html>

View File

@@ -13,7 +13,7 @@ This will automatically include {{requested-by|abbreviate:25}} in the team, so t
Click the link below to provide team access:
{{ public-uri }}/#/dashboard/team/{{team-id}}/members?invite-email={{requested-by-email|urlescape}}
{{ public-uri }}/#/dashboard/members?team-id{{team-id}}&invite-email={{requested-by-email|urlescape}}
@@ -23,8 +23,7 @@ Alternatively, you can create and share a view-only link to the file. This will
Click the link below to generate and send the link:
{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}&section=interactions&index=0&share=true
{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}&section=interactions&index=0&share=true
If you do not wish to grant access at this time, you can simply disregard this email.

View File

@@ -202,12 +202,12 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="#31EFB8" role="presentation"
style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#31EFB8;"
<td align="center" bgcolor="#6911d4" role="presentation"
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
valign="middle">
<a href="{{ public-uri }}/#/dashboard/team/{{team-id}}/members?invite-email={{requested-by-email|urlescape}}"
style="display:inline-block;background:#31EFB8;color:#1F1F1F;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;"
target="_blank"> Give access to “{{team-name|abbreviate:25}}” </a>
<a href="{{ public-uri }}/#/dashboard/members?team-id={{team-id}}&invite-email={{requested-by-email|urlescape}}"
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
target="_blank"> GIVE ACCESS TO “{{team-name|abbreviate:25}}” TEAM </a>
</td>
</tr>
</table>
@@ -249,4 +249,4 @@
</div>
</body>
</html>
</html>

View File

@@ -4,7 +4,7 @@ Hello!
To provide access, please click the link below:
{{ public-uri }}/#/dashboard/team/{{team-id}}/members?invite-email={{requested-by-email|urlescape}}
{{ public-uri }}/#/dashboard/members?team-id={{team-id}}&invite-email={{requested-by-email|urlescape}}
If you do not wish to grant access at this time, you can simply disregard this email.

View File

@@ -18,7 +18,7 @@ if [ -f ./environ ]; then
source ./environ
fi
export JVM_OPTS="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow -Dpolyglot.engine.WarnInterpreterOnly=false --enable-preview $JVM_OPTS"
export JVM_OPTS="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow --enable-preview $JVM_OPTS"
ENTRYPOINT=${1:-app.main};

View File

@@ -9,7 +9,6 @@
binfile format implementations and management rpc methods."
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.features :as cfeat]
[app.common.files.helpers :as cfh]
@@ -21,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]
@@ -219,10 +217,8 @@
"Given a set of file-id's, return all matching relations with the libraries"
[cfg ids]
(dm/assert!
"expected a set of uuids"
(and (set? ids)
(every? uuid? ids)))
(assert (set? ids) "expected a set of uuids")
(assert (every? uuid? ids) "expected a set of uuids")
(db/run! cfg (fn [{:keys [::db/conn]}]
(let [ids (db/create-array conn "uuid" ids)
@@ -310,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))]
@@ -378,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
@@ -409,8 +405,9 @@
[cfg data file-id]
(let [library-ids (get-libraries cfg [file-id])]
(reduce (fn [data library-id]
(let [library (get-file cfg library-id)]
(ctf/absorb-assets data (:data library))))
(if-let [library (get-file cfg library-id)]
(ctf/absorb-assets data (:data library))
data))
data
library-ids)))
@@ -502,9 +499,7 @@
specific, should not be used outside of binfile domain"
[{:keys [::timestamp] :as cfg} file & {:as opts}]
(dm/assert!
"expected valid timestamp"
(dt/instant? timestamp))
(assert (dt/instant? timestamp) "expected valid timestamp")
(let [file (-> file
(assoc :created-at timestamp)
@@ -512,12 +507,11 @@
(assoc :ignore-sync-until (dt/plus timestamp (dt/duration {:seconds 5})))
(update :features
(fn [features]
(let [features (cfeat/check-supported-features! features)]
(-> (::features cfg #{})
(set/union features)
;; We never want to store
;; frontend-only features on file
(set/difference cfeat/frontend-only-features))))))]
(-> (::features cfg #{})
(set/union features)
;; We never want to store
;; frontend-only features on file
(set/difference cfeat/frontend-only-features)))))]
(when (contains? cf/flags :file-schema-validation)
(fval/validate-file-schema! file))
@@ -528,34 +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
(ex/raise :type :internal
:code :no-migration-defined
:hint (str/ffmt "no migation for feature '%' on file importation" feature)
:feature feature))))

View File

@@ -0,0 +1,45 @@
;; 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
(ex/raise :type :internal
:code :no-migration-defined
:hint (str/ffmt "no migation for feature '%' on file importation" feature)
:feature feature))))

View File

@@ -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

View File

@@ -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]
@@ -530,7 +531,7 @@
(defn- match-storage-entry-fn
[]
(let [pattern (str "^objects/([^/]+).json$")
(let [pattern "^objects/([^/]+).json$"
pattern (re-pattern pattern)]
(fn [entry]
(when-let [[_ id] (re-matches pattern (zip-entry-name entry))]
@@ -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')))
@@ -875,14 +876,17 @@
:manifest manifest))
;; Check if all files referenced on manifest are present
(doseq [{file-id :id} (:files manifest)]
(doseq [{file-id :id features :features} (:files manifest)]
(let [path (str "files/" file-id ".json")]
(when-not (get-zip-entry input path)
(ex/raise :type :validation
:code :invalid-binfile-v3
:hint "some files referenced on manifest not found"
:path path
:file-id file-id))))
:file-id file-id))
(cfeat/check-supported-features! features)))
(events/tap :progress {:section :manifest})
@@ -912,7 +916,7 @@
(import-file-media cfg)
(import-file-thumbnails cfg)
(bfc/apply-pending-migrations! cfg)
(bfm/apply-pending-migrations! cfg)
ids)))))))

View File

@@ -12,6 +12,7 @@
[app.common.exceptions :as ex]
[app.common.flags :as flags]
[app.common.schema :as sm]
[app.common.uri :as u]
[app.common.version :as v]
[app.util.overrides]
[app.util.time :as dt]
@@ -228,19 +229,16 @@
[:objects-storage-s3-endpoint {:optional true} ::sm/uri]
[:objects-storage-s3-io-threads {:optional true} ::sm/int]]))
(def default-flags
[:enable-backend-api-doc
:enable-backend-openapi-doc
:enable-backend-worker
:enable-secure-session-cookies
:enable-email-verification
:enable-v2-migration])
(defn- parse-flags
[config]
(flags/parse flags/default
default-flags
(:flags config)))
(let [public-uri (c/get config :public-uri)
public-uri (some-> public-uri (u/uri))
extra-flags (if (and public-uri
(= (:scheme public-uri) "http")
(not= (:host public-uri) "localhost"))
#{:disable-secure-session-cookies}
#{})]
(flags/parse flags/default extra-flags (:flags config))))
(defn read-env
[prefix]

View File

@@ -12,6 +12,7 @@
[app.binfile.v3 :as bf.v3]
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.features :as cfeat]
[app.common.logging :as l]
[app.common.pprint :as pp]
[app.common.uuid :as uuid]
@@ -21,6 +22,7 @@
[app.rpc.commands.auth :as auth]
[app.rpc.commands.files-create :refer [create-file]]
[app.rpc.commands.profile :as profile]
[app.rpc.commands.teams :as teams]
[app.setup :as-alias setup]
[app.srepl.helpers :as srepl]
[app.storage :as-alias sto]
@@ -317,7 +319,10 @@
:hint "missing upload file"))
(let [profile (profile/get-profile pool profile-id)
project-id (:default-project-id profile)]
project-id (:default-project-id profile)
team (teams/get-team pool
:profile-id profile-id
:project-id project-id)]
(when-not project-id
(ex/raise :type :validation
@@ -329,7 +334,8 @@
cfg (assoc cfg
::bfc/profile-id profile-id
::bfc/project-id project-id
::bfc/input path)]
::bfc/input path
::bfc/features (cfeat/get-team-enabled-features cf/flags team))]
(if (= format :binfile-v3)
(bf.v3/import-files! cfg)

View File

@@ -55,13 +55,16 @@
::yres/body (ex-data err)})
(defmethod handle-error :restriction
[err _ _]
[err request _]
(let [{:keys [code] :as data} (ex-data err)]
(if (= code :method-not-allowed)
{::yres/status 405
::yres/body data}
{::yres/status 400
::yres/body data})))
(binding [l/*context* (request->context request)]
(l/err :hint "restriction error" :data data)
{::yres/status 400
::yres/body data}))))
(defmethod handle-error :rate-limit
[err _ _]

View File

@@ -59,7 +59,7 @@
:props (pp/pprint-str props :length 50)
:hint (or (ex-message cause) @message)
:trace (or (::trace record)
(ex/format-throwable cause :data? false :explain? false :header? false :summary? false))}
(some-> cause (ex/format-throwable :data? false :explain? false :header? false :summary? false)))}
(when-let [params (or (:request/params context) (:params context))]
{:params (pp/pprint-str params :length 30 :level 13)})
@@ -74,9 +74,8 @@
{:explain explain})))))
(defn error-record?
[{:keys [::l/level ::l/cause]}]
(and (= :error level)
(ex/exception? cause)))
[{:keys [::l/level]}]
(= :error level))
(defn- handle-event
[{:keys [::db/pool]} {:keys [::l/id] :as record}]

View File

@@ -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]

View File

@@ -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;

View File

@@ -10,8 +10,10 @@
[app.binfile.common :as bfc]
[app.binfile.v1 :as bf.v1]
[app.binfile.v3 :as bf.v3]
[app.common.features :as cfeat]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.config :as cf]
[app.db :as db]
[app.http.sse :as sse]
[app.loggers.audit :as-alias audit]
@@ -20,6 +22,7 @@
[app.rpc :as-alias rpc]
[app.rpc.commands.files :as files]
[app.rpc.commands.projects :as projects]
[app.rpc.commands.teams :as teams]
[app.rpc.doc :as-alias doc]
[app.tasks.file-gc]
[app.util.services :as sv]
@@ -91,41 +94,30 @@
;; --- Command: import-binfile
(defn- import-binfile-v1
[{:keys [::wrk/executor] :as cfg} {:keys [project-id profile-id name file]}]
(let [cfg (-> cfg
(assoc ::bfc/project-id project-id)
(assoc ::bfc/profile-id profile-id)
(assoc ::bfc/name name)
(assoc ::bfc/input (:path file)))]
;; NOTE: the importation process performs some operations that are
;; not very friendly with virtual threads, and for avoid
;; unexpected blocking of other concurrent operations we dispatch
;; that operation to a dedicated executor.
(px/invoke! executor (partial bf.v1/import-files! cfg))))
(defn- import-binfile-v3
[{:keys [::wrk/executor] :as cfg} {:keys [project-id profile-id name file]}]
(let [cfg (-> cfg
(assoc ::bfc/project-id project-id)
(assoc ::bfc/profile-id profile-id)
(assoc ::bfc/name name)
(assoc ::bfc/input (:path file)))]
;; NOTE: the importation process performs some operations that are
;; not very friendly with virtual threads, and for avoid
;; unexpected blocking of other concurrent operations we dispatch
;; that operation to a dedicated executor.
(px/invoke! executor (partial bf.v3/import-files! cfg))))
(defn- import-binfile
[{:keys [::db/pool] :as cfg} {:keys [project-id version] :as params}]
(let [result (case (int version)
1 (import-binfile-v1 cfg params)
3 (import-binfile-v3 cfg params))]
[{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [profile-id project-id version name file]}]
(let [team (teams/get-team pool
:profile-id profile-id
:project-id project-id)
cfg (-> cfg
(assoc ::bfc/features (cfeat/get-team-enabled-features cf/flags team))
(assoc ::bfc/project-id project-id)
(assoc ::bfc/profile-id profile-id)
(assoc ::bfc/name name)
(assoc ::bfc/input (:path file)))
;; NOTE: the importation process performs some operations that are
;; not very friendly with virtual threads, and for avoid
;; unexpected blocking of other concurrent operations we dispatch
;; that operation to a dedicated executor.
result (case (int version)
1 (px/invoke! executor (partial bf.v1/import-files! cfg))
3 (px/invoke! executor (partial bf.v3/import-files! cfg)))]
(db/update! pool :project
{:modified-at (dt/now)}
{:id project-id})
result))
(def ^:private schema:import-binfile

View File

@@ -38,6 +38,8 @@
(def r-mentions-split #"@\[[^\]]*\]\([^\)]*\)")
(def r-mentions #"@\[([^\]]*)\]\(([^\)]*)\)")
(def comment-max-length 750)
(defn- format-comment
[{:keys [content]}]
(->> (d/interleave-all
@@ -442,7 +444,7 @@
[:map {:title "create-comment-thread"}
[:file-id ::sm/uuid]
[:position ::gpt/point]
[:content [:string {:max 750}]]
[:content [:string {:max comment-max-length}]]
[:page-id ::sm/uuid]
[:frame-id ::sm/uuid]
[:share-id {:optional true} [:maybe ::sm/uuid]]
@@ -585,7 +587,7 @@
schema:create-comment
[:map {:title "create-comment"}
[:thread-id ::sm/uuid]
[:content [:string {:max 250}]]
[:content [:string {:max comment-max-length}]]
[:share-id {:optional true} [:maybe ::sm/uuid]]
[:mentions {:optional true} [::sm/set ::sm/uuid]]])
@@ -655,7 +657,7 @@
schema:update-comment
[:map {:title "update-comment"}
[:id ::sm/uuid]
[:content [:string {:max 250}]]
[:content [:string {:max comment-max-length}]]
[:share-id {:optional true} [:maybe ::sm/uuid]]
[:mentions {:optional true} [::sm/set ::sm/uuid]]])

View File

@@ -384,8 +384,10 @@
f.revn,
f.vern,
f.is_shared,
ft.media_id AS thumbnail_id
ft.media_id AS thumbnail_id,
p.team_id
from file as f
inner join project as p on (p.id = f.project_id)
left join file_thumbnail as ft on (ft.file_id = f.id
and ft.revn = f.revn
and ft.deleted_at is null)
@@ -539,7 +541,8 @@
f.modified_at,
f.name,
f.is_shared,
ft.media_id
ft.media_id,
p.team_id
from file as f
inner join project as p on (p.id = f.project_id)
left join file_thumbnail as ft on (ft.file_id = f.id and ft.revn = f.revn and ft.deleted_at is null)
@@ -549,7 +552,6 @@
and p.team_id = ?
order by f.modified_at desc")
(defn- get-library-summary
[cfg {:keys [id data] :as file}]
(letfn [(assets-sample [assets limit]
@@ -686,7 +688,8 @@
f.name,
f.is_shared,
ft.media_id AS thumbnail_id,
row_number() over w as row_num
row_number() over w as row_num,
p.team_id
from file as f
inner join project as p on (p.id = f.project_id)
left join file_thumbnail as ft on (ft.file_id = f.id

View File

@@ -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"}

View File

@@ -406,12 +406,16 @@
:prefix "penpot.template."
:suffix ""
:min-age "30m")
format (bfc/parse-file-format template)
format (bfc/parse-file-format template)
team (teams/get-team conn
:profile-id profile-id
:project-id project-id)
cfg (-> cfg
(assoc ::bfc/project-id project-id)
(assoc ::bfc/profile-id profile-id)
(assoc ::bfc/input template))
(assoc ::bfc/input template)
(assoc ::bfc/features (cfeat/get-team-enabled-features cf/flags team)))
result (if (= format :binfile-v3)
(px/invoke! executor (partial bf.v3/import-files! cfg))

View File

@@ -58,7 +58,8 @@
[:welcome-file-id {:optional true} [:maybe ::sm/boolean]]
[:release-notes-viewed {:optional true}
[::sm/text {:max 100}]]
[:notifications {:optional true} schema:props-notifications]])
[:notifications {:optional true} schema:props-notifications]
[:workspace-visited {:optional true} ::sm/boolean]])
(def schema:profile
[:map {:title "Profile"}

View File

@@ -6,6 +6,7 @@
(ns app.rpc.commands.teams-invitations
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.features :as cfeat]
@@ -15,7 +16,6 @@
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.db.sql :as sql]
[app.email :as eml]
[app.loggers.audit :as audit]
[app.main :as-alias main]
@@ -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,62 +120,65 @@
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}))
(defn- add-user-to-team
[conn profile team role email]
itoken)))))
(defn- add-member-to-team
[conn profile team role member]
(let [team-id (:id team)
member (db/get* conn :profile
{:email (str/lower email)}
{::sql/columns [:id :email]})
params (merge
{:team-id team-id
:profile-id (:id member)}
(get types.team/permissions-for-role role))]
;; Do not allow blocked users to join teams.
;; Do not allow blocked users to join teams.
(when (:is-blocked member)
(ex/raise :type :restriction
:code :profile-blocked))
@@ -205,29 +203,33 @@
(eml/send! {::eml/conn conn
::eml/factory eml/join-team
:public-uri (cf/get :public-uri)
:to email
:to (:email member)
:invited-by (:fullname profile)
:team (:name team)
:team-id (:id team)})))
(def sql:valid-requests-email
"SELECT p.email
(def ^:private sql:valid-access-request-profiles
"SELECT p.id, p.email, p.is_blocked
FROM team_access_request AS tr
JOIN profile AS p ON (tr.requester_id = p.id)
WHERE tr.team_id = ?
AND tr.auto_join_until > now()")
AND tr.auto_join_until > now()
AND (p.deleted_at IS NULL OR
p.deleted_at > now())")
(defn- get-valid-requests-email
(defn- get-valid-access-request-profiles
[conn team-id]
(db/exec! conn [sql:valid-requests-email team-id]))
(db/exec! conn [sql:valid-access-request-profiles team-id]))
(def ^:private xf:map-email
(map :email))
(def ^:private xf:map-email (map :email))
(defn- create-team-invitations
[{:keys [::db/conn] :as cfg} {:keys [profile team role emails] :as params}]
(let [join-requests (into #{} xf:map-email
(get-valid-requests-email conn (:id team)))
(let [emails (set emails)
join-requests (->> (get-valid-access-request-profiles conn (:id team))
(d/index-by :email))
team-members (into #{} xf:map-email
(teams/get-team-members conn (:id team)))
@@ -245,8 +247,10 @@
;; For requested invitations, do not send invitation emails, add
;; the user directly to the team
(->> (filter join-requests emails)
(run! (partial add-user-to-team conn profile team role)))
(->> join-requests
(filter #(contains? emails (key %)))
(map val)
(run! (partial add-member-to-team conn profile team role)))
invitations))
@@ -572,5 +576,3 @@
(with-meta {:request request}
{::audit/props {:request 1}}))))

View File

@@ -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))

View File

@@ -55,7 +55,7 @@
(let [conn (db/get-connection h/*system*)
used (cfh/collect-used-media data)
ids (db/create-array conn "uuid" used)
sql (str "SELECT * FROM file_media_object WHERE id = ANY(?)")
sql "SELECT * FROM file_media_object WHERE id = ANY(?)"
rows (db/exec! conn [sql ids])
index (reduce (fn [index media]
(if (not= (:file-id media) id)

View File

@@ -20,7 +20,7 @@
(mapcat (fn [object]
(->> (cfh/collect-shape-media-refs object)
(map (fn [id]
{:object-id (:id object)
{:shape-id (:id object)
:id id}))))))
process-page
(fn [result page-id container]

View File

@@ -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)
@@ -39,10 +40,7 @@
([id]
(get-file (or *system* main/system) id))
([system id]
(db/run! system
(fn [system]
(->> (bfc/get-file system id ::db/for-update true)
(bfc/decode-file system))))))
(db/run! system bfc/get-file id)))
(defn get-raw-file
"Get the migrated data of one file."
@@ -99,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!
@@ -135,9 +136,10 @@
(bfc/get-file system id))))
(d/index-by :id)))
file' (if with-libraries?
(update-fn file libs opts)
(update-fn file opts))]
file' (when file
(if with-libraries?
(update-fn file libs opts)
(update-fn file opts)))]
(when (and (some? file')
(not (identical? file file')))
@@ -145,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')

View File

@@ -431,25 +431,40 @@
process-file
(fn [file-id idx tpoint]
(try
(l/trc :hint "process:file:start" :file-id (str file-id) :index idx)
(let [system (assoc main/system ::db/rollback rollback?)]
(db/tx-run! system (fn [system]
(binding [h/*system* system]
(h/process-file! system file-id update-fn opts)))))
(catch Throwable cause
(l/wrn :hint "unexpected error on processing file (skiping)"
(let [thread-id (px/get-thread-id)]
(try
(l/trc :hint "process:file:start"
:tid thread-id
:file-id (str file-id)
:index idx
:cause cause))
(finally
(ps/release! sjobs)
(let [elapsed (dt/format-duration (tpoint))]
(l/trc :hint "process:file:end"
:index idx)
(let [system (assoc main/system ::db/rollback rollback?)]
(db/tx-run! system (fn [system]
(binding [h/*system* system]
(h/process-file! system file-id update-fn opts)))))
(catch Throwable cause
(l/wrn :hint "unexpected error on processing file (skiping)"
:tid thread-id
:file-id (str file-id)
:index idx
:elapsed elapsed)))))
:cause cause))
(finally
(when-let [pause (:pause opts)]
(Thread/sleep (int pause)))
(ps/release! sjobs)
(let [elapsed (dt/format-duration (tpoint))]
(l/trc :hint "process:file:end"
:tid thread-id
:file-id (str file-id)
:index idx
:elapsed elapsed))))))
process-file*
(fn [idx file-id]
(ps/acquire! sjobs)
(px/run! executor (partial process-file file-id idx (dt/tpoint)))
(inc idx))
process-files
(fn [{:keys [::db/conn] :as system}]
@@ -457,14 +472,12 @@
(db/exec! conn ["SET idle_in_transaction_session_timeout = 0"])
(try
(reduce (fn [idx file-id]
(ps/acquire! sjobs)
(px/run! executor (partial process-file file-id idx (dt/tpoint)))
(inc idx))
0
(->> (db/cursor conn [query] {:chunk-size 1})
(take max-items)
(map :id)))
(->> (db/plan conn [query])
(transduce (comp
(take max-items)
(map :id))
(completing process-file*)
0))
(finally
;; Close and await tasks
(pu/close! executor))))]

View File

@@ -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}

View File

@@ -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]

View File

@@ -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)))))

View File

@@ -37,18 +37,17 @@
:role :editor}]
;; invite external user without complaints
(let [data (assoc data :emails ["foo@bar.com"])
out (th/command! data)
(let [data (assoc data :emails ["foo@bar.com"])
out (th/command! data)
;; retrieve the value from the database and check its content
invitation (db/exec-one!
th/*pool*
["select count(*) as num from team_invitation where team_id = ? and email_to = ?"
(:team-id data) "foo@bar.com"])]
invitations (th/db-query :team-invitation
{:team-id (:team-id data)
:email-to "foo@bar.com"})]
;; (th/print-result! out)
(t/is (th/success? out))
(t/is (= 1 (:call-count (deref mock))))
(t/is (= 1 (:num invitation))))
(t/is (= 1 (count invitations))))
;; invite internal user without complaints
(th/reset-mock! mock)
@@ -102,6 +101,105 @@
(t/is (= :validation (:type edata)))
(t/is (= :member-is-muted (:code edata))))))))
(t/deftest create-team-invitations-with-request-access
(with-mocks [mock {:target 'app.email/send! :return nil}]
(let [profile1 (th/create-profile* 1 {:is-active true})
requester (th/create-profile* 2 {:is-active true :email "requester@example.com"})
team (th/create-team* 1 {:profile-id (:id profile1)})
proj (th/create-project* 1 {:profile-id (:id profile1)
:team-id (:id team)})
file (th/create-file* 1 {:profile-id (:id profile1)
:project-id (:id proj)})]
(let [data {::th/type :create-team-access-request
::rpc/profile-id (:id requester)
:file-id (:id file)}
out (th/command! data)]
(t/is (th/success? out))
(t/is (= 1 (:call-count @mock))))
(th/reset-mock! mock)
(let [data {::th/type :create-team-invitations
::rpc/profile-id (:id profile1)
:team-id (:id team)
:role :editor
:emails ["requester@example.com"]}
out (th/command! data)]
(t/is (th/success? out))
(t/is (= 1 (:call-count @mock)))
;; Check that request is properly removed
(let [requests (th/db-query :team-access-request
{:requester-id (:id requester)})]
(t/is (= 0 (count requests))))
(let [rows (th/db-query :team-profile-rel {:team-id (:id team)})]
(t/is (= 2 (count rows))))))))
(t/deftest create-team-invitations-with-request-access-2
(with-mocks [mock {:target 'app.email/send! :return nil}]
(let [profile1 (th/create-profile* 1 {:is-active true})
requester (th/create-profile* 2 {:is-active true
:email "requester@example.com"})
team (th/create-team* 1 {:profile-id (:id profile1)})
proj (th/create-project* 1 {:profile-id (:id profile1)
:team-id (:id team)})
file (th/create-file* 1 {:profile-id (:id profile1)
:project-id (:id proj)})]
;; Create the first access request
(let [data {::th/type :create-team-access-request
::rpc/profile-id (:id requester)
:file-id (:id file)}
out (th/command! data)]
(t/is (th/success? out))
(t/is (= 1 (:call-count @mock))))
(th/reset-mock! mock)
;; Proceed to delete the requester user
(th/db-update! :profile
{:deleted-at (dt/in-past "1h")}
{:id (:id requester)})
;; Create a new profile with the same email
(let [requester' (th/create-profile* 3 {:is-active true :email "requester@example.com"})]
;; Create a request access with new requester
(let [data {::th/type :create-team-access-request
::rpc/profile-id (:id requester')
:file-id (:id file)}
out (th/command! data)]
(t/is (th/success? out))
(t/is (= 1 (:call-count @mock))))
(th/reset-mock! mock)
;; Create an invitation for the requester email
(let [data {::th/type :create-team-invitations
::rpc/profile-id (:id profile1)
:team-id (:id team)
:role :editor
:emails ["requester@example.com"]}
out (th/command! data)]
(t/is (th/success? out))
(t/is (= 1 (:call-count @mock))))
;; Check that request is properly removed
(let [requests (th/db-query :team-access-request
{:requester-id (:id requester')})]
(t/is (= 0 (count requests))))
(let [[r1 r2 :as rows] (th/db-query :team-profile-rel
{:team-id (:id team)}
{:order-by [:created-at]})]
(t/is (= 2 (count rows)))
(t/is (= (:profile-id r1) (:id profile1)))
(t/is (= (:profile-id r2) (:id requester'))))))))
(t/deftest invitation-tokens
(with-mocks [mock {:target 'app.email/send! :return nil}]
@@ -486,14 +584,12 @@
;; request success
(let [out (th/command! data)
;; retrieve the value from the database and check its content
request (db/exec-one!
th/*pool*
["select count(*) as num from team_access_request where team_id = ? and requester_id = ?"
(:id team) (:id requester)])]
requests (th/db-query :team-access-request
{:team-id (:id team)
:requester-id (:id requester)})]
(t/is (th/success? out))
(t/is (= 1 (:call-count @mock)))
(t/is (= 1 (:num request))))
(t/is (= 1 (count requests))))
;; request again fails
(th/reset-mock! mock)
@@ -509,10 +605,10 @@
;; request again when is expired success
(th/reset-mock! mock)
(db/exec-one!
th/*pool*
["update team_access_request set valid_until = ? where team_id = ? and requester_id = ?"
(dt/in-past "1h") (:id team) (:id requester)])
(th/db-update! :team-access-request
{:valid-until (dt/in-past "1h")}
{:team-id (:id team)
:requester-id (:id requester)})
(t/is (th/success? (th/command! data)))
(t/is (= 1 (:call-count @mock))))))

View File

@@ -141,7 +141,7 @@
(keep flag->feature))
(defn get-enabled-features
"Get the globally enabled fratures set."
"Get the globally enabled features set."
[flags]
(into default-features xf-flag-to-feature flags))

View File

@@ -685,7 +685,7 @@
(d/update-in-when data [:components component-id] update-container))))
(defn- process-operations
[objects {:keys [id operations] :as change}]
[objects {:keys [page-id id operations] :as change}]
(if-let [shape (get objects id)]
(let [shape (reduce process-operation shape operations)
touched? (-> shape meta ::ctn/touched)]
@@ -694,6 +694,10 @@
;; need to report them for to be used in the second
;; phase of changes procesing
(when touched? (some-> *touched-changes* (vswap! conj change)))
(when (and *state* page-id)
(swap! *state* collect-shape-media-refs shape page-id))
(assoc objects id shape))
objects))

View File

@@ -570,10 +570,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
@@ -724,7 +723,7 @@
(defn split-by-last-period
"Splits a string into two parts:
the text before and including the last period,
the text before and including the last period,
and the text after the last period."
[s]
(if-let [last-period (str/last-index-of s ".")]

View File

@@ -7,13 +7,145 @@
(ns app.common.flags
"Flags parsing algorithm."
(:require
[clojure.set :as set]
[cuerdas.core :as str]))
(def login
"Flags related to login features"
#{;; Allows registration with login / password
;; if disabled, it's still possible to register/login with providers
:registration
;; Redundant flag. TODO: remove it
:login
;; enables the section of Access Tokens on profile.
:access-tokens
;; Uses email and password as credentials.
:login-with-password
;; Uses Github authentication as credentials.
:login-with-github
;; Uses GitLab authentication as credentials.
:login-with-gitlab
;; Uses Google/Gmail authentication as credentials.
:login-with-google
;; Uses LDAP authentication as credentials.
:login-with-ldap
;; Uses any generic authentication provider that implements OIDC protocol as credentials.
:login-with-oidc
;; Allows registration with Open ID
:oidc-registration
;; This logs to console the invitation tokens. It's useful in case the SMTP is not configured.
:log-invitation-tokens})
(def email
"Flags related to email features"
#{;; Uses the domains in whitelist as the only allowed domains to register in the application.
;; Used with PENPOT_REGISTRATION_DOMAIN_WHITELIST
:email-whitelist
;; Prevents the domains in blacklist to register in the application.
;; Used with PENPOT_REGISTRATION_DOMAIN_BLACKLIST
:email-blacklist
;; Skips the email verification process. Not recommended for production environments.
:email-verification
;; Only used if SMTP is disabled. Logs the emails into the console.
:log-emails
;; Enable it to configure email settings.
:smtp
;; Enables the debug mode of the SMTP library.
:smtp-debug})
(def varia
"Rest of the flags"
#{:audit-log
:audit-log-archive
:audit-log-gc
:auto-file-snapshot
;; enables the `/api/doc` endpoint that lists all the rpc methods available.
:backend-api-doc
;; TODO: remove it and use only `backend-api-doc` flag
:backend-openapi-doc
;; Disable it to start the RPC without the worker.
:backend-worker
;; Only for development
:component-thumbnails
;; enables the default cors configuration that allows all domains (currently this configuration is only used for development).
:cors
;; Enables the templates dialog on Penpot dashboard.
:dashboard-templates-section
;; disabled by default. When enabled, Penpot create demo users with a 7 days expiration.
:demo-users
;; disabled by default. When enabled, it displays a warning that this is a test instance and data will be deleted periodically.
:demo-warning
;; Activates the schema validation during update file.
:file-schema-validation
;; Reports the schema validation errors internally.
:soft-file-schema-validation
;; Activates the referential integrity validation during update file; related to components-v2.
:file-validation
;; Reports the referential integrity validation errors internally.
:soft-file-validation
;; TODO: deprecate this flag and consolidate the code
:frontend-svgo
;; TODO: deprecate this flag and consolidate the code
:exporter-svgo
;; TODO: deprecate this flag and consolidate the code
:backend-svgo
;; If enabled, it makes the Google Fonts available.
:google-fonts-provider
;; Only for development.
:nrepl-server
;; Interactive repl. Only for development.
:urepl-server
;; Programatic access to the runtime, used in administrative tasks.
;; It's mandatory to enable it to use the `manage.py` script.
:prepl-server
;; Shows the onboarding modals right after registration.
:onboarding
:quotes
:soft-quotes
;; Concurrency limit.
:rpc-climit
;; Rate limit.
:rpc-rlimit
;; Soft rate limit.
:soft-rpc-rlimit
;; Disable it if you want to serve Penpot under a different domain than `http://localhost` without HTTPS.
:secure-session-cookies
;; If `cors` enabled, this is ignored.
:strict-session-cookies
:telemetry
:terms-and-privacy-checkbox
;; Only for developtment.
:tiered-file-data-storage
:transit-readable-response
:user-feedback
;; TODO: remove this flag.
:v2-migration
:webhooks
;; TODO: deprecate this flag and consolidate the code
:export-file-v3
:render-wasm-dpr
:hide-release-modal})
(def all-flags
(set/union email login varia))
(def default
"A common flags that affects both: backend and frontend."
"Flags with default configuration"
[:enable-registration
:enable-login-with-password
:enable-export-file-v3
:enable-login-with-password])
:enable-frontend-svgo
:enable-exporter-svgo
:enable-backend-svgo
:enable-backend-api-doc
:enable-backend-openapi-doc
:enable-backend-worker
:enable-secure-session-cookies
:enable-email-verification
:enable-onboarding
:enable-dashboard-templates-section
:enable-google-fonts-provider
:enable-component-thumbnails])
(defn parse
[& flags]

View File

@@ -212,8 +212,10 @@
(if (= type :column)
[:column :column-span]
[:row :row-span])
from-idx (dec (get cell prop))
to-idx (+ (dec (get cell prop)) (get cell prop-span))
from-idx (-> (dec (get cell prop))
(mth/clamp 0 (dec (count track-list))))
to-idx (-> (+ (dec (get cell prop)) (get cell prop-span))
(mth/clamp 0 (dec (count track-list))))
tracks (subvec track-list from-idx to-idx)]
(some? (->> tracks (d/seek #(= :flex (:type %)))))))
@@ -291,8 +293,10 @@
(fn [allocated cell]
(let [shape-id (first (:shapes cell))
from-idx (dec (get cell prop))
to-idx (+ (dec (get cell prop)) (get cell prop-span))
from-idx (-> (dec (get cell prop))
(mth/clamp 0 (dec (count track-list))))
to-idx (-> (+ (dec (get cell prop)) (get cell prop-span))
(mth/clamp 0 (dec (count track-list))))
indexed-tracks (subvec (d/enumerate track-list) from-idx to-idx)
to-allocate (size-to-allocate type parent (get children-map shape-id) cell bounds objects)
@@ -597,11 +601,10 @@
row (nth row-tracks (dec (:row grid-cell)) nil)
column-start-p (:start-p column)
row-start-p (:start-p row)
start-p (gpt/add origin
(gpt/add
(gpt/to-vec origin column-start-p)
(gpt/to-vec origin row-start-p)))]
(assoc grid-cell :start-p start-p)))))
row-start-p (:start-p row)]
(when (and (some? column-start-p) (some? row-start-p))
(let [start-p (gpt/add origin
(gpt/add
(gpt/to-vec origin column-start-p)
(gpt/to-vec origin row-start-p)))]
(assoc grid-cell :start-p start-p)))))))

View File

@@ -114,61 +114,62 @@
(defn child-position-delta
[parent child child-bounds child-width child-height layout-data cell-data]
(let [cell-bounds (cell-bounds layout-data cell-data)
child-origin (gpo/origin child-bounds)
(if-let [cell-bounds (cell-bounds layout-data cell-data)]
(let [child-origin (gpo/origin child-bounds)
align (:layout-align-items parent)
justify (:layout-justify-items parent)
align-self (:align-self cell-data)
justify-self (:justify-self cell-data)
align (:layout-align-items parent)
justify (:layout-justify-items parent)
align-self (:align-self cell-data)
justify-self (:justify-self cell-data)
align-self (when (and align-self (not= align-self :auto)) align-self)
justify-self (when (and justify-self (not= justify-self :auto)) justify-self)
align-self (when (and align-self (not= align-self :auto)) align-self)
justify-self (when (and justify-self (not= justify-self :auto)) justify-self)
align (or align-self align)
justify (or justify-self justify)
align (or align-self align)
justify (or justify-self justify)
origin-h (gpo/project-point cell-bounds :h child-origin)
origin-v (gpo/project-point cell-bounds :v child-origin)
hv (partial gpo/start-hv cell-bounds)
vv (partial gpo/start-vv cell-bounds)
origin-h (gpo/project-point cell-bounds :h child-origin)
origin-v (gpo/project-point cell-bounds :v child-origin)
hv (partial gpo/start-hv cell-bounds)
vv (partial gpo/start-vv cell-bounds)
[top-m right-m bottom-m left-m] (ctl/child-margins child)
[top-m right-m bottom-m left-m] (ctl/child-margins child)
;; Adjust alignment/justify
[from-h to-h]
(case justify
:end
[(gpt/add origin-h (hv child-width))
(gpt/subtract (nth cell-bounds 1) (hv right-m))]
;; Adjust alignment/justify
[from-h to-h]
(case justify
:end
[(gpt/add origin-h (hv child-width))
(gpt/subtract (nth cell-bounds 1) (hv right-m))]
:center
[(gpt/add origin-h (hv (/ child-width 2)))
(-> (gpo/project-point cell-bounds :h (gpo/center cell-bounds))
(gpt/add (hv (/ left-m 2)))
(gpt/subtract (hv (/ right-m 2))))]
:center
[(gpt/add origin-h (hv (/ child-width 2)))
(-> (gpo/project-point cell-bounds :h (gpo/center cell-bounds))
(gpt/add (hv (/ left-m 2)))
(gpt/subtract (hv (/ right-m 2))))]
[origin-h
(gpt/add (first cell-bounds) (hv left-m))])
[origin-h
(gpt/add (first cell-bounds) (hv left-m))])
[from-v to-v]
(case align
:end
[(gpt/add origin-v (vv child-height))
(gpt/subtract (nth cell-bounds 3) (vv bottom-m))]
[from-v to-v]
(case align
:end
[(gpt/add origin-v (vv child-height))
(gpt/subtract (nth cell-bounds 3) (vv bottom-m))]
:center
[(gpt/add origin-v (vv (/ child-height 2)))
(-> (gpo/project-point cell-bounds :v (gpo/center cell-bounds))
(gpt/add (vv top-m))
(gpt/subtract (vv bottom-m)))]
:center
[(gpt/add origin-v (vv (/ child-height 2)))
(-> (gpo/project-point cell-bounds :v (gpo/center cell-bounds))
(gpt/add (vv top-m))
(gpt/subtract (vv bottom-m)))]
[origin-v
(gpt/add (first cell-bounds) (vv top-m))])]
[origin-v
(gpt/add (first cell-bounds) (vv top-m))])]
(-> (gpt/point)
(gpt/add (gpt/to-vec from-h to-h))
(gpt/add (gpt/to-vec from-v to-v)))))
(-> (gpt/point)
(gpt/add (gpt/to-vec from-h to-h))
(gpt/add (gpt/to-vec from-v to-v))))
(gpt/point 0 0)))
(defn child-modifiers
[parent parent-bounds child child-bounds layout-data cell-data]

View File

@@ -275,7 +275,7 @@
[_ _ _ {:keys [::logger ::props ::level ::cause ::trace ::message]}]
(when (enabled? logger level)
(let [hstyles (str/ffmt "font-weight: 600; color: %" (level->color level))
mstyles (str/ffmt "font-weight: 300; color: %" "#282a2e")
mstyles (str/ffmt "font-weight: 300; color: %" (level->color level))
header (str/concat "%c" (level->name level) " [" logger "] ")
message (str/concat header "%c" @message)]

View File

@@ -12,7 +12,6 @@
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.grid-layout :as gslg]
[app.common.logging :as log]
[app.common.logic.shapes :as cls]
[app.common.spec :as us]
@@ -226,18 +225,19 @@
changes
(if (ctl/grid-layout? objects (:parent-id first-shape))
(let [target-cell (-> position meta :cell)
[row column]
(if (some? target-cell)
[(:row target-cell) (:column target-cell)]
(gslg/get-drop-cell (:parent-id first-shape) objects position))]
(when (some? target-cell)
[(:row target-cell) (:column target-cell)])]
(-> changes
(pcb/update-shapes
[(:parent-id first-shape)]
(fn [shape objects]
(-> shape
(ctl/assign-cells objects)
(ctl/push-into-cell [(:id first-shape)] row column)
(ctl/assign-cells objects)))
(cond-> (and (some? row) (some? column))
(-> (ctl/push-into-cell [(:id first-shape)] row column)
(ctl/assign-cells objects)))))
{:with-objects? true})
(pcb/reorder-grid-children [(:parent-id first-shape)])))
changes)

View File

@@ -963,7 +963,6 @@
{:title "string"
:description "not whitespace string"
:gen/gen (sg/word-string)
:error/code "errors.invalid-text"
:error/fn
(fn [{:keys [value schema]}]
(let [{:keys [max min] :as props} (properties schema)]
@@ -971,16 +970,23 @@
(and (string? value)
(number? max)
(> (count value) max))
["errors.field-max-length" max]
{:code ["errors.field-max-length" max]}
(and (string? value)
(number? min)
(< (count value) min))
["errors.field-min-length" min]
{:code ["errors.field-min-length" min]}
(and (string? value)
(str/empty? value))
{:code "errors.field-missing"}
(and (string? value)
(str/blank? value))
"errors.field-not-all-whitespace")))}})
{:code "errors.field-not-all-whitespace"}
:else
{:code "errors.invalid-text"})))}})
(register!
{:type ::password

View File

@@ -29,7 +29,7 @@
(sm/register! ::component schema:component)
(def check-component!
(def check-component
(sm/check-fn schema:component))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@@ -535,10 +535,11 @@
(letfn [(get-frame [parent-id]
(if (cfh/frame-shape? objects parent-id) parent-id (get-in objects [parent-id :frame-id])))]
(let [parent (get objects parent-id)
;; We can always move the children to the parent they already have
;; We can always move the children to the parent they already have.
no-changes?
(->> children (every? #(= parent-id (:parent-id %))))]
(if (or no-changes? (not (invalid-structure-for-component? objects parent children pasting? libraries)))
;; In case no-changes is true we must ensure we are copy pasting the children in the same position
(if (or (and no-changes? (not pasting?)) (not (invalid-structure-for-component? objects parent children pasting? libraries)))
[parent-id (get-frame parent-id)]
(recur (:parent-id parent) objects children pasting? libraries))))))

View File

@@ -5,6 +5,7 @@
;; Copyright (c) KALEIDOS INC
(ns app.common.types.page
(:refer-clojure :exclude [empty?])
(:require
[app.common.data :as d]
[app.common.geom.point :as-alias gpt]
@@ -98,3 +99,8 @@
(defn get-frame-flow
[flows frame-id]
(d/seek #(= (:starting-frame %) frame-id) (vals flows)))
(defn is-empty?
"Check if page is empty or contains shapes"
[page]
(= 1 (count (:objects page))))

View File

@@ -588,51 +588,51 @@
;; - Blur
;; - Border radius
(def ^:private basic-extract-props
[:fills
:strokes
:opacity
#{:fills
:strokes
:opacity
;; Layout Item
:layout-item-margin
:layout-item-margin-type
:layout-item-h-sizing
:layout-item-v-sizing
:layout-item-max-h
:layout-item-min-h
:layout-item-max-w
:layout-item-min-w
:layout-item-absolute
:layout-item-z-index
;; Layout Item
:layout-item-margin
:layout-item-margin-type
:layout-item-h-sizing
:layout-item-v-sizing
:layout-item-max-h
:layout-item-min-h
:layout-item-max-w
:layout-item-min-w
:layout-item-absolute
:layout-item-z-index
;; Constraints
:constraints-h
:constraints-v
;; Constraints
:constraints-h
:constraints-v
:shadow
:blur
:shadow
:blur
;; Radius
:r1
:r2
:r3
:r4])
;; Radius
:r1
:r2
:r3
:r4})
(def ^:private layout-extract-props
[:layout
:layout-flex-dir
:layout-gap-type
:layout-gap
:layout-wrap-type
:layout-align-items
:layout-align-content
:layout-justify-items
:layout-justify-content
:layout-padding-type
:layout-padding
:layout-grid-dir
:layout-grid-rows
:layout-grid-columns
:layout-grid-cells])
#{:layout
:layout-flex-dir
:layout-gap-type
:layout-gap
:layout-wrap-type
:layout-align-items
:layout-align-content
:layout-justify-items
:layout-justify-content
:layout-padding-type
:layout-padding
:layout-grid-dir
:layout-grid-rows
:layout-grid-columns
:layout-grid-cells})
(defn extract-props
"Retrieves an object with the 'pasteable' properties for a shape."
@@ -668,10 +668,13 @@
[props shape]
(d/patch-object props (select-keys shape layout-extract-props)))]
(-> shape
(select-keys basic-extract-props)
(cond-> (cfh/text-shape? shape) (extract-text-props shape))
(cond-> (ctsl/any-layout? shape) (extract-layout-props shape)))))
(let [;; For texts we don't extract the fill
extract-props
(cond-> basic-extract-props (cfh/text-shape? shape) (disj :fills))]
(-> shape
(select-keys extract-props)
(cond-> (cfh/text-shape? shape) (extract-text-props shape))
(cond-> (ctsl/any-layout? shape) (extract-layout-props shape))))))
(defn patch-props
"Given the object of `extract-props` applies it to a shape. Adapt the shape if necesary"

View File

@@ -1307,9 +1307,9 @@
"Push the shapes into the row/column cell and moves the rest"
[parent shape-ids row column]
(let [cells (vec (get-cells parent {:sort? true}))
(let [parent (-> parent (free-cell-shapes shape-ids))
cells (vec (get-cells parent {:sort? true}))
[start-index start-cell] (seek-indexed-cell cells row column)]
(if (some? start-cell)
(let [;; start-index => to-index is the range where the shapes inserted will be added
to-index (min (+ start-index (count shape-ids)) (dec (count cells)))]

View File

@@ -11,6 +11,7 @@ RUN set -ex; \
ADD ./bundle-frontend/ /var/www/app/
ADD ./files/config.js /var/www/app/js/config.js
ADD ./files/nginx.conf /etc/nginx/nginx.conf.template
ADD ./files/resolvers.conf /etc/nginx/overrides.d/resolvers.conf.template
ADD ./files/nginx-mime.types /etc/nginx/mime.types
ADD ./files/nginx-entrypoint.sh /entrypoint.sh

View File

@@ -21,10 +21,14 @@ update_flags /var/www/app/js/config.js
export PENPOT_BACKEND_URI=${PENPOT_BACKEND_URI:-http://penpot-backend:6060};
export PENPOT_EXPORTER_URI=${PENPOT_EXPORTER_URI:-http://penpot-exporter:6061};
export PENPOT_INTERNAL_RESOLVER=${PENPOT_INTERNAL_RESOLVER:-127.0.0.11};
PENPOT_DEFAULT_INTERNAL_RESOLVER="$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf)";
export PENPOT_INTERNAL_RESOLVER=${PENPOT_INTERNAL_RESOLVER:-$PENPOT_DEFAULT_INTERNAL_RESOLVER};
export PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=${PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE:-367001600}; # Default to 350MiB
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_INTERNAL_RESOLVER,\$PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE" \
< /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE" \
< /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf;
envsubst "\$PENPOT_INTERNAL_RESOLVER" \
< /etc/nginx/overrides.d/resolvers.conf.template > /etc/nginx/overrides.d/resolvers.conf;
exec "$@";

View File

@@ -46,7 +46,6 @@ http {
proxy_buffer_size 16k;
proxy_busy_buffers_size 24k; # essentially, proxy_buffer_size + 2 small buffers of 4k
proxy_buffers 32 4k;
resolver $PENPOT_INTERNAL_RESOLVER ipv6=off;
map $http_upgrade $connection_upgrade {
default upgrade;

View File

@@ -0,0 +1 @@
resolver $PENPOT_INTERNAL_RESOLVER ipv6=off valid=10s;

View File

@@ -71,7 +71,9 @@
</main>
<div class="pre-footer">
<a href="https://github.com/penpot/penpot/blob/main/docs/{{ page.inputPath }}">Edit this page on GitHub</a>
<a href="https://github.com/penpot/penpot/blob/main/docs/{{ page.inputPath }}">Edit this page on GitHub</a>
&nbsp;or ask a&nbsp;
<a href="https://github.com/penpot/penpot/issues/new/choose">question</a>.
</div>
<footer class="footer">
<div class="footer-inside">

BIN
docs/img/dev-tools-1.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
docs/img/dev-tools-2.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Binary file not shown.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
docs/img/penpot-report.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

Binary file not shown.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -61,7 +61,7 @@ Take a look at the Penpot Library methods in the <a target="_blank" href="https:
### Is there a place where I can share my plugin?
You will be able to share your plugin with the <a target="_blank" href="https://community.penpot.app/">Penpot community</a>. In the future, we plan to create a place where we will publish the plugins we know about, but this is still something we have to define.
<a target="_blank" href="https://penpot.app/penpothub">Penpot Hub</a> is where you can share plugins, templates and libraries all made possible through open-source collaboration. To add your plugin to our catalog, simply fill out <a target="_blank" href="https://penpot.app/penpothub/plugins/create-plugin">this form</a> with your plugin's details.
### My plugin works on my local machine, but I couldnt install it on Penpot. What could be the problem?

View File

@@ -2,73 +2,107 @@
title: 2. Penpot Configuration
---
# Penpot Configuration #
# Penpot Configuration
This section intends to explain all available configuration options, when you
are self-hosting Penpot or also if you are using the Penpot developer setup.
This section explains the configuration options, both for self-hosting and developer setup.
Penpot is configured using environment variables. All variables start with <code class="language-bash">PENPOT_</code>
prefix.
<p class="advice">
Penpot is configured using environment variables and flags.
</p>
Variables are initialized in the <code class="language-bash">docker-compose.yaml</code> file, as explained in the
Self-hosting guide with [Elestio][1] or [Docker][2].
## How the configuration works
Additionally, if you are using the developer environment, you may override their values in
the startup scripts, as explained in the [Developer Guide][3].
Penpot is configured using environment variables and flags. **Environment variables** start
with <code class="language-bash">PENPOT_</code>. **Flags** use the format
<code class="language-bash"><enable|disable>-<flag-name></code>.
**NOTE**: All the examples that have values represent the **default** values, and the
examples that do not have values are optional, and inactive by default.
## Common ##
This section will list all common configuration between backend and frontend.
There are two types of configuration: options (properties that require some value) and
flags (that just enables or disables something). All flags are set in a single
<code class="language-bash">PENPOT_FLAGS</code> environment variable. The envvar is a list of strings using this
format: <code class="language-bash"><enable|disable>-\<flag-name></code>. For example:
Flags are used to enable/disable a feature or behaviour (registration, feedback),
while environment variables are used to configure the settings (auth, smtp, etc).
Flags and evironment variables are also used together; for example:
```bash
PENPOT_FLAGS: enable-smtp disable-registration disable-email-verification
# This flag enables the use of SMTP email
PENPOT_FLAGS: enable-smtp
# These environment variables configure the specific SMPT service
# Backend
PENPOT_SMTP_HOST: <host>
PENPOT_SMTP_PORT: 587
```
### Registration ###
**Flags** are configured in a single list, no matter they affect the backend, the frontend,
the exporter, or all of them; on the other hand, **environment variables** are configured for
each specific service. For example:
Penpot comes with an option to completely disable the registration process;
for this, use the following variable:
```bash
PENPOT_FLAGS: enable-login-with-google
# Backend
PENPOT_GOOGLE_CLIENT_ID: <client-id>
PENPOT_GOOGLE_CLIENT_SECRET: <client-secret>
```
Check the configuration guide for [Elestio][1] or [Docker][2]. Additionally, if you are using
the developer environment, you may override its values in the startup scripts,
as explained in the [Developer Guide][3].
**NOTE**: All the examples that have value represent the **default** value, and the
examples that do not have value are optional, and inactive or disabled by default.
## Telemetries
Penpot uses anonymous telemetries from the self-hosted instances to improve the platform experience.
Consider sharing these anonymous telemetries enabling the corresponding flag:
```bash
PENPOT_FLAGS: enable-telemetries
```
## Registration and authentication
There are different ways of registration and authentication in Penpot:
- email/password
- Authentication providers like Google, Github or GitLab
- LDAP
You can choose one of them or combine several methods, depending on your needs.
By default, the email/password registration is enabled and the rest are disabled.
### Penpot
This method of registration and authentication is enabled by default. For a production environment,
it should be configured next to the SMTP settings, so there is a proper registration and verification
process.
You may want to restrict the registrations to a closed list of domains,
or exclude a specific list of domains:
```bash
# Backend
# comma separated list of domains
PENPOT_REGISTRATION_DOMAIN_WHITELIST:
# Backend
# or a file with a domain per line
PENPOT_EMAIL_DOMAIN_WHITELIST: path/to/whitelist.txt
PENPOT_EMAIL_DOMAIN_BLACKLIST: path/to/blacklist.txt
```
__Since version 2.1__
Email whitelisting should be explicitly
enabled with <code class="language-bash">enable-email-whitelist</code> flag. For backward compatibility, we
autoenable it when <code class="language-bash">PENPOT_REGISTRATION_DOMAIN_WHITELIST</code> is set with
not-empty content.
Penpot also comes with an option to completely disable the registration process;
for this, use the following flag:
```bash
PENPOT_FLAGS: [...] disable-registration
```
You may also want to restrict the registrations to a closed list of domains:
```bash
# comma separated list of domains (backend only)
PENPOT_REGISTRATION_DOMAIN_WHITELIST:
# OR (backend only)
PENPOT_EMAIL_DOMAIN_WHITELIST: path/to/whitelist.txt
```
**NOTE**: Since version 2.1, email whitelisting should be explicitly
enabled with <code class="language-bash">enable-email-whitelist</code> flag. For backward compatibility, we
autoenable it when <code class="language-bash">PENPOT_REGISTRATION_DOMAIN_WHITELIST</code> is set with
not-empty content.
### Demo users ###
Penpot comes with facilities for fast creation of demo users without the need of a
registration process. The demo users by default have an expiration time of 7 days, and
once expired they are completely deleted with all the generated content. Very useful for
testing or demonstration purposes.
You can enable demo users using the following variable:
```bash
PENPOT_FLAGS: [...] enable-demo-users
```
This option is only recommended for demo instances, not for production environments.
### Authentication Providers
@@ -82,7 +116,6 @@ The callback has the following format:
https://<your_domain>/api/auth/oauth/<oauth_provider>/callback
```
You will need to change <your_domain> and <oauth_provider> according to your setup.
This is how it looks with Gitlab provider:
@@ -90,22 +123,6 @@ This is how it looks with Gitlab provider:
https://<your_domain>/api/auth/oauth/gitlab/callback
```
#### Penpot
Consists on registration and authentication via email / password. It is enabled by default,
but login can be disabled with the following flags:
```bash
PENPOT_FLAGS: [...] disable-login-with-password
```
And the registration also can be disabled with:
```bash
PENPOT_FLAGS: [...] disable-registration
```
#### Google
Allows integrating with Google as OAuth provider:
@@ -145,7 +162,7 @@ PENPOT_GITHUB_CLIENT_SECRET: <client-secret>
#### OpenID Connect
**NOTE:** Since version 1.5.0
__Since version 1.5.0__
Allows integrating with a generic authentication provider that implements the OIDC
protocol (usually used for SSO).
@@ -155,7 +172,7 @@ All the other options are backend only:
```bash
PENPOT_FLAGS: [...] enable-login-with-oidc
## Backend only
# Backend
PENPOT_OIDC_CLIENT_ID: <client-id>
# Mainly used for auto discovery the openid endpoints
@@ -231,7 +248,6 @@ register with another method.
PENPOT_FLAGS: [...] enable-oidc-registration
```
#### Azure Active Directory using OpenID Connect
Allows integrating with Azure Active Directory as authentication provider:
@@ -240,12 +256,12 @@ Allows integrating with Azure Active Directory as authentication provider:
# Backend & Frontend
PENPOT_OIDC_CLIENT_ID: <client-id>
## Backend only
# Backend
PENPOT_OIDC_BASE_URI: https://login.microsoftonline.com/<tenant-id>/v2.0/
PENPOT_OIDC_CLIENT_SECRET: <client-secret>
```
### LDAP ###
### LDAP
Penpot comes with support for *Lightweight Directory Access Protocol* (LDAP). This is the
example configuration we use internally for testing this authentication backend.
@@ -253,7 +269,7 @@ example configuration we use internally for testing this authentication backend.
```bash
PENPOT_FLAGS: [...] enable-login-with-ldap
## Backend only
# Backend
PENPOT_LDAP_HOST: ldap
PENPOT_LDAP_PORT: 10389
PENPOT_LDAP_SSL: false
@@ -268,39 +284,34 @@ PENPOT_LDAP_ATTRS_FULLNAME: cn
PENPOT_LDAP_ATTRS_PHOTO: jpegPhoto
```
If you miss something, please open an issue and we discuss it.
## Penpot URI
## Backend ##
This section enumerates the backend only configuration variables.
### Database
We only support PostgreSQL and we highly recommend >=13 version. If you are using official
docker images this is already solved for you.
Essential database configuration:
You will need to set the <code class="language-bash">PENPOT_PUBLIC_URI</code> environment variable in case you go to serve Penpot to the users;
it should point to public URI where users will access the application:
```bash
# Backend
PENPOT_DATABASE_USERNAME: penpot
PENPOT_DATABASE_PASSWORD: penpot
PENPOT_DATABASE_URI: postgresql://127.0.0.1/penpot
PENPOT_PUBLIC_URI: https://penpot.mycompany.com
# Frontend
PENPOT_PUBLIC_URI: https://penpot.mycompany.com
# Exporter
PENPOT_PUBLIC_URI: https://penpot.mycompany.com
```
The username and password are optional. These settings should be compatible with the ones
in the postgres configuration:
If you're using the official <code class="language-bash">docker-compose.yml</code> you only need to configure the
<code class="language-bash">PENPOT_PUBLIC_URI</code> envvar in the top of the file.
```bash
# Postgres
POSTGRES_DATABASE: penpot
POSTGRES_USER: penpot
POSTGRES_PASSWORD: penpot
```
<p class="advice">
If you plan to serve Penpot under different domain than `localhost` without HTTPS,
you need to disable the `secure` flag on cookies, with the `disable-secure-session-cookies` flag.
This is a configuration NOT recommended for production environments; as some browser APIs do
not work properly under non-https environments, this unsecure configuration
may limit the usage of Penpot; as an example, the clipboard does not work with HTTP.
</p>
### Email (SMTP)
## Email configuration
By default, <code class="language-bash">smpt</code> flag is disabled, the email will be
printed to the console, which means that the emails will be shown in the stdout.
@@ -326,6 +337,7 @@ Enable SMTP:
```bash
PENPOT_FLAGS: [...] enable-smtp
# Backend
PENPOT_SMTP_HOST: <host>
PENPOT_SMTP_PORT: 587
@@ -334,14 +346,108 @@ PENPOT_SMTP_PASSWORD: <password>
PENPOT_SMTP_TLS: true
```
If you are not using SMTP configuration and want to log the emails in the console, you should use the following flag:
```bash
PENPOT_FLAGS: [...] enable-log-emails
```
## Redis
The Redis configuration is very simple, just provide a valid redis URI. Redis is used
mainly for websocket notifications coordination.
```bash
# Backend
PENPOT_REDIS_URI: redis://localhost/0
# Exporter
PENPOT_REDIS_URI: redis://localhost/0
```
If you are using the official docker compose file, this is already configurRed.
## Demo environment
Penpot comes with facilities to create a demo environment so you can test the system quickly.
This is an example of a demo configuration:
```bash
PENPOT_FLAGS: disable-registration enable-demo-users enable-demo-warning
```
**disable-registration** prevents any user from registering in the platform.
**enable-demo-users** creates users with a default expiration time of 7 days, and
once expired they are completely deleted with all the generated content.
From the registration page, there is a link with a `Create demo account` which creates one of these
users and logs in automatically.
**enable-demo-warning** is a modal in the registration and login page saying that the
environment is a testing one and the data may be wiped without notice.
Another way to work in a demo environment is allowing users to register but removing the
verification process:
```bash
PENPOT_FLAGS: disable-email-verification enable-demo-warning
```
## Backend
This section enumerates the backend only configuration variables.
### Secret key
The <code class="language-bash">PENPOT_SECRET_KEY</code> envvar serves a master key from which other keys
for subsystems (eg http sessions, or invitations) are derived.
If you don't use it, all created sessions and invitations will become invalid on container restart
or service restart.
To use it, we recommend using a truly randomly generated 512 bits base64 encoded string here.
You can generate one with:
```bash
python3 -c "import secrets; print(secrets.token_urlsafe(64))"
```
And configure it:
```bash
# Backend
PENPOT_SECRET_KEY: my-super-secure-key
```
### Database
Penpot only supports PostgreSQL and we highly recommend >=13 version. If you are using official
docker images this is already solved for you.
Essential database configuration:
```bash
# Backend
PENPOT_DATABASE_USERNAME: penpot
PENPOT_DATABASE_PASSWORD: penpot
PENPOT_DATABASE_URI: postgresql://127.0.0.1/penpot
```
The username and password are optional. These settings should be compatible with the ones
in the postgres configuration:
```bash
# Postgres
POSTGRES_DATABASE: penpot
POSTGRES_USER: penpot
POSTGRES_PASSWORD: penpot
```
### Storage
Storage refers to storage used for store the user uploaded assets.
Storage refers to storing the user uploaded assets.
Assets storage is implemented using "plugable" backends. Currently there are three
Assets storage is implemented using "plugable" backends. Currently there are two
backends available: <code class="language-bash">fs</code> and <code class="language-bash">s3</code> (for AWS S3).
#### FS Backend (default) ####
#### FS Backend (default)
This is the default backend when you use the official docker images and the default
configuration looks like this:
@@ -360,8 +466,7 @@ configure the nginx yourself.
In case you want understand how it internally works, you can take a look on the [nginx
configuration file][4] used in the docker images.
#### AWS S3 Backend ####
#### AWS S3 Backend
This backend uses AWS S3 bucket for store the user uploaded assets. For use it you should
have an appropriate account on AWS cloud and have the credentials, region and the bucket.
@@ -369,11 +474,9 @@ have an appropriate account on AWS cloud and have the credentials, region and th
This is how configuration looks for S3 backend:
```bash
# AWS Credentials
# Backend
AWS_ACCESS_KEY_ID: <you-access-key-id-here>
AWS_SECRET_ACCESS_KEY: <your-secret-access-key-here>
# Backend configuration
PENPOT_ASSETS_STORAGE_BACKEND: assets-s3
PENPOT_STORAGE_ASSETS_S3_REGION: <aws-region>
PENPOT_STORAGE_ASSETS_S3_BUCKET: <bucket-name>
@@ -382,38 +485,11 @@ PENPOT_STORAGE_ASSETS_S3_BUCKET: <bucket-name>
PENPOT_STORAGE_ASSETS_S3_ENDPOINT: <endpoint-uri>
```
### Redis
The redis configuration is very simple, just provide with a valid redis URI. Redis is used
mainly for websocket notifications coordination.
```bash
# Backend
PENPOT_REDIS_URI: redis://localhost/0
```
If you are using the official docker compose file, this is already configured.
### HTTP
You will need to set the <code class="language-bash">PENPOT_PUBLIC_URI</code> environment
variable in case you go to serve Penpot to the users; it should point to public URI
where users will access the application:
```bash
PENPOT_PUBLIC_URI: http://localhost:9001
```
<p class="advice">
If you plan to serve Penpot under different domain than `localhost` without HTTPS,
you need to disable the `secure` flag on cookies, with the `disable-secure-session-cookies` flag.
This is a configuration NOT recommended for production environments.
These settings are equally useful if you have a Minio storage system.
</p>
Check all the [flags](#other-flags) to fully customize your instance.
## Frontend ##
## Frontend
In comparison with backend, frontend only has a small number of runtime configuration
options, and they are located in the <code class="language-bash">\<dist>/js/config.js</code> file.
@@ -422,10 +498,7 @@ If you are using the official docker images, the best approach to set any config
using environment variables, and the image automatically generates the <code class="language-bash">config.js</code> from
them.
**NOTE**: many frontend related configuration variables are explained in the
[Common](#common) section, this section explains **frontend only** options.
But in case you have a custom setup you probably need setup the following environment
In case you have a custom setup, you probably need to configure the following environment
variables on the frontend container:
To connect the frontend to the exporter and backend, you need to fill out these environment variables.
@@ -438,54 +511,36 @@ PENPOT_EXPORTER_URI: http://your-penpot-exporter:6061
These variables are used for generate correct nginx.conf file on container startup.
### Demo warning ###
If you want to show a warning in the register and login page saying that this is a
demonstration purpose instance (no backups, periodical data wipe, ...), set the following
variable:
```bash
PENPOT_FLAGS: [...] enable-demo-warning
```
## Other flags
There are other flags that are useful for a more customized Penpot experience. This section has the list of the flags meant
for the user:
- <code class="language-bash">enable-cors</code>: Enables the default cors cofiguration that allows all domains
(this configuration is designed only for dev purposes right now)
- <code class="language-bash">enable-backend-api-doc</code>: Enables the <code class="language-bash">/api/doc</code>
endpoint that lists all rpc methods available on backend
- <code class="language-bash">disable-email-verification</code>: Deactivates the email verification process
(only recommended for local or internal setups)
- <code class="language-bash">disable-secure-session-cookies</code>: By default, Penpot uses the
<code class="language-bash">secure</code> flag on cookies, this flag disables it;
it is useful if you plan to serve Penpot under different
domain than <code class="language-bash">localhost</code> without HTTPS
- <code class="language-bash">disable-login-with-password</code>: allows disable password based login form
- <code class="language-bash">disable-registration</code>: disables registration (still enabled for invitations only).
- <code class="language-bash">enable-prepl-server</code>: enables PREPL server, used by manage.py and other additional
tools for communicate internally with Penpot backend
tools to communicate internally with Penpot backend. Check the [CLI section][5] to get more detail.
__Since version 1.13.0__
- <code class="language-bash">enable-log-invitation-tokens</code>: for cases where you don't have email configured, this
will log to console the invitation tokens
- <code class="language-bash">enable-log-emails</code>: if you want to log in console send emails. This only works if smtp
is not configured
will log to console the invitation tokens.
__Since version 2.0.0__
- <code class="language-bash">disable-onboarding-team</code>: for disable onboarding team creation modal
- <code class="language-bash">disable-onboarding-newsletter</code>: for disable onboarding newsletter modal
- <code class="language-bash">disable-onboarding-questions</code>: for disable onboarding survey
- <code class="language-bash">disable-onboarding</code>: for disable onboarding modal
- <code class="language-bash">disable-dashboard-templates-section</code>: for hide the templates section from dashboard
- <code class="language-bash">enable-webhooks</code>: for enable webhooks
- <code class="language-bash">enable-access-tokens</code>: for enable access tokens
- <code class="language-bash">disable-google-fonts-provider</code>: disables the google fonts provider (frontend)
- <code class="language-bash">disable-onboarding</code>: disables the onboarding modals.
- <code class="language-bash">disable-dashboard-templates-section</code>: hides the templates section from dashboard.
- <code class="language-bash">enable-webhooks</code>: enables webhooks. More detail about this configuration in [webhooks section][6].
- <code class="language-bash">enable-access-tokens</code>: enables access tokens. More detail about this configuration in [access tokens section][7].
- <code class="language-bash">disable-google-fonts-provider</code>: disables the google fonts provider.
[1]: /technical-guide/getting-started#configure-penpot-with-elestio
[2]: /technical-guide/getting-started#configure-penpot-with-docker
[3]: /technical-guide/developer/common#dev-environment
[4]: https://github.com/penpot/penpot/blob/main/docker/images/files/nginx.conf
[5]: /technical-guide/getting-started/#using-the-cli-for-administrative-tasks
[6]: /technical-guide/integration/#webhooks
[7]: /technical-guide/integration/#access-tokens

View File

@@ -319,14 +319,14 @@ cd penpot/frontend
3. Run the tests with <code class="language-bash">yarn</code>:
```bash
yarn e2e:test
yarn test:e2e
```
> 💡 **TIP:** By default, the tests will _not_ run in parallel. You can set the amount of workers to run the tests with <code class="language-bash">--workers</code>. Note that, depending on your machine, this might make some tests flaky.
```bash
# run in parallel with 4 workers
yarn e2e:test --workers 4
yarn test:e2e --workers 4
```
#### Running the tests in Chromium

View File

@@ -195,23 +195,23 @@ If you want to stop running Penpot, just type
docker compose -p penpot -f docker-compose.yaml down
```
### Configure Penpot with Docker
The configuration is defined using environment variables in the <code class="language-bash">docker-compose.yaml</code>
file. The default downloaded file already comes with the essential variables already set,
The configuration is defined using flags and environment variables in the <code class="language-bash">docker-compose.yaml</code>
file. The default downloaded file comes with the essential flags and variables already set,
and other ones commented out with some explanations.
#### Create users using CLI
You can find all configuration options in the [Configuration][1] section.
By default (or when <code class="language-bash">disable-email-verification</code> flag is used), the email verification process
is completely disabled for new registrations but it is highly recommended enabling email
verification or disabling registration if you are going to expose your penpot instance to
the internet.
### Using the CLI for administrative tasks
Penpot provides a script (`manage.py`) with some administrative tasks to perform in the server.
If you have registration disabled, you can create additional profiles using the
command line interface:
**NOTE**: this script will only work with the <code class="language-bash">enable-prepl-server</code>
flag set in the docker-compose.yaml file. For older versions of docker-compose.yaml file,
this flag is set in the backend service.
For instance, if the registration is disabled, the only way to create a new user is with this script:
```bash
docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profile
@@ -221,12 +221,6 @@ docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profile
For example it could be <code class="language-bash">penpot-penpot-backend-1</code> or <code class="language-bash">penpot_penpot-backend-1</code>.
You can check the correct name executing <code class="language-bash">docker ps</code>.
**NOTE:** This script only will works when you properly have the <code class="language-bash">enable-prepl-server</code>
flag set on backend (is set by default on the latest docker-compose.yaml file)
You can find all configuration options in the [Configuration][1] section.
### Update Penpot
To get the latest version of Penpot in your local installation, you just need to
@@ -286,6 +280,126 @@ 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.
### Configure the proxy
Your host configuration needs to make a proxy to http://localhost:9001.
#### Example with NGINX
```bash
server {
listen 80;
server_name penpot.mycompany.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name penpot.mycompany.com;
# This value should be in sync with the corresponding in the docker-compose.yml
# PENPOT_HTTP_SERVER_MAX_BODY_SIZE: 31457280
client_max_body_size 31457280;
# Logs: Configure your logs following the best practices inside your company
access_log /path/to/penpot.access.log;
error_log /path/to/penpot.error.log;
# TLS: Configure your TLS following the best practices inside your company
ssl_certificate /path/to/fullchain;
ssl_certificate_key /path/to/privkey;
# Websockets
location /ws/notifications {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://localhost:9001/ws/notifications;
}
# Proxy pass
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_pass http://localhost:9001/;
}
}
```
#### Example with CADDY SERVER
```bash
penpot.mycompany.com {
reverse_proxy :9001
tls /path/to/fullchain.pem /path/to/privkey.pem
log {
output file /path/to/penpot.log
}
}
```
### 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 <code class="language-bash">Console</code>, you can see the exact version that's being used.
<figure>
<a href="/img/dev-tools-1.png" target="_blank">
<img src="/img/dev-tools-1.png" alt="Devtools > Console" />
</a>
</figure>
Other interesting tab in the devtools is the <code class="language-bash">Network</code> tab, to check if there is a request that throws errors.
<figure>
<a href="/img/dev-tools-2.png" target="_blank">
<img src="/img/dev-tools-2.png" alt="Devtools > Network" />
</a>
</figure>
**Penpot Report**
When Penpot crashes, it provides a report with very useful information. Don't miss it!
<figure>
<a href="/img/penpot-report.png" target="_blank">
<img src="/img/penpot-report.png" alt="Penpot report" />
</a>
</figure>
## Install with Kubernetes
@@ -297,7 +411,6 @@ you need.
Therefore, your prerequisite will be to have a Kubernetes cluster on which we can install
Helm.
### What is Helm
*Helm* is the package manager for Kubernetes. A *Chart* is a Helm package. It contains
@@ -427,7 +540,7 @@ are using during you setup).
There are some other options, **NOT SUPPORTED BY PENPOT**:
* Install with <a href="https://community.penpot.app/t/how-to-develop-penpot-with-podman-penpotman/2113" target="_blank">Podman</a> instead of Docker.
* Try the under development <a href="https://community.penpot.app/t/introducing-penpot-desktop/1468" target="_blank">Penpot Desktop app</a>.
* Try the under development <a href="https://github.com/author-more/penpot-desktop/releases/latest" target="_blank">Penpot Desktop app</a>.
* Try a simple Kubernetes Deployment option <a href="https://github.com/degola/penpot-kubernetes" target="_blank">penpot-kubernetes</a>.
* Or try a fully manual installation if you have a really specific use case.. For help, you can look at the [Architecture][2] section and the <a href="https://github.com/penpot/penpot/tree/develop/docker/images" target="_blank">Docker configuration files</a>.

View File

@@ -38,7 +38,7 @@ title: 14· Import/export files
<figure><img src="/img/import-export/export-libraries.webp" alt="Export penpot file" /></figure>
<h2 id="files-import">Import Penpot files</h2>
<p class="advice">Importing files from other tools and services is among the main priorities of the Penpot team. Related features are coming soon.</p>
<p class="advice">The maximum file import size is currently limited to 1GB.</p>
<p>The import option is at the projects menu. Press “Import files” and then select one or more .penpot files to import. You can import a .zip file as well.</p>
<figure><img src="/img/import-export/import-menu.webp" alt="Import penpot file" /></figure>
@@ -50,7 +50,7 @@ title: 14· Import/export files
<p>Unlike other design tools, <strong>Penpot's format is built on standard languages</strong>. The exported file is essentially a ZIP archive containing binary assets (such as bitmap and vector images) alongside a readable JSON structure. By avoiding proprietary formats, Penpot empowers users with autonomy from specific tools while enabling seamless third-party integrations.</p>
<h3>Deprecated Penpot file formats</h3>
<p class="advice">These formats can only be exported from version 2.3 or earlier versions, but can be imported to any Penpot version</p>
<p class="advice">These formats can only be exported from version 2.3 or earlier versions, but can be imported to any Penpot version.</p>
<p>There are two different deprecated Penpot file formats in which you can import/export Penpot files. A standard one and a binary one. You always have the chance to use both for any file.</p>
<h4>[Deprecated] Penpot file (.penpot).</h4>
<p>The fast one. Binary Penpot specific.</p>

View File

@@ -130,6 +130,21 @@ title: Shortcuts
<td style="text-align: center;"><kbd>Ctrl</kbd><kbd>Z</kbd></td>
<td style="text-align: center;"><kbd>⌘</kbd><kbd>Z</kbd></td>
</tr>
<tr>
<td>Copy link to board</td>
<td style="text-align: center;"><kbd>Shift</kbd><kbd>Alt</kbd><kbd>C</kbd></td>
<td style="text-align: center;"><kbd>⇧</kbd><kbd>Alt</kbd><kbd>C</kbd></td>
</tr>
<tr>
<td>Copy properties</td>
<td style="text-align: center;"><kbd>Ctrl</kbd><kbd>Alt</kbd><kbd>C</kbd></td>
<td style="text-align: center;"><kbd>⌘</kbd><kbd>⌥</kbd><kbd>C</kbd></td>
</tr>
<tr>
<td>Paste properties</td>
<td style="text-align: center;"><kbd>Ctrl</kbd><kbd>Alt</kbd><kbd>V</kbd></td>
<td style="text-align: center;"><kbd>⌘</kbd><kbd>⌥</kbd><kbd>V</kbd></td>
</tr>
</tbody>
</table>

View File

@@ -103,7 +103,6 @@ title: 04· Layer basics
<p>At the dropdown menu (right click on a layer to show it) there's the option "Select layer" that allows the user to select one layer among the ones that are under the cursor's location.</p>
<p><img src="/img/layers-select-menu.gif" alt="layers select" /></p>
<h2 id="group-layers">Group layers</h2>
<p>Grouped layers can be moved, transformed or styled at the same time. </p>
<ul>
@@ -128,7 +127,6 @@ title: 04· Layer basics
</video>
</figure>
<h2 id="move-layers">Move layers</h2>
<p>To move one or more layers on the viewport you have to select them first and then click and drag the selection where you want to place them. You can also use the design panel to set a precise position relative to the viewport or the board.</p>
<figure>
@@ -137,7 +135,6 @@ title: 04· Layer basics
</video>
</figure>
<h2 id="resize-layers">Resize layers</h2>
<p>To resize a selected layer you can use the handles at the edges of the selection box. Make sure the cursor is in resizing mode. You can also use the design panel where you can link width and height.</p>
<ul>
@@ -204,6 +201,18 @@ title: 04· Layer basics
</video>
</figure>
<h2 id="scale-elements">Copy CSS properties</h2>
<p>To copy CSS properties from layers:</p>
<ol>
<li>Select one or more layers.</li>
<li>Right click to show the layer menu.</li>
<li>Press <strong>Copy/Paste as... > Copy as CSS</strong> in case you only want to get the CSS properties from the selected layer/s.</li>
<li>Press <strong>Copy/Paste as... > Copy as CSS (nested layers)</strong> in case you only want to get the CSS properties from the selected layer/s and all the contained layers.</li>
</ol>
<figure>
<img alt="Copy CSS properties" src="/img/layers/copy-css.webp"/>
</figure>
<h2 id="collapse-groups">Collapse groups and boards</h2>
<p>Groups and boards can have their contents expanded and collapsed. Click on the arrow at the
right side to toggle the visibility of their contents. </p>

View File

@@ -33,6 +33,20 @@ are shown by default at the <a href="/user-guide/view-mode">View mode</a>, actin
</video>
</figure>
<h3>Rename boards</h3>
<p>There several ways to rename boards:</p>
<ul>
<li>Double click on the board name at the workspace viewport.</li>
<li>Double click on the board name at the layers panel.</li>
<li>Press <kbd>Alt/⌥</kbd> + <kbd>N</kbd> to rename the board at the layers panel.</li>
<li>Right click to show the menu and select "Rename".</li>
</ul>
<figure>
<video title="Rename board" muted="" playsinline="" controls="" width="auto" poster="/img/objects/board-rename.webp" height="auto">
<source src="/img/objects/board-rename.webm" type="video/webm">
</video>
</figure>
<h3>Set board as thumbnail</h3>
<p>Select a specific board to be the file thumbnail that will be shown at <a href="/user-guide/the-interface/#dashboard-interface" target="_blank">the dashboard</a> in the file card.</p>
<p>To set a custom thumbnail:</p>
@@ -46,6 +60,17 @@ are shown by default at the <a href="/user-guide/view-mode">View mode</a>, actin
</video>
</figure>
<h3>Copy link to board</h3>
<p>You can get the link to each individual board, making it easy to share them with team members or include direct links in documentation.</p>
<figure>
<img src="/img/objects/board-copy-link.webp" alt="copy link to board">
</figure>
<p>There are two ways to copy a direct link to a board:</p>
<ul>
<li>Using the menu: Select the board, right click and select the "Copy link" option.</li>
<li>Using the shortcut: Select the board and press <kbd>Shift/⇧</kbd> + <kbd>Alt/⌥</kbd> + <kbd>C</kbd>.</li>
</ul>
<h3>Clip content</h3>
<p>Boards offer the option to clip its content (or not).</p>
<figure>
@@ -73,6 +98,12 @@ Penpot allows you to decide if the fill of an artboard will be shown in exports,
<img src="/img/objects/board-fill.webp" alt="show board fill in exports">
</figure>
<h3>Resize board to fit to content</h3>
<p>You can adjust the board size to fit its content by clicking the icon in the design sidebar.</p>
<figure>
<img src="/img/objects/board-fit.webp" alt="Resize board to fit to content button">
</figure>
<h3>Board guides</h3>
<p>You can set guides on boards that will assist with aligning objects.</p>
<p>Read more about <a href="/user-guide/workspace-basics/#guides">guides</a>.</p>
@@ -171,7 +202,6 @@ You can choose to edit individual nodes or create new ones. Press <kbd>Esc</kbd>
</video>
</figure>
<h2 id="images">Images</h2>
<h3>Insert images</h3>
<p>There are several options for inserting an image into a Penpot file:</p>

View File

@@ -51,13 +51,24 @@ title: 06· Styling
<ol>
<li><strong>Eyedropper</strong> - Allows you to pick any color of the objects at the viewport.</li>
<li><strong>Color profiles</strong> - Select between RGB, the Harmony Wheel or HSV.</li>
<li><strong>Color type</strong> - Solid, linear gradient, radial gradient or image.</li>
<li><strong>Color type</strong> - Solid, gradient, or image.</li>
<li><strong>Sliders</strong> - Easily manage settings like brightness, saturation or opacity.</li>
<li><strong>Values</strong> - Set precise color values of red(R), green(G), blue(B) and transparency(A).</li>
<li><strong>Libraries</strong> - Switch between recent colors and libraries.</li>
<li><strong>Color palette</strong> - A quick launcher of the palette with the selected library.</li>
</ol>
<h3 id="color-picker-gradients">Gradients</h3>
<p>You can apply gradient fills to layers. To do that select the Gradient type at the color picker.</p>
<figure>
<img alt="Gradient" src="/img/styling/color-picker-gradient.webp"/>
</figure>
<p>You can choose between two types of gradients:</p>
<ul>
<li><strong>Linear Gradient:</strong> A smooth transition between two or more colors along a straight line, with the option to adjust the angle.</li>
<li><strong>Radial Gradient:</strong> A circular color transition that starts with one color at the center and gradually shifts to another at the edges, which could be a different color or a fade to transparency.</li>
</ul>
<h2 id="color-palette">Color palette</h2>
<p>The color palette allows you to have a selected color library in plain sight.</p>
<figure>
@@ -181,4 +192,29 @@ title: 06· Styling
<li><strong>Saturation</strong></li>
<li><strong>Color</strong></li>
<li><strong>Luminosity</strong></li>
</ul>
</ul>
<h2 id="copy-paste-properties">Copy/Paste properties</h2>
<p>You can copy and apply properties, including fills, strokes, shadows, and others from one layer to another—or multiple layers with just a few clicks. You can do it using the layer's menu or shortcuts.</p>
<figure>
<video title="Apply blur to a layer" muted="" playsinline="" controls="" width="100%" poster="/img/styling/copy-properties.webp" height="auto">
<source src="/img/styling/copy-properties.mp4" type="video/mp4">
</video>
</figure>
<p>Using the layer menu</p>
<ol>
<li>Select one layer.</li>
<li>Right click to show the layer menu.</li>
<li>Press <strong>Copy/Paste as... > Copy properties</strong>.</li>
<li>Select one or more other layers.</li>
<li>Right click to show the layer/s menu.</li>
<li>Press <strong>Copy/Paste as... > Paste properties</strong>.</li>
</ol>
<p>Using Shortcuts</p>
<ul>
<li><strong>Copy properties</strong>: <kbd>Ctrl/⌘</kbd> + <kbd>Alt/⌥</kbd> + <kbd>C</kbd></li>
<li><strong>Paste properties</strong>: <kbd>Ctrl/⌘</kbd> + <kbd>Alt/⌥</kbd> + <kbd>V</kbd></li>
</ul>

View File

@@ -41,8 +41,6 @@ member is allowed to do depends on their permissions.</p>
<li><strong>Admin:</strong> Admins have the same permissions as editors, with the added ability to change every other member's role except owners. They can invite members and update team settings.</strong></li>
<li><strong>Owner:</strong> There's only one owner per team, the role is automatically assigned to the team creator. Owners have all the permissions of admins, with the additional ability to change any member's role, including transferring ownership. Owners can update team settings, invite members and delete teams.</strong></li>
</ul>
<figure><img src="/img/teams/teams-permissions.webp" alt="Team members" /></figure>
<p class="advice">More team roles will be eventually available, as well as fine grained permissions management to control members access and actions.</p>
<h3>Transfer ownership</h3>
<p>An owner can transfer their ownership to another team member anytime and is requested to transfer it before leaving the team.</p>

View File

@@ -5,52 +5,6 @@ title: 02· The interface
<h1 id="the-interface">The interface</h1>
<p class="main-paragraph">The Penpot interface has three main areas: Dashboard, Workspace and View mode. Lets take a look at their composition and main features.</p>
<h2 id="interface-dashboard">Dashboard</h2>
<p>The Dashboard is the place where you will be able to organize your files, libraries, projects and teams.</p>
<figure>
<a href="/img/interface/dashboard-dark.webp" target="_blank">
<img src="/img/interface/dashboard-dark.webp" alt="Penpot's dashboard" />
</a>
</figure>
<p class="hint">
<strong>1)</strong> Teams
<strong>2)</strong> Search files
<strong>3)</strong> Projects
<strong>4)</strong> Drafts
<strong>5)</strong> Shared Libraries
<strong>6)</strong> Custom fonts
<strong>7)</strong> Pinned projects
<strong>8)</strong> User area
<strong>9)</strong> Comment notifications
<strong>10)</strong> Create project
<strong>11)</strong> File card
<strong>12)</strong> Libraries & Templates module
</p>
<ol>
<li><strong>Teams:</strong> A team allows you to collaborate with other Penpot users. Team members are allowed to work with any project or file within the team depending on their permissions. Members with admin permissions can also invite other members. Create or join as many teams as you need with different groups of people.</li>
<li><strong>Search:</strong> If you are looking for a specific file just type its title at the search box.</li>
<li><strong>Projects:</strong> A project allows you to group design files. It works pretty much like a folder in a file system. You can create as many projects as you need. If you are going to work with more people in a project, you should create it inside a team.</li>
<li><strong>Drafts:</strong> The drafts section is where you can find the design files that are not inside any project.</li>
<li><strong>Shared Libraries:</strong> In this section you will find all the design files that have been added as shared libraries. That way you will be able to better control the files that are sharing their assets. </li>
<li><strong>Custom fonts:</strong> If you have purchased or own personal fonts that are not included in the catalog provided by Penpot, you can upload them from your computer and use them across the files of a team.</li>
<li><strong>Pinned projects:</strong> If you want to keep some projects handy (for instance because youre currently working on them) you can pin them to make them quickly available at the sidebar.</li>
<li><strong>User area:</strong> This must be you! Access your profile settings, Penpot tutorials, the Penpot Community and more. You can also find here a way to leave us feedback. Wed love to read your thoughts :)</li>
<li><strong>Comments notifications:</strong> Here you will be able to see if you have unread comments inside the files of the team.</li>
<li><strong>Create project:</strong> Create as many projects as you need to organize your designs.</li>
<li><strong>File card:</strong> Basic information about a file at plain sight. A preview, update info or if its added as a Shared Library. From there you can perform several actions over the file (rename, duplicate, move, download, delete).</li>
<li><strong>Libraries & Templates module:</strong> A curated selection of Libraries & Templates files ready to import.</li>
</ol>
<h3 id="your-account">Your account</h3>
<p>Your account settings can be changed at the user area, in <b>Your account</b>. Here you can make changes to your profile, password or account language, as well as generate personal access tokens and access release notes.</p>
<p>If you want to change the email address associated to your account or remove your account entirely, this can be done in the <b>Profile</b> section.</p>
<h2 id="interface-workspace">Workspace</h2>
<p>The Workspace is where you actually create your designs. You have an infinite canvas where you can work directly but you also have the ability to create and work inside boards that will help you to create pages and exportation units.</p>
@@ -102,8 +56,6 @@ title: 02· The interface
<li><strong>Assets panel:</strong> Each file has a default library (File Library) where you can store elements and styles that are likely to be reused within a project. That includes components, colors and typographies. To add an asset to a library just click the “+” button at the header of each asset group.</li>
</ol>
<h2 id="interface-viewmode">View mode</h2>
<p>Launch the view mode to present and share your designs, comment on them and play with the interactions set at the workspace. You also have an Inspect mode where you can get properties specifications and code snippets. <a href="/user-guide/view-mode/">More about the View mode.</a></p>
@@ -145,6 +97,83 @@ title: 02· The interface
<li><strong>Navigation buttons:</strong> Forward and backwards buttons.</li>
</ol>
<h2 id="interface-dashboard">Dashboard</h2>
<p>The Dashboard is the place where you will be able to organize your files, libraries, projects and teams.</p>
<figure>
<a href="/img/interface/dashboard-dark.webp" target="_blank">
<img src="/img/interface/dashboard-dark.webp" alt="Penpot's dashboard" />
</a>
</figure>
<p class="hint">
<strong>1)</strong> Teams
<strong>2)</strong> Search files
<strong>3)</strong> Projects
<strong>4)</strong> Drafts
<strong>5)</strong> Shared Libraries
<strong>6)</strong> Custom fonts
<strong>7)</strong> Pinned projects
<strong>8)</strong> User area
<strong>9)</strong> Comment notifications
<strong>10)</strong> Create project
<strong>11)</strong> File card
<strong>12)</strong> Libraries & Templates module
</p>
<ol>
<li><strong>Teams:</strong> A team allows you to collaborate with other Penpot users. Team members are allowed to work with any project or file within the team depending on their permissions. Members with admin permissions can also invite other members. Create or join as many teams as you need with different groups of people.</li>
<li><strong>Search:</strong> If you are looking for a specific file just type its title at the search box.</li>
<li><strong>Projects:</strong> A project allows you to group design files. It works pretty much like a folder in a file system. You can create as many projects as you need. If you are going to work with more people in a project, you should create it inside a team.</li>
<li><strong>Drafts:</strong> The drafts section is where you can find the design files that are not inside any project.</li>
<li><strong>Shared Libraries:</strong> In this section you will find all the design files that have been added as shared libraries. That way you will be able to better control the files that are sharing their assets. </li>
<li><strong>Custom fonts:</strong> If you have purchased or own personal fonts that are not included in the catalog provided by Penpot, you can upload them from your computer and use them across the files of a team.</li>
<li><strong>Pinned projects:</strong> If you want to keep some projects handy (for instance because youre currently working on them) you can pin them to make them quickly available at the sidebar.</li>
<li><strong>User area:</strong> This must be you! Access your profile settings, Penpot tutorials, the Penpot Community and more. You can also find here a way to leave us feedback. Wed love to read your thoughts :)</li>
<li><strong>Comments notifications:</strong> Here you will be able to see if you have unread comments inside the files of the team.</li>
<li><strong>Create project:</strong> Create as many projects as you need to organize your designs.</li>
<li><strong>File card:</strong> Basic information about a file at plain sight. A preview, update info or if its added as a Shared Library. From there you can perform several actions over the file (rename, duplicate, move, download, delete).</li>
<li><strong>Libraries & Templates module:</strong> A curated selection of Libraries & Templates files ready to import.</li>
</ol>
<h3 id="your-account">Your account</h3>
<p>Your account settings can be changed at the user area, in <b>Your account</b>. Here you can make changes to your profile, password or account language, as well as generate personal access tokens and access release notes.</p>
<h4 id="your-account-profile">Profile
<a class="direct-link" href="#your-account-profile">#</a>
</h3>
<p>If you want to change the email address associated to your account or remove your account entirely, this can be done in the <b>Profile</b> section.</p>
<figure>
<img src="/img/interface/youraccount-profile.webp" alt="Penpot's profile" />
</figure>
<h4 id="your-account-password">Password
<a class="direct-link" href="#your-account-password">#</a>
</h3>
<p>If you want to change your password to a new one, this can be done in the <b>Password</b> section.</p>
<figure>
<img src="/img/interface/youraccount-password.webp" alt="Penpot's password" />
</figure>
<h4 id="your-account-notifications">Notifications
<a class="direct-link" href="#your-account-notifications">#</a>
</h3>
<p>At the <strong>Notifications</strong> section you can configure the email and dashboard notifications.</p>
<figure>
<img src="/img/interface/youraccount-notifications.webp" alt="Penpot's notifications" />
</figure>
<h4 id="your-account-settings">Settings
<a class="direct-link" href="#your-account-settings">#</a>
</h3>
<p>At the <strong>Settings</strong> section you can change the language and the UI color theme.</p>
<figure>
<img src="/img/interface/youraccount-settings.webp" alt="Penpot's settings" />
</figure>
<h4 id="your-account-accesstokens">Access tokens
<a class="direct-link" href="#your-account-accesstokens">#</a>
</h3>
<p>At the <strong>Asset tokens</strong> section you can manage your access tokens. <a href="https://help.penpot.app/technical-guide/integration/#access-tokens" target="_blank">Read more about access tokens here</a>.</p>
<h2 id="interface-ui-theme">UI Theme</h2>
<p>Penpot's default interface is dark but you can switch anytime to a light option. You have 2 ways to change the theme:</p>
@@ -170,4 +199,4 @@ title: 02· The interface
<img src="/img/interface/viewmode-light.webp" alt="Penpot's view mode" />
</a>
<figcaption>Penpot's view mode in light mode</figcaption>
</figure>
</figure>

View File

@@ -3,12 +3,13 @@
set -ex
export CURRENT_VERSION=$1;
yarn install
rm -rf target
export NODE_ENV=production;
corepack enable;
corepack up || exit 1;
yarn install || exit 1;
rm -rf target
# Build the application
clojure -M:dev:shadow-cljs release main;

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,43 @@
{
"~#set": [
{
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
"~:name": "Lorem Ipsum",
"~:revn": 2,
"~:modified-at": "~m1739356261950",
"~:vern": 0,
"~:id": "~u69b52fcf-7de0-81cd-8005-b9b180a0bfb5",
"~:thumbnail-id": "~u55bb9e08-6eed-4a64-a94d-2bcce7006e79",
"~:is-shared": true,
"~:project-id": "~u1ad2931c-eb80-8098-8005-b86c1d9d26c2",
"~:created-at": "~m1739356217030",
"~:library-summary": {
"~:components": {
"~:count": 0,
"~:sample": []
},
"~:media": {
"~:count": 0,
"~:sample": []
},
"~:colors": {
"~:count": 1,
"~:sample": [
{
"~:path": "",
"~:color": "#0087ff",
"~:name": "#0087ff",
"~:modified-at": "~m1739356244863",
"~:opacity": 1,
"~:id": "~u0449ccff-62fe-805c-8005-b9b194b094dd"
}
]
},
"~:typographies": {
"~:count": 0,
"~:sample": []
}
}
}
]
}

View File

@@ -0,0 +1,48 @@
[
{
"~:features": {
"~#set": [
"layout/grid",
"styles/v2",
"fdata/pointer-map",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true
},
"~:name": "Default",
"~:modified-at": "~m1713533116375",
"~:id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
"~:created-at": "~m1713533116375",
"~:is-default": true
},
{
"~:features": {
"~#set": [
"layout/grid",
"styles/v2",
"fdata/pointer-map",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true
},
"~:name": "Second team",
"~:modified-at": "~m1701164272671",
"~:id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
"~:created-at": "~m1701164272671",
"~:is-default": false
}
]

View File

@@ -0,0 +1,115 @@
{
"~:features": {
"~#set": [
"layout/grid",
"fdata/pointer-map",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true,
"~:can-read": true,
"~:is-logged": true
},
"~:has-media-trimmed": false,
"~:comment-thread-seqn": 0,
"~:name": "10113 - Emtpy lib",
"~:revn": 1,
"~:modified-at": "~m1739365936352",
"~:vern": 0,
"~:id": "~u5b7ebd2b-2907-80db-8005-b9d67c20cf2e",
"~:is-shared": false,
"~:migrations": {
"~#ordered-set": [
"legacy-2",
"legacy-3",
"legacy-5",
"legacy-6",
"legacy-7",
"legacy-8",
"legacy-9",
"legacy-10",
"legacy-11",
"legacy-12",
"legacy-13",
"legacy-14",
"legacy-16",
"legacy-17",
"legacy-18",
"legacy-19",
"legacy-25",
"legacy-26",
"legacy-27",
"legacy-28",
"legacy-29",
"legacy-31",
"legacy-32",
"legacy-33",
"legacy-34",
"legacy-36",
"legacy-37",
"legacy-38",
"legacy-39",
"legacy-40",
"legacy-41",
"legacy-42",
"legacy-43",
"legacy-44",
"legacy-45",
"legacy-46",
"legacy-47",
"legacy-48",
"legacy-49",
"legacy-50",
"legacy-51",
"legacy-52",
"legacy-53",
"legacy-54",
"legacy-55",
"legacy-56",
"legacy-57",
"legacy-59",
"legacy-62",
"legacy-65",
"legacy-66",
"legacy-67"
]
},
"~:version": 67,
"~:project-id": "~u1ad2931c-eb80-8098-8005-b86c1d9d26c2",
"~:created-at": "~m1739365911709",
"~:data": {
"~:pages": [
"~u5b7ebd2b-2907-80db-8005-b9d67c20cf2f"
],
"~:pages-index": {
"~u5b7ebd2b-2907-80db-8005-b9d67c20cf2f": {
"~#penpot/pointer": [
"~u5b7ebd2b-2907-80db-8005-b9d67c21cbd3",
{
"~:created-at": "~m1739365911687"
}
]
}
},
"~:id": "~u5b7ebd2b-2907-80db-8005-b9d67c20cf2e",
"~:options": {
"~:components-v2": true
},
"~:colors": {
"~u84a1567d-3f0f-804e-8005-b9d6907e3c8a": {
"~:path": "",
"~:color": "#0087ff",
"~:name": "#0087ff",
"~:modified-at": "~m1739365936355",
"~:opacity": 1,
"~:id": "~u84a1567d-3f0f-804e-8005-b9d6907e3c8a"
}
}
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,101 @@
{
"~:id": "~u5b7ebd2b-2907-80db-8005-b9d67c21cbd3",
"~:file-id": "~u5b7ebd2b-2907-80db-8005-b9d67c20cf2e",
"~:created-at": "~m1739365911680",
"~:data": {
"~:options": {},
"~:objects": {
"~u00000000-0000-0000-0000-000000000000": {
"~#shape": {
"~:y": 0,
"~:hide-fill-on-export": false,
"~:transform": {
"~#matrix": {
"~:a": 1.0,
"~:b": 0.0,
"~:c": 0.0,
"~:d": 1.0,
"~:e": 0.0,
"~:f": 0.0
}
},
"~:rotation": 0,
"~:name": "Root Frame",
"~:width": 0.01,
"~:type": "~:frame",
"~:points": [
{
"~#point": {
"~:x": 0.0,
"~:y": 0.0
}
},
{
"~#point": {
"~:x": 0.01,
"~:y": 0.0
}
},
{
"~#point": {
"~:x": 0.01,
"~:y": 0.01
}
},
{
"~#point": {
"~:x": 0.0,
"~:y": 0.01
}
}
],
"~:r2": 0,
"~:proportion-lock": false,
"~:transform-inverse": {
"~#matrix": {
"~:a": 1.0,
"~:b": 0.0,
"~:c": 0.0,
"~:d": 1.0,
"~:e": 0.0,
"~:f": 0.0
}
},
"~:r3": 0,
"~:r1": 0,
"~:id": "~u00000000-0000-0000-0000-000000000000",
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
"~:strokes": [],
"~:x": 0,
"~:proportion": 1.0,
"~:r4": 0,
"~:selrect": {
"~#rect": {
"~:x": 0,
"~:y": 0,
"~:width": 0.01,
"~:height": 0.01,
"~:x1": 0,
"~:y1": 0,
"~:x2": 0.01,
"~:y2": 0.01
}
},
"~:fills": [
{
"~:fill-color": "#FFFFFF",
"~:fill-opacity": 1
}
],
"~:flip-x": null,
"~:height": 0.01,
"~:flip-y": null,
"~:shapes": []
}
}
},
"~:id": "~u5b7ebd2b-2907-80db-8005-b9d67c20cf2f",
"~:name": "Page 1"
}
}

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,5 @@
{
"~:id": "~u5b7ebd2b-2907-80db-8005-b9d67c20cf2e",
"~:name": "10113 - Emtpy lib",
"~:is-shared": true
}

View File

@@ -72,7 +72,7 @@ export class DashboardPage extends BaseWebSocketPage {
this.draftsLink = this.sidebar.getByText("Drafts");
this.fontsLink = this.sidebar.getByText("Fonts");
this.libsLink = this.sidebar.getByText("Libraries");
this.librariesLink = this.sidebar.getByText("Libraries");
this.searchButton = page.getByRole("button", { name: "dashboard-search" });
this.searchInput = page.getByPlaceholder("Search…");
@@ -155,6 +155,9 @@ export class DashboardPage extends BaseWebSocketPage {
await this.mockRPC("search-files", "dashboard/search-files.json", {
method: "POST",
});
await this.mockRPC("delete-team", "dashboard/delete-team.json", {
method: "POST",
});
await this.mockRPC("search-files", "dashboard/search-files.json");
await this.mockRPC("get-teams", "logged-in-user/get-teams-complete.json");
}
@@ -278,6 +281,13 @@ export class DashboardPage extends BaseWebSocketPage {
await this.userProfileOption.click();
}
async goToLibraries() {
await this.page.goto(
`#/dashboard/libraries?team-id=${DashboardPage.anyTeamId}`,
);
await expect(this.mainHeading).toHaveText("Libraries");
}
}
export default DashboardPage;

View File

@@ -221,7 +221,7 @@ export class WorkspacePage extends BaseWebSocketPage {
}
async openLibrariesModal(clickOptions = {}) {
await this.sidebar.getByText("Libraries").click(clickOptions);
await this.sidebar.getByTestId("libraries").click(clickOptions);
await expect(this.librariesModal).toBeVisible();
}

Some files were not shown because too many files have changed in this diff Show More