Compare commits

...

402 Commits

Author SHA1 Message Date
Andrey Antukh
cc5b1c950b Merge branch 'translations' into staging 2023-08-30 10:35:47 +02:00
Andrey Antukh
52851f4c6f 📎 Add dutch language 2023-08-30 10:35:33 +02:00
Andrey Antukh
9bd42be771 Merge remote-tracking branch 'weblate/develop' into translations 2023-08-30 10:26:28 +02:00
Andrey Antukh
5f65960d42 Merge pull request #3568 from penpot/eva-fix-tooltip-visibility
🐛 Fix tooltip on toggle visibility and toggle lock buttons
2023-08-29 13:15:54 +02:00
Eva
dc813732c3 🐛 Fix tooltip on toggle visibility and toggle lock buttons 2023-08-29 13:15:40 +02:00
Andrey Antukh
661e4a001a Merge pull request #3569 from penpot/superalex-fix-invalid-file-amount-after-moving-files
🐛 Bugfixing
2023-08-29 13:13:36 +02:00
Alejandro Alonso
53d1624f3f 🐛 Fix deleted pages comments shown in right sidebar 2023-08-29 13:13:12 +02:00
Alejandro Alonso
514ba6604b 🐛 Fix invalid file amount after moving files 2023-08-29 13:13:11 +02:00
Alejandro
0aa361013a Merge pull request #3551 from penpot/niwinz-bugfixes-1
🐛 Fix unexpected output on get-page when invalid object-id is pro…
2023-08-29 13:04:34 +02:00
Andrey Antukh
ddbc828342 🐛 Fix unexpected output on get-page when invalid object-id is provided 2023-08-29 13:04:23 +02:00
Sebastiaan Pasma
67cff1ed74 🌐 Add translations for: Dutch.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2023-08-28 16:57:12 +02:00
Sebastiaan Pasma
22c88a19e2 🌐 Add translations for: Dutch.
Currently translated at 83.2% (1007 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2023-08-26 15:54:37 +02:00
Andrey Antukh
159ac92021 Merge pull request #3561 from penpot/superalex-click-flow-tag-open-viewer
 Click on flow tag open viewer
2023-08-25 12:40:10 +02:00
Andrey Antukh
1a92657c7c Merge pull request #3559 from penpot/superalex-fix-alt-l-shortcuts
🐛 Fix alt+l shortcuts
2023-08-25 12:37:10 +02:00
Alejandro Alonso
8669207086 Click on flow tag open viewer 2023-08-25 11:40:56 +02:00
Alejandro Alonso
b82ce671b9 🐛 Fix alt+l shortcuts 2023-08-25 10:54:34 +02:00
Aitor Moreno
ff14208a95 Merge pull request #3555 from penpot/superalex-navigate-up-in-layer-hierarchy-with-shift-enter-shortcut
 Navigate up in layer hierarchy with Shift+Enter shortcut
2023-08-24 13:42:12 +02:00
Aitor
8593ca1310 🐛 Fix scroll automatically to layer item 2023-08-24 13:31:47 +02:00
Alejandro Alonso
f69e141ac1 Navigate up in layer lierarchy with Shift+Enter shortcut 2023-08-24 12:25:03 +02:00
Alejandro
b0497f1352 Merge pull request #3554 from penpot/niwinz-staging-bugfixes-8
🐛 Prevent rollback for idle-in-transaction errors on cron tasks
2023-08-24 12:02:13 +02:00
Alejandro Alonso
aaf9c6e50b Enable access tokens by default 2023-08-24 12:00:56 +02:00
Andrey Antukh
d80aa7593b 🐛 Fix unexpected exception on encoding error response 2023-08-24 11:37:59 +02:00
Andrey Antukh
5275c35002 🐛 Prevent rollback for idle-in-transaction errors on cron tasks 2023-08-24 11:18:56 +02:00
Alejandro Alonso
f02b5765d7 🐛 Fix safe number max values 2023-08-23 14:59:08 +02:00
Alejandro Alonso
1f31722571 📎 Update version.txt file 2023-08-23 09:38:23 +02:00
Alejandro Alonso
834c18323e Revert "📎 Update version.txt file"
This reverts commit a7f39e89f6.
2023-08-23 09:38:07 +02:00
andy
1d2f5b6c0b 🌐 Add translations for: Norwegian Bokmål.
Currently translated at 12.5% (152 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nb_NO/
2023-08-22 11:49:11 +02:00
Alejandro
ab87db099a Merge pull request #3542 from penpot/niwinz-bugfixes-1
🐛 Fix inconsistencies on handlong :file-image attr on import and file-gc task
2023-08-22 06:50:58 +02:00
Andrey Antukh
661a916a5f 🐛 Fix reference counting of file-media objects in :fill-image attr 2023-08-21 19:11:55 +02:00
Andrey Antukh
b8dee17075 🐛 Fix incorrect streams handling on thumbnail_render 2023-08-21 19:11:55 +02:00
Stas Haas
c8d5e4ef35 🌐 Add translations for: German.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2023-08-19 13:55:24 +02:00
Alejandro Alonso
a7f39e89f6 📎 Update version.txt file 2023-08-18 10:35:12 +02:00
Pablo Alba
70bb34118c Merge pull request #3532 from penpot/hiru-fix-component-modified
🐛 Fix component modified date in v1
2023-08-17 17:18:48 +02:00
Andrés Moya
f409dfd3d1 🐛 Fix component modified date in v1 2023-08-17 16:05:54 +02:00
Alejandro Alonso
e1954b5dd7 🐛 Fix old files with invalid refs for texts and fills 2023-08-17 09:48:18 +02:00
Sebastiaan Pasma
196d57dd5c 🌐 Add translations for: Dutch.
Currently translated at 74.1% (897 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2023-08-15 15:50:00 +02:00
Alejandro
a1ac839b2a Merge pull request #3517 from penpot/niwinz-enhancements-push-notifications
🎉 Add the ability to send push notifications
2023-08-14 12:24:43 +02:00
Andrey Antukh
1e9a4d74eb 🐛 Add safechecks to binfile exportation 2023-08-14 12:13:31 +02:00
Andrey Antukh
7a9777419c Backport db module improvements from develop 2023-08-14 12:13:31 +02:00
Andrey Antukh
28836d82cd Add minor improvements to error report template 2023-08-14 12:13:31 +02:00
Andrey Antukh
da62a6809c Stop report oidc failed operations as exceptions 2023-08-14 12:13:31 +02:00
Andrey Antukh
5d5d238fec 💄 Add minor cosmetic improvements on dashboard ui component 2023-08-14 12:13:31 +02:00
Andrey Antukh
e5dedb1e3d 🎉 Add push notifications support 2023-08-14 12:13:31 +02:00
Sebastiaan Pasma
4c7cd02f56 🌐 Add translations for: Dutch.
Currently translated at 54.0% (654 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2023-08-12 14:49:53 +02:00
Alejandro Alonso
b3128bd32b 🐛 Fix overlay manual positioning 2023-08-10 11:15:42 +02:00
Alejandro Alonso
15a9035ed1 🐛 Fix multiple elements export 2023-08-09 12:19:27 +02:00
Vincas Dundzys
82e51d358b 🌐 Add translations for: Lithuanian.
Currently translated at 10.3% (125 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lt/
2023-08-09 09:04:18 +02:00
Alejandro
fbcc2494b4 Merge pull request #3509 from penpot/niwinz-staging-bugfixes-7
 Add improvements to api doc
2023-08-09 08:00:02 +02:00
Andrey Antukh
4a016dce14 📎 Add minor improvements on params formating on error logger 2023-08-08 14:53:55 +02:00
Andrey Antukh
53f40043aa 📎 Fix typo on api doc main template 2023-08-08 14:52:39 +02:00
Alejandro Alonso
937dd5a857 🐛 Fix zip importer for none fills 2023-08-08 13:52:09 +02:00
Andrey Antukh
36b167956c Add improvements to api doc 2023-08-08 13:44:47 +02:00
Alejandro
695152274c Merge pull request #3506 from penpot/niwinz-staging-bugfixes-7
 Improve error report of invalid image
2023-08-08 13:17:34 +02:00
Andrey Antukh
486c638076 🐛 Fix image upload issues on safari with drag&drop 2023-08-08 12:58:39 +02:00
Andrey Antukh
81facd58c9 Improve error report of invalid image 2023-08-08 12:57:49 +02:00
Alejandro
2a0031d23c Merge pull request #3505 from penpot/niwinz-staging-bugfixes-7
💄 Add  minor cosmetic improvement on error report template
2023-08-08 11:07:02 +02:00
Andrey Antukh
63a3186e6d 💄 Add minor cosmetic improvement on error report template 2023-08-08 10:42:26 +02:00
Alejandro Alonso
fcdf33b134 🐛 Fix backend api doc generation for auth required endpoints 2023-08-08 10:39:09 +02:00
Alejandro Alonso
19d88cc1a6 🐛 Fix backend api doc generation 2023-08-08 09:55:32 +02:00
Alejandro
1f68c6164a Merge pull request #3501 from penpot/niwinz-staging-bugfixes-7
 Improve get-user-info implementation (oidc)
2023-08-07 16:36:19 +02:00
Andrey Antukh
c39702fbf7 Improve get-user-info implementation (oidc) 2023-08-07 15:55:54 +02:00
Alejandro Alonso
b3f0683d02 🐛 Fix image validation 2023-08-07 15:06:59 +02:00
Alejandro
211de1bb9c Merge pull request #3498 from penpot/niwinz-staging-bugfixes-6
🐛 Allow nil values on bool content params
2023-08-07 12:57:30 +02:00
Andrey Antukh
fe80aab394 🐛 Allow nil values on bool content params 2023-08-07 11:46:19 +02:00
Alejandro
a494b89bba Merge pull request #3497 from penpot/niwinz-staging-bugfixes-6
🐛 Fix incorrect implementation on error reporting context collection
2023-08-07 11:20:00 +02:00
Andrey Antukh
6e313dff84 🐛 Add workaround for unexpected exception on fix-broken-shapes
which happens when we have a component shape tree with an ephimeral
shape with id ZERO (unused and with invalid children)
2023-08-07 11:12:27 +02:00
Alejandro Alonso
766040198a 🐛 Fix text validation 2023-08-07 09:29:04 +02:00
Andrey Antukh
7afaa9d31f 🐛 Fix incorrect implementation on error reporting context collection 2023-08-04 18:40:47 +02:00
Alejandro Alonso
cf68a9cf1e 🐛 Fix safe number max values 2023-08-04 15:16:43 +02:00
Alejandro
c69f6da2d7 Merge pull request #3493 from penpot/niwinz-staging-bugfixes-6
🐛 Several bugfixes
2023-08-04 13:35:12 +02:00
Andrey Antukh
259b05db51 Add more improvements to error reporting 2023-08-04 13:10:36 +02:00
Andrey Antukh
2ba7996116 🐛 Fix unexpected viewport update on leave workspace 2023-08-04 12:58:27 +02:00
Andrey Antukh
66e877ed40 🐛 Fix stroke-width parsing on svg upload
And refactor a bit the stroke parsing function
2023-08-04 12:58:27 +02:00
Alejandro
f3bf04e1c9 Merge pull request #3488 from penpot/niwinz-staging-bugfixes-5
 Add better error reporting on response encoding middleware
2023-08-03 16:46:05 +02:00
Alejandro Alonso
79e3aadfcf 🐛 Fix undo change for multiple shapes 2023-08-03 16:38:21 +02:00
Andrey Antukh
0527c55398 Add better exception handling on json content type handling 2023-08-03 16:31:35 +02:00
Andrey Antukh
54bb89b2bb ⬆️ Upgrade yetti to v9.16 (fixes exception unwrapping) 2023-08-03 16:31:35 +02:00
Andrey Antukh
9334f935eb Add better error reporting on response encoding middleware 2023-08-03 16:10:41 +02:00
Alejandro
fed31d366f Merge pull request #3480 from penpot/azazeln28-bugfixing-1
🐛 Bug fixing
2023-08-03 07:28:28 +02:00
Aitor Moreno
55b7bba944 Merge pull request #3484 from penpot/superalex-fix-duplicate-board
🐛 Fix duplicate board
2023-08-02 18:27:18 +02:00
Alejandro Alonso
3ff13f1d8f 🐛 Fix duplicate board 2023-08-02 18:22:46 +02:00
Aitor
4b28685a6d 🐛 Fix prototype selects preventing ctrl-z 2023-08-02 16:15:08 +02:00
Alejandro
53001921d5 Merge pull request #3481 from penpot/niwinz-staging-hotfix-4
🐛 Bugfixes & Improvements
2023-08-02 16:08:12 +02:00
Andrey Antukh
046f501152 Improve error reporting context 2023-08-02 14:51:12 +02:00
Andrey Antukh
00f7c94377 Improve database error reporter 2023-08-02 13:43:53 +02:00
Andrey Antukh
eae5dfc828 🐛 Don't send empty changes on fix broken shape links 2023-08-02 13:43:53 +02:00
Andrey Antukh
88261c2ec3 Increase network timeout on exporter dockerfile 2023-08-02 13:43:53 +02:00
Andrey Antukh
1bfc28f63d Add missing index on server_error_report table 2023-08-02 13:43:53 +02:00
Alejandro Alonso
e7a82579c1 🐛 Fix paste groups without shapes attr 2023-08-02 11:17:20 +02:00
Alejandro
30c786741f Merge pull request #3478 from penpot/niwinz-staging-hotfix-4
🐛 Fix broken shape relations on workspace initialization
2023-08-02 11:13:39 +02:00
Andrey Antukh
3eb2569465 Add better exception reporting on commit-changes 2023-08-02 10:45:11 +02:00
Andrey Antukh
7efeeec9b1 Add workspace initialization fix for broken shape references
Is the code that executes at workspace initialization that checks all
the shape children for broken references and proceed to emit a special
event that fixes the shape children references.
2023-08-02 10:45:11 +02:00
Aitor
67f56dd0f8 🐛 Fix color picker not working when using shortcut 2023-08-02 10:18:40 +02:00
Alejandro
2ec5a3ba6a Merge pull request #3476 from penpot/niwinz-staging-hotfix-4
 Improve ws-conn handling on session expiration
2023-08-01 14:41:15 +02:00
Andrey Antukh
958931d264 Improve ws-conn handling on session expiration 2023-08-01 13:09:51 +02:00
Alejandro Alonso
e3f69bcc98 🐛 Fix path validation 2023-08-01 12:39:33 +02:00
Alejandro
9c53a33bac Merge pull request #3472 from penpot/niwinz-staging-hotfix-3
🐛 Ensure :shapes attr on importing an svg with an empty group
2023-07-31 16:33:06 +02:00
Andrey Antukh
f72206bba3 🐛 Ensure :shapes attr on importing an svg with an empty group
This commit should not not be backported to, because the affected
code is already refactored and the issue is already fixed on develop
branch
2023-07-31 16:26:03 +02:00
Alejandro
37a19aa6b5 Merge pull request #3471 from penpot/niwinz-staging-hotfix-3
🐛 Hot Fixes
2023-07-31 16:20:47 +02:00
Andrey Antukh
17ea8300ed 🐛 Accept nil values for :fill-color-gradient attr 2023-07-31 15:58:32 +02:00
Andrey Antukh
aac044fa0a 🐛 Fix incorrect schema on bool-content 2023-07-31 15:49:42 +02:00
Alejandro
e935ccae76 Merge pull request #3469 from penpot/niwinz-staging-hotfix-2
🐛 Allow nil values for x,y,width and height on paths
2023-07-31 13:41:22 +02:00
Andrey Antukh
13312dc467 🐛 Allow nil values for x,y,width and height on paths 2023-07-31 13:36:28 +02:00
Alejandro Alonso
0ec49e5e95 🐛 Fix remove content from boolean 2023-07-31 13:02:52 +02:00
Alejandro
a49999186f Merge pull request #3466 from penpot/niwinz-staging-hotfix-1
🐛 Remove limits that can cause unexpected exceptions
2023-07-31 12:09:58 +02:00
Andrey Antukh
fc416ee4af 🐛 Make grid params type optional 2023-07-31 12:06:31 +02:00
Andrey Antukh
37bd537bfd 🐛 Remove limits that can cause unexpected exceptions 2023-07-31 11:54:29 +02:00
Alejandro
17798dbf40 Merge pull request #3459 from penpot/niwinz-staging-bugfixes
🐛 Bugfixes & Enhancements
2023-07-31 10:20:44 +02:00
Alejandro
4e1dfcce32 Merge pull request #3453 from penpot/azazeln28-fix-thumbnail-rendering-flashing
🐛 Fix thumbnail rendering flashing
2023-07-31 09:21:55 +02:00
Aitor
c28da17515 🐛 Fix thumbnail rendering flashing 2023-07-31 09:03:33 +02:00
Alejandro
9f0e65a042 Merge pull request #3450 from penpot/azazeln28-fix-ctrl-z-select-issue
🐛 Fix CTRL+Z in workspace select
2023-07-31 08:46:51 +02:00
Aitor
f1cf5d8ba8 🐛 Fix ctrl+z in workspace select issue 2023-07-31 08:38:48 +02:00
Eva Marco
cc682a382f Merge pull request #3455 from penpot/azazeln28-fix-layers-scroll-breaking-new-css-system
Fix layers scroll breaking new css system
2023-07-31 08:03:34 +02:00
Andrey Antukh
1f98b168ba 🐛 Set correct modification date on projects on file move operation 2023-07-28 13:20:57 +02:00
Andrey Antukh
21430cbd7d Show project modified date consistently 2023-07-28 13:20:57 +02:00
Andrey Antukh
f174264f7f 🎉 Add flex layout playground template to the dashboard carousel 2023-07-28 13:20:57 +02:00
Aitor Moreno
6eaa905f0c Merge pull request #3456 from penpot/niwinz-bugfixes
🐛 Bugfixes & Enhancements
2023-07-28 11:51:36 +02:00
Andrey Antukh
1c23e4e8be 🎉 Add v1.19 release notes dialog 2023-07-28 11:27:23 +02:00
Andrey Antukh
e0ad6c0b95 🐛 Fix unexpected exception on saving boolean shapes 2023-07-28 10:43:03 +02:00
Aitor
f1d73d5662 🐛 Fix layers scroll breaking new css system 2023-07-28 10:37:17 +02:00
Aitor Moreno
bbe3021aed Merge pull request #3448 from penpot/superalex-bugfixing-19
🐛 Bugfixing
2023-07-26 15:16:53 +02:00
Alejandro Alonso
934c6c5aae 🐛 Avoid just white spaces for old password 2023-07-26 15:12:35 +02:00
Alejandro Alonso
7036dddad1 🐛 Fix enable undo just after using pencil 2023-07-26 07:37:23 +02:00
Alejandro Alonso
92ee6320f5 🐛 Fix enable comment mode and insert image keeps on comment mode 2023-07-26 06:18:24 +02:00
Alejandro Alonso
8a3c580d0f 🐛 Fix undo layer mode preview 2023-07-26 06:18:08 +02:00
Aitor Moreno
08a11929ca Merge pull request #3442 from penpot/eva-bugfixing-11
Bugfixing
2023-07-25 17:42:05 +02:00
Alejandro
b460a8f64e Merge pull request #3447 from penpot/superalex-fix-retrieve-unread-comment-threads-extra-calls
🐛 Fix retrieve unread comment threads extra calls
2023-07-25 14:56:21 +02:00
Alejandro Alonso
1aa7960863 🐛 Fix retrieve unread comment threads extra calls 2023-07-25 14:50:42 +02:00
Alejandro
89edcb5651 Merge pull request #3446 from penpot/niwinz-improve-connection-error-handling-on-save
 Improve connection errors handling on workspace save operation
2023-07-25 13:56:37 +02:00
Eva
653bc66b8f 🐛 Fix dropdown width 2023-07-25 13:27:07 +02:00
Andrey Antukh
bec09fb5d1 Improve connection errors handling on workspace save operation 2023-07-25 12:52:47 +02:00
Eva
9048c01308 🐛 Fix copy color information in several formats 2023-07-25 11:57:41 +02:00
Eva
959e069ea9 🐛 Fix unnecessary button 2023-07-25 11:57:39 +02:00
Eva
955bf0ef9e 🐛 Fix empty reply comments 2023-07-25 11:57:20 +02:00
Eva Marco
9a60ac477f Merge pull request #3434 from penpot/superalex-bugfixing-18
🐛 Superalex bugfixing
2023-07-25 10:36:32 +02:00
Alejandro Alonso
ec131382b3 🐛 Fix error when a user different than the thread creator edits a comment 2023-07-25 10:32:11 +02:00
Alejandro Alonso
ea2e25b46d 🐛 Making old-password non required again 2023-07-25 10:32:11 +02:00
Alejandro Alonso
db7c4a9265 🐛 Fix export multiple images when only one of them has export settings 2023-07-25 10:32:11 +02:00
Alejandro Alonso
1b31a02c14 🐛 Fix when user deletes one file during import it is impossible to finish importing of second file 2023-07-25 10:32:09 +02:00
Alejandro
dcbf57d8d2 Merge pull request #3443 from penpot/palba-fix-incorrect-style-layers-tab-titles
Fix incorrect style for layers tab titles
2023-07-25 09:55:18 +02:00
Pablo Alba
6e73e7cc71 Fix incorrect style for layers tab titles 2023-07-25 09:45:52 +02:00
Aitor
44e31f1890 📚 Add missing change in CHANGES.md 2023-07-25 09:05:59 +02:00
Aitor
fb4ee4a355 🐛 Fix text gradient handlers 2023-07-25 06:56:25 +02:00
Yaron Shahrabani
1a92bd0478 🌐 Add translations for: Hebrew.
Currently translated at 99.7% (1206 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2023-07-24 21:05:43 +02:00
Alejandro
d254184057 Merge pull request #3428 from penpot/alotor-bugfixes-6
Bugfixes
2023-07-24 07:42:03 +02:00
AlexTECPlayz
cd55adefb8 🌐 Add translations for: Romanian.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ro/
2023-07-18 15:02:57 +02:00
Andrey Antukh
7e73ac307a Merge pull request #3426 from penpot/alotor-undo-transaction-fixes
Create guard for undo transactions
2023-07-14 17:25:17 +02:00
alonso.torres
f611584bb3 🐛 Create guard for undo transactions 2023-07-14 15:37:49 +02:00
alonso.torres
e1faba2ddc 🐛 Fix absolute positioned layouts not showing flex properties 2023-07-14 15:06:50 +02:00
Alejandro Alonso
0f60f115f5 🐛 Fix focus list for texts 2023-07-14 14:59:06 +02:00
Eva Marco
13560bc866 Merge pull request #3422 from penpot/palba-fix-library-title-style
🐛 Fix incorrect style for asset libraries titles
2023-07-14 14:43:43 +02:00
alonso.torres
c670089c03 🐛 Fix problem with skew transformations 2023-07-14 14:30:26 +02:00
alonso.torres
b1f0d09501 🐛 Fix assets right click button for multiple selection 2023-07-14 14:30:26 +02:00
alonso.torres
53b4c6383b 🐛 Fix undo when updating several texts 2023-07-14 14:30:26 +02:00
Eva Marco
e9819ab063 Merge pull request #3423 from penpot/fix-invite-cursor-position
🐛 Fix position of text cursor is a bit too high in Invitations se…
2023-07-14 14:05:05 +02:00
Pablo Alba
9b9f2c39b9 🐛 Fix duplicate a component copy missing shape-ref 2023-07-14 12:36:13 +02:00
Pablo Alba
203b6c63a4 🐛 Fix incorrect style for asset libraries titles 2023-07-14 12:27:42 +02:00
Pablo Alba
217ca66720 🐛 Fix position of text cursor is a bit too high in Invitations section 2023-07-14 12:25:01 +02:00
Stas Haas
3006ed7966 🌐 Add translations for: German.
Currently translated at 99.8% (1207 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2023-07-14 10:49:25 +02:00
Alejandro
1106ebc377 Merge pull request #3418 from penpot/alotor-fix-safari-thumbs
🐛 Fix problem with safari thumbnails
2023-07-14 07:34:13 +02:00
alonso.torres
9bcb3e9e7f 🐛 Fix problem with Safari thumbnails 2023-07-13 17:05:25 +02:00
Andrey Antukh
6c13925930 🐛 Fix bad interaction of file migrations components-v2 and pointer-map feature 2023-07-13 15:00:28 +02:00
Alejandro
39b46b3bc7 Merge pull request #3417 from penpot/azazeln28-fix-previous-thumbnail-rendered
🐛 Fix previous thumbnail rendered
2023-07-13 13:34:38 +02:00
Alejandro
529ef75058 Merge pull request #3414 from penpot/azazeln28-improve-layers-autoscroll
  Improve layers autoscroll
2023-07-13 13:14:51 +02:00
Aitor
2977709468 🐛 Fix previous thumbnail being rendered when fill is transparent 2023-07-13 13:14:41 +02:00
Alejandro
c4ca40da16 Merge pull request #3410 from penpot/eva-fix
🐛 Some frontend fixes
2023-07-13 13:13:45 +02:00
Alejandro
a6818a8a55 Merge pull request #3407 from penpot/azazeln28-fix-svg-text-thumbnail-rendering
🐛 Fix SVG text rendering on thumbnails
2023-07-13 12:59:20 +02:00
Aitor
a72e50f674 🐛 Fix SVG text rendering on thumbnails 2023-07-13 12:47:15 +02:00
Eva
965c4fe243 🐛 Fix create empty comments 2023-07-13 12:45:01 +02:00
Eva
13b1762873 🐛 Fix exports menu on viewer mode 2023-07-13 12:45:01 +02:00
Eva
ee73384993 🐛 Fix create typography with section closed 2023-07-13 12:45:01 +02:00
Eva
a940c7e912 🐛 Fix onboarding modal height 2023-07-13 12:44:59 +02:00
Pablo Alba
119b3a405c 🐛 Fix duplicate page with comnponents duplicates the components 2023-07-13 11:42:31 +02:00
Alejandro Alonso
fc018b18b3 🐛 Fix rotate several elements in bulk 2023-07-13 11:28:17 +02:00
Aitor
f57ed6a763 Set smooth/instant autoscroll depending on distance 2023-07-13 10:52:49 +02:00
Aitor Moreno
8b7f791509 Merge pull request #3400 from penpot/alotor-bugfixes-4
Bugfixes
2023-07-12 13:11:16 +02:00
alonso.torres
369192a353 🐛 Locks shapes when moved inside a locked parent 2023-07-12 13:06:42 +02:00
alonso.torres
1b0a6b26ce 🐛 Fix problem with bool contents 2023-07-12 13:06:42 +02:00
alonso.torres
fc35b0b853 🐛 Fix retrieve user comments in dashboard 2023-07-12 13:06:42 +02:00
alonso.torres
872648d393 🐛 Fix new-file button on project not redirecting to the new file 2023-07-12 13:06:42 +02:00
alonso.torres
5631204567 🐛 Fix paste elements at bottom of frame 2023-07-12 13:06:42 +02:00
alonso.torres
9f121cb38b 🐛 Fix problem with comments not sticking 2023-07-12 13:06:42 +02:00
alonso.torres
5072c903c5 🐛 Fix bad frame-id for certain componentes 2023-07-12 13:06:42 +02:00
alonso.torres
66559d3ce3 🐛 Fix error screen on image upload failure 2023-07-12 13:06:42 +02:00
alonso.torres
7e0a612818 🐛 Fix problem when sliding color picker in selected-colors 2023-07-12 13:06:40 +02:00
Alejandro
e9ce327eef Merge pull request #3390 from penpot/hiru-fix-overlay
Fix several bugs related to interaction overlays
2023-07-12 10:57:18 +02:00
Andrés Moya
491251f5ce 🐛 Fix overlay position with elements fixed when scrolling 2023-07-12 09:46:46 +02:00
Andrés Moya
65598aa724 🐛 Fix overlay position when it has shadow or blur 2023-07-12 09:46:46 +02:00
Andrés Moya
e563611c05 🐛 Fix overlay close from an artboard 2023-07-12 09:46:46 +02:00
Andrés Moya
a2d1ce8120 🐛 Fix overlay position in open-overlay 2023-07-12 09:46:45 +02:00
Aitor Moreno
91037caa55 Merge pull request #3406 from penpot/eva-bugfixing-10
🐛 Fix several frontend errors
2023-07-11 16:21:12 +02:00
Eva
b94885a764 🐛 Fix shortcut translation 2023-07-11 13:31:59 +02:00
Eva
52545692df 🐛 Fix border radius values with decimals 2023-07-11 13:31:59 +02:00
Eva
3dcd640a99 🐛 Fix search bar width on layer tab 2023-07-11 13:31:59 +02:00
Eva
2e461b3070 🐛 Fix text menu order on design tab 2023-07-11 13:31:59 +02:00
Eva
41924246aa 🐛 Fix text decoration on button 2023-07-11 13:31:58 +02:00
Alejandro
2b37a3c613 Merge pull request #3405 from penpot/niwinz-bugfixes-2023-w26-2
🐛 Bugfixes & Enhancements
2023-07-11 12:55:27 +02:00
Andrey Antukh
f30ba5876e Add performance oriented changes to dashboard teams section 2023-07-11 12:00:16 +02:00
Andrey Antukh
23c8043f34 🐛 Fix incorrect message on sending invitation to a member 2023-07-11 12:00:16 +02:00
Alejandro
a6fc60a88d Merge pull request #3403 from penpot/palba-fix-library-backup-order
🐛 Fix library backup assets order
2023-07-11 11:12:39 +02:00
Alejandro
3c9d3bd5af Merge pull request #3404 from penpot/superalex-fix-select-text-javascript-function
🐛 Fix select text javascript function
2023-07-11 10:42:14 +02:00
Alejandro Alonso
8e1c4238cb 🐛 Fix select text javascript function 2023-07-11 10:17:39 +02:00
Pablo Alba
2d57523e00 Merge pull request #3402 from penpot/superalex-bugfixing-16
🐛 Alex bugfixing
2023-07-11 08:10:56 +02:00
Pablo Alba
8e0c6da1d6 🐛 Fix library backup assets order 2023-07-11 08:05:56 +02:00
Alejandro Alonso
8007794cba 🐛 Fix dissolve interaction 2023-07-11 07:45:29 +02:00
Alejandro Alonso
8b81f700a5 Refactor select all on pending numeric input 2023-07-11 07:45:12 +02:00
Alejandro
ea753da0ae Merge pull request #3401 from penpot/niwinz-bugfixes-2023-w26-2
🐛 Bugfixes
2023-07-10 15:19:49 +02:00
Andrey Antukh
d1a7c58c53 Report error on something goes wrong on image processing 2023-07-10 15:07:17 +02:00
Andrey Antukh
e5a7edeaf6 Always fetch fresh library templates 2023-07-10 15:07:17 +02:00
Andrey Antukh
d0a422e8bd 💄 Add cosmetic improvement to backend main ns 2023-07-10 15:07:17 +02:00
Andrey Antukh
7ea92529f9 Make template thumbnails available offline 2023-07-10 15:07:17 +02:00
Andrey Antukh
494c585e2f Make builtin templates download ondemand if cache is not present 2023-07-10 15:07:17 +02:00
Andrey Antukh
02b41abaf8 Improve builtin template fetching management 2023-07-10 13:58:45 +02:00
Andrey Antukh
a665339c98 ♻️ Move dashboard libraries templates to other namespace
And refactor its internal state management
2023-07-10 13:58:45 +02:00
Alejandro
9c0e594294 Merge pull request #3388 from penpot/niwinz-bugfixes-2023-w26-2
 Add backward compatibility layer for v1.20 and other fixes
2023-07-10 12:48:43 +02:00
Andrey Antukh
ad53d0b55a 🐛 Update project modified-at field after file import 2023-07-10 12:44:24 +02:00
Andrey Antukh
decaeda2fe 🐛 Set bigger maximum token length on backend validation 2023-07-10 12:44:24 +02:00
Andrey Antukh
60130d4db2 🐛 Use correct fullname after OICD registration process 2023-07-10 12:44:24 +02:00
Andrey Antukh
f85a9011ee 🐛 Fix excessive data fetching on workspace comments 2023-07-10 12:44:24 +02:00
Andrey Antukh
9dbf6ffd14 🐛 Fix focus handling on comment edition 2023-07-10 12:44:24 +02:00
Andrey Antukh
992dd04b47 💄 Add cosmetic improvements to comments ns 2023-07-10 12:44:24 +02:00
Andrey Antukh
010a3ef3a7 💄 Add minor cosmetic chanes to workspace comments ns 2023-07-10 12:44:24 +02:00
Andrey Antukh
3da0d85d8f 🐛 Set correct project modified-at on moving files between projects
Happens when you use drag and drop on dashboard for moving files between
projects, but also if you use a context menu actions
2023-07-10 12:44:22 +02:00
Andrey Antukh
7a837110f0 Add proper on-accept callback on features related restriction error
Which redirects user to the dashboard if the team-id and project-id
is available in stante; if not just flushes hard refresh
2023-07-10 12:44:07 +02:00
Andrey Antukh
09d28d8583 Add better file feature handling on file retrieval 2023-07-10 12:44:07 +02:00
Andrey Antukh
90f5b4b631 Qualify json encoding warning log messages as errors 2023-07-10 12:44:07 +02:00
Alejandro
52ad26d4e7 Merge pull request #3391 from penpot/alotor-bugfixes-3
Alotor bugfixes 3
2023-07-10 12:39:48 +02:00
Andrey Antukh
5c92ad727d Merge pull request #3398 from penpot/superalex-fix-nginx-locations-with-regex
🐛 Fix nginx locations with regex
2023-07-10 12:22:22 +02:00
Alejandro Alonso
7823a3270a 🐛 Fix nginx locations with regex 2023-07-10 12:00:29 +02:00
alonso.torres
b565e20f1a 🐛 Fix problem with slashes in layers names for exporter 2023-07-10 09:56:06 +02:00
alonso.torres
735170debf 🐛 Fix problem with HSV color picker 2023-07-10 09:56:06 +02:00
alonso.torres
a2fbf93ec1 🐛 Fix problem with importation process 2023-07-07 14:15:14 +02:00
Andrey Antukh
7b887d3188 Merge pull request #3389 from penpot/superalex-bugfixing-15
🐛 bugfixing
2023-07-07 13:03:15 +02:00
Alejandro Alonso
c1dd4e5e6f 🐛 Fix popup 'Create a group' appears each time after single graphics is moving into already existed group 2023-07-07 13:01:56 +02:00
Alejandro Alonso
7d7b4074b2 🐛 Fix picking a gradient color in recent colors for a new color in the assets tab 2023-07-07 13:01:56 +02:00
Alejandro Alonso
51462ba476 🐛 Fix finalize editor state to consider existing position-data 2023-07-07 12:00:23 +02:00
Alejandro Alonso
99693f0fc2 🐛 Fix cut/delete text layer when while creating text 2023-07-07 12:00:22 +02:00
Andrey Antukh
fdbabe49df Merge pull request #3382 from penpot/alotor-bugfixes-2
Bugfixes
2023-07-07 10:54:48 +02:00
alonso.torres
996a614ed7 🐛 Fix grid not being cutted in frames 2023-07-07 10:18:28 +02:00
alonso.torres
7a499bfc90 🐛 Fix problem with images patterns repeating 2023-07-07 10:18:28 +02:00
alonso.torres
647beec1e8 🐛 Fix problem with comments when user left the team 2023-07-07 10:18:28 +02:00
alonso.torres
dd9f637f02 🐛 Fix problem with comments mode not staying 2023-07-07 10:18:28 +02:00
alonso.torres
00450565c8 🐛 Makes height priority for the rows/columns grids 2023-07-07 10:18:27 +02:00
Alejandro Alonso
cf9fb7face 🐛 Fix 404 errors 2023-07-06 19:00:10 +02:00
Alejandro
44514a0961 Merge pull request #3383 from penpot/niwinz-bugfixes-2023-w26-2
🐛 Bugfixes
2023-07-06 18:27:04 +02:00
Alejandro Alonso
bfc490bd63 🐛 Fix 404 errors 2023-07-06 15:22:55 +02:00
Andrey Antukh
0a9cad76c3 💄 Add minor cosmetic improvements to typography menu components 2023-07-06 12:46:51 +02:00
Andrey Antukh
26ef8df79c ⬆️ Update frontend dependencies (only bugfixes) 2023-07-06 12:46:51 +02:00
Andrey Antukh
cd2f50fdb4 🐛 Fix react warnings on font-selector 2023-07-06 12:46:51 +02:00
Andrey Antukh
59d02314e2 ⬆️ Update google fonts 2023-07-06 12:46:51 +02:00
Andrey Antukh
88ac27788b 🐛 Fix whitespace handling on color assets name 2023-07-06 12:46:51 +02:00
Andrey Antukh
c16de52b49 ♻️ Add minor refactor to shared-link dialog component
Fixes the issue of creating incorrect link when only non-current pages
are selected on the shared link permissions
2023-07-06 12:46:51 +02:00
Andrey Antukh
8d6d589a0c 💄 Add minor cosmetic change to viewer-page component 2023-07-06 12:29:33 +02:00
Andrey Antukh
0817c4e140 Print js trace on exceptional state error is raised 2023-07-06 12:29:33 +02:00
Andrey Antukh
aad70d9df8 💄 Add minor cosmetic improvement to viewer events ns 2023-07-06 12:29:33 +02:00
Andrey Antukh
bbcf9c00a5 🐛 Remove conditional cache handling from get-view-only-bundle rpc method
The cond/etag handling is the cause of incorrect number of shared links
returned by the endpoint. Because of incorrect cache invalidation.
2023-07-06 12:29:33 +02:00
Eva
49df4a9404 🐛 Fix several frontend validations 2023-07-06 12:28:47 +02:00
Eva
acfeae8638 🐛 Fix select all checkbox on shared link config 2023-07-06 12:28:47 +02:00
Eva
7216a514e6 🐛 Fix context menu z-index 2023-07-06 12:28:47 +02:00
Eva
48d9541d46 🐛 Fix scroll of comment list on viewer 2023-07-06 12:28:47 +02:00
Alejandro Alonso
01ec22d662 🐛 Fix finalize text editor state when blur 2023-07-05 13:22:50 +02:00
Alejandro Alonso
b43d09e5ce 🐛 Fix email change validation 2023-07-05 13:22:50 +02:00
Alejandro Alonso
009236bbe3 🐛 Fix export from shared prototype 2023-07-05 13:22:50 +02:00
Alejandro Alonso
0d87dc5680 🐛 Fix drag and drop in the dashboard generates import file error message 2023-07-05 13:22:50 +02:00
Alejandro
8b0339bbab Merge pull request #3379 from penpot/alotor-bugfixes
Alotor bugfixes
2023-07-05 10:58:48 +02:00
alonso.torres
302bfd3007 🐛 Fix problems with locked frames 2023-07-05 08:44:59 +02:00
alonso.torres
302750bd7e 🐛 Fix issue with paths line to curve and concurrent editing 2023-07-05 08:10:54 +02:00
alonso.torres
66e32e9cbd 🐛 Fix problem with selection shortcuts 2023-07-05 08:10:50 +02:00
alonso.torres
e40245e187 🐛 Fixed problem with styles inside def for svg import 2023-07-05 08:09:48 +02:00
Alejandro
16854e7e83 Merge pull request #3376 from penpot/niwinz-bugfixes-2023-w26-2
🐛 Bugfixes
2023-07-05 06:25:37 +02:00
Kristijan Žic
53ed1404e7 🌐 Add translations for: Croatian.
Currently translated at 84.9% (1027 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/
2023-07-05 04:17:05 +02:00
Andrey Antukh
5a8df0dfae Add better validation of profile rpc methods 2023-07-04 19:28:52 +02:00
Andrey Antukh
8f8d90abbc Revert some changes to the audit validation 2023-07-04 19:28:52 +02:00
Alejandro
bf297539ae Merge pull request #3374 from penpot/niwinz-bugfixes-2023-w26-2
🐛 Bugfixes
2023-07-04 16:27:17 +02:00
Andrey Antukh
be652b909e Add stronger validationt to auth/register rpc methods 2023-07-04 14:36:31 +02:00
Andrey Antukh
068d2f13f4 Add min-max validation to word-string schema 2023-07-04 13:55:58 +02:00
Andrey Antukh
1464f5da90 Ensure that all emails are under 250chars 2023-07-04 13:55:58 +02:00
Andrey Antukh
7b0d3bdcab Add stricter validation on events endpoint 2023-07-04 13:55:58 +02:00
Alejandro
5d42631c7a Merge pull request #3370 from penpot/niwinz-improvements
 Add some improvements to the oidc module
2023-07-04 12:36:39 +02:00
Andrey Antukh
e0c0b251a9 💄 Add minor cosmetic change to CHANGES.md file 2023-07-04 11:19:19 +02:00
Andrey Antukh
a868dcf8e6 🐛 Don't allow empty strings and whitespace-only strings on media name 2023-07-04 11:19:19 +02:00
Andrey Antukh
b64a9f0cf4 🐛 Fix graphic item rename on assets pannel 2023-07-04 11:19:19 +02:00
Alejandro
45a909f5ff Merge pull request #3371 from penpot/niwinz-bugfixes-2023-w26
🐛 Don't allow empty or whitespace-only names on components
2023-07-04 06:52:05 +02:00
Andrey Antukh
dcc15e485d 🐛 Don't allow empty or whitespace-only names on components 2023-07-03 17:03:18 +02:00
Alejandro
6849a5b0e0 Merge pull request #3357 from penpot/eva-bugfixin-8
🐛 Fix some bugs
2023-07-03 14:04:42 +02:00
Eva
ef3fedee59 🐛 Fix some warnings and format some files 2023-07-03 13:58:58 +02:00
Eva
8955f87d5a 🐛 Fix z-index nillable input when static position 2023-07-03 13:58:57 +02:00
Eva
94b5c98042 🐛 Fix context menu outside screen 2023-07-03 13:58:40 +02:00
Eva
82183ec71a 🐛 Fix create and account only with spaces 2023-07-03 13:58:22 +02:00
Eva
e75b53ff8d 🐛 Fix search font visualitation 2023-07-03 13:58:01 +02:00
Eva
9a880f007c 🐛 Fix focus title on layers sidebar 2023-07-03 13:57:48 +02:00
Eva
02466d603c 🐛 Fix allow team name to be all blank 2023-07-03 13:57:47 +02:00
Eva
4d4e9703cc 🐛 Fix drag projects on dahsboard 2023-07-03 13:57:30 +02:00
Eva
a737c125d5 🐛 Fix unpublish more than one library at the same time 2023-07-03 13:57:15 +02:00
Alejandro Alonso
e461745479 📎 Update CHANGES.md file and version.txt 2023-07-03 13:32:36 +02:00
Andrey Antukh
8cda8924df Add the ability to select user info source
using the PENPOT_OIDC_USER_INFO_SOURCE environment variable
with two possible values: token and userinfo
2023-07-03 10:46:29 +02:00
Andrey Antukh
dda67af5cc Update oidc impl with latest buddy-sign improvements 2023-07-03 10:46:25 +02:00
Andrey Antukh
cadcc1607d Increase default argon2id iterations 2023-07-03 10:43:26 +02:00
Andrey Antukh
63c8798264 ⬆️ Update backend and common dependencies 2023-07-03 10:43:26 +02:00
Alejandro Alonso
74dd4f1ff8 🐛 Fix text content validation 2023-07-03 09:35:55 +02:00
Alejandro Alonso
53cee87701 🐛 Fix deleted fonts present in recent block 2023-07-03 09:35:55 +02:00
Alejandro Alonso
d939a86e75 🐛 Fix null vlaues for grid columns/rows 2023-07-03 09:28:57 +02:00
Linerly
f691f8d5b5 🌐 Add translations for: Indonesian.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/
2023-07-02 19:52:28 +02:00
Amine Gdoura
2c68e8309e 🌐 Add translations for: Arabic.
Currently translated at 61.4% (743 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/
2023-07-02 19:52:27 +02:00
Amerey.eu
dce8b5b37c 🌐 Add translations for: Czech.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/cs/
2023-07-01 14:52:54 +02:00
Mikel Larreategi
6546bfc889 🌐 Add translations for: Basque.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/eu/
2023-07-01 14:52:52 +02:00
Linerly
b915abb2d2 🌐 Add translations for: Indonesian.
Currently translated at 97.0% (1173 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/
2023-07-01 14:52:51 +02:00
Alejandro
050646506e Merge pull request #3358 from penpot/niwinz-oidc-improvements
 Add the ability to parse OIDC JWT token
2023-06-28 10:17:11 +02:00
Andrey Antukh
6339b07fba Add the ability to parse OIDC JWT token
If jwks-uri is provided or properly discovered, they will be used
for unsign JWT token and get use info data from that token instead
of making an additional call to the userinfo endpoint
2023-06-28 00:25:48 +02:00
Pablo Alba
e61aaaecf3 🐛 Fix libraries are truncated on 'Libraries' page 2023-06-27 14:47:23 +02:00
Alejandro
3ea5b1a8de Merge pull request #3356 from penpot/superalex-fix-file-etag-calculation
🐛 fix file etag calculation
2023-06-27 14:10:50 +02:00
Andrey Antukh
17731db28b 🐛 Fix file etag calculation considering the profile id too 2023-06-27 13:55:55 +02:00
Aitor
5b40fdf3f0 🔧 Add VSCode settings 2023-06-27 13:54:07 +02:00
Alejandro Alonso
9ab067b6d8 🐛 Fix add group to graphics and components 2023-06-27 13:53:17 +02:00
Pablo Alba
2648dc3d27 🐛 Fix menu for create annotation appears on components that already have annotation 2023-06-27 13:31:12 +02:00
Pablo Alba
9d06a34df4 🐛 Fix Annotation is not shown on View mode 2023-06-27 13:17:40 +02:00
Pablo Alba
1770bb995b 🐛 Fix annotations size: adjust textarea height according to annotation content 2023-06-27 13:08:39 +02:00
Pablo Alba
85e1899f6b 🐛 Fix '(...)' is truncated for 'Typographies' section in Library view 2023-06-27 12:47:22 +02:00
Pablo Alba
0716aaeff6 🐛 Fix missing view for empty library on Libraries page 2023-06-27 12:47:22 +02:00
Alejandro Alonso
af114ee9d0 Merge branch 'astudentinearth-astudentinearth-change-radius-tooltips' into staging 2023-06-27 10:53:09 +02:00
Alejandro Alonso
2249bf9745 📎 Update CHANGES.md file 2023-06-27 10:52:56 +02:00
astudentinearth
c3c6112ade 🐛 Change independent corner radius input tooltips
Make the inputs show a tooltip for the relevant corner(e.g. "Top left") instead of "Radius"

Signed-off-by: Burak Yeniçeri <burak.yn.dev@gmail.com>
2023-06-27 10:52:56 +02:00
Alejandro
5ea80c018f Merge pull request #3352 from penpot/niwinz-bugfixes
 Improvements & bugfixes
2023-06-27 10:40:30 +02:00
Alejandro Alonso
287213cfaf Refactor select all on input text click 2023-06-27 10:32:50 +02:00
Andrey Antukh
51d829a4b3 🐛 Fix incorrect handling of SSL param on email sending subsystem
Fixes #3213
2023-06-27 09:50:05 +02:00
Andrey Antukh
f166fe1926 🐛 Add proper validation of registration domain whitelist on oidc
Fixes #3348
2023-06-26 18:14:56 +02:00
Andrey Antukh
f60d09eb8f 🎉 Add uuid->short-id helper
Mainly helps encode a safer subset of bits (96) of an uuid using
a more compact encoding (base62) which is compatible with CSS and
URL's
2023-06-26 18:03:16 +02:00
Andrey Antukh
339903f567 🐛 Fix incorrect handling of error on thumbnail renderer 2023-06-26 14:51:49 +02:00
Andrey Antukh
7f16a79af5 🐛 Fix email printing to the logging subsystem
Fixes #3239
2023-06-26 11:16:37 +02:00
Andrey Antukh
97af5f71eb Merge branch 'staging' into develop 2023-06-26 10:21:34 +02:00
Andrey Antukh
ba4ef66cdc Merge branch 'main' into staging 2023-06-26 10:19:58 +02:00
Alejandro Alonso
7191fe847c Merge remote-tracking branch 'origin/staging' into develop 2023-06-26 09:49:54 +02:00
Alejandro
dad13ed826 Merge pull request #3350 from penpot/superalex-fix-internal-error-on-team-settings
🐛 Fix internal server error occurred when user wants to open team…
2023-06-26 09:49:34 +02:00
Alejandro Alonso
6cab413a8f 🐛 Fix internal server error occurred when user wants to open team settings 2023-06-26 09:41:08 +02:00
Alejandro
a895eaf61c Merge pull request #3347 from penpot/niwinz-fonts-local-caching
🐛 Fix several bugs related to fonts and components migration
2023-06-23 16:36:39 +02:00
Andrey Antukh
7977d75e3d Reduce the dashboard thumbnail size 2023-06-23 16:28:52 +02:00
Andrey Antukh
7746649eb8 🐛 Fix minor issues with fonts caching 2023-06-23 16:28:52 +02:00
Andrey Antukh
840801ea15 🐛 Don't update modified_at field on applying components migration 2023-06-23 16:28:52 +02:00
Andrey Antukh
cacaf2bf95 ⬆️ Update devenv dockerfile 2023-06-23 16:28:52 +02:00
Alejandro
4607d9f210 Merge pull request #3342 from penpot/niwinz-fonts-local-caching
 Add several improvements to fonts loading
2023-06-23 14:07:25 +02:00
Andrey Antukh
8f0a4e8333 🎉 Add local caching of gfonts styles 2023-06-23 13:32:38 +02:00
Andrey Antukh
ef5c9babe1 Merge remote-tracking branch 'origin/staging' into develop 2023-06-23 13:22:33 +02:00
Alejandro Alonso
f75b111564 🐛 Fix impossible to add group to typographies 2023-06-23 13:21:36 +02:00
Alejandro Alonso
a8e058ada6 🐛 Fix add asset color, invalid color appears 2023-06-23 13:21:36 +02:00
Alejandro Alonso
c988d54925 🐛 Fix hide rulers option not working 2023-06-23 13:21:36 +02:00
Alejandro
921ea61e6c Merge pull request #3344 from penpot/alotor-fix-viewer-scroll
🐛 Fix problem with scroll in viewer mode
2023-06-23 13:12:23 +02:00
Alejandro
71a6ee51fa Merge pull request #3343 from penpot/niwinz-onmpremise-improvements
 Add minor improvements for onpremise users
2023-06-23 13:10:14 +02:00
Andrey Antukh
b138550c0d 🐛 Fix issue on awsns http handler 2023-06-23 13:05:48 +02:00
Andrey Antukh
81658c90d1 Add the ability to disable dashboard templates section 2023-06-23 13:05:48 +02:00
alonso.torres
ca1e6c342f 🐛 Fix problem with scroll in viewer mode 2023-06-23 12:55:49 +02:00
Andrey Antukh
7feda98eb3 Add the ability to disable the google fonts provider 2023-06-23 12:55:22 +02:00
Alejandro
33e0e6293b Merge pull request #3341 from penpot/niwinz-bugfix-thumbnails
🐛 Fix thumbnails handling on dashboard libraries
2023-06-23 12:53:18 +02:00
Andrey Antukh
2a81d8563a 🐛 Fix thumbnails handling on dashboard libraries 2023-06-23 12:24:49 +02:00
Alejandro Alonso
ae9d6b627d Merge remote-tracking branch 'origin/staging' into develop 2023-06-22 14:38:12 +02:00
Alejandro
2db5925e60 Merge pull request #3337 from penpot/superalex-fix-text-fills-with-gradient
🐛 Fix text fills with gradient
2023-06-22 14:37:19 +02:00
Alejandro Alonso
d02f3ba011 🐛 Fix text fills with gradient 2023-06-22 14:08:21 +02:00
Alejandro
74e8081574 Merge pull request #3272 from penpot/azazeln28-thumbnail-renderer
🎉 Add thumbnail renderer service
2023-06-22 13:45:07 +02:00
Pablo Alba
1817d4ce38 🐛 It is possible to create empty component annotation (2) 2023-06-22 13:34:27 +02:00
Andrey Antukh
433b1b68c3 🐛 Improve fonts loading related to thumbnals rendering 2023-06-22 13:19:48 +02:00
Pablo Alba
776159c1e8 🐛 It is possible to create empty component annotation 2023-06-22 12:44:44 +02:00
Pablo Alba
45e76bc38b 🐛 Fix delete component annotation 2023-06-22 12:44:44 +02:00
Pablo Alba
54cee6ea72 🐛 Fix annotation is not duplicated together with main component 2023-06-22 12:44:44 +02:00
Andrey Antukh
a97929992e Convert to schema some specs on file-thumbnails rpc methods 2023-06-22 09:34:13 +02:00
Alejandro Alonso
a66a952573 Merge remote-tracking branch 'origin/staging' into develop 2023-06-22 09:08:56 +02:00
Andrey Antukh
10205e51cc 🔥 Remove atom wrapping on several config props 2023-06-21 20:10:49 +02:00
Andrey Antukh
0aefd044dc Remove atom wrapping on public-uri 2023-06-21 20:10:49 +02:00
Andrey Antukh
d11b007795 Add thumbnail renderer
And integrate the dashboard thumbnails to use that service
2023-06-21 20:10:49 +02:00
Alejandro Alonso
5af2489315 Merge remote-tracking branch 'origin/staging' into develop 2023-06-21 17:06:47 +02:00
Alejandro Alonso
6242c62bcb 📎 Update CHANGES.md file 2023-06-21 17:05:09 +02:00
Alejandro Alonso
69969d9815 Merge remote-tracking branch 'origin/staging' into develop 2023-06-21 17:03:54 +02:00
Alejandro Alonso
a0535de30c 📎 Update CHANGES.md file 2023-06-21 12:53:47 +02:00
Alejandro Alonso
9bd658661d Merge remote-tracking branch 'origin/staging' 2023-06-21 12:50:11 +02:00
Alejandro Alonso
50bdad3450 Merge remote-tracking branch 'origin/staging' 2023-06-21 12:44:56 +02:00
Stas Haas
5cb5df63d9 🌐 Add translations for: German.
Currently translated at 99.8% (1207 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2023-06-19 13:49:09 +02:00
Stas Haas
74552a4989 🌐 Add translations for: Russian.
Currently translated at 63.1% (763 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2023-06-19 13:49:08 +02:00
K.B.Dharun Krishna
b72b8a6d53 🌐 Add translations for: Tamil.
Currently translated at 4.2% (51 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ta/
2023-06-17 11:51:35 +02:00
Mikel Larreategi
0a74696874 🌐 Add translations for: Basque.
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/eu/
2023-06-17 11:51:35 +02:00
Stas Haas
6548fe069e 🌐 Add translations for: German.
Currently translated at 99.4% (1202 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2023-06-17 11:51:33 +02:00
Stas Haas
22d852fca8 🌐 Add translations for: German.
Currently translated at 98.6% (1193 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2023-06-09 03:52:52 +02:00
王世阳
17c2f44780 🌐 Add translations for: Chinese (Simplified).
Currently translated at 100.0% (1209 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2023-06-09 03:52:51 +02:00
Sebastiaan Pasma
40286c81d4 🌐 Add translations for: Dutch.
Currently translated at 11.6% (141 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2023-06-06 10:49:44 +02:00
Stas Haas
3b262f2ae5 🌐 Add translations for: German.
Currently translated at 97.5% (1179 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2023-06-04 14:49:45 +02:00
Ņikita K
80dd910d58 🌐 Add translations for: Latvian.
Currently translated at 98.8% (1195 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2023-06-03 02:50:25 +02:00
王世阳
21a066ec64 🌐 Add translations for: Chinese (Simplified).
Currently translated at 99.8% (1207 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2023-06-03 02:50:24 +02:00
andy
29c091a26b 🌐 Added translation for: Dutch. 2023-06-02 17:17:44 +02:00
Ņikita K
b249cd1b72 🌐 Add translations for: Latvian.
Currently translated at 96.5% (1167 of 1209 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2023-06-01 08:51:33 +02:00
Alejandro Alonso
eeb71982c8 Merge remote-tracking branch 'origin/staging' 2023-05-09 14:39:07 +02:00
Alejandro Alonso
8352c9c6fd Merge remote-tracking branch 'origin/staging' 2023-05-09 10:22:55 +02:00
Hosted Weblate
e2a0a40704 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/
2023-04-24 16:55:45 +02:00
Alejandro Alonso
d657f5df49 Merge remote-tracking branch 'origin/staging' 2023-04-24 09:16:52 +02:00
Alejandro Alonso
974bbd5ff4 Merge remote-tracking branch 'origin/staging' 2023-04-14 13:27:34 +02:00
Alejandro Alonso
b992c876e9 Merge remote-tracking branch 'origin/staging' 2023-04-13 16:33:27 +02:00
Alejandro
724b8990be Merge pull request #3136 from penpot/alotor-hotfix-1.18.3
Alotor hotfix 1.18.3
2023-04-13 16:28:58 +02:00
alonso.torres
452dcb5eec 🐛 Fix problem when "show in view mode" flag 2023-04-13 14:16:03 +02:00
alonso.torres
ae3de34033 🐛 Fix problem with rulers not placing correctly 2023-04-13 14:15:49 +02:00
Alejandro Alonso
c3a4dbb871 Merge remote-tracking branch 'origin/staging' 2023-04-13 12:24:39 +02:00
Alejandro Alonso
3905ba4ce2 Merge remote-tracking branch 'origin/staging' 2023-04-13 09:16:52 +02:00
Alejandro Alonso
0dcb3e94ce Merge remote-tracking branch 'origin/staging' 2023-04-11 06:51:09 +02:00
Andrey Antukh
6abca96da1 📎 Add improved docstring for penpot_secret_key 2023-04-07 08:57:08 +02:00
Alejandro Alonso
4926c826af Merge remote-tracking branch 'origin/staging' 2023-04-03 12:09:48 +02:00
Alejandro
04b7d8e1e2 Merge pull request #3094 from penpot/hotfix-1.17
🐛 Fix problem with invalid geometry
2023-03-31 14:10:36 +02:00
alonso.torres
745cf1c79d 🐛 Fix problem with invalid geometry 2023-03-31 12:05:59 +02:00
309 changed files with 25098 additions and 11138 deletions

View File

@@ -6,7 +6,6 @@
rumext.v2/defc clojure.core/defn
rumext.v2/fnc clojure.core/fn
app.common.data/export clojure.core/def
app.db/with-atomic clojure.core/with-open
app.common.data.macros/get-in clojure.core/get-in
app.common.data.macros/with-open clojure.core/with-open
app.common.data.macros/select-keys clojure.core/select-keys
@@ -17,6 +16,7 @@
{app.common.data.macros/export hooks.export/export
potok.core/reify hooks.export/potok-reify
app.util.services/defmethod hooks.export/service-defmethod
app.db/with-atomic hooks.export/penpot-with-atomic
}}
:output

View File

@@ -39,6 +39,43 @@
other))]
{:node result})))
(defn penpot-with-atomic
[{:keys [node]}]
(let [[_ params & other] (:children node)
result (if (api/vector-node? params)
(api/list-node
(into [(api/token-node (symbol "clojure.core" "with-open")) params] other))
(api/list-node
(into [(api/token-node (symbol "clojure.core" "with-open"))
(api/vector-node [params params])]
other)))
]
{:node result}))
(defn penpot-defrecord
[{:keys [:node]}]
(let [[rnode rtype rparams & other] (:children node)
nodes [(api/token-node (symbol "do"))
(api/list-node
(into [(api/token-node (symbol (name (:value rnode)))) rtype rparams] other))
(api/list-node
[(api/token-node (symbol "defn"))
(api/token-node (symbol (str "pos->" (:string-value rtype))))
(api/vector-node
(->> (:children rparams)
(mapv (fn [t]
(api/token-node (symbol (str "_" (:string-value t))))))))
(api/token-node nil)])]
result (api/list-node nodes)]
;; (prn "=====>" (into {} rparams))
;; (prn (api/sexpr result))
{:node result}))
(defn clojure-specify
[{:keys [:node]}]
(let [[rnode rtype & other] (:children node)
@@ -48,7 +85,6 @@
other))]
{:node result}))
(defn service-defmethod
[{:keys [:node]}]
(let [[rnode rtype ?meta & other] (:children node)

13
.editorconfig Normal file
View File

@@ -0,0 +1,13 @@
root = true
[*.{cljs,cljc,clj,js,css,scss,html,yml,yaml,json,mustache}]
charset = utf-8
indent_size = 2
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

9
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"files.exclude": {
"**/.clj-kondo": true,
"**/.cpcache": true,
"**/.lsp": true,
"**/.shadow-cljs": true,
"**/node_modules": true
}
}

View File

@@ -1,23 +1,134 @@
# CHANGELOG
## :rocket: 1.19.0
## 1.19.2
### :sparkles: New features
- Navigate up in layer hierarchy with Shift+Enter shortcut [Taiga #5734](https://tree.taiga.io/project/penpot/us/5734)
- Click on the flow tags open viewer with the selected frame [Taiga #5044](https://tree.taiga.io/project/penpot/us/5044)
- Add Dutch language & update translation files with weblate
### :bug: Bugs fixed
- Fix unexpected output on get-page rpc method when invalid object-id is provided [Github #3546](https://github.com/penpot/penpot/issues/3546)
- Fix Invalid files amount after moving file from Project to Drafts [Taiga #5638](https://tree.taiga.io/project/penpot/us/5638)
- Fix deleted pages comments shown in right sidebar [Taiga #5648](https://tree.taiga.io/project/penpot/us/5648)
- Fix tooltip on toggle visibility and toggle lock buttons [Taiga #5141](https://tree.taiga.io/project/penpot/issue/5141)
## 1.19.1
### :bug: Bugs fixed
- Fix components not registered as updated [Taiga #5725](https://tree.taiga.io/project/penpot/issue/5725)
## 1.19.0
### :boom: Breaking changes & Deprecations
### :sparkles: New features
- Default naming of text layers [Taiga #2836](https://tree.taiga.io/project/penpot/us/2836)
- Create typography style from a selected text layer[Taiga #3041](https://tree.taiga.io/project/penpot/us/3041)
- Create typography style from a selected text layer [Taiga #3041](https://tree.taiga.io/project/penpot/us/3041)
- Board as ruler origin [Taiga #4833](https://tree.taiga.io/project/penpot/us/4833)
- Access tokens support [Taiga #4460](https://tree.taiga.io/project/penpot/us/4460)
- Show interactions setting at the view mode [Taiga #1330](https://tree.taiga.io/project/penpot/issue/1330)
- Improve dashboard performance related to thumbnails; now the thumbnails are
rendered as bitmap images.
- Add the ability to disable google fonts provider with the `disable-google-fonts-provider` flag
- Add the ability to disable dashboard templates section with the `disable-dashboard-templates-section` flag
- Add the ability to use the registration whitelist with OICD [Github #3348](https://github.com/penpot/penpot/issues/3348)
- Add support for local caching of google fonts (this avoids exposing the final user IP to
goolge and reduces the amount of request sent to google)
- Set smooth/instant autoscroll depending on distance [GitHub #3377](https://github.com/penpot/penpot/issues/3377)
### :bug: Bugs fixed
- Fix files can be opened from multiple urls [Taiga #5310](https://tree.taiga.io/project/penpot/issue/5310)
- Fix asset color item was created from the selected layer [Taiga #5180](https://tree.taiga.io/project/penpot/issue/5180)
- Fix unpublish more than one library at the same time [Taiga #5532](https://tree.taiga.io/project/penpot/issue/5532)
- Fix drag projects on dahsboard [Taiga #5531](https://tree.taiga.io/project/penpot/issue/5531)
- Fix allow team name to be all blank [Taiga #5527](https://tree.taiga.io/project/penpot/issue/5527)
- Fix search font visualitation [Taiga #5523](https://tree.taiga.io/project/penpot/issue/5523)
- Fix create and account only with spaces [Taiga #5518](https://tree.taiga.io/project/penpot/issue/5518)
- Fix context menu outside screen [Taiga #5524](https://tree.taiga.io/project/penpot/issue/5524)
- Fix graphic item rename on assets pannel [Taiga #5556](https://tree.taiga.io/project/penpot/issue/5556)
- Fix component and media name validation on assets panel [Taiga #5555](https://tree.taiga.io/project/penpot/issue/5555)
- Fix problem with selection shortcuts [Taiga #5492](https://tree.taiga.io/project/penpot/issue/5492)
- Fix issue with paths line to curve and concurrent editing [Taiga #5191](https://tree.taiga.io/project/penpot/issue/5191)
- Fix problems with locked layers [Taiga #5139](https://tree.taiga.io/project/penpot/issue/5139)
- Fix export from shared prototype [Taiga #5565](https://tree.taiga.io/project/penpot/issue/5565)
- Fix email change: validation error displaying even after both fields are identical [Taiga #5514](https://tree.taiga.io/project/penpot/issue/5514)
- Fix scroll on viewer comment list [Taiga #5563](https://tree.taiga.io/project/penpot/issue/5563)
- Fix context menu z-index [Taiga #5561](https://tree.taiga.io/project/penpot/issue/5561)
- Fix select all checkbox on shared link config [Taiga #5566](https://tree.taiga.io/project/penpot/issue/5566)
- Fix validation on full name input on account creation [Taiga #5516](https://tree.taiga.io/project/penpot/issue/5516)
- Fix validation on team name input [Taiga #5510](https://tree.taiga.io/project/penpot/issue/5510)
- Fix incorrect uri generation issues on share-link modal [Taiga #5564](https://tree.taiga.io/project/penpot/issue/5564)
- Fix cache issues with share-links [Taiga #5559](https://tree.taiga.io/project/penpot/issue/5559)
- Makes height priority for the rows/columns grids [#2774](https://github.com/penpot/penpot/issues/2774)
- Fix problem with comments mode not staying [#3363](https://github.com/penpot/penpot/issues/3363)
- Fix problem with comments when user left the team [Taiga #5562](https://tree.taiga.io/project/penpot/issue/5562)
- Fix problem with images patterns repeating [#3372](https://github.com/penpot/penpot/issues/3372)
- Fix grid not being clipped in frames [#3365](https://github.com/penpot/penpot/issues/3365)
- Fix cut/delete text layer when while creating text [Taiga #5602](https://tree.taiga.io/project/penpot/issue/5602)
- Fix picking a gradient color in recent colors for a new color in the assets tab [Taiga #5601](https://tree.taiga.io/project/penpot/issue/5601)
- Fix problem with importation process [Taiga #5597](https://tree.taiga.io/project/penpot/issue/5597)
- Fix problem with HSV color picker [#3317](https://github.com/penpot/penpot/issues/3317)
- Fix problem with slashes in layers names for exporter [#3276](https://github.com/penpot/penpot/issues/3276)
- Fix incorrect modified data on moving files on dashboard [Taiga #5530](https://tree.taiga.io/project/penpot/issue/5530)
- Fix focus handling on comments edition [Taiga #5560](https://tree.taiga.io/project/penpot/issue/5560)
- Fix incorrect fullname use on registring user after OIDC authentication [Taiga #5517](https://tree.taiga.io/project/penpot/issue/5517)
- Fix incorrect modified-at on project after import file [Taiga #5268](https://tree.taiga.io/project/penpot/issue/5268)
- Fix incorrect message after sending invitation to already member [Taiga 5599](https://tree.taiga.io/project/penpot/issue/5599)
- Fix text decoration on button [Taiga #5301](https://tree.taiga.io/project/penpot/issue/5301)
- Fix menu order on design tab [Taiga #5195](https://tree.taiga.io/project/penpot/issue/5195)
- Fix search bar width on layer tab [Taiga #5445](https://tree.taiga.io/project/penpot/issue/5445)
- Fix border radius values with decimals [Taiga #5283](https://tree.taiga.io/project/penpot/issue/5283)
- Fix shortcuts translations not homogenized [Taiga #5141](https://tree.taiga.io/project/penpot/issue/5141)
- Fix overlay manual position in nested boards [Taiga #5135](https://tree.taiga.io/project/penpot/issue/5135)
- Fix close overlay from a nested board [Taiga #5587](https://tree.taiga.io/project/penpot/issue/5587)
- Fix overlay position when it has shadow or blur [Taiga #4752](https://tree.taiga.io/project/penpot/issue/4752)
- Fix overlay position when there are elements fixed when scrolling [Taiga #4383](https://tree.taiga.io/project/penpot/issue/4383)
- Fix problem when sliding color picker in selected-colors [#3150](https://github.com/penpot/penpot/issues/3150)
- Fix error screen on upload image error [Taiga #5608](https://tree.taiga.io/project/penpot/issue/5608)
- Fix bad frame-id for certain componentes [#3205](https://github.com/penpot/penpot/issues/3205)
- Fix paste elements at bottom of frame [Taig #5253](https://tree.taiga.io/project/penpot/issue/5253)
- Fix new-file button on project not redirecting to the new file [Taiga #5610](https://tree.taiga.io/project/penpot/issue/5610)
- Fix retrieve user comments in dashboard [Taiga #5607](https://tree.taiga.io/project/penpot/issue/5607)
- Locks shapes when moved inside a locked parent [Taiga #5252](https://tree.taiga.io/project/penpot/issue/5252)
- Fix rotate several elements in bulk [Taiga #5165](https://tree.taiga.io/project/penpot/issue/5165)
- Fix onboarding slides height [Taiga #5373](https://tree.taiga.io/project/penpot/issue/5373)
- Fix create typography with section closed [Taiga #5574](https://tree.taiga.io/project/penpot/issue/5574)
- Fix exports menu on viewer mode [Taiga #5568](https://tree.taiga.io/project/penpot/issue/5568)
- Fix create empty comments [Taiga #5536](https://tree.taiga.io/project/penpot/issue/5536)
- Fix position of text cursor is a bit too high in Invitations section [Taiga #5511](https://tree.taiga.io/project/penpot/issue/5511)
- Fix undo when updating several texts [Taiga #5197](https://tree.taiga.io/project/penpot/issue/5197)
- Fix assets right click button for multiple selection [Taiga #5545](https://tree.taiga.io/project/penpot/issue/5545)
- Fix problem with precision in resizes [Taiga #5623](https://tree.taiga.io/project/penpot/issue/5623)
- Fix absolute positioned layouts not showing flex properties [Taiga #5630](https://tree.taiga.io/project/penpot/issue/5630)
- Fix text gradient handlers [Taiga #4047](https://tree.taiga.io/project/penpot/issue/4047)
- Fix when user deletes one file during import it is impossible to finish importing of second file [Taiga #5656](https://tree.taiga.io/project/penpot/issue/5656)
- Fix export multiple images when only one of them has export settings [Taiga #5649](https://tree.taiga.io/project/penpot/issue/5649)
- Fix error when a user different than the thread creator edits a comment [Taiga #5647](https://tree.taiga.io/project/penpot/issue/5647)
- Fix unnecessary button [Taiga #3312](https://tree.taiga.io/project/penpot/issue/3312)
- Fix copy color information in several formats [Taiga #4723](https://tree.taiga.io/project/penpot/issue/4723)
- Fix dropdown width [Taiga #5541](https://tree.taiga.io/project/penpot/issue/5541)
- Fix enable comment mode and insert image keeps on comment mode [Taiga #5678](https://tree.taiga.io/project/penpot/issue/5678)
- Fix enable undo just after using pencil [Taiga #5674](https://tree.taiga.io/project/penpot/issue/5674)
- Fix 400 error when user changes password [Taiga #5643](https://tree.taiga.io/project/penpot/issue/5643)
- Fix cannot undo layer styles [Taiga #5676](https://tree.taiga.io/project/penpot/issue/5676)
- Fix unexpected exception on boolean shapes [Taiga #5685](https://tree.taiga.io/project/penpot/issue/5685)
- Fix ctrl+z on select not working [Taiga #5677](https://tree.taiga.io/project/penpot/issue/5677)
- Fix thubmnail rendering flashing [Taiga #5675](https://tree.taiga.io/project/penpot/issue/5675)
### :arrow_up: Deps updates
- Update google fonts catalog (at 2023/07/06) [Taiga #5592](https://tree.taiga.io/project/penpot/issue/5592)
### :heart: Community contributions by (Thank you!)
- Update Typography palette order (by @akshay-gupta7) [Github #3156](https://github.com/penpot/penpot/pull/3156)
- Palettes (color, typographies) empty state (by @akshay-gupta7) [Github #3160](https://github.com/penpot/penpot/pull/3160)
- Duplicate objects via drag + alt (by @akshay-gupta7) [Github #3147](https://github.com/penpot/penpot/pull/3147)
@@ -31,6 +142,7 @@
- Open project in new tab from workspace (by @akshay-gupta7) [Github #3246](https://github.com/penpot/penpot/pull/3246)
- Distribute fix enabled when two elements were selected (by @dfelinto) [Github #3266](https://github.com/penpot/penpot/pull/3266)
- Distribute vertical spacing failing for overlapped text (by @dfelinto) [Github #3267](https://github.com/penpot/penpot/pull/3267)
- bug Change independent corner radius input tooltips #3332 (by @astudentinearth) [Github #3332](https://github.com/penpot/penpot/pull/3332)
## 1.18.6

View File

@@ -6,7 +6,7 @@
org.clojure/clojure {:mvn/version "1.11.1"}
org.clojure/core.async {:mvn/version "1.6.673"}
com.github.luben/zstd-jni {:mvn/version "1.5.2-5"}
com.github.luben/zstd-jni {:mvn/version "1.5.5-4"}
io.prometheus/simpleclient {:mvn/version "0.16.0"}
io.prometheus/simpleclient_hotspot {:mvn/version "0.16.0"}
@@ -17,17 +17,17 @@
io.prometheus/simpleclient_httpserver {:mvn/version "0.16.0"}
io.lettuce/lettuce-core {:mvn/version "6.2.2.RELEASE"}
io.lettuce/lettuce-core {:mvn/version "6.2.4.RELEASE"}
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
funcool/yetti
{:git/tag "v9.15"
:git/sha "aa9b967"
{:git/tag "v9.16"
:git/sha "7df3e08"
:git/url "https://github.com/funcool/yetti.git"
:exclusions [org.slf4j/slf4j-api]}
com.github.seancorfield/next.jdbc {:mvn/version "1.3.847"}
metosin/reitit-core {:mvn/version "0.5.18"}
com.github.seancorfield/next.jdbc {:mvn/version "1.3.883"}
metosin/reitit-core {:mvn/version "0.6.0"}
org.postgresql/postgresql {:mvn/version "42.6.0"}
@@ -35,12 +35,12 @@
io.whitfin/siphash {:mvn/version "2.0.0"}
buddy/buddy-hashers {:mvn/version "1.8.158"}
buddy/buddy-sign {:mvn/version "3.4.333"}
buddy/buddy-hashers {:mvn/version "2.0.167"}
buddy/buddy-sign {:mvn/version "3.5.351"}
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.1.5"}
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.1.6"}
org.jsoup/jsoup {:mvn/version "1.15.3"}
org.jsoup/jsoup {:mvn/version "1.16.1"}
org.im4java/im4java
{:git/tag "1.4.0-penpot-2"
:git/sha "e2b3e16"
@@ -49,14 +49,14 @@
org.lz4/lz4-java {:mvn/version "1.8.0"}
org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"}
integrant/integrant {:mvn/version "0.8.0"}
integrant/integrant {:mvn/version "0.8.1"}
dawran6/emoji {:mvn/version "0.1.5"}
markdown-clj/markdown-clj {:mvn/version "1.11.4"}
;; Pretty Print specs
pretty-spec/pretty-spec {:mvn/version "0.1.4"}
software.amazon.awssdk/s3 {:mvn/version "2.19.29"}
software.amazon.awssdk/s3 {:mvn/version "2.20.96"}
}
:paths ["src" "resources" "target/classes"]

View File

@@ -1,36 +1,30 @@
[{:id "material-design-3"
:name "Material Design 3"
:thumbnail-uri "https://penpot.app/images/libraries/cover-md3.jpg"
:file-uri "https://github.com/penpot/penpot-files/raw/main/Material%20Design%203.penpot"}
{:id "tutorial-for-beginners"
:name "Tutorial for beginners"
:thumbnail-uri "https://penpot.app/images/libraries/tutorial-for-beginners.jpg"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/tutorial-for-beginners.penpot"}
{:id "penpot-design-system"
:name "Penpot Design System"
:thumbnail-uri "https://penpot.app/images/libraries/cover-ds-penpot.jpg"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Penpot-Design-system.penpot"}
{:id "flex-layout-playground"
:name "Flex Layout Playground"
:file-uri "https://github.com/penpot/penpot-files/raw/main/Flex%20Layout%20Playground.penpot"}
{:id "wireframing-kit"
:name "Wireframing Kit"
:thumbnail-uri "https://penpot.app/images/libraries/cover-wireframes.jpg"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/wireframing-kit.penpot"}
{:id "ant-design"
:name "Ant Design UI Kit (lite)"
:thumbnail-uri "https://penpot.app/images/libraries/cover-ant-design.jpg"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Ant-Design-UI-Kit-Lite.penpot"}
{:id "cocomaterial"
:name "Cocomaterial"
:thumbnail-uri "https://penpot.app/images/libraries/cover-cocomaterial.jpg"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Cocomaterial.penpot"}
{:id "circum-icons"
:name "Circum Icons pack"
:thumbnail-uri "https://penpot.app/images/libraries/cover-circum.jpg"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/CircumIcons.penpot"}
{:id "coreui"
:name "CoreUI"
:thumbnail-uri "https://penpot.app/images/libraries/cover-coreui.jpg"
:file-uri "https://github.com/penpot/penpot-files/raw/main/CoreUI%20DesignSystem%20(DEMO).penpot"}
{:id "whiteboarding-kit"
:name "Whiteboarding Kit"
:thumbnail-uri "https://penpot.app/images/libraries/cover-whiteboards.jpg"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Whiteboarding-mapping-kit.penpot"}]

View File

@@ -22,11 +22,7 @@
{% endif %}
{% if item.params-schema-js %}
<span class="tag">
<span>SC</span>
</span>
{% else %}
<span class="tag">
<span>SP</span>
<span>SCHEMA</span>
</span>
{% endif %}
</div>

View File

@@ -38,7 +38,7 @@
<h2>GENERAL NOTES</h2>
<h3>Authentication</h3>
<p>The penpot backend right now offerts two way for authenticate the request:
<p>The penpot backend right now offers two way for authenticate the request:
<b>cookies</b> (the same mechanism that we use ourselves on accessing the API from the
web application) and <b>access tokens</b>.</p>

View File

@@ -6,13 +6,19 @@ penpot - error list
{% block content %}
<nav>
<h1>Latest error reports:</h1>
<div class="title">
<h1>Error reports (last 200)</h1>
</div>
</nav>
<main class="horizontal-list">
<ul>
{% for item in items %}
<li><a class="date" href="/dbg/error/{{item.id}}">{{item.created-at}}</a>
<span class="title">{{item.hint|abbreviate:150}}</span></li>
<li>
<a class="date" href="/dbg/error/{{item.id}}">{{item.created-at}}</a>
<a class="hint" href="/dbg/error/{{item.id}}">
<span class="title">{{item.hint|abbreviate:150}}</span>
</a>
</li>
{% endfor %}
</ul>
</main>

View File

@@ -1,13 +1,13 @@
{% extends "app/templates/base.tmpl" %}
{% block title %}
penpot - error report v2 {{id}}
Report: {{hint|abbreviate:150}} - {{id}} - Penpot Error Report (v3)
{% endblock %}
{% block content %}
<nav>
<div>[<a href="/dbg/error">⮜</a>]</div>
<div>[<a href="#message">message</a>]</div>
<div>[<a href="#head">head</a>]</div>
<div>[<a href="#props">props</a>]</div>
<div>[<a href="#context">context</a>]</div>
{% if params %}
@@ -29,10 +29,11 @@ penpot - error report v2 {{id}}
<main>
<div class="table">
<div class="table-row multiline">
<div id="message" class="table-key">MESSAGE: </div>
<div id="head" class="table-key">HEAD</div>
<div class="table-val">
<h1>{{hint}}</h1>
<h1><span class="not-important">Hint:</span> <br/> {{hint}}</h1>
<h2><span class="not-important">Reported at:</span> <br/> {{created-at}}</h2>
<h2><span class="not-important">Report ID:</span> <br/> {{id}}</h2>
</div>
</div>
@@ -71,7 +72,7 @@ penpot - error report v2 {{id}}
{% if value %}
<div class="table-row multiline">
<div id="value" class="table-key">VALIDATION VALUE: </div>
<div id="value" class="table-key">VALUE: </div>
<div class="table-val">
<pre>{{value}}</pre>
</div>

View File

@@ -36,6 +36,11 @@ small {
color: #888;
}
.not-important {
color: #888;
font-weight: 200;
}
small > strong {
font-size: 9px;
}
@@ -50,7 +55,13 @@ nav {
background: #e3e3e3;
}
nav > h1 {
nav > .title {
display: flex;
justify-content: center;
width: 100%;
}
nav > .title > h1 {
padding: 0px;
margin: 0px;
font-size: 11px;
@@ -151,7 +162,6 @@ nav > div:not(:last-child) {
line-height: 18px;
min-width: 210px;
margin: 0px 20px;
cursor: pointer;
display: flex;
border-radius: 3px;
}

View File

@@ -18,6 +18,8 @@ cp scripts/manage.py target/dist/manage.py
chmod +x target/dist/run.sh;
chmod +x target/dist/manage.py
# Prefetch
# Prefetch templates
rm -rf builtin-templates;
mkdir builtin-templates;
bb ./scripts/prefetch-templates.clj resources/app/onboarding.edn builtin-templates/
cp -r builtin-templates target/dist/

View File

@@ -15,7 +15,7 @@ export PENPOT_FLAGS="\
enable-fdata-storage-objets-map \
disable-secure-session-cookies \
enable-smtp \
enable-webhooks";
enable-access-tokens";
set -ex

View File

@@ -6,13 +6,15 @@
(ns app.auth
(:require
[app.config :as cf]
[buddy.hashers :as hashers]
[cuerdas.core :as str]
[promesa.exec :as px]))
(def default-params
{:alg :argon2id
:memory (* 32768 2)
:iterations 5
:memory (* 32768 2) ;; 64 MiB
:iterations 7
:parallelism (px/get-available-processors)})
(defn derive-password
@@ -27,3 +29,16 @@
{:update false
:valid false})))
(defn email-domain-in-whitelist?
"Returns true if email's domain is in the given whitelist or if
given whitelist is an empty string."
([email]
(let [domains (cf/get :registration-domain-whitelist)]
(email-domain-in-whitelist? domains email)))
([domains email]
(if (or (nil? domains) (empty? domains))
true
(let [[_ candidate] (-> (str/lower email)
(str/split #"@" 2))]
(contains? domains candidate)))))

View File

@@ -7,6 +7,7 @@
(ns app.auth.oidc
"OIDC client implementation."
(:require
[app.auth :as auth]
[app.auth.oidc.providers :as-alias providers]
[app.common.data :as d]
[app.common.data.macros :as dm]
@@ -24,6 +25,8 @@
[app.tokens :as tokens]
[app.util.json :as json]
[app.util.time :as dt]
[buddy.sign.jwk :as jwk]
[buddy.sign.jwt :as jwt]
[clojure.set :as set]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
@@ -47,36 +50,29 @@
(defn- discover-oidc-config
[cfg {:keys [base-uri] :as opts}]
(let [discovery-uri (u/join base-uri ".well-known/openid-configuration")
response (ex/try! (http/req! cfg
{:method :get :uri (str discovery-uri)}
{:sync? true}))]
(cond
(ex/exception? response)
(do
(l/warn :hint "unable to discover oidc configuration"
:discover-uri (str discovery-uri)
:cause response)
nil)
(= 200 (:status response))
(let [data (json/decode (:body response))
(let [uri (dm/str (u/join base-uri ".well-known/openid-configuration"))
rsp (http/req! cfg {:method :get :uri uri} {:sync? true})]
(if (= 200 (:status rsp))
(let [data (-> rsp :body json/decode)
token-uri (get data :token_endpoint)
auth-uri (get data :authorization_endpoint)
user-uri (get data :userinfo_endpoint)]
user-uri (get data :userinfo_endpoint)
jwks-uri (get data :jwks_uri)]
(l/debug :hint "oidc uris discovered"
:token-uri token-uri
:auth-uri auth-uri
:user-uri user-uri)
:user-uri user-uri
:jwks-uri jwks-uri)
{:token-uri token-uri
:auth-uri auth-uri
:user-uri user-uri})
:else
:user-uri user-uri
:jwks-uri jwks-uri})
(do
(l/warn :hint "unable to discover OIDC configuration"
:uri (str discovery-uri)
:response-status-code (:status response))
:discover-uri uri
:http-status (:status rsp))
nil))))
(defn- prepare-oidc-opts
@@ -87,6 +83,7 @@
:token-uri (cf/get :oidc-token-uri)
:auth-uri (cf/get :oidc-auth-uri)
:user-uri (cf/get :oidc-user-uri)
:jwks-uri (cf/get :oidc-jwks-uri)
:scopes (cf/get :oidc-scopes #{"openid" "profile" "email"})
:roles-attr (cf/get :oidc-roles-attr)
:roles (cf/get :oidc-roles)
@@ -101,8 +98,42 @@
(string? (:user-uri opts))
(string? (:auth-uri opts)))
opts
(some-> (discover-oidc-config cfg opts)
(merge opts {:discover? true}))))))
(try
(-> (discover-oidc-config cfg opts)
(merge opts {:discover? true}))
(catch Throwable cause
(l/warn :hint "unable to discover OIDC configuration"
:cause cause)))))))
(defn- process-oidc-jwks
[keys]
(reduce (fn [result {:keys [kid] :as kdata}]
(let [pkey (ex/try! (jwk/public-key kdata))]
(if (ex/exception? pkey)
(do
(l/warn :hint "unable to create public key"
:kid (:kid kdata)
:cause pkey)
result)
(assoc result kid pkey))))
{}
keys))
(defn- fetch-oidc-jwks
[cfg {:keys [jwks-uri]}]
(when jwks-uri
(try
(let [{:keys [status body]} (http/req! cfg {:method :get :uri jwks-uri} {:sync? true})]
(if (= 200 status)
(-> body json/decode :keys process-oidc-jwks)
(do
(l/warn :hint "unable to retrieve JWKs (unexpected response status code)"
:http-status status
:http-body body)
nil)))
(catch Throwable cause
(l/warn :hint "unable to retrieve JWKs (unexpected exception)"
:cause cause)))))
(defmethod ig/pre-init-spec ::providers/generic [_]
(s/keys :req [::http/client]))
@@ -111,7 +142,7 @@
[_ cfg]
(when (contains? cf/flags :login-with-oidc)
(if-let [opts (prepare-oidc-opts cfg)]
(do
(let [jwks (fetch-oidc-jwks cfg opts)]
(l/info :hint "provider initialized"
:provider "oidc"
:method (if (:discover? opts) "discover" "manual")
@@ -122,8 +153,9 @@
:user-uri (:user-uri opts)
:token-uri (:token-uri opts)
:roles-attr (:roles-attr opts)
:roles (:roles opts))
opts)
:roles (:roles opts)
:keys (str/join "," (map str (keys jwks))))
(assoc opts :jwks jwks))
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider "oidc")
nil))))
@@ -164,7 +196,7 @@
[cfg tdata props]
(or (some-> props :github/email)
(let [params {:uri "https://api.github.com/user/emails"
:headers {"Authorization" (dm/str (:type tdata) " " (:token tdata))}
:headers {"Authorization" (dm/str (:token/type tdata) " " (:token/access tdata))}
:timeout 6000
:method :get}
@@ -273,7 +305,7 @@
{}
props))
(defn retrieve-access-token
(defn fetch-access-token
[{:keys [provider] :as cfg} code]
(let [params {:client_id (:client-id provider)
:client_secret (:client-secret provider)
@@ -297,8 +329,9 @@
(l/trace :hint "access token response" :status status :body body)
(if (= status 200)
(let [data (json/decode body)]
{:token (get data :access_token)
:type (get data :token_type)})
{:token/access (get data :access_token)
:token/id (get data :id_token)
:token/type (get data :token_type)})
(ex/raise :type :internal
:code :unable-to-retrieve-token
@@ -306,12 +339,11 @@
:http-status status
:http-body body)))))
(defn- retrieve-user-info
[{:keys [provider] :as cfg} tdata]
(defn- process-user-info
[provider tdata info]
(letfn [(get-email [props]
;; Allow providers hook into this for custom email
;; retrieval method.
(if-let [get-email-fn (:get-email-fn provider)]
(get-email-fn tdata props)
(let [attr-kw (cf/get :oidc-email-attr "email")
@@ -322,48 +354,54 @@
(let [attr-kw (cf/get :oidc-name-attr "name")
attr-ph (parse-attr-path provider attr-kw)]
(get-in props attr-ph)))
]
(process-response [response]
(let [info (-> response :body json/decode)
props (qualify-props provider info)
email (get-email props)]
{:backend (:name provider)
:fullname (or (get-name props) email)
:email email
:props props}))]
(let [props (qualify-props provider info)
email (get-email props)]
{:backend (:name provider)
:fullname (or (get-name props) email)
:email email
:props props})))
(l/trace :hint "request user info"
:uri (:user-uri provider)
:token (obfuscate-string (:token tdata))
:token-type (:type tdata))
(defn- fetch-user-info
[{:keys [provider] :as cfg} tdata]
(l/trace :hint "fetch user info"
:uri (:user-uri provider)
:token (obfuscate-string (:token/access tdata)))
(let [request {:uri (:user-uri provider)
:headers {"Authorization" (str (:type tdata) " " (:token tdata))}
:timeout 6000
:method :get}
response (http/req! cfg request {:sync? true})]
(let [params {:uri (:user-uri provider)
:headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))}
:timeout 6000
:method :get}
response (http/req! cfg params {:sync? true})]
(l/trace :hint "user info response"
:status (:status response)
:body (:body response))
(l/trace :hint "user info response"
:status (:status response)
:body (:body response))
(when-not (s/int-in-range? 200 300 (:status response))
(ex/raise :type :internal
:code :unable-to-retrieve-user-info
:hint "unable to retrieve user info"
:http-status (:status response)
:http-body (:body response)))
(when-not (s/int-in-range? 200 300 (:status response))
(ex/raise :type :internal
:code :unable-to-retrieve-user-info
:hint "unable to retrieve user info"
:http-status (:status response)
:http-body (:body response)))
(let [info (process-response response)]
(l/trace :hint "authentication info" :info info)
(-> response :body json/decode)))
(when-not (s/valid? ::info info)
(l/warn :hint "received incomplete profile info object (please set correct scopes)" :info info)
(ex/raise :type :internal
:code :incomplete-user-info
:hint "inconmplete user info"
:info info))
info))))
(defn- get-user-info
[{:keys [provider]} tdata]
(try
(when (:token/id tdata)
(let [{:keys [kid alg] :as theader} (jwt/decode-header (:token/id tdata))]
(when-let [key (if (str/starts-with? (name alg) "hs")
(:client-secret provider)
(get-in provider [:jwks kid]))]
(let [claims (jwt/unsign (:token/id tdata) key {:alg alg})]
(dissoc claims :exp :iss :iat :sid :aud :sub)))))
(catch Throwable cause
(l/warn :hint "unable to get user info from JWT token (unexpected exception)"
:cause cause))))
(s/def ::backend ::us/not-empty-string)
(s/def ::email ::us/not-empty-string)
@@ -376,7 +414,7 @@
::props]))
(defn get-info
[{:keys [provider] :as cfg} {:keys [params] :as request}]
[{:keys [provider ::main/props] :as cfg} {:keys [params] :as request}]
(when-let [error (get params :error)]
(ex/raise :type :internal
:code :error-on-retrieving-code
@@ -385,9 +423,24 @@
(let [state (get params :state)
code (get params :code)
state (tokens/verify (::main/props cfg) {:token state :iss :oauth})
token (retrieve-access-token cfg code)
info (retrieve-user-info cfg token)]
state (tokens/verify props {:token state :iss :oauth})
tdata (fetch-access-token cfg code)
info (case (cf/get :oidc-user-info-source)
:token (get-user-info cfg tdata)
:userinfo (fetch-user-info cfg tdata)
(or (get-user-info cfg tdata)
(fetch-user-info cfg tdata)))
info (process-user-info provider tdata info)]
(l/trace :hint "user info" :info info)
(when-not (s/valid? ::info info)
(l/warn :hint "received incomplete profile info object (please set correct scopes)" :info info)
(ex/raise :type :internal
:code :incomplete-user-info
:hint "inconmplete user info"
:info info))
;; If the provider is OIDC, we can proceed to check
;; roles if they are defined.
@@ -430,10 +483,24 @@
::yrs/headers {"location" (str uri)}})
(defn- generate-error-redirect
[_ error]
(let [uri (-> (u/uri (cf/get :public-uri))
(assoc :path "/#/auth/login")
(assoc :query (u/map->query-string {:error "unable-to-auth" :hint (ex-message error)})))]
[_ cause]
(let [data (if (ex/error? cause) (ex-data cause) nil)
code (or (:code data) :unexpected)
type (or (:type data) :internal)
hint (or (:hint data)
(if (ex/exception? cause)
(ex-message cause)
(str cause)))
params {:error "unable-to-auth"
:hint hint
:type type
:code code}
uri (-> (u/uri (cf/get :public-uri))
(assoc :path "/#/auth/login")
(assoc :query (u/map->query-string params)))]
(redirect-response uri)))
(defn- generate-redirect
@@ -463,19 +530,23 @@
(->> (redirect-response uri)
(sxf request)))
(let [info (assoc info
:iss :prepared-register
:is-active true
:exp (dt/in-future {:hours 48}))
token (tokens/generate (::main/props cfg) info)
params (d/without-nils
{:token token
:fullname (:fullname info)})
uri (-> (u/uri (cf/get :public-uri))
(assoc :path "/#/auth/register/validate")
(assoc :query (u/map->query-string params)))]
(redirect-response uri))))
(if (auth/email-domain-in-whitelist? (:email info))
(let [info (assoc info
:iss :prepared-register
:is-active true
:exp (dt/in-future {:hours 48}))
token (tokens/generate (::main/props cfg) info)
params (d/without-nils
{:token token
:fullname (:fullname info)})
uri (-> (u/uri (cf/get :public-uri))
(assoc :path "/#/auth/register/validate")
(assoc :query (u/map->query-string params)))]
(redirect-response uri))
(generate-error-redirect cfg "email-domain-not-allowed"))))
(defn- auth-handler
[cfg {:keys [params] :as request}]
@@ -496,7 +567,7 @@
profile (get-profile cfg info)]
(generate-redirect cfg request info profile))
(catch Throwable cause
(l/error :hint "error on oauth process" :cause cause)
(l/warn :hint "error on oauth process" :cause cause)
(generate-error-redirect cfg cause))))
(def provider-lookup

View File

@@ -146,11 +146,13 @@
(s/def ::google-client-id ::us/string)
(s/def ::google-client-secret ::us/string)
(s/def ::oidc-client-id ::us/string)
(s/def ::oidc-user-info-source ::us/keyword)
(s/def ::oidc-client-secret ::us/string)
(s/def ::oidc-base-uri ::us/string)
(s/def ::oidc-token-uri ::us/string)
(s/def ::oidc-auth-uri ::us/string)
(s/def ::oidc-user-uri ::us/string)
(s/def ::oidc-jwks-uri ::us/string)
(s/def ::oidc-scopes ::us/set-of-strings)
(s/def ::oidc-roles ::us/set-of-strings)
(s/def ::oidc-roles-attr ::us/string)
@@ -241,10 +243,12 @@
::google-client-secret
::oidc-client-id
::oidc-client-secret
::oidc-user-info-source
::oidc-base-uri
::oidc-token-uri
::oidc-auth-uri
::oidc-user-uri
::oidc-jwks-uri
::oidc-scopes
::oidc-roles-attr
::oidc-email-attr

View File

@@ -5,7 +5,7 @@
;; Copyright (c) KALEIDOS INC
(ns app.db
(:refer-clojure :exclude [get])
(:refer-clojure :exclude [get run!])
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
@@ -218,7 +218,13 @@
(defmacro with-atomic
[& args]
`(jdbc/with-transaction ~@args))
(if (symbol? (first args))
(let [cfgs (first args)
body (rest args)]
`(jdbc/with-transaction [conn# (::pool ~cfgs)]
(let [~cfgs (assoc ~cfgs ::conn conn#)]
~@body)))
`(jdbc/with-transaction ~@args)))
(defn open
[pool]
@@ -293,6 +299,10 @@
:hint "database object not found"))
row))
(defn plan
[ds sql]
(jdbc/plan ds sql sql/default-opts))
(defn get-by-id
[ds table id & {:as opts}]
(get ds table {:id id} opts))
@@ -381,6 +391,52 @@
([^Connection conn ^Savepoint sp]
(.rollback conn sp)))
(defn tx-run!
[cfg f]
(cond
(connection? cfg)
(tx-run! {::conn cfg} f)
(pool? cfg)
(tx-run! {::pool cfg} f)
(::conn cfg)
(let [conn (::conn cfg)
sp (savepoint conn)]
(try
(let [result (f cfg)]
(release! conn sp)
result)
(catch Throwable cause
(rollback! sp)
(throw cause))))
(::pool cfg)
(with-atomic [conn (::pool cfg)]
(f (assoc cfg ::conn conn)))
:else
(throw (IllegalArgumentException. "invalid arguments"))))
(defn run!
[cfg f]
(cond
(connection? cfg)
(run! {::conn cfg} f)
(pool? cfg)
(run! {::pool cfg} f)
(::conn cfg)
(f cfg)
(::pool cfg)
(with-open [^Connection conn (open (::pool cfg))]
(f (assoc cfg ::conn conn)))
:else
(throw (IllegalArgumentException. "invalid arguments"))))
(defn interval
[o]
(cond

View File

@@ -305,7 +305,7 @@
(fn [params]
(when (contains? cf/flags :smtp)
(let [session (create-smtp-session cfg)]
(with-open [transport (.getTransport session (if (:ssl cfg) "smtps" "smtp"))]
(with-open [transport (.getTransport session (if (::ssl cfg) "smtps" "smtp"))]
(.connect ^Transport transport
^String (::username cfg)
^String (::password cfg))
@@ -341,7 +341,7 @@
(map :content)
first)))
(println "******** end email" (:id email) "**********"))]
(l/info ::l/raw out)))
(l/raw! :info out)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EMAIL FACTORIES

View File

@@ -36,10 +36,10 @@
(defmethod ig/init-key ::routes
[_ {:keys [::wrk/executor] :as cfg}]
(letfn [(handler [request respond _]
(letfn [(handler [request]
(let [data (-> request yrq/body slurp)]
(px/run! executor #(handle-request cfg data)))
(respond {::yrs/status 200}))]
{::yrs/status 200})]
["/sns" {:handler handler
:allowed-methods #{:post}}]))

View File

@@ -238,9 +238,11 @@
(-> (io/resource "app/templates/error-report.v2.tmpl")
(tmpl/render report)))
(render-template-v3 [{report :content}]
(render-template-v3 [{:keys [content id created-at]}]
(-> (io/resource "app/templates/error-report.v3.tmpl")
(tmpl/render report)))
(tmpl/render (-> content
(assoc :id id)
(assoc :created-at (dt/format-instant created-at :rfc1123))))))
]
(when-not (authorized? pool request)
@@ -264,7 +266,7 @@
content->>'~:hint' AS hint
FROM server_error_report
ORDER BY created_at DESC
LIMIT 100")
LIMIT 200")
(defn error-list-handler
[{:keys [::db/pool]} request]

View File

@@ -10,6 +10,7 @@
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.config :as cf]
[app.http :as-alias http]
[app.http.access-token :as-alias actoken]
[app.http.session :as-alias session]
@@ -30,14 +31,14 @@
(let [claims (-> {}
(into (::session/token-claims request))
(into (::actoken/token-claims request)))]
{:path (:path request)
:method (:method request)
:params (:params request)
:ip-addr (parse-client-ip request)
:user-agent (yrq/get-header request "user-agent")
:profile-id (:uid claims)
:version (or (yrq/get-header request "x-frontend-version")
"unknown")}))
{:request/path (:path request)
:request/method (:method request)
:request/params (:params request)
:request/user-agent (yrq/get-header request "user-agent")
:request/ip-addr (parse-client-ip request)
:request/profile-id (:uid claims)
:version/frontend (or (yrq/get-header request "x-frontend-version") "unknown")
:version/backend (:full cf/version)}))
(defmulti handle-exception
(fn [err & _rest]
@@ -73,14 +74,14 @@
::yrs/headers headers}))
(defmethod handle-exception :validation
[err _]
[err request]
(let [{:keys [code] :as data} (ex-data err)]
(cond
(= code :spec-validation)
(let [explain (ex/explain data)]
{::yrs/status 400
::yrs/body (-> data
(dissoc ::s/problems ::s/value)
(dissoc ::s/problems ::s/value ::s/spec)
(cond-> explain (assoc :explain explain)))})
(= code :params-validation)
@@ -94,6 +95,11 @@
(= code :request-body-too-large)
{::yrs/status 413 ::yrs/body data}
(= code :invalid-image)
(binding [l/*context* (request->context request)]
(l/error :hint "unexpected error on processing image" :cause err)
{::yrs/status 400 ::yrs/body data})
:else
{::yrs/status 400 ::yrs/body data})))

View File

@@ -22,9 +22,10 @@
(:import
com.fasterxml.jackson.core.JsonParseException
com.fasterxml.jackson.core.io.JsonEOFException
com.fasterxml.jackson.databind.exc.MismatchedInputException
io.undertow.server.RequestTooBigException
java.io.OutputStream
java.io.InputStream))
java.io.InputStream
java.io.OutputStream))
(set! *warn-on-reflection* true)
@@ -78,11 +79,13 @@
(or (instance? JsonEOFException cause)
(instance? JsonParseException cause))
(instance? JsonParseException cause)
(instance? MismatchedInputException cause))
(raise (ex/error :type :validation
:code :malformed-json
:hint (ex-message cause)
:cause cause))
:else
(raise cause)))]
@@ -118,8 +121,9 @@
(t/write! tw data)))
(catch java.io.IOException _)
(catch Throwable cause
(l/warn :hint "unexpected error on encoding response"
:cause cause))
(binding [l/*context* {:value data}]
(l/error :hint "unexpected error on encoding response"
:cause cause)))
(finally
(.close ^OutputStream output-stream))))))
@@ -132,8 +136,9 @@
(catch java.io.IOException _)
(catch Throwable cause
(l/warn :hint "unexpected error on encoding response"
:cause cause))
(binding [l/*context* {:value data}]
(l/error :hint "unexpected error on encoding response"
:cause cause)))
(finally
(.close ^OutputStream output-stream))))))

View File

@@ -11,6 +11,7 @@
[app.common.logging :as l]
[app.common.pprint :as pp]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.db :as db]
[app.http.session :as session]
[app.metrics :as mtx]
@@ -99,7 +100,10 @@
(sp/pipe ch output-ch false)
;; Subscribe to the profile topic on msgbus/redis
(mbus/sub! msgbus :topic profile-id :chan ch)))
(mbus/sub! msgbus :topic profile-id :chan ch)
;; Subscribe to the system topic on msgbus/redis
(mbus/sub! msgbus :topic (str uuid/zero) :chan ch)))
(defmethod handle-message :close
[{:keys [::mbus/msgbus]} {:keys [::ws/id ::ws/state ::profile-id ::session-id]} _]

View File

@@ -40,35 +40,33 @@
[{:keys [::l/context ::l/message ::l/props ::l/logger ::l/level ::l/cause] :as record}]
(us/assert! ::l/record record)
(let [data (ex-data cause)]
(let [data (ex-data cause)
ctx (-> context
(assoc :tenant (cf/get :tenant))
(assoc :host (cf/get :host))
(assoc :public-uri (cf/get :public-uri))
(assoc :logger/name logger)
(assoc :logger/level level)
(dissoc :request/params :value :params :data))]
(merge
{:context (-> context
(assoc :tenant (cf/get :tenant))
(assoc :host (cf/get :host))
(assoc :public-uri (cf/get :public-uri))
(assoc :version (:full cf/version))
(assoc :logger-name logger)
(assoc :logger-level level)
(dissoc :params)
(pp/pprint-str :width 200))
:props (pp/pprint-str props :width 200)
{:context (-> (into (sorted-map) ctx)
(pp/pprint-str :width 200 :length 50 :level 10))
:props (pp/pprint-str props :width 200 :length 50)
:hint (or (ex-message cause) @message)
:trace (ex/format-throwable cause :data? false :explain? false :header? false :summary? false)}
(when-let [params (:params context)]
{:params (pp/pprint-str params :width 200)})
(when-let [params (or (:request/params context) (:params context))]
{:params (pp/pprint-str params :width 200 :length 50 :level 10)})
(when-let [value (:value context)]
{:value (pp/pprint-str value :width 200 :length 50 :level 10)})
(when-let [data (some-> data (dissoc ::s/problems ::s/value ::s/spec ::sm/explain :hint))]
{:data (pp/pprint-str data :width 200)})
(when-let [value (-> data ::sm/explain :value)]
{:value (pp/pprint-str value :width 200)})
(when-let [explain (ex/explain data)]
(when-let [explain (ex/explain data {:level 10 :length 50})]
{:explain explain}))))
(defn error-record?
[{:keys [::l/level ::l/cause]}]
(and (= :error level)

View File

@@ -30,7 +30,9 @@
"```\n"
"- host: `" (:host report) "`\n"
"- tenant: `" (:tenant report) "`\n"
"- version: `" (:version report) "`\n"
"- request-path: `" (:request-path report) "`\n"
"- frontend-version: `" (:frontend-version report) "`\n"
"- backend-version: `" (:backend-version report) "`\n"
"\n"
"Trace:\n"
(:trace report)
@@ -50,13 +52,15 @@
(defn record->report
[{:keys [::l/context ::l/id ::l/cause] :as record}]
(us/assert! ::l/record record)
{:id id
:tenant (cf/get :tenant)
:host (cf/get :host)
:public-uri (cf/get :public-uri)
:version (:full cf/version)
:profile-id (:profile-id context)
:trace (ex/format-throwable cause :detail? false :header? false)})
{:id id
:tenant (cf/get :tenant)
:host (cf/get :host)
:public-uri (cf/get :public-uri)
:backend-version (or (:version/backend context) (:full cf/version))
:frontend-version (:version/frontend context)
:profile-id (:request/profile-id context)
:request-path (:request/path context)
:trace (ex/format-throwable cause :detail? false :header? false)})
(defn handle-event
[cfg record]

View File

@@ -29,6 +29,7 @@
[app.redis :as-alias rds]
[app.rpc :as-alias rpc]
[app.rpc.doc :as-alias rpc.doc]
[app.setup :as-alias setup]
[app.srepl :as-alias srepl]
[app.storage :as-alias sto]
[app.storage.fs :as-alias sto.fs]
@@ -220,7 +221,7 @@
{::db/pool (ig/ref ::db/pool)}
::http.awsns/routes
{::props (ig/ref :app.setup/props)
{::props (ig/ref ::setup/props)
::db/pool (ig/ref ::db/pool)
::http.client/client (ig/ref ::http.client/client)
::wrk/executor (ig/ref ::wrk/executor)}
@@ -263,7 +264,7 @@
::oidc/routes
{::http.client/client (ig/ref ::http.client/client)
::db/pool (ig/ref ::db/pool)
::props (ig/ref :app.setup/props)
::props (ig/ref ::setup/props)
::oidc/providers {:google (ig/ref ::oidc.providers/google)
:github (ig/ref ::oidc.providers/github)
:gitlab (ig/ref ::oidc.providers/gitlab)
@@ -275,7 +276,7 @@
::db/pool (ig/ref ::db/pool)
::rpc/routes (ig/ref ::rpc/routes)
::rpc.doc/routes (ig/ref ::rpc.doc/routes)
::props (ig/ref :app.setup/props)
::props (ig/ref ::setup/props)
::mtx/routes (ig/ref ::mtx/routes)
::oidc/routes (ig/ref ::oidc/routes)
::http.debug/routes (ig/ref ::http.debug/routes)
@@ -322,11 +323,10 @@
::rpc/climit (ig/ref ::rpc/climit)
::rpc/rlimit (ig/ref ::rpc/rlimit)
::props (ig/ref :app.setup/props)
::setup/templates (ig/ref ::setup/templates)
::props (ig/ref ::setup/props)
:pool (ig/ref ::db/pool)
:templates (ig/ref :app.setup/builtin-templates)
}
:app.rpc.doc/routes
@@ -337,7 +337,7 @@
::db/pool (ig/ref ::db/pool)
::wrk/executor (ig/ref ::wrk/executor)
::session/manager (ig/ref ::session/manager)
::props (ig/ref :app.setup/props)}
::props (ig/ref ::setup/props)}
::wrk/registry
{::mtx/metrics (ig/ref ::mtx/metrics)
@@ -390,7 +390,7 @@
:app.tasks.telemetry/handler
{::db/pool (ig/ref ::db/pool)
::http.client/client (ig/ref ::http.client/client)
::props (ig/ref :app.setup/props)}
::props (ig/ref ::setup/props)}
[::srepl/urepl ::srepl/server]
{::srepl/port (cf/get :urepl-port 6062)
@@ -400,10 +400,9 @@
{::srepl/port (cf/get :prepl-port 6063)
::srepl/host (cf/get :prepl-host "localhost")}
:app.setup/builtin-templates
{::http.client/client (ig/ref ::http.client/client)}
::setup/templates {}
:app.setup/props
::setup/props
{::db/pool (ig/ref ::db/pool)
::key (cf/get :secret-key)
@@ -412,7 +411,7 @@
::migrations (ig/ref :app.migrations/migrations)}
::audit.tasks/archive
{::props (ig/ref :app.setup/props)
{::props (ig/ref ::setup/props)
::db/pool (ig/ref ::db/pool)
::http.client/client (ig/ref ::http.client/client)}

View File

@@ -324,6 +324,9 @@
{:name "0104-mod-file-thumbnail-table"
:fn (mg/resource "app/migrations/sql/0104-mod-file-thumbnail-table.sql")}
{:name "0105-mod-server-error-report-table"
:fn (mg/resource "app/migrations/sql/0105-mod-server-error-report-table.sql")}
])
(defn apply-migrations!

View File

@@ -0,0 +1,2 @@
CREATE INDEX server_error_report__created_at__idx
ON server_error_report ( created_at );

View File

@@ -9,7 +9,7 @@
(:require
[app.common.data :as d]
[app.common.logging :as l]
[app.common.spec :as us]
[app.common.schema :as sm]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@@ -19,9 +19,7 @@
[app.rpc.climit :as-alias climit]
[app.rpc.doc :as-alias doc]
[app.rpc.helpers :as rph]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s]))
[app.util.services :as sv]))
(defn- event->row [event]
[(uuid/next)
@@ -52,26 +50,25 @@
(when (seq events)
(db/insert-multi! pool :audit-log event-columns events))))
(s/def ::name ::us/string)
(s/def ::type ::us/string)
(s/def ::props (s/map-of ::us/keyword any?))
(s/def ::timestamp dt/instant?)
(s/def ::context (s/map-of ::us/keyword any?))
(def schema:event
[:map {:title "Event"}
[:name [:string {:max 250}]]
[:type [:string {:max 250}]]
[:props
[:map-of :keyword :any]]
[:context {:optional true}
[:map-of :keyword :any]]])
(s/def ::event
(s/keys :req-un [::type ::name ::props ::timestamp]
:opt-un [::context]))
(s/def ::events (s/every ::event))
(s/def ::push-audit-events
(s/keys :req [::rpc/profile-id]
:req-un [::events]))
(def schema:push-audit-events
[:map {:title "push-audit-events"}
[:events [:vector schema:event]]])
(sv/defmethod ::push-audit-events
{::climit/id :submit-audit-events-by-profile
::climit/key-fn ::rpc/profile-id
::sm/params schema:push-audit-events
::audit/skip true
::doc/skip true
::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} params]
(if (or (db/read-only? pool)

View File

@@ -6,10 +6,12 @@
(ns app.rpc.commands.auth
(:require
[app.auth :as auth]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.spec :as us]
[app.common.schema :as sm]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@@ -25,31 +27,13 @@
[app.tokens :as tokens]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]))
(s/def ::email ::us/email)
(s/def ::fullname ::us/not-empty-string)
(s/def ::lang ::us/string)
(s/def ::path ::us/string)
(s/def ::password ::us/not-empty-string)
(s/def ::old-password ::us/not-empty-string)
(s/def ::theme ::us/string)
(s/def ::invitation-token ::us/not-empty-string)
(s/def ::token ::us/not-empty-string)
(def schema:password
[::sm/word-string {:max 500}])
;; ---- HELPERS
(defn email-domain-in-whitelist?
"Returns true if email's domain is in the given whitelist or if
given whitelist is an empty string."
[domains email]
(if (or (empty? domains)
(nil? domains))
true
(let [[_ candidate] (-> (str/lower email)
(str/split #"@" 2))]
(contains? domains candidate))))
(def schema:token
[::sm/word-string {:max 6000}])
;; ---- COMMAND: login with password
@@ -113,22 +97,22 @@
(rph/with-meta {::audit/props (audit/profile->props profile)
::audit/profile-id (:id profile)}))))))
(s/def ::login-with-password
(s/keys :req-un [::email ::password]
:opt-un [::invitation-token]))
(def schema:login-with-password
[:map {:title "login-with-password"}
[:email ::sm/email]
[:password schema:password]
[:invitation-token {:optional true} schema:token]])
(sv/defmethod ::login-with-password
"Performs authentication using penpot password."
{::rpc/auth false
::doc/added "1.15"}
::doc/added "1.15"
::sm/params schema:login-with-password}
[cfg params]
(login-with-password cfg params))
;; ---- COMMAND: Logout
(s/def ::logout
(s/keys :opt [::rpc/profile-id]))
(sv/defmethod ::logout
"Clears the authentication cookie and logout the current session."
{::rpc/auth false
@@ -153,13 +137,15 @@
(update-password conn))
nil)))
(s/def ::token ::us/not-empty-string)
(s/def ::recover-profile
(s/keys :req-un [::token ::password]))
(def schema:recover-profile
[:map {:title "recover-profile"}
[:token schema:token]
[:password schema:password]])
(sv/defmethod ::recover-profile
{::rpc/auth false
::doc/added "1.15"}
::doc/added "1.15"
::sm/params schema:recover-profile}
[cfg params]
(recover-profile cfg params))
@@ -180,10 +166,9 @@
:code :email-does-not-match-invitation
:hint "email should match the invitation"))))
(when-let [domains (cf/get :registration-domain-whitelist)]
(when-not (email-domain-in-whitelist? domains (:email params))
(ex/raise :type :validation
:code :email-domain-is-not-allowed)))
(when-not (auth/email-domain-in-whitelist? (:email params))
(ex/raise :type :validation
:code :email-domain-is-not-allowed))
;; Don't allow proceed in preparing registration if the profile is
;; already reported as spammer.
@@ -241,13 +226,16 @@
(with-meta {:token token}
{::audit/profile-id uuid/zero})))
(s/def ::prepare-register-profile
(s/keys :req-un [::email ::password]
:opt-un [::invitation-token]))
(def schema:prepare-register-profile
[:map {:title "prepare-register-profile"}
[:email ::sm/email]
[:password schema:password]
[:invitation-token {:optional true} schema:token]])
(sv/defmethod ::prepare-register-profile
{::rpc/auth false
::doc/added "1.15"}
::doc/added "1.15"
::sm/params schema:prepare-register-profile}
[cfg params]
(prepare-register cfg params))
@@ -257,7 +245,7 @@
"Create the profile entry on the database with limited set of input
attrs (all the other attrs are filled with default values)."
[conn {:keys [email] :as params}]
(us/assert! ::us/email email)
(dm/assert! ::sm/email email)
(let [id (or (:id params) (uuid/next))
props (-> (audit/extract-utm-params params)
(merge (:props params))
@@ -335,9 +323,9 @@
:extra-data ptoken})))
(defn register-profile
[{:keys [::db/conn] :as cfg} {:keys [token] :as params}]
[{:keys [::db/conn] :as cfg} {:keys [token fullname] :as params}]
(let [claims (tokens/verify (::main/props cfg) {:token token :iss :prepared-register})
params (merge params claims)
params (assoc claims :fullname fullname)
is-active (or (:is-active params)
(not (contains? cf/flags :email-verification)))
@@ -404,12 +392,16 @@
{::audit/replace-props (audit/profile->props profile)
::audit/profile-id (:id profile)})))))
(s/def ::register-profile
(s/keys :req-un [::token ::fullname]))
(def schema:register-profile
[:map {:title "register-profile"}
[:token schema:token]
[:fullname [::sm/word-string {:max 100}]]])
(sv/defmethod ::register-profile
{::rpc/auth false
::doc/added "1.15"}
::doc/added "1.15"
::sm/params schema:register-profile}
[{:keys [::db/pool] :as cfg} params]
(db/with-atomic [conn pool]
(-> (assoc cfg ::db/conn conn)
@@ -461,12 +453,15 @@
(create-recovery-token)
(send-email-notification conn))))))
(s/def ::request-profile-recovery
(s/keys :req-un [::email]))
(def schema:request-profile-recovery
[:map {:title "request-profile-recovery"}
[:email ::sm/email]])
(sv/defmethod ::request-profile-recovery
{::rpc/auth false
::doc/added "1.15"}
::doc/added "1.15"
::sm/params schema:request-profile-recovery}
[cfg params]
(request-profile-recovery cfg params))

View File

@@ -294,28 +294,40 @@
[output & {:keys [level] :or {level 0}}]
(ZstdOutputStream. ^OutputStream output (int level)))
(defn- retrieve-file
[pool file-id]
(dm/with-open [conn (db/open pool)]
(binding [pmap/*load-fn* (partial files/load-pointer conn file-id)]
(some-> (db/get* conn :file {:id file-id})
(files/decode-row)
(files/process-pointers deref)))))
(defn- get-files
[cfg ids]
(letfn [(get-files* [{:keys [::db/conn]}]
(let [sql (str "SELECT id FROM file "
" WHERE id = ANY(?) ")
ids (db/create-array conn "uuid" ids)]
(->> (db/exec! conn [sql ids])
(into [] (map :id))
(not-empty))))]
(def ^:private sql:file-media-objects
"SELECT * FROM file_media_object WHERE id = ANY(?)")
(db/run! cfg get-files*)))
(defn- retrieve-file-media
[pool {:keys [data id] :as file}]
(defn- get-file
[cfg file-id]
(letfn [(get-file* [{:keys [::db/conn]}]
(binding [pmap/*load-fn* (partial files/load-pointer conn file-id)]
(some-> (db/get* conn :file {:id file-id} {::db/remove-deleted? false})
(files/decode-row)
(files/process-pointers deref))))]
(db/run! cfg get-file*)))
(defn- get-file-media
[{:keys [::db/pool]} {:keys [data id] :as file}]
(dm/with-open [conn (db/open pool)]
(let [ids (app.tasks.file-gc/collect-used-media data)
ids (db/create-array conn "uuid" ids)]
ids (db/create-array conn "uuid" ids)
sql (str "SELECT * FROM file_media_object WHERE id = ANY(?)")]
;; We assoc the file-id again to the file-media-object row
;; because there are cases that used objects refer to other
;; files and we need to ensure in the exportation process that
;; all ids matches
(->> (db/exec! conn [sql:file-media-objects ids])
(->> (db/exec! conn [sql ids])
(mapv #(assoc % :file-id id))))))
(def ^:private storage-object-id-xf
@@ -325,34 +337,32 @@
(def ^:private sql:file-libraries
"WITH RECURSIVE libs AS (
SELECT fl.id, fl.deleted_at
SELECT fl.id
FROM file AS fl
JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id)
WHERE flr.file_id = ANY(?)
UNION
SELECT fl.id, fl.deleted_at
SELECT fl.id
FROM file AS fl
JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id)
JOIN libs AS l ON (flr.file_id = l.id)
)
SELECT DISTINCT l.id
FROM libs AS l
WHERE l.deleted_at IS NULL OR l.deleted_at > now();")
FROM libs AS l")
(defn- retrieve-libraries
[pool ids]
(defn- get-libraries
[{:keys [::db/pool]} ids]
(dm/with-open [conn (db/open pool)]
(let [ids (db/create-array conn "uuid" ids)]
(map :id (db/exec! pool [sql:file-libraries ids])))))
(def ^:private sql:file-library-rels
"SELECT * FROM file_library_rel
WHERE file_id = ANY(?)")
(defn- retrieve-library-relations
[pool ids]
(dm/with-open [conn (db/open pool)]
(db/exec! conn [sql:file-library-rels (db/create-array conn "uuid" ids)])))
(defn- get-library-relations
[cfg ids]
(db/run! cfg (fn [{:keys [::db/conn]}]
(let [ids (db/create-array conn "uuid" ids)
sql (str "SELECT flr.* FROM file_library_rel AS flr "
" WHERE flr.file_id = ANY(?)")]
(db/exec! conn [sql ids])))))
(defn- create-or-update-file
[conn params]
@@ -378,7 +388,7 @@
;; --- EXPORT WRITER
(defn- embed-file-assets
[data conn file-id]
[data cfg file-id]
(letfn [(walk-map-form [form state]
(cond
(uuid? (:fill-color-ref-file form))
@@ -408,7 +418,7 @@
;; NOTE: there is a possibility that shape refers to an
;; non-existant file because the file was removed. In this
;; case we just ignore the asset.
(if-let [lib (retrieve-file conn lib-id)]
(if-let [lib (get-file cfg lib-id)]
(reduce (partial process-asset lib) data items)
data))
@@ -476,31 +486,39 @@
[:v1/metadata :v1/files :v1/rels :v1/sobjects])))))
(defmethod write-section :v1/metadata
[{:keys [::db/pool ::output ::file-ids ::include-libraries?]}]
(let [libs (when include-libraries?
(retrieve-libraries pool file-ids))
files (into file-ids libs)]
(write-obj! output {:version cf/version :files files})
(vswap! *state* assoc :files files)))
[{:keys [::output ::file-ids ::include-libraries?] :as cfg}]
(if-let [fids (get-files cfg file-ids)]
(let [lids (when include-libraries?
(get-libraries cfg file-ids))
ids (into fids lids)]
(write-obj! output {:version cf/version :files ids})
(vswap! *state* assoc :files ids))
(ex/raise :type :not-found
:code :files-not-found
:hint "unable to retrieve files for export")))
(defmethod write-section :v1/files
[{:keys [::db/pool ::output ::embed-assets?]}]
[{:keys [::output ::embed-assets?] :as cfg}]
;; Initialize SIDS with empty vector
(vswap! *state* assoc :sids [])
(doseq [file-id (-> *state* deref :files)]
(let [file (cond-> (retrieve-file pool file-id)
(let [file (cond-> (get-file cfg file-id)
embed-assets?
(update :data embed-file-assets pool file-id))
(update :data embed-file-assets cfg file-id))
media (retrieve-file-media pool file)]
media (get-file-media cfg file)]
(l/debug :hint "write penpot file"
:id file-id
:name (:name file)
:media (count media)
::l/sync? true)
(doseq [item media]
(l/debug :hint "write penpot file media object" :id (:id item) ::l/sync? true))
(doto output
(write-obj! file)
(write-obj! media))
@@ -508,9 +526,10 @@
(vswap! *state* update :sids into storage-object-id-xf media))))
(defmethod write-section :v1/rels
[{:keys [::db/pool ::output ::include-libraries?]}]
(let [rels (when include-libraries?
(retrieve-library-relations pool (-> *state* deref :files)))]
[{:keys [::output ::include-libraries?] :as cfg}]
(let [ids (-> *state* deref :files)
rels (when include-libraries?
(get-library-relations cfg ids))]
(l/debug :hint "found rels" :total (count rels) ::l/sync? true)
(write-obj! output rels)))
@@ -518,6 +537,7 @@
[{:keys [::sto/storage ::output]}]
(let [sids (-> *state* deref :sids)
storage (media/configure-assets-storage storage)]
(l/debug :hint "found sobjects"
:items (count sids)
::l/sync? true)
@@ -592,7 +612,7 @@
(let [options (-> options
(assoc ::section section)
(assoc ::input input)
(assoc :conn conn))]
(assoc ::db/conn conn))]
(binding [*options* options]
(read-section options))))
[:v1/metadata :v1/files :v1/rels :v1/sobjects])
@@ -620,7 +640,7 @@
(update :components pmap-wrap))))
(defmethod read-section :v1/files
[{:keys [conn ::input ::migrate? ::project-id ::timestamp ::overwrite?]}]
[{:keys [::db/conn ::input ::migrate? ::project-id ::timestamp ::overwrite?]}]
(doseq [expected-file-id (-> *state* deref :files)]
(let [file (read-obj! input)
media' (read-obj! input)
@@ -630,6 +650,8 @@
(when (not= file-id expected-file-id)
(ex/raise :type :validation
:code :inconsistent-penpot-file
:found-id file-id
:expected-id expected-file-id
:hint "the penpot file seems corrupt, found unexpected uuid (file-id)"))
;; Update index using with media
@@ -678,22 +700,31 @@
(db/delete! conn :file-thumbnail {:file-id file-id'})))))))
(defmethod read-section :v1/rels
[{:keys [conn ::input ::timestamp]}]
(let [rels (read-obj! input)]
[{:keys [::db/conn ::input ::timestamp]}]
(let [rels (read-obj! input)
ids (into #{} (-> *state* deref :files))]
;; Insert all file relations
(doseq [rel rels]
(doseq [{:keys [library-file-id] :as rel} rels]
(let [rel (-> rel
(assoc :synced-at timestamp)
(update :file-id lookup-index)
(update :library-file-id lookup-index))]
(l/debug :hint "create file library link"
:file-id (:file-id rel)
:lib-id (:library-file-id rel)
::l/sync? true)
(db/insert! conn :file-library-rel rel)))))
(if (contains? ids library-file-id)
(do
(l/debug :hint "create file library link"
:file-id (:file-id rel)
:lib-id (:library-file-id rel)
::l/sync? true)
(db/insert! conn :file-library-rel rel))
(l/warn :hint "ignoring file library link"
:file-id (:file-id rel)
:lib-id (:library-file-id rel)
::l/sync? true))))))
(defmethod read-section :v1/sobjects
[{:keys [::sto/storage conn ::input ::overwrite?]}]
[{:keys [::sto/storage ::db/conn ::input ::overwrite?]}]
(let [storage (media/configure-assets-storage storage)
ids (read-obj! input)]
@@ -742,7 +773,7 @@
(defn- lookup-index
[id]
(let [val (get-in @*state* [:index id])]
(l/trace :fn "lookup-index" :id id :val val ::l/sync? true)
(l/debug :fn "lookup-index" :id id :val val ::l/sync? true)
(when (and (not (::ignore-index-errors? *options*)) (not val))
(ex/raise :type :validation
:code :incomplete-index
@@ -755,7 +786,7 @@
index index]
(if-let [id (first items)]
(let [new-id (if (::overwrite? *options*) id (uuid/next))]
(l/trace :fn "update-index" :id id :new-id new-id ::l/sync? true)
(l/debug :fn "update-index" :id id :new-id new-id ::l/sync? true)
(recur (rest items)
(assoc index id new-id)))
index)))
@@ -773,8 +804,7 @@
(update-in [:metadata :id] lookup-index)
;; Relink paths with fill image
(and (map? (:fill-image form))
(= :path (:type form)))
(map? (:fill-image form))
(update-in [:fill-image :id] lookup-index)
;; This covers old shapes and the new :fills.
@@ -929,5 +959,10 @@
::input (:path file)
::project-id project-id
::ignore-index-errors? true))]
(db/update! conn :project
{:modified-at (dt/now)}
{:id project-id})
(rph/with-meta ids
{::audit/props {:file nil :file-ids ids}}))))

View File

@@ -468,8 +468,8 @@
{::doc/added "1.15"}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id ::rpc/request-at id share-id content] :as params}]
(db/with-atomic [conn pool]
(let [{:keys [thread-id] :as comment} (get-comment conn id ::db/for-update? true)
{:keys [file-id page-id owner-id] :as thread} (get-comment-thread conn thread-id ::db/for-update? true)]
(let [{:keys [thread-id owner-id] :as comment} (get-comment conn id ::db/for-update? true)
{:keys [file-id page-id] :as thread} (get-comment-thread conn thread-id ::db/for-update? true)]
(files/check-comment-permissions! conn profile-id file-id share-id)

View File

@@ -38,6 +38,11 @@
;; --- FEATURES
(defn resolve-public-uri
[media-id]
(when media-id
(str (cf/get :public-uri) "/assets/by-id/" media-id)))
(def supported-features
#{"storage/objects-map"
"storage/pointer-map"
@@ -184,6 +189,8 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn check-features-compatibility!
"Function responsible to check if provided features are supported by
the current backend"
[features]
(let [not-supported (set/difference features supported-features)]
(when (seq not-supported)
@@ -243,50 +250,53 @@
(into #{} (comp (filter pmap/pointer-map?)
(map pmap/get-id)))))
;; FIXME: file locking
(defn- process-components-v2-feature
"A special case handling of the components/v2 feature."
[{:keys [features data] :as file}]
(let [data (ctf/migrate-to-components-v2 data)
features (conj features "components/v2")]
(-> file
(assoc ::pmg/migrated true)
(assoc :features features)
(assoc :data data))))
(defn handle-file-features!
[{:keys [features] :as file} client-features]
;; Check features compatibility between the currently supported features on
;; the current backend instance and the file retrieved from the database
(check-features-compatibility! features)
(cond-> file
(and (contains? features "components/v2")
(not (contains? client-features "components/v2")))
(as-> file (ex/raise :type :restriction
:code :feature-mismatch
:feature "components/v2"
:hint "file has 'components/v2' feature enabled but frontend didn't specifies it"
:file-id (:id file)))
;; This operation is needed because the components migration generates a new
;; page with random id which is returned to the client; without persisting
;; the migration this can cause that two simultaneous clients can have a
;; different view of the file data and end persisting two pages with main
;; components and breaking the whole file."
(and (contains? client-features "components/v2")
(not (contains? features "components/v2")))
(as-> file (process-components-v2-feature file))
;; This operation is needed for backward comapatibility with frontends that
;; does not support pointer-map resolution mechanism; this just resolves the
;; pointers on backend and return a complete file.
(and (contains? features "storage/pointer-map")
(not (contains? client-features "storage/pointer-map")))
(process-pointers deref)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; QUERY COMMANDS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn handle-file-features!
[conn {:keys [id features data] :as file} client-features]
(when (and (contains? features "components/v2")
(not (contains? client-features "components/v2")))
(ex/raise :type :restriction
:code :feature-mismatch
:feature "components/v2"
:hint "file has 'components/v2' feature enabled but frontend didn't specifies it"))
;; NOTE: this operation is needed because the components migration
;; generates a new page with random id which is returned to the
;; client; without persisting the migration this can cause that two
;; simultaneous clients can have a different view of the file data
;; and end persisting two pages with main components and breaking
;; the whole file
(let [file (if (and (contains? client-features "components/v2")
(not (contains? features "components/v2")))
(binding [pmap/*tracked* (atom {})]
(let [data (ctf/migrate-to-components-v2 data)
features (conj features "components/v2")
modified-at (dt/now)
features' (db/create-array conn "text" features)]
(db/update! conn :file
{:data (blob/encode data)
:modified-at modified-at
:features features'}
{:id id})
(persist-pointers! conn id)
(-> file
(assoc :modified-at modified-at)
(assoc :features features)
(assoc :data data))))
file)]
(cond-> file
(and (contains? features "storage/pointer-map")
(not (contains? client-features "storage/pointer-map")))
(process-pointers deref))))
;; --- COMMAND QUERY: get-file (by id)
(sm/def! ::features
@@ -329,32 +339,41 @@
([conn id client-features]
(get-file conn id client-features nil))
([conn id client-features project-id]
;; here we check if client requested features are supported
;; here we check if client requested features are supported
(check-features-compatibility! client-features)
(binding [pmap/*load-fn* (partial load-pointer conn id)]
(binding [pmap/*load-fn* (partial load-pointer conn id)
pmap/*tracked* (atom {})]
(let [params (merge {:id id}
(when (some? project-id)
{:project-id project-id}))
(when (some? project-id)
{:project-id project-id}))
file (-> (db/get conn :file params)
(decode-row)
(pmg/migrate-file))
file (handle-file-features! conn file client-features)]
file (handle-file-features! file client-features)]
;; NOTE: if migrations are applied, probably new pointers generated so
;; instead of persiting them on each get-file, we just resolve them until
;; user updates the file and permanently persists the new pointers
(cond-> file
(pmg/migrated? file)
(process-pointers deref))))))
;; NOTE: when file is migrated, we break the rule of no perform
;; mutations on get operations and update the file with all
;; migrations applied
(when (pmg/migrated? file)
(let [features (db/create-array conn "text" (:features file))]
(db/update! conn :file
{:data (blob/encode (:data file))
:features features}
{:id id})
(persist-pointers! conn id)))
file))))
(defn get-minimal-file
[{:keys [::db/pool] :as cfg} id]
(db/get pool :file {:id id} {:columns [:id :modified-at :revn]}))
(defn get-file-etag
[{:keys [modified-at revn]}]
(str (dt/format-instant modified-at :iso) "-" revn))
[{:keys [::rpc/profile-id]} {:keys [modified-at revn]}]
(str profile-id (dt/format-instant modified-at :iso) revn))
(sv/defmethod ::get-file
"Retrieve a file by its ID. Only authenticated users."
@@ -364,12 +383,12 @@
::sm/params ::get-file
::sm/result ::file-with-permissions}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id features project-id] :as params}]
(dm/with-open [conn (db/open pool)]
(db/with-atomic [conn pool]
(let [perms (get-permissions conn profile-id id)]
(check-read-permissions! perms)
(let [file (-> (get-file conn id features project-id)
(assoc :permissions perms))]
(vary-meta file assoc ::cond/key (get-file-etag file))))))
(vary-meta file assoc ::cond/key (get-file-etag params file))))))
;; --- COMMAND QUERY: get-file-fragment (by id)
@@ -413,15 +432,23 @@
f.modified_at,
f.name,
f.revn,
f.is_shared
f.is_shared,
ft.media_id
from file as f
left join file_thumbnail as ft on (ft.file_id = f.id and ft.revn = f.revn)
where f.project_id = ?
and f.deleted_at is null
order by f.modified_at desc")
(defn get-project-files
[conn project-id]
(db/exec! conn [sql:project-files project-id]))
(->> (db/exec! conn [sql:project-files project-id])
(mapv (fn [row]
(if-let [media-id (:media-id row)]
(-> row
(dissoc :media-id)
(assoc :thumbnail-uri (resolve-public-uri media-id)))
(dissoc row :media-id))))))
(sv/defmethod ::get-project-files
"Get all files for the specified project."
@@ -471,7 +498,8 @@
other not needed objects removed from the `:objects` data
structure."
[{:keys [objects] :as page} object-id]
(let [objects (cph/get-children-with-self objects object-id)]
(let [objects (->> (cph/get-children-with-self objects object-id)
(filter some?))]
(assoc page :objects (d/index-by :id objects))))
(defn- prune-thumbnails
@@ -502,6 +530,7 @@
[:map {:title "GetPage"}
[:file-id ::sm/uuid]
[:page-id {:optional true} ::sm/uuid]
[:share-id {:optional true} ::sm/uuid]
[:object-id {:optional true} ::sm/uuid]
[:features {:optional true} ::features]])
@@ -517,14 +546,12 @@
Mainly used for rendering purposes."
{::doc/added "1.17"
::sm/params ::get-page}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id share-id] :as params}]
(dm/with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id file-id)
(binding [pmap/*load-fn* (partial load-pointer conn file-id)]
(get-page conn params))))
(let [perms (get-permissions conn profile-id file-id share-id)]
(check-read-permissions! perms)
(binding [pmap/*load-fn* (partial load-pointer conn file-id)]
(get-page conn params)))))
;; --- COMMAND QUERY: get-team-shared-files
@@ -536,9 +563,11 @@
f.created_at,
f.modified_at,
f.name,
f.is_shared
f.is_shared,
ft.media_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)
where f.is_shared = true
and f.deleted_at is null
and p.deleted_at is null
@@ -569,6 +598,12 @@
(->> (db/exec! conn [sql:team-shared-files team-id])
(into #{} (comp
(map decode-row)
(map (fn [row]
(if-let [media-id (:media-id row)]
(-> row
(dissoc :media-id)
(assoc :thumbnail-uri (resolve-public-uri media-id)))
(dissoc row :media-id))))
(map #(assoc % :library-summary (library-summary %)))
(map #(dissoc % :data)))))))
@@ -668,9 +703,11 @@
f.modified_at,
f.name,
f.is_shared,
ft.media_id,
row_number() over w as row_num
from file as f
join project as p on (p.id = f.project_id)
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)
where p.team_id = ?
and p.deleted_at is null
and f.deleted_at is null
@@ -681,7 +718,13 @@
(defn get-team-recent-files
[conn team-id]
(db/exec! conn [sql:team-recent-files team-id]))
(->> (db/exec! conn [sql:team-recent-files team-id])
(mapv (fn [row]
(if-let [media-id (:media-id row)]
(-> row
(dissoc :media-id)
(assoc :thumbnail-uri (resolve-public-uri media-id)))
(dissoc row :media-id))))))
(s/def ::get-team-recent-files
(s/keys :req [::rpc/profile-id]

View File

@@ -86,16 +86,16 @@
(ex/raise :type :validation
:code :cant-persist-already-persisted-file))
(loop [revs (seq revs)
data (blob/decode (:data file))]
(if-let [rev (first revs)]
(recur (rest revs)
(->> rev :changes blob/decode (cp/process-changes data)))
(db/update! conn :file
{:deleted-at nil
:revn revn
:data (blob/encode data)}
{:id id})))
(let [data
(->> revs
(mapcat #(->> % :changes blob/decode))
(cp/process-changes (blob/decode (:data file))))]
(db/update! conn :file
{:deleted-at nil
:revn revn
:data (blob/encode data)}
{:id id}))
nil))
(s/def ::persist-temp-file

View File

@@ -14,7 +14,6 @@
[app.common.schema :as sm]
[app.common.spec :as us]
[app.common.types.shape-tree :as ctt]
[app.config :as cf]
[app.db :as db]
[app.db.sql :as sql]
[app.loggers.audit :as-alias audit]
@@ -39,10 +38,6 @@
;; --- COMMAND QUERY: get-file-object-thumbnails
(defn- get-public-uri
[media-id]
(str (cf/get :public-uri) "/assets/by-id/" media-id))
(defn- get-object-thumbnails
([conn file-id]
(let [sql (str/concat
@@ -52,7 +47,7 @@
res (db/exec! conn [sql file-id])]
(->> res
(d/index-by :object-id (fn [row]
(or (some-> row :media-id get-public-uri)
(or (some-> row :media-id files/resolve-public-uri)
(:data row))))
(d/without-nils))))
@@ -65,13 +60,14 @@
res (db/exec! conn [sql file-id ids])]
(d/index-by :object-id
(fn [row]
(or (some-> row :media-id get-public-uri)
(or (some-> row :media-id files/resolve-public-uri)
(:data row)))
res))))
(sv/defmethod ::get-file-object-thumbnails
"Retrieve a file object thumbnails."
{::doc/added "1.17"
::doc/module :files
::sm/params [:map {:title "get-file-object-thumbnails"}
[:file-id ::sm/uuid]]
::sm/result [:map-of :string :string]
@@ -85,8 +81,6 @@
;; --- COMMAND QUERY: get-file-thumbnail
;; FIXME: refactor to support uploading data to storage
(defn get-file-thumbnail
[conn file-id revn]
(let [sql (sql/select :file-thumbnail
@@ -95,10 +89,15 @@
{:limit 1
:order-by [[:revn :desc]]})
row (db/exec-one! conn sql)]
(when-not row
(ex/raise :type :not-found
:code :file-thumbnail-not-found))
(when-not (:data row)
(ex/raise :type :not-found
:code :file-thumbnail-not-found))
{:data (:data row)
:props (some-> (:props row) db/decode-transit-pgobject)
:revn (:revn row)
@@ -113,20 +112,17 @@
:opt-un [::revn]))
(sv/defmethod ::get-file-thumbnail
"Method used in frontend for obtain the file thumbnail (used in the
dashboard)."
{::doc/added "1.17"}
{::doc/added "1.17"
::doc/module :files
::doc/deprecated "1.19"}
[{:keys [::db/pool]} {:keys [::rpc/profile-id file-id revn]}]
(dm/with-open [conn (db/open pool)]
(files/check-read-permissions! conn profile-id file-id)
(-> (get-file-thumbnail conn file-id revn)
(rph/with-http-cache long-cache-duration))))
;; --- COMMAND QUERY: get-file-data-for-thumbnail
;; FIXME: performance issue, handle new media_id
;;
;; We need to improve how we set frame for thumbnail in order to avoid
;; loading all pages into memory for find the frame set for thumbnail.
@@ -226,6 +222,7 @@
mainly for render thumbnails on dashboard."
{::doc/added "1.17"
::doc/module :files
::sm/params [:map {:title "get-file-data-for-thumbnail"}
[:file-id ::sm/uuid]
[:features {:optional true} ::files/features]]
@@ -273,6 +270,7 @@
(sv/defmethod ::upsert-file-object-thumbnail
{::doc/added "1.17"
::doc/module :files
::doc/deprecated "1.19"
::audit/skip true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
@@ -310,14 +308,18 @@
(:id media) (:id media)])))
(s/def ::media (s/nilable ::media/upload))
(s/def ::create-file-object-thumbnail
(s/keys :req [::rpc/profile-id]
:req-un [::file-id ::object-id ::media]))
(def schema:create-file-object-thumbnail
[:map {:title "create-file-object-thumbnail"}
[:file-id ::sm/uuid]
[:object-id :string]
[:media ::media/upload]])
(sv/defmethod ::create-file-object-thumbnail
{:doc/added "1.19"
::audit/skip true}
::doc/module :files
::audit/skip true
::sm/params schema:create-file-object-thumbnail}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id object-id media]}]
(db/with-atomic [conn pool]
(files/check-edition-permissions! conn profile-id file-id)
@@ -353,6 +355,7 @@
(sv/defmethod ::delete-file-object-thumbnail
{:doc/added "1.19"
::doc/module :files
::audit/skip true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id object-id]}]
@@ -380,7 +383,6 @@
(db/exec-one! conn [sql:upsert-file-thumbnail
file-id revn data props data props])))
(s/def ::revn ::us/integer)
(s/def ::props map?)
@@ -392,6 +394,7 @@
"Creates or updates the file thumbnail. Mainly used for paint the
grid thumbnails."
{::doc/added "1.17"
::doc/module :files
::doc/deprecated "1.19"
::audit/skip true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
@@ -427,24 +430,28 @@
:bucket "file-thumbnail"})]
(db/exec-one! conn [sql:create-file-thumbnail file-id revn
(:id media) props
(:id media) props])))
(s/def ::media ::media/upload)
(s/def ::create-file-thumbnail
(s/keys :req [::rpc/profile-id]
:req-un [::file-id ::revn ::props ::media]))
(:id media) props])
media))
(sv/defmethod ::create-file-thumbnail
"Creates or updates the file thumbnail. Mainly used for paint the
grid thumbnails."
{::doc/added "1.19"
::audit/skip true}
::doc/module :files
::audit/skip true
::sm/params [:map {:title "create-file-thumbnail"}
[:file-id ::sm/uuid]
[:revn :int]
[:media ::media/upload]]
}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(files/check-edition-permissions! conn profile-id file-id)
(when-not (db/read-only? conn)
(-> cfg
(update ::sto/storage media/configure-assets-storage)
(assoc ::db/conn conn)
(create-file-thumbnail! params))
nil)))
(let [media (-> cfg
(update ::sto/storage media/configure-assets-storage)
(assoc ::db/conn conn)
(create-file-thumbnail! params))]
{:uri (files/resolve-public-uri (:id media))}))))

View File

@@ -36,6 +36,7 @@
(s/def ::id ::us/uuid)
(s/def ::name ::us/not-empty-string)
(s/def ::project-id ::us/uuid)
(s/def ::share-id ::us/uuid)
(s/def ::style valid-style)
(s/def ::team-id ::us/uuid)
(s/def ::weight valid-weight)
@@ -47,7 +48,8 @@
(s/keys :req [::rpc/profile-id]
:opt-un [::team-id
::file-id
::project-id])
::project-id
::share-id])
(fn [o]
(or (contains? o :team-id)
(contains? o :file-id)
@@ -55,7 +57,7 @@
(sv/defmethod ::get-font-variants
{::doc/added "1.18"}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id file-id project-id] :as params}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id file-id project-id share-id] :as params}]
(dm/with-open [conn (db/open pool)]
(cond
(uuid? team-id)
@@ -74,11 +76,12 @@
(uuid? file-id)
(let [file (db/get-by-id conn :file file-id {:columns [:id :project-id]})
project (db/get-by-id conn :project (:project-id file) {:columns [:id :team-id]})]
(files/check-read-permissions! conn profile-id file-id)
project (db/get-by-id conn :project (:project-id file) {:columns [:id :team-id]})
perms (files/get-permissions conn profile-id file-id share-id)]
(files/check-read-permissions! perms)
(db/query conn :team-font-variant
{:team-id (:team-id project)
:deleted-at nil})))))
{:team-id (:team-id project)
:deleted-at nil})))))
(declare create-font-variant)

View File

@@ -38,7 +38,8 @@
"Performs the authentication using LDAP backend. Only works if LDAP
is properly configured and enabled with `login-with-ldap` flag."
{::rpc/auth false
::doc/added "1.15"}
::doc/added "1.15"
::doc/module :auth}
[{:keys [::main/props ::ldap/provider] :as cfg} params]
(when-not provider
(ex/raise :type :restriction

View File

@@ -10,6 +10,7 @@
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.pages.migrations :as pmg]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.db :as db]
@@ -20,12 +21,15 @@
[app.rpc.commands.projects :as proj]
[app.rpc.commands.teams :as teams :refer [create-project-role create-project]]
[app.rpc.doc :as-alias doc]
[app.setup :as-alias setup]
[app.setup.templates :as tmpl]
[app.util.blob :as blob]
[app.util.pointer-map :as pmap]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
[clojure.walk :as walk]))
[clojure.walk :as walk]
[promesa.exec :as px]))
;; --- COMMAND: Duplicate File
@@ -233,7 +237,7 @@
(let [project (-> (db/get-by-id conn :project project-id)
(assoc :is-pinned false))
files (db/query conn :file
{:project-id (:id project)
:deleted-at nil}
@@ -319,6 +323,18 @@
;; delete possible broken relations on moved files
(db/exec-one! conn [sql:delete-broken-relations pids])
;; Update the modification date of the all affected projects
;; ensuring that the destination project is the most recent one.
(doseq [project-id (into (list project-id) source)]
;; NOTE: as this is executed on virtual thread, sleeping does
;; not causes major issues, and allows an easy way to set a
;; trully different modification date to each file.
(px/sleep 10)
(db/update! conn :project
{:modified-at (dt/now)}
{:id project-id}))
nil))
(s/def ::ids (s/every ::us/uuid :kind set?))
@@ -361,7 +377,6 @@
nil))
(s/def ::move-project
(s/keys :req [::rpc/profile-id]
:req-un [::team-id ::project-id]))
@@ -376,46 +391,54 @@
;; --- COMMAND: Clone Template
(declare clone-template)
(s/def ::template-id ::us/not-empty-string)
(s/def ::clone-template
(s/keys :req [::rpc/profile-id]
:req-un [::project-id ::template-id]))
(sv/defmethod ::clone-template
"Clone into the specified project the template by its id."
{::doc/added "1.16"
::webhooks/event? true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
(db/with-atomic [conn pool]
(-> (assoc cfg :conn conn)
(clone-template (assoc params :profile-id profile-id)))))
(defn- clone-template
[{:keys [conn templates] :as cfg} {:keys [profile-id template-id project-id]}]
(let [template (d/seek #(= (:id %) template-id) templates)
(defn- clone-template!
[{:keys [::db/conn] :as cfg} {:keys [profile-id template-id project-id]}]
(let [template (tmpl/get-template-stream cfg template-id)
project (db/get-by-id conn :project project-id {:columns [:id :team-id]})]
(teams/check-edition-permissions! conn profile-id (:team-id project))
(when-not template
(ex/raise :type :not-found
:code :template-not-found
:hint "template not found"))
(teams/check-edition-permissions! conn profile-id (:team-id project))
(-> cfg
(assoc ::binfile/input (:path template))
;; FIXME: maybe reuse the conn instead of creating more
;; connections in the import process?
(dissoc ::db/conn)
(assoc ::binfile/input template)
(assoc ::binfile/project-id (:id project))
(assoc ::binfile/ignore-index-errors? true)
(assoc ::binfile/migrate? true)
(binfile/import!))))
(def schema:clone-template
[:map {:title "clone-template"}
[:project-id ::sm/uuid]
[:template-id ::sm/word-string]])
;; --- COMMAND: Retrieve list of builtin templates
(sv/defmethod ::clone-template
"Clone into the specified project the template by its id."
{::doc/added "1.16"
::webhooks/event? true
::sm/params schema:clone-template}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
(db/with-atomic [conn pool]
(-> (assoc cfg ::db/conn conn)
(clone-template! (assoc params :profile-id profile-id)))))
;; --- COMMAND: Get list of builtin templates
(s/def ::retrieve-list-of-builtin-templates any?)
(sv/defmethod ::retrieve-list-of-builtin-templates
{::doc/added "1.10"
::doc/deprecated "1.19"}
[cfg _params]
(mapv #(select-keys % [:id :name :thumbnail-uri]) (:templates cfg)))
(mapv #(select-keys % [:id :name]) (::setup/templates cfg)))
(sv/defmethod ::get-builtin-templates
{::doc/added "1.19"}
[cfg _params]
(mapv #(select-keys % [:id :name]) (::setup/templates cfg)))

View File

@@ -171,7 +171,8 @@
:opt-un [::id ::name]))
(sv/defmethod ::create-file-media-object-from-url
{::doc/added "1.17"}
{::doc/added "1.17"
::doc/deprecated "1.19"}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(let [cfg (update cfg ::sto/storage media/configure-assets-storage)]
(files/check-edition-permissions! pool profile-id file-id)

View File

@@ -27,7 +27,6 @@
[app.tokens :as tokens]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]))
(declare check-profile-existence!)
@@ -41,7 +40,7 @@
(def schema:profile
[:map {:title "Profile"}
[:id ::sm/uuid]
[:fullname :string]
[:fullname [::sm/word-string {:max 250}]]
[:email ::sm/email]
[:is-active {:optional true} :boolean]
[:is-blocked {:optional true} :boolean]
@@ -82,12 +81,15 @@
;; --- MUTATION: Update Profile (own)
(def schema:update-profile
[:map {:title "update-profile"}
[:fullname [::sm/word-string {:max 250}]]
[:lang {:optional true} [:string {:max 5}]]
[:theme {:optional true} [:string {:max 250}]]])
(sv/defmethod ::update-profile
{::doc/added "1.0"
::sm/params [:map {:title "UpdateProfileParams"}
[:fullname {:min 1} :string]
[:lang {:optional true} :string]
[:theme {:optional true} :string]]
::sm/params schema:update-profile
::sm/result schema:profile}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id fullname lang theme] :as params}]
@@ -128,11 +130,15 @@
(declare update-profile-password!)
(declare invalidate-profile-session!)
(def schema:update-profile-password
[:map {:title "update-profile-password"}
[:password [::sm/word-string {:max 500}]]
;; Social registered users don't have old-password
[:old-password {:optional true} [:maybe [::sm/word-string {:max 500}]]]])
(sv/defmethod ::update-profile-password
{:doc/added "1.0"
::sm/params [:map {:title "UpdateProfilePasswordParams"}
[:password :string]
[:old-password :string]]
::sm/params schema:update-profile-password
::sm/result :nil}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id password] :as params}]
@@ -178,10 +184,13 @@
(declare upload-photo)
(declare update-profile-photo)
(def schema:update-profile-photo
[:map {:title "update-profile-photo"}
[:file ::media/upload]])
(sv/defmethod ::update-profile-photo
{:doc/added "1.1"
::sm/params [:map {:title "UpdateProfilePhotoParams"}
[:file ::media/upload]]
::sm/params schema:update-profile-photo
::sm/result :nil}
[cfg {:keys [::rpc/profile-id file] :as params}]
;; Validate incoming mime type
@@ -239,11 +248,13 @@
(declare ^:private request-email-change!)
(declare ^:private change-email-immediately!)
(s/def ::request-email-change
(s/keys :req [::rpc/profile-id]
:req-un [::email]))
(def schema:request-email-change
[:map {:title "request-email-change"}
[:email ::sm/email]])
(sv/defmethod ::request-email-change
{::doc/added "1.0"
::sm/params schema:request-email-change}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id email] :as params}]
(db/with-atomic [conn pool]
(let [profile (db/get-by-id conn :profile profile-id)
@@ -304,12 +315,13 @@
;; --- MUTATION: Update Profile Props
(s/def ::props map?)
(s/def ::update-profile-props
(s/keys :req [::rpc/profile-id]
:req-un [::props]))
(def schema:update-profile-props
[:map {:title "update-profile-props"}
[:props [:map-of :keyword :any]]])
(sv/defmethod ::update-profile-props
{::doc/added "1.0"
::sm/params schema:update-profile-props}
[{:keys [::db/pool]} {:keys [::rpc/profile-id props]}]
(db/with-atomic [conn pool]
(let [profile (get-profile conn profile-id ::db/for-update? true)
@@ -329,15 +341,12 @@
(filter-props props))))
;; --- MUTATION: Delete Profile
(declare ^:private get-owned-teams-with-participants)
(s/def ::delete-profile
(s/keys :req [::rpc/profile-id]))
(sv/defmethod ::delete-profile
{::doc/added "1.0"}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
(db/with-atomic [conn pool]
(let [teams (get-owned-teams-with-participants conn profile-id)

View File

@@ -64,6 +64,7 @@
:opt-un [::search-term]))
(sv/defmethod ::search-files
{::doc/added "1.17"}
{::doc/added "1.17"
::doc/module :files}
[{:keys [::db/pool]} {:keys [::rpc/profile-id team-id search-term]}]
(some->> search-term (search-files pool profile-id team-id)))

View File

@@ -10,6 +10,7 @@
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -719,29 +720,22 @@
itoken))))
(s/def ::email ::us/email)
(s/def ::emails ::us/set-of-valid-emails)
(s/def ::create-team-invitations
(s/keys :req [::rpc/profile-id]
:req-un [::team-id ::role]
:opt-un [::email ::emails]))
(def ^:private schema:create-team-invitations
[:map {:title "create-team-invitations"}
[:team-id ::sm/uuid]
[:role [::sm/one-of #{:owner :admin :editor}]]
[:emails ::sm/set-of-emails]])
(sv/defmethod ::create-team-invitations
"A rpc call that allow to send a single or multiple invitations to
join the team."
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id email emails role] :as params}]
{::doc/added "1.17"
::sm/params schema:create-team-invitations}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id emails role] :as params}]
(db/with-atomic [conn pool]
(let [perms (get-permissions conn profile-id team-id)
profile (db/get-by-id conn :profile profile-id)
team (db/get-by-id conn :team team-id)
;; Members emails. We don't re-send inviation to already existing members
member? (into #{}
(map :email)
(db/exec! conn [sql:team-members team-id]))
emails (cond-> (or emails #{}) (string? email) (conj email))]
team (db/get-by-id conn :team team-id)]
(run! (partial quotes/check-quote! conn)
(list {::quotes/id ::quotes/invitations-per-team
@@ -764,9 +758,13 @@
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
(let [cfg (assoc cfg ::db/conn conn)
invitations (into []
members (->> (db/exec! conn [sql:team-members team-id])
(into #{} (map :email)))
invitations (into #{}
(comp
(remove member?)
;; We don't re-send inviation to already existing members
(remove (partial contains? members))
(map (fn [email]
{:email (str/lower email)
:team team
@@ -774,7 +772,8 @@
:role role}))
(keep (partial create-invitation cfg)))
emails)]
(with-meta invitations
(with-meta {:total (count invitations)
:invitations invitations}
{::audit/props {:invitations (count invitations)}})))))

View File

@@ -34,7 +34,8 @@
(sv/defmethod ::verify-token
{::rpc/auth false
::doc/added "1.15"}
::doc/added "1.15"
::doc/module :auth}
[{:keys [::db/pool] :as cfg} {:keys [token] :as params}]
(db/with-atomic [conn pool]
(let [claims (tokens/verify (::main/props cfg) {:token token})

View File

@@ -87,9 +87,6 @@
(sv/defmethod ::get-view-only-bundle
{::rpc/auth false
::cond/get-object #(files/get-minimal-file %1 (:file-id %2))
::cond/key-fn files/get-file-etag
::cond/reuse-key? true
::doc/added "1.17"
::sm/params ::get-view-only-bundle}
[{:keys [::db/pool]} {:keys [::rpc/profile-id] :as params}]

View File

@@ -27,6 +27,8 @@
[app.common.logging :as l]
[app.rpc.helpers :as rph]
[app.util.services :as-alias sv]
[buddy.core.codecs :as bc]
[buddy.core.hash :as bh]
[yetti.response :as yrs]))
(def
@@ -34,9 +36,16 @@
:doc "Runtime flag for enable/disable conditional processing of RPC methods."}
*enabled* false)
(defn- encode
[s]
(-> s
bh/blake2b-256
bc/bytes->b64u
bc/bytes->str))
(defn- fmt-key
[s]
(str "W/\"" s "\""))
(str "W/\"" (encode s) "\""))
(defn wrap
[_ f {:keys [::get-object ::key-fn ::reuse-key?] :as mdata}]
@@ -46,9 +55,8 @@
(fn [cfg {:keys [::key] :as params}]
(if *enabled*
(let [key' (when (or key reuse-key?)
(some-> (get-object cfg params) key-fn fmt-key))]
(if (and (some? key)
(= key key'))
(some->> (get-object cfg params) (key-fn params) (fmt-key)))]
(if (and (some? key) (= key key'))
(fn [_] {::yrs/status 304})
(let [result (f cfg params)
etag (or (and reuse-key? key')

View File

@@ -54,14 +54,14 @@
{:name (::sv/name mdata)
:module (or (some-> (::module mdata) d/name)
(-> (:ns mdata) (str/split ".") last))
:auth (:auth mdata true)
:auth (::rpc/auth mdata true)
:webhook (::webhooks/event? mdata false)
:docs (::sv/docstring mdata)
:deprecated (::deprecated mdata)
:added (::added mdata)
:changes (some->> (::changes mdata) (partition-all 2) (map vec))
:spec (fmt-spec mdata)
:entrypoint (str (cf/get :public-uri) "/api/rpc/commands/" (::sv/name mdata))
:entrypoint (str (cf/get :public-uri) "/api/rpc/command/" (::sv/name mdata))
:params-schema-js (fmt-schema :js mdata ::sm/params)
:result-schema-js (fmt-schema :js mdata ::sm/result)
@@ -75,6 +75,7 @@
(->> methods
(map val)
(map first)
(remove ::skip)
(map get-context)
(sort-by (juxt :module :name)))}))
@@ -155,7 +156,7 @@
(map (partial gen-method-doc options))
(sort-by (juxt :module :name))
(map (fn [doc]
[(str/ffmt "/commands/%" (:name doc)) (:repr doc)]))
[(str/ffmt "/command/%" (:name doc)) (:repr doc)]))
(into {})))]
{:openapi "3.0.0"
:info {:version (:main cf/version)}

View File

@@ -12,8 +12,8 @@
[app.common.uuid :as uuid]
[app.db :as db]
[app.main :as-alias main]
[app.setup.builtin-templates]
[app.setup.keys :as keys]
[app.setup.templates]
[buddy.core.codecs :as bc]
[buddy.core.nonce :as bn]
[clojure.spec.alpha :as s]

View File

@@ -1,72 +0,0 @@
;; 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.setup.builtin-templates
"A service/module that is responsible for download, load & internally
expose a set of builtin penpot file templates."
(:require
[app.common.logging :as l]
[app.common.spec :as us]
[app.http.client :as http]
[clojure.edn :as edn]
[clojure.java.io :as io]
[clojure.spec.alpha :as s]
[datoteka.fs :as fs]
[integrant.core :as ig]))
(declare download-all!)
(s/def ::id ::us/not-empty-string)
(s/def ::name ::us/not-empty-string)
(s/def ::thumbnail-uri ::us/not-empty-string)
(s/def ::file-uri ::us/not-empty-string)
(s/def ::path fs/path?)
(s/def ::template
(s/keys :req-un [::id ::name ::thumbnail-uri ::file-uri]
:opt-un [::path]))
(defmethod ig/pre-init-spec :app.setup/builtin-templates [_]
(s/keys :req [::http/client]))
(defmethod ig/init-key :app.setup/builtin-templates
[_ cfg]
(let [presets (-> "app/onboarding.edn" io/resource slurp edn/read-string)]
(l/info :hint "loading template files" :total (count presets))
(let [result (download-all! cfg presets)]
(us/conform (s/coll-of ::template) result))))
(defn- download-preset!
[cfg {:keys [path file-uri] :as preset}]
(let [response (http/req! cfg
{:method :get
:uri file-uri}
{:response-type :input-stream
:sync? true})]
(us/verify! (= 200 (:status response)) "unexpected response found on fetching preset")
(with-open [output (io/output-stream path)]
(with-open [input (io/input-stream (:body response))]
(io/copy input output)))))
(defn- download-all!
"Download presets to the default directory, if preset is already
downloaded, no action will be performed."
[cfg presets]
(let [dest (fs/join fs/*cwd* "builtin-templates")]
(when-not (fs/exists? dest)
(fs/create-dir dest))
(doall
(map (fn [item]
(let [path (fs/join dest (:id item))
item (assoc item :path path)]
(if (fs/exists? path)
(l/trace :hint "template file already present" :id (:id item))
(do
(l/trace :hint "downloading template file" :id (:id item) :dest (str path))
(download-preset! cfg item)))
item))
presets))))

View File

@@ -0,0 +1,64 @@
;; 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.setup.templates
"A service/module that is responsible for download, load & internally
expose a set of builtin penpot file templates."
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.http.client :as http]
[app.setup :as-alias setup]
[clojure.edn :as edn]
[clojure.java.io :as io]
[datoteka.fs :as fs]
[integrant.core :as ig]))
(def ^:private schema:template
[:map {:title "Template"}
[:id ::sm/word-string]
[:name ::sm/word-string]
[:file-uri ::sm/word-string]])
(def ^:private schema:templates
[:vector schema:template])
(defmethod ig/init-key ::setup/templates
[_ _]
(let [templates (-> "app/onboarding.edn" io/resource slurp edn/read-string)
dest (fs/join fs/*cwd* "builtin-templates")]
(dm/verify!
"expected a valid templates file"
(sm/valid? schema:templates templates))
(doseq [{:keys [id path] :as template} templates]
(let [path (or path (fs/join dest id))]
(if (fs/exists? path)
(l/debug :hint "template file" :id id :state "present" :path (dm/str path))
(l/debug :hint "template file" :id id :state "absent"))))
templates))
(defn get-template-stream
[cfg template-id]
(when-let [template (d/seek #(= (:id %) template-id)
(::setup/templates cfg))]
(let [dest (fs/join fs/*cwd* "builtin-templates")
path (or (:path template) (fs/join dest template-id))]
(if (fs/exists? path)
(io/input-stream path)
(let [resp (http/req! cfg
{:method :get :uri (:file-uri template)}
{:response-type :input-stream :sync? true})]
(dm/verify!
"unexpected response found on fetching template"
(= 200 (:status resp)))
(io/input-stream (:body resp)))))))

View File

@@ -8,10 +8,15 @@
"A collection of adhoc fixes scripts."
#_:clj-kondo/ignore
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.logging :as l]
[app.common.pprint :as p]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.msgbus :as mbus]
[app.rpc.commands.auth :as auth]
[app.rpc.commands.profile :as profile]
[app.srepl.fixes :as f]
@@ -164,3 +169,106 @@
(alter-var-root var (fn [f]
(or (::original (meta f)) f))))
(defn notify!
[{:keys [::mbus/msgbus ::db/pool]} & {:keys [dest code message level]
:or {code :generic level :info}
:as params}]
(dm/verify!
["invalid level %" level]
(contains? #{:success :error :info :warning} level))
(dm/verify!
["invalid code: %" code]
(contains? #{:generic :upgrade-version} code))
(letfn [(send [dest]
(l/inf :hint "sending notification" :dest (str dest))
(let [message {:type :notification
:code code
:level level
:version (:full cf/version)
:subs-id dest
:message message}
message (->> (dissoc params :dest :code :message :level)
(merge message))]
(mbus/pub! msgbus
:topic (str dest)
:message message)))
(resolve-profile [email]
(some-> (db/get* pool :profile {:email (str/lower email)} {:columns [:id]}) :id vector))
(resolve-team [team-id]
(->> (db/query pool :team-profile-rel
{:team-id team-id}
{:columns [:profile-id]})
(map :profile-id)))
(parse-uuid [v]
(if (uuid? v)
v
(d/parse-uuid v)))
(resolve-dest [dest]
(cond
(uuid? dest)
[dest]
(string? dest)
(some-> dest parse-uuid resolve-dest)
(nil? dest)
(resolve-dest uuid/zero)
(map? dest)
(sequence (comp
(map vec)
(mapcat resolve-dest))
dest)
(and (coll? dest)
(every? coll? dest))
(sequence (comp
(map vec)
(mapcat resolve-dest))
dest)
(vector? dest)
(let [[op param] dest]
(cond
(= op :email)
(cond
(and (coll? param)
(every? string? param))
(sequence (comp
(keep resolve-profile)
(mapcat identity))
param)
(string? param)
(resolve-profile param))
(= op :team-id)
(cond
(coll? param)
(sequence (comp
(mapcat resolve-team)
(keep parse-uuid))
param)
(uuid? param)
(resolve-team param)
(string? param)
(some-> param parse-uuid resolve-team))
(= op :profile-id)
(if (coll? param)
(sequence (keep parse-uuid) param)
(resolve-dest param))))))
]
(->> (resolve-dest dest)
(filter some?)
(into #{})
(run! send))))

View File

@@ -113,8 +113,15 @@
(mapcat vals)
(keep (fn [{:keys [type] :as obj}]
(case type
:path (get-in obj [:fill-image :id])
:path (get-in obj [:fill-image :id])
:bool (get-in obj [:fill-image :id])
;; NOTE: because of some bug, we ended with
;; many shape types having the ability to
;; have fill-image attribute (which initially
;; designed for :path shapes).
:group (get-in obj [:fill-image :id])
:image (get-in obj [:metadata :id])
nil))))
pages (concat
(vals (:pages-index data))
@@ -184,7 +191,7 @@
(when (seq res)
(doseq [media-id (into #{} (keep :media-id) res)]
;; Mark as deleted the storage object related with the
;; photo-id field.
;; media-id field.
(l/trace :hint "mark storage object as deleted" :id media-id)
(sto/del-object! storage media-id))

View File

@@ -489,16 +489,8 @@
(l/error :hint "worker: unhandled exception" :cause cause))))))
(defn- get-error-context
[error item]
(let [data (ex-data error)]
(merge
{:hint (ex-message error)
:spec-problems (some->> data ::s/problems (take 10) seq vec)
:spec-value (some->> data ::s/value)
:data (some-> data (dissoc ::s/problems ::s/value ::s/spec))
:params item}
(when-let [explain (ex/explain data)]
{:spec-explain explain}))))
[_ item]
{:params item})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CRON
@@ -582,8 +574,11 @@
(l/trace :hint "register cron task" :id id :cron (str cron))
(db/exec-one! conn [sql:upsert-cron-task id (str cron) (str cron)]))))
(def sql:lock-cron-task
"select id from scheduled_task where id=? for update skip locked")
(defn- lock-scheduled-task!
[conn id]
(let [sql (str "SELECT id FROM scheduled_task "
" WHERE id=? FOR UPDATE SKIP LOCKED")]
(some? (db/exec-one! conn [sql (d/name id)]))))
(defn- execute-cron-task
[{:keys [::db/pool] :as cfg} {:keys [id] :as task}]
@@ -591,16 +586,21 @@
{:name (str "penpot/cront-task/" id)}
(try
(db/with-atomic [conn pool]
(when (db/exec-one! conn [sql:lock-cron-task (d/name id)])
(db/exec-one! conn ["SET statement_timeout=0;"])
(db/exec-one! conn ["SET idle_in_transaction_session_timeout=0;"])
(when (lock-scheduled-task! conn id)
(l/trace :hint "cron: execute task" :task-id id)
((:fn task) task)))
((:fn task) task))
(db/rollback! conn))
(catch InterruptedException _
(l/debug :hint "cron: task interrupted" :task-id id))
(catch Throwable cause
(l/error :hint "cron: unhandled exception on running task"
::l/context (get-error-context cause task)
:task-id id
:cause cause))
(binding [l/*context* (get-error-context cause task)]
(l/error :hint "cron: unhandled exception on running task"
:task-id id
:cause cause)))
(finally
(when-not (px/interrupted? :current)
(schedule-cron-task cfg task))))))

View File

@@ -14,6 +14,7 @@
[app.common.pages :as cp]
[app.common.pprint :as pp]
[app.common.spec :as us]
[app.common.schema :as sm]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@@ -127,7 +128,7 @@
(assoc-in [::db/pool ::db/uri] (:database-uri config))
(assoc-in [::db/pool ::db/username] (:database-username config))
(assoc-in [::db/pool ::db/password] (:database-password config))
(assoc-in [:app.rpc/methods :templates] templates)
(assoc-in [:app.rpc/methods :app.setup/templates] templates)
(dissoc :app.srepl/server
:app.http/server
:app.http/router
@@ -135,7 +136,7 @@
:app.auth.oidc/gitlab-provider
:app.auth.oidc/github-provider
:app.auth.oidc/generic-provider
:app.setup/builtin-templates
:app.setup/templates
:app.auth.oidc/routes
:app.worker/monitor
:app.http.oauth/handler
@@ -414,6 +415,14 @@
(println
(us/pretty-explain data))
(= :params-validation (:code data))
(app.common.pprint/pprint
(sm/humanize-data (::sm/explain data)))
(= :data-validation (:code data))
(app.common.pprint/pprint
(sm/humanize-data (::sm/explain data)))
(= :service-error (:type data))
(print-error! (.getCause ^Throwable error))

View File

@@ -39,8 +39,8 @@
params {::th/type :push-audit-events
::rpc/profile-id (:id prof)
:events [{:name "navigate"
:props {:project-id proj-id
:team-id team-id
:props {:project-id (str proj-id)
:team-id (str team-id)
:route "dashboard-files"}
:context {:engine "blink"}
:profile-id (:id prof)
@@ -71,8 +71,8 @@
params {::th/type :push-audit-events
::rpc/profile-id (:id prof)
:events [{:name "navigate"
:props {:project-id proj-id
:team-id team-id
:props {:project-id (str proj-id)
:team-id (str team-id)
:route "dashboard-files"}
:context {:engine "blink"}
:profile-id uuid/zero
@@ -91,6 +91,8 @@
(t/is (= 1 (count rows)))
(t/is (= (:id prof) (:profile-id row)))
(t/is (= "navigate" (:name row)))
(t/is (= "frontend" (:source row)))))))
(t/is (= "frontend" (:source row))))
)))

View File

@@ -252,6 +252,7 @@
:components-v2 true
:changes changes}
out (th/command! params)]
;; (th/print-result! out)
(t/is (nil? (:error out)))
(:result out)))]
@@ -278,7 +279,7 @@
[{:type :add-obj
:page-id page-id
:id shid
:parent-id uuid/zero
:parent-id uuid/zero
:frame-id uuid/zero
:components-v2 true
:obj {:id shid
@@ -286,7 +287,7 @@
:frame-id uuid/zero
:parent-id uuid/zero
:type :image
:metadata {:id (:id fmo1)}}}])
:metadata {:id (:id fmo1) :width 200 :height 200 :mtype "image/jpeg"}}}])
;; Check that reference storage objects on filemediaobjects
;; are the same because of deduplication feature.

View File

@@ -141,7 +141,7 @@
)))
(t/deftest upsert-file-thumbnail
(t/deftest create-file-thumbnail
(let [storage (::sto/storage th/*system*)
profile (th/create-profile* 1)
file (th/create-file* 1 {:profile-id (:id profile)
@@ -159,7 +159,6 @@
data2 {::th/type :create-file-thumbnail
::rpc/profile-id (:id profile)
:file-id (:id file)
:props {}
:revn 2
:media {:filename "sample.jpg"
:size 7923
@@ -169,7 +168,6 @@
data3 {::th/type :create-file-thumbnail
::rpc/profile-id (:id profile)
:file-id (:id file)
:props {}
:revn 3
:media {:filename "sample.jpg"
:size 312043
@@ -183,11 +181,11 @@
(let [out (th/command! data2)]
;; (th/print-result! out)
(t/is (nil? (:error out)))
(t/is (nil? (:result out))))
(t/is (contains? (:result out) :uri)))
(let [out (th/command! data3)]
(t/is (nil? (:error out)))
(t/is (nil? (:result out))))
(t/is (contains? (:result out) :uri)))
(let [[row1 row2 row3 :as rows] (th/db-query :file-thumbnail
{:file-id (:id file)}

View File

@@ -10,7 +10,7 @@
[app.config :as cf]
[app.db :as db]
[app.rpc :as-alias rpc]
[app.rpc.commands.auth :as cauth]
[app.auth :as auth]
[app.tokens :as tokens]
[app.util.time :as dt]
[backend-tests.helpers :as th]
@@ -226,11 +226,11 @@
(t/deftest registration-domain-whitelist
(let [whitelist #{"gmail.com" "hey.com" "ya.ru"}]
(t/testing "allowed email domain"
(t/is (true? (cauth/email-domain-in-whitelist? whitelist "username@ya.ru")))
(t/is (true? (cauth/email-domain-in-whitelist? #{} "username@somedomain.com"))))
(t/is (true? (auth/email-domain-in-whitelist? whitelist "username@ya.ru")))
(t/is (true? (auth/email-domain-in-whitelist? #{} "username@somedomain.com"))))
(t/testing "not allowed email domain"
(t/is (false? (cauth/email-domain-in-whitelist? whitelist "username@somedomain.com"))))))
(t/is (false? (auth/email-domain-in-whitelist? whitelist "username@somedomain.com"))))))
(t/deftest prepare-register-and-register-profile-1
(let [data {::th/type :prepare-register-profile
@@ -278,7 +278,7 @@
(let [error (:error out)]
(t/is (th/ex-info? error))
(t/is (th/ex-of-type? error :validation))
(t/is (th/ex-of-code? error :spec-validation))))
(t/is (th/ex-of-code? error :params-validation))))
;; try correct register
(let [data {::th/type :register-profile

View File

@@ -37,7 +37,7 @@
:role :editor}]
;; invite external user without complaints
(let [data (assoc data :email "foo@bar.com")
(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!
@@ -52,7 +52,7 @@
;; invite internal user without complaints
(th/reset-mock! mock)
(let [data (assoc data :email (:email profile2))
(let [data (assoc data :emails [(:email profile2)])
out (th/command! data)]
(t/is (th/success? out))
(t/is (= 1 (:call-count (deref mock)))))
@@ -60,7 +60,7 @@
;; invite user with complaint
(th/create-global-complaint-for pool {:type :complaint :email "foo@bar.com"})
(th/reset-mock! mock)
(let [data (assoc data :email "foo@bar.com")
(let [data (assoc data :emails ["foo@bar.com"])
out (th/command! data)]
(t/is (th/success? out))
(t/is (= 1 (:call-count (deref mock)))))
@@ -79,7 +79,7 @@
(th/reset-mock! mock)
(th/create-global-complaint-for pool {:type :bounce :email "foo@bar.com"})
(let [data (assoc data :email "foo@bar.com")
(let [data (assoc data :emails ["foo@bar.com"])
out (th/command! data)]
(t/is (not (th/success? out)))
@@ -92,7 +92,7 @@
;; invite internal user that is muted
(th/reset-mock! mock)
(let [data (assoc data :email (:email profile3))
(let [data (assoc data :emails [(:email profile3)])
out (th/command! data)]
(t/is (not (th/success? out)))
@@ -118,7 +118,7 @@
;; Try to invite a not existing user
(let [data {::th/type :create-team-invitations
::rpc/profile-id (:id profile1)
:email "notexisting@example.com"
:emails ["notexisting@example.com"]
:team-id (:id team)
:role :editor}
out (th/command! data)]
@@ -126,15 +126,15 @@
;; (th/print-result! out)
(t/is (th/success? out))
(t/is (= 1 (:call-count @mock)))
(t/is (= 1 (-> out :result count)))
(t/is (= 1 (-> out :result :total)))
(let [token (-> out :result first)
(let [token (-> out :result :invitations first)
claims (tokens/decode sprops token)]
(t/is (= :team-invitation (:iss claims)))
(t/is (= (:id profile1) (:profile-id claims)))
(t/is (= :editor (:role claims)))
(t/is (= (:id team) (:team-id claims)))
(t/is (= (:email data) (:member-email claims)))
(t/is (= (first (:emails data)) (:member-email claims)))
(t/is (nil? (:member-id claims)))))
(th/reset-mock! mock)
@@ -142,7 +142,7 @@
;; Try to invite existing user
(let [data {::th/type :create-team-invitations
::rpc/profile-id (:id profile1)
:email (:email profile2)
:emails [(:email profile2)]
:team-id (:id team)
:role :editor}
out (th/command! data)]
@@ -150,15 +150,15 @@
;; (th/print-result! out)
(t/is (th/success? out))
(t/is (= 1 (:call-count @mock)))
(t/is (= 1 (-> out :result count)))
(t/is (= 1 (-> out :result :total)))
(let [token (-> out :result first)
(let [token (-> out :result :invitations first)
claims (tokens/decode sprops token)]
(t/is (= :team-invitation (:iss claims)))
(t/is (= (:id profile1) (:profile-id claims)))
(t/is (= :editor (:role claims)))
(t/is (= (:id team) (:team-id claims)))
(t/is (= (:email data) (:member-email claims)))
(t/is (= (first (:emails data)) (:member-email claims)))
(t/is (= (:id profile2) (:member-id claims)))))
)))
@@ -264,7 +264,7 @@
;; invite internal user without complaints
(with-redefs [app.config/flags #{}]
(th/reset-mock! mock)
(let [data (assoc data :email (:email profile2))
(let [data (assoc data :emails [(:email profile2)])
out (th/command! data)]
(t/is (th/success? out))
(t/is (= 0 (:call-count (deref mock)))))

View File

@@ -1,40 +1,37 @@
{:deps
{org.clojure/clojure {:mvn/version "1.11.1"}
org.clojure/data.json {:mvn/version "2.4.0"}
org.clojure/tools.cli {:mvn/version "1.0.214"}
org.clojure/tools.cli {:mvn/version "1.0.219"}
org.clojure/clojurescript {:mvn/version "1.11.60"}
org.clojure/test.check {:mvn/version "1.1.1"}
org.clojure/data.fressian {:mvn/version "1.0.0"}
;; Logging
org.apache.logging.log4j/log4j-api {:mvn/version "2.19.0"}
org.apache.logging.log4j/log4j-core {:mvn/version "2.19.0"}
org.apache.logging.log4j/log4j-web {:mvn/version "2.19.0"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.19.0"}
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.19.0"}
org.slf4j/slf4j-api {:mvn/version "2.0.6"}
pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.26"}
org.apache.logging.log4j/log4j-api {:mvn/version "2.20.0"}
org.apache.logging.log4j/log4j-core {:mvn/version "2.20.0"}
org.apache.logging.log4j/log4j-web {:mvn/version "2.20.0"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.20.0"}
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.20.0"}
org.slf4j/slf4j-api {:mvn/version "2.0.7"}
pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.30"}
selmer/selmer {:mvn/version "1.12.55"}
selmer/selmer {:mvn/version "1.12.58"}
criterium/criterium {:mvn/version "0.4.6"}
metosin/jsonista {:mvn/version "0.3.7"}
metosin/malli {:mvn/version "0.11.0"}
expound/expound {:mvn/version "0.9.0"}
com.cognitect/transit-clj {:mvn/version "1.0.329"}
com.cognitect/transit-clj {:mvn/version "1.0.333"}
com.cognitect/transit-cljs {:mvn/version "0.8.280"}
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
funcool/cuerdas {:mvn/version "2022.06.16-403"}
funcool/promesa
{:git/tag "11.0-alpha13"
:git/sha "f6cab38"
:git/url "https://github.com/funcool/promesa.git"}
funcool/promesa {:mvn/version "11.0.671"}
funcool/datoteka {:mvn/version "3.0.66"
:exclusions [funcool/promesa]}
lambdaisland/uri {:mvn/version "1.13.95"
lambdaisland/uri {:mvn/version "1.15.125"
:exclusions [org.clojure/data.json]}
frankiesardo/linked {:mvn/version "1.3.0"}
@@ -44,7 +41,7 @@
;; exception printing
fipp/fipp {:mvn/version "0.6.26"}
io.aviso/pretty {:mvn/version "1.3"}
io.aviso/pretty {:mvn/version "1.4.4"}
environ/environ {:mvn/version "1.2.0"}}
:paths ["src" "target/classes"]
:aliases

View File

@@ -752,6 +752,12 @@
[key (delay (generator-fn key))]))
keys))
(defn opacity-to-hex [opacity]
(let [opacity (* opacity 255)
value (mth/round opacity)]
(.. value
(toString 16)
(padStart 2 "0"))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; String Functions

View File

@@ -0,0 +1,211 @@
/**
* 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
*/
"use strict";
goog.require("cljs.core");
goog.provide("app.common.encoding_impl");
goog.scope(function() {
const core = cljs.core;
const global = goog.global;
const self = app.common.encoding_impl;
const hexMap = [];
for (let i = 0; i < 256; i++) {
hexMap[i] = (i + 0x100).toString(16).substr(1);
}
function hexToBuffer(input) {
if (typeof input !== "string") {
throw new TypeError("Expected input to be a string");
}
// Accept UUID hex format
input = input.replace(/-/g, "");
if ((input.length % 2) !== 0) {
throw new RangeError("Expected string to be an even number of characters")
}
const view = new Uint8Array(input.length / 2);
for (let i = 0; i < input.length; i += 2) {
view[i / 2] = parseInt(input.substring(i, i + 2), 16);
}
return view.buffer;
}
function bufferToHex(source, isUuid) {
if (source instanceof Uint8Array) {
} else if (ArrayBuffer.isView(source)) {
source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
} else if (Array.isArray(source)) {
source = Uint8Array.from(source);
}
if (source.length != 16) {
throw new RangeError("only 16 bytes array is allowed");
}
const spacer = isUuid ? "-" : "";
let i = 0;
return (hexMap[source[i++]] +
hexMap[source[i++]] +
hexMap[source[i++]] +
hexMap[source[i++]] + spacer +
hexMap[source[i++]] +
hexMap[source[i++]] + spacer +
hexMap[source[i++]] +
hexMap[source[i++]] + spacer +
hexMap[source[i++]] +
hexMap[source[i++]] + spacer +
hexMap[source[i++]] +
hexMap[source[i++]] +
hexMap[source[i++]] +
hexMap[source[i++]] +
hexMap[source[i++]] +
hexMap[source[i++]]);
}
self.hexToBuffer = hexToBuffer;
self.bufferToHex = bufferToHex;
// base-x encoding / decoding
// Copyright (c) 2018 base-x contributors
// Copyright (c) 2014-2018 The Bitcoin Core developers (base58.cpp)
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php.
// WARNING: This module is NOT RFC3548 compliant, it cannot be used
// for base16 (hex), base32, or base64 encoding in a standards
// compliant manner.
function getBaseCodec (ALPHABET) {
if (ALPHABET.length >= 255) { throw new TypeError("Alphabet too long"); }
let BASE_MAP = new Uint8Array(256);
for (let j = 0; j < BASE_MAP.length; j++) {
BASE_MAP[j] = 255;
}
for (let i = 0; i < ALPHABET.length; i++) {
let x = ALPHABET.charAt(i);
let xc = x.charCodeAt(0);
if (BASE_MAP[xc] !== 255) { throw new TypeError(x + " is ambiguous"); }
BASE_MAP[xc] = i;
}
let BASE = ALPHABET.length;
let LEADER = ALPHABET.charAt(0);
let FACTOR = Math.log(BASE) / Math.log(256); // log(BASE) / log(256), rounded up
let iFACTOR = Math.log(256) / Math.log(BASE); // log(256) / log(BASE), rounded up
function encode (source) {
if (source instanceof Uint8Array) {
} else if (ArrayBuffer.isView(source)) {
source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
} else if (Array.isArray(source)) {
source = Uint8Array.from(source);
}
if (!(source instanceof Uint8Array)) { throw new TypeError("Expected Uint8Array"); }
if (source.length === 0) { return ""; }
// Skip & count leading zeroes.
let zeroes = 0;
let length = 0;
let pbegin = 0;
let pend = source.length;
while (pbegin !== pend && source[pbegin] === 0) {
pbegin++;
zeroes++;
}
// Allocate enough space in big-endian base58 representation.
let size = ((pend - pbegin) * iFACTOR + 1) >>> 0;
let b58 = new Uint8Array(size);
// Process the bytes.
while (pbegin !== pend) {
let carry = source[pbegin];
// Apply "b58 = b58 * 256 + ch".
let i = 0;
for (let it1 = size - 1; (carry !== 0 || i < length) && (it1 !== -1); it1--, i++) {
carry += (256 * b58[it1]) >>> 0;
b58[it1] = (carry % BASE) >>> 0;
carry = (carry / BASE) >>> 0;
}
if (carry !== 0) { throw new Error("Non-zero carry"); }
length = i;
pbegin++;
}
// Skip leading zeroes in base58 result.
let it2 = size - length;
while (it2 !== size && b58[it2] === 0) {
it2++;
}
// Translate the result into a string.
let str = LEADER.repeat(zeroes);
for (; it2 < size; ++it2) { str += ALPHABET.charAt(b58[it2]); }
return str;
}
function decodeUnsafe (source) {
if (typeof source !== "string") { throw new TypeError("Expected String"); }
if (source.length === 0) { return new Uint8Array(); }
let psz = 0;
// Skip and count leading '1's.
let zeroes = 0;
let length = 0;
while (source[psz] === LEADER) {
zeroes++;
psz++;
}
// Allocate enough space in big-endian base256 representation.
let size = (((source.length - psz) * FACTOR) + 1) >>> 0; // log(58) / log(256), rounded up.
let b256 = new Uint8Array(size);
// Process the characters.
while (source[psz]) {
// Decode character
let carry = BASE_MAP[source.charCodeAt(psz)];
// Invalid character
if (carry === 255) { return; }
let i = 0;
for (let it3 = size - 1; (carry !== 0 || i < length) && (it3 !== -1); it3--, i++) {
carry += (BASE * b256[it3]) >>> 0;
b256[it3] = (carry % 256) >>> 0;
carry = (carry / 256) >>> 0;
}
if (carry !== 0) { throw new Error("Non-zero carry"); }
length = i;
psz++;
}
// Skip leading zeroes in b256.
let it4 = size - length;
while (it4 !== size && b256[it4] === 0) {
it4++;
}
let vch = new Uint8Array(zeroes + (size - it4));
let j = zeroes;
while (it4 !== size) {
vch[j++] = b256[it4++];
}
return vch;
}
function decode (string) {
let buffer = decodeUnsafe(string);
if (buffer) { return buffer; }
throw new Error("Non-base" + BASE + " character");
}
return {
encode: encode,
decodeUnsafe: decodeUnsafe,
decode: decode
};
}
// MORE bases here: https://github.com/cryptocoinjs/base-x/tree/master
const BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
self.bufferToBase62 = getBaseCodec(BASE62).encode;
});

View File

@@ -341,13 +341,15 @@
:else
(let [objects (lookup-objects file)
bool-content (gsh/calc-bool-content bool objects)
bool' (gsh/update-bool-selrect bool children objects)]
(commit-change
file
{:type :mod-obj
:id bool-id
:operations
[{:type :set :attr :selrect :val (:selrect bool') :ignore-touched true}
[{:type :set :attr :bool-content :val bool-content :ignore-touched true}
{:type :set :attr :selrect :val (:selrect bool') :ignore-touched true}
{:type :set :attr :points :val (:points bool') :ignore-touched true}
{:type :set :attr :x :val (-> bool' :selrect :x) :ignore-touched true}
{:type :set :attr :y :val (-> bool' :selrect :y) :ignore-touched true}

View File

@@ -323,3 +323,13 @@
:rfn (fn [^Reader rdr]
(let [^List x (read-object! rdr)]
(Matrix. (.get x 0) (.get x 1) (.get x 2) (.get x 3) (.get x 4) (.get x 5))))})
;; Backward compatibility for 1.19 with v1.20;
(add-handlers!
{:name "penpot/geom/rect"
:rfn read-map-like}
{:name "penpot/shape"
:rfn read-map-like})

View File

@@ -136,6 +136,7 @@
(dm/export gco/center-rect)
(dm/export gco/center-points)
(dm/export gco/transform-points)
(dm/export gco/shape->points)
(dm/export gpr/make-rect)
(dm/export gpr/make-selrect)

View File

@@ -147,6 +147,7 @@
:else
(cph/reduce-objects
objects
(fn [shape]
(and (d/not-empty? (:shapes shape))
(or (not (cph/frame-shape? shape))

View File

@@ -84,3 +84,15 @@
(or (mth/nan? (:x p))
(mth/nan? (:y p))))
points)))
(defn shape->points
[{:keys [transform points]}]
(if (gmt/unit? transform)
;; Fix problem with precision could skew the shape
;; when there are no transforms the points are the selrect shape
(let [p0 (nth points 0) ;; left top
p2 (nth points 2) ;; right bottom
p1 (gpt/point (:x p2) (:y p0))
p3 (gpt/point (:x p0) (:y p2))]
[p0 p1 p2 p3])
points))

View File

@@ -463,7 +463,7 @@
(cond-> modif-tree
snap-pixel? (gpp/adjust-pixel-precision objects snap-precision snap-ignore-axis))
bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points]))
bounds (d/lazy-map (keys objects) #(gco/shape->points (get objects %)))
bounds (cond-> bounds
(some? old-modif-tree)
(transform-bounds objects old-modif-tree))

View File

@@ -58,7 +58,7 @@
(defn set-pixel-precision
"Adjust modifiers so they adjust to the pixel grid"
[modifiers shape precision ignore-axis]
(let [points (-> shape :points (gco/transform-points (ctm/modifiers->transform modifiers)))
(let [points (-> shape gco/shape->points (gco/transform-points (ctm/modifiers->transform modifiers)))
has-resize? (not (ctm/only-move? modifiers))
[modifiers points]

View File

@@ -218,7 +218,7 @@
(make-selrect (min xp1 xp2) (min yp1 yp2) (abs (- xp1 xp2)) (abs (- yp1 yp2)))))
(defn clip-selrect
[{:keys [x1 y1 x2 y2] :as sr} bounds]
[{:keys [x1 y1 x2 y2] :as sr} clip-rect]
(when (some? sr)
(let [{bx1 :x1 by1 :y1 bx2 :x2 by2 :y2} (rect->selrect bounds)]
(let [{bx1 :x1 by1 :y1 bx2 :x2 by2 :y2 :as sr2} (rect->selrect clip-rect)]
(corners->selrect (max bx1 x1) (max by1 y1) (min bx2 x2) (min by2 y2)))))

View File

@@ -317,7 +317,7 @@
its properties. We adjust de x,y,width,height and create a custom transform"
[shape transform-mtx]
(let [points' (:points shape)
(let [points' (gco/shape->points shape)
points (gco/transform-points points' transform-mtx)
shape (-> shape (adjust-shape-flips points))
bool? (= (:type shape) :bool)

View File

@@ -239,7 +239,7 @@
#?(:clj
(defn slf4j-log-handler
{:no-doc true}
[_ _ _ {:keys [::logger ::level ::props ::cause ::trace ::message]}]
[_ _ _ {:keys [::logger ::level ::trace ::message] }]
(when-let [logger (enabled? logger level)]
(let [message (cond-> @message
(some? trace)
@@ -307,6 +307,18 @@
(l/set-level! logger level)))
config)))
(defmacro raw!
[level message]
(let [cljs? (:ns &env)]
`(do
(~(if cljs?
`(partial console-log-handler nil nil nil)
`(partial slf4j-log-handler nil nil nil))
{::logger ~(str *ns*)
::level ~level
::message (delay ~message)})
nil)))
(defmacro info
[& params]
`(do

View File

@@ -93,6 +93,13 @@
[:component-id {:optional true} ::sm/uuid]
[:ignore-touched {:optional true} :boolean]]]
[:fix-obj
[:map {:title "FixObjChange"}
[:type [:= :fix-obj]]
[:id ::sm/uuid]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]]]
[:mov-objects
[:map {:title "MovObjectsChange"}
[:type [:= :mov-objects]]
@@ -218,7 +225,7 @@
(sm/def! ::changes
[:sequential {:gen/max 2} ::change])
(def change?
(sm/pred-fn ::change))
@@ -246,9 +253,8 @@
(let [shape-old (dm/get-in data-old [:pages-index page-id :objects id])
shape-new (dm/get-in data-new [:pages-index page-id :objects id])]
;; If object has changed verify is correct
(when (and (some? shape-old)
(some? shape-new)
;; If object has changed or is new verify is correct
(when (and (some? shape-new)
(not= shape-old shape-new))
(dm/verify! (cts/shape? shape-new)))))]
@@ -323,7 +329,9 @@
component-root (ctn/get-component-shape objects shape {:allow-main? true})]
(if (and (some? component-root) (ctk/main-instance? component-root))
(ctkl/set-component-modified data (:component-id component-root))
data))
(if (some? component-id)
(ctkl/set-component-modified data component-id)
data)))
data))]
(as-> data $
@@ -338,6 +346,12 @@
(d/update-in-when data [:pages-index page-id] ctst/delete-shape id ignore-touched)
(d/update-in-when data [:components component-id] ctst/delete-shape id ignore-touched)))
(defmethod process-change :fix-obj
[data {:keys [page-id component-id] :as params}]
(if page-id
(d/update-in-when data [:pages-index page-id] ctst/fix-shape-children params)
(d/update-in-when data [:components component-id] ctst/fix-shape-children params)))
;; FIXME: remove, seems like this method is already unused
;; reg-objects operation "regenerates" the geometry and selrect of the parent groups
(defmethod process-change :reg-objects

View File

@@ -603,7 +603,9 @@
(apply-changes-local))))
(defn add-component
[changes id path name new-shapes updated-shapes main-instance-id main-instance-page]
([changes id path name new-shapes updated-shapes main-instance-id main-instance-page]
(add-component changes id path name new-shapes updated-shapes main-instance-id main-instance-page nil))
([changes id path name new-shapes updated-shapes main-instance-id main-instance-page annotation]
(assert-page-id changes)
(assert-objects changes)
(let [page-id (::page-id (meta changes))
@@ -641,7 +643,8 @@
:path path
:name name
:main-instance-id main-instance-id
:main-instance-page main-instance-page}
:main-instance-page main-instance-page
:annotation annotation}
(some? new-shapes) ;; this will be null in components-v2
(assoc :shapes (vec new-shapes))))
(into (map mk-change) updated-shapes))))
@@ -655,7 +658,7 @@
(map lookupf)
(map mk-change))
updated-shapes))))
(apply-changes-local))))
(apply-changes-local)))))
(defn update-component
[changes id update-fn]

View File

@@ -12,7 +12,7 @@
[app.common.schema :as sm]
[app.common.uuid :as uuid]))
(def file-version 20)
(def file-version 22)
(def default-color clr/gray-20)
(def root uuid/zero)

View File

@@ -16,6 +16,7 @@
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.common.text :as txt]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[cuerdas.core :as str]))
@@ -45,8 +46,9 @@
(defn migrated?
[{:keys [data] :as file}]
(> (:version data)
(::orig-version file)))
(or (::migrated file)
(> (:version data)
(::orig-version file))))
;; Default handler, noop
(defmethod migrate :default [data] data)
@@ -436,7 +438,7 @@
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(defmethod migrate 20
(defmethod migrate 21
[data]
(letfn [(update-object [objects object]
(let [frame-id (:frame-id object)
@@ -464,3 +466,38 @@
;; TODO: pending to do a migration for delete already not used fill
;; and stroke props. This should be done for >1.14.x version.
(defmethod migrate 22
[data]
(letfn [(valid-ref? [ref]
(or (uuid? ref)
(nil? ref)))
(valid-node? [node]
(and (valid-ref? (:typography-ref-file node))
(valid-ref? (:typography-ref-id node))
(valid-ref? (:fill-color-ref-file node))
(valid-ref? (:fill-color-ref-id node))))
(fix-ref [ref]
(if (valid-ref? ref) ref nil))
(fix-node [node]
(-> node
(d/update-when :typography-ref-file fix-ref)
(d/update-when :typography-ref-id fix-ref)
(d/update-when :fill-color-ref-file fix-ref)
(d/update-when :fill-color-ref-id fix-ref)))
(update-object [object]
(let [invalid-node? (complement valid-node?)]
(cond-> object
(cph/text-shape? object)
(update :content #(txt/transform-nodes invalid-node? fix-node %)))))
(update-container [container]
(update container :objects update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))

View File

@@ -50,12 +50,15 @@
(defn update-curve-to
[command h1 h2]
(-> command
(assoc :command :curve-to)
(assoc-in [:params :c1x] (:x h1))
(assoc-in [:params :c1y] (:y h1))
(assoc-in [:params :c2x] (:x h2))
(assoc-in [:params :c2y] (:y h2))))
(let [params {:x (-> command :params :x)
:y (-> command :params :y)
:c1x (:x h1)
:c1y (:y h1)
:c2x (:x h2)
:c2y (:y h2)}]
(-> command
(assoc :command :curve-to)
(assoc :params params))))
(defn make-curve-to
[to h1 h2]

View File

@@ -282,7 +282,9 @@
(def! ::email
{:type ::email
:pred (fn [s]
(and (string? s) (re-seq email-re s)))
(and (string? s)
(< (count s) 250)
(re-seq email-re s)))
:type-properties
{:title "email"
:description "string with valid email address"
@@ -380,8 +382,10 @@
keyword
identity)}}))})
(def max-safe-int (int 1e6))
(def min-safe-int (int -1e6))
;; Integer/MAX_VALUE
(def max-safe-int 2147483647)
;; Integer/MIN_VALUE
(def min-safe-int -2147483648)
(def! ::safe-int
{:type ::safe-int
@@ -464,6 +468,7 @@
(def! ::word-string
{:type ::word-string
:pred #(and (string? %) (not (str/blank? %)))
:property-pred (m/-min-max-pred count)
:type-properties
{:title "string"
:description "string"

View File

@@ -29,8 +29,10 @@
(def uuid-rx
#"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
(def max-safe-int (int 1e6))
(def min-safe-int (int -1e6))
;; Integer/MAX_VALUE
(def max-safe-int 2147483647)
;; Integer/MIN_VALUE
(def min-safe-int -2147483648)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DEFAULT SPECS

View File

@@ -48,7 +48,7 @@
(sm/def! ::gradient
[:map {:title "Gradient"}
[:type [::sm/one-of #{:linear :radial}]]
[:type [::sm/one-of #{:linear :radial "linear" "radial"}]]
[:start-x ::sm/safe-number]
[:start-y ::sm/safe-number]
[:end-x ::sm/safe-number]

View File

@@ -30,13 +30,12 @@
(assoc component :modified-at (dt/now)))
(defn add-component
[fdata {:keys [id name path main-instance-id main-instance-page shapes]}]
[fdata {:keys [id name path main-instance-id main-instance-page shapes annotation]}]
(let [components-v2 (dm/get-in fdata [:options :components-v2])
fdata (update fdata :components assoc id (touch {:id id :name name :path path}))]
(if components-v2
(update-in fdata [:components id] assoc
:main-instance-id main-instance-id
:main-instance-page main-instance-page)
(cond-> (update-in fdata [:components id] assoc :main-instance-id main-instance-id :main-instance-page main-instance-page)
annotation (update-in [:components id] assoc :annotation annotation))
(let [wrap-object-fn feat/*wrap-with-objects-map-fn*]
(assoc-in fdata [:components id :objects]
@@ -116,3 +115,9 @@
[]
[[(:id shape) (:component-id shape) :component]]))
[]))
(defn get-component-annotation
[shape libraries]
(let [library (dm/get-in libraries [(:component-file shape) :data])
component (get-component library (:component-id shape) true)]
(:annotation component)))

View File

@@ -96,22 +96,25 @@
"Get the parent shape linked to a component for this shape, if any"
([objects shape] (get-component-shape objects shape nil))
([objects shape {:keys [allow-main?] :or {allow-main? false} :as options}]
(cond
(nil? shape)
nil
(cond
(nil? shape)
nil
(and (not (ctk/in-component-copy? shape)) (not allow-main?))
nil
(= uuid/zero (:id shape))
nil
(ctk/instance-root? shape)
shape
(and (not (ctk/in-component-copy? shape)) (not allow-main?))
nil
:else
(get-component-shape objects (get objects (:parent-id shape)) options))))
(ctk/instance-root? shape)
shape
:else
(get-component-shape objects (get objects (:parent-id shape)) options))))
(defn in-component-main?
"Check if the shape is inside a component non-main instance.
Note that we must iterate on the parents because non-root shapes in
a main component have not any discriminating attribute."
[objects shape]

View File

@@ -291,7 +291,7 @@
been modified after the given date."
[file-data library since-date]
(letfn [(used-assets-shape [shape]
(concat
(concat
(ctkl/used-components-changed-since shape library since-date)
(ctcl/used-colors-changed-since shape library since-date)
(ctyl/used-typographies-changed-since shape library since-date)))
@@ -299,7 +299,7 @@
(used-assets-container [container]
(->> (mapcat used-assets-shape (ctn/shapes-seq container))
(map #(cons (:id container) %))))]
(mapcat used-assets-container (containers-seq file-data))))
(defn get-or-add-library-page
@@ -407,7 +407,7 @@
(update page :objects update-vals root-to-board))]
(-> file-data
(add-instance-grid (sort-by :name components))
(add-instance-grid (reverse (sort-by :name components)))
(update :pages-index update-vals roots-to-board)
(assoc-in [:options :components-v2] true))))))))

View File

@@ -21,15 +21,15 @@
(sm/def! ::column-params
[:map
[:color ::grid-color]
[:type [::sm/one-of #{:stretch :left :center :right}]]
[:size {:optional true} ::sm/safe-number]
[:type {:optional true} [::sm/one-of #{:stretch :left :center :right}]]
[:size {:optional true} [:maybe ::sm/safe-number]]
[:margin {:optional true} [:maybe ::sm/safe-number]]
[:item-length {:optional true} [:maybe ::sm/safe-number]]
[:gutter {:optional true} [:maybe ::sm/safe-number]]])
(sm/def! ::square-params
[:map
[:size ::sm/safe-number]
[:size {:optional true} [:maybe ::sm/safe-number]]
[:color ::grid-color]])
(sm/def! ::grid

View File

@@ -72,10 +72,10 @@
[:vector {:gen/max 5} ::gpt/point])
(sm/def! ::fill
[:map {:title "Fill" :min 1}
[:map {:title "Fill"}
[:fill-color {:optional true} ::ctc/rgb-color]
[:fill-opacity {:optional true} ::sm/safe-number]
[:fill-color-gradient {:optional true} ::ctc/gradient]
[:fill-color-gradient {:optional true} [:maybe ::ctc/gradient]]
[:fill-color-ref-file {:optional true} [:maybe ::sm/uuid]]
[:fill-color-ref-id {:optional true} [:maybe ::sm/uuid]]])
@@ -156,7 +156,7 @@
[:map {:title "GroupAttrs"}
[:type [:= :group]]
[:id ::sm/uuid]
[:shapes [:vector {:min 1 :gen/max 10 :gen/min 1} ::sm/uuid]]])
[:shapes {:optional true} [:maybe [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]]])
(sm/def! ::frame-attrs
[:map {:title "FrameAttrs"}
@@ -172,18 +172,20 @@
[:map {:title "BoolAttrs"}
[:type [:= :bool]]
[:id ::sm/uuid]
[:shapes [:vector {:min 1 :gen/max 10 :gen/min 1} ::sm/uuid]]
[:shapes {:optional true} [:maybe [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]]
;; FIXME: improve this schema
[:bool-type :keyword]
;; FIXME: improve this schema
[:bool-content
[:vector {:gen/max 2}
[:map
[:command :keyword]
[:relative :boolean]
[:params [:map-of {:gen/max 5} :keyword ::sm/safe-number]]]]]])
[:relative {:optional true} :boolean]
[:prev-pos {:optional true} ::gpt/point]
[:params {:optional true}
[:maybe
[:map-of {:gen/max 5} :keyword ::sm/safe-number]]]]]]])
(sm/def! ::rect-attrs
[:map {:title "RectAttrs"}
@@ -208,14 +210,19 @@
[:map
[:width :int]
[:height :int]
[:mtype :string]
[:mtype {:optional true} [:maybe :string]]
[:id ::sm/uuid]]]])
(sm/def! ::path-attrs
[:map {:title "PathAttrs"}
[:type [:= :path]]
[:id ::sm/uuid]
[:x {:optional true} [:maybe ::sm/safe-number]]
[:y {:optional true} [:maybe ::sm/safe-number]]
[:width {:optional true} [:maybe ::sm/safe-number]]
[:height {:optional true} [:maybe ::sm/safe-number]]
[:content
{:optional true}
[:vector
[:map
[:command :keyword]
@@ -225,7 +232,7 @@
[:map {:title "TextAttrs"}
[:id ::sm/uuid]
[:type [:= :text]]
[:content ::ctsx/content]])
[:content {:optional true} [:maybe ::ctsx/content]]])
(sm/def! ::shape
[:multi {:dispatch :type :title "Shape"}

View File

@@ -495,8 +495,7 @@
"expected compatible interaction map"
(has-overlay-opts interaction))
(let [
;; When the interactive item is inside a nested frame we need to add to the offset the position
(let [;; When the interactive item is inside a nested frame we need to add to the offset the position
;; of the parent-frame otherwise the position won't match
shape-frame (cph/get-frame objects shape)
@@ -505,10 +504,10 @@
(cph/root-frame? shape-frame)
(cph/root? shape-frame))
frame-offset
(gpt/add frame-offset (gpt/point shape-frame)))
]
(gpt/add frame-offset (gpt/point shape-frame)))]
(if (nil? dest-frame)
(gpt/point 0 0)
[(gpt/point 0 0) [:top :left]]
(let [overlay-size (gsb/get-object-bounds objects dest-frame)
base-frame-size (:selrect base-frame)
relative-to-shape-size (:selrect relative-to-shape)
@@ -526,37 +525,46 @@
overlay-position
{:x (- (:x overlay-position) (:x relative-to-adjusted-to-base-frame))
:y (- (:y overlay-position) (:y relative-to-adjusted-to-base-frame))})]
(case (:overlay-pos-type interaction)
:center
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
(+ (:y base-position) (/ (- (:height relative-to-shape-size) (:height overlay-size)) 2)))
[(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
(+ (:y base-position) (/ (- (:height relative-to-shape-size) (:height overlay-size)) 2)))
[:center :center]]
:top-left
(gpt/point (:x base-position) (:y base-position))
[(gpt/point (:x base-position) (:y base-position))
[:top :left]]
:top-right
(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
(:y base-position))
[(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
(:y base-position))
[:top :right]]
:top-center
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
(:y base-position))
[(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
(:y base-position))
[:top :center]]
:bottom-left
(gpt/point (:x base-position)
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
[(gpt/point (:x base-position)
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
[:bottom :left]]
:bottom-right
(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
[(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
[:bottom :right]]
:bottom-center
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
[(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
[:bottom :center]]
:manual
(gpt/point (+ (:x base-position) (:x overlay-position))
(+ (:y base-position) (:y overlay-position))))))))
[(gpt/point (+ (:x base-position) (:x overlay-position))
(+ (:y base-position) (:y overlay-position)))
[:top :left]])))))
(defn has-animation?
[interaction]

View File

@@ -21,44 +21,46 @@
[:type [:= "root"]]
[:key {:optional true} :string]
[:children
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
[:type [:= "paragraph-set"]]
[:key {:optional true} :string]
[:children
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
[:type [:= "paragraph"]]
[:key {:optional true} :string]
[:fills {:optional true}
[:maybe
[:vector {:gen/max 2} ::shape/fill]]]
[:font-family {:optional true} :string]
[:font-size {:optional true} :string]
[:font-style {:optional true} :string]
[:font-weight {:optional true} :string]
[:direction {:optional true} :string]
[:text-decoration {:optional true} :string]
[:text-transform {:optional true} :string]
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]
[:children
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
[:text :string]
[:key {:optional true} :string]
[:fills {:optional true}
[:maybe
[:vector {:gen/max 2} ::shape/fill]]]
[:font-family {:optional true} :string]
[:font-size {:optional true} :string]
[:font-style {:optional true} :string]
[:font-weight {:optional true} :string]
[:direction {:optional true} :string]
[:text-decoration {:optional true} :string]
[:text-transform {:optional true} :string]
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]])
{:optional true}
[:maybe
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
[:type [:= "paragraph-set"]]
[:key {:optional true} :string]
[:children
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
[:type [:= "paragraph"]]
[:key {:optional true} :string]
[:fills {:optional true}
[:maybe
[:vector {:gen/max 2} ::shape/fill]]]
[:font-family {:optional true} :string]
[:font-size {:optional true} :string]
[:font-style {:optional true} :string]
[:font-weight {:optional true} :string]
[:direction {:optional true} :string]
[:text-decoration {:optional true} :string]
[:text-transform {:optional true} :string]
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]
[:children
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
[:text :string]
[:key {:optional true} :string]
[:fills {:optional true}
[:maybe
[:vector {:gen/max 2} ::shape/fill]]]
[:font-family {:optional true} :string]
[:font-size {:optional true} :string]
[:font-style {:optional true} :string]
[:font-weight {:optional true} :string]
[:direction {:optional true} :string]
[:text-decoration {:optional true} :string]
[:text-transform {:optional true} :string]
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]]])

View File

@@ -90,16 +90,24 @@
(delete-from-objects [objects]
(if-let [target (get objects shape-id)]
(let [parent-id (or (:parent-id target)
(:frame-id target))
children-ids (cph/get-children-ids objects shape-id)]
(-> (reduce dissoc objects children-ids)
(dissoc shape-id)
(let [parent-id (or (:parent-id target)
(:frame-id target))
children-ids (cph/get-children-ids objects shape-id)]
(-> (reduce dissoc objects (cons shape-id children-ids))
(d/update-when parent-id delete-from-parent)))
objects))]
(update container :objects delete-from-objects))))
(defn fix-shape-children
"Checks and fix the children relations of the shape. If a children does not
exists on the objects tree, it will be removed from shape."
[{:keys [objects] :as container} {:keys [id] :as params}]
(let [contains? (partial contains? objects)]
(d/update-in-when container [:objects id :shapes]
(fn [shapes]
(into [] (filter contains?) shapes)))))
(defn get-frames
"Retrieves all frame objects as vector"
([objects] (get-frames objects nil))
@@ -260,7 +268,11 @@
(let [frame-ids (cond->> (all-frames-by-position objects position)
(some? excluded)
(remove excluded))
(remove excluded)
:always
(remove #(or (dm/get-in objects [% :hidden])
(dm/get-in objects [% :blocked]))))
frame-set (set frame-ids)]
@@ -276,7 +288,10 @@
"Search the top nested frame in a list of ids"
[objects ids]
(let [frame-ids (->> ids (filter #(cph/frame-shape? objects %)))
(let [frame-ids (->> ids
(filter #(cph/frame-shape? objects %))
(remove #(or (dm/get-in objects [% :hidden])
(dm/get-in objects [% :blocked]))))
frame-set (set frame-ids)]
(loop [current-id (first frame-ids)]
(let [current-shape (get objects current-id)
@@ -343,6 +358,7 @@
(some? force-id) force-id
keep-ids? (:id object)
:else (uuid/next))]
(loop [child-ids (seq (:shapes object))
new-direct-children []
new-children []

View File

@@ -4,9 +4,11 @@
;;
;; Copyright (c) KALEIDOS INC
#_:clj-kondo/ignore
(ns app.common.uuid
(:refer-clojure :exclude [next uuid zero?])
(:refer-clojure :exclude [next uuid zero? short])
(:require
[app.common.data.macros :as dm]
#?(:clj [clojure.core :as c])
#?(:cljs [app.common.uuid-impl :as impl])
#?(:cljs [cljs.core :as c]))
@@ -66,3 +68,10 @@
(let [buf (ByteBuffer/wrap o)]
(UUID. ^long (.getLong buf)
^long (.getLong buf)))))
#?(:cljs
(defn uuid->short-id
"Return a shorter string of a safe subset of bytes of an uuid encoded
with base62. It is only safe to use with uuid v4 and penpot custom v8"
[id]
(impl/short-v8 (dm/str id))))

View File

@@ -8,13 +8,17 @@
"use strict";
goog.require("cljs.core");
goog.require("app.common.encoding_impl");
goog.provide("app.common.uuid_impl");
goog.scope(function() {
const core = cljs.core;
const global = goog.global;
const encoding = app.common.encoding_impl;
const self = app.common.uuid_impl;
const timeRef = 1640995200000; // ms since 2022-01-01T00:00:00
const fill = (() => {
if (typeof global.crypto !== "undefined" &&
typeof global.crypto.getRandomValues !== "undefined") {
@@ -45,12 +49,8 @@ goog.scope(function() {
}
})();
const hexMap = [];
for (let i = 0; i < 256; i++) {
hexMap[i] = (i + 0x100).toString(16).substr(1);
}
function toHexString(buf) {
const hexMap = encoding.hexMap;
let i = 0;
return (hexMap[buf[i++]] +
hexMap[buf[i++]] +
@@ -68,18 +68,7 @@ goog.scope(function() {
hexMap[buf[i++]] +
hexMap[buf[i++]] +
hexMap[buf[i++]]);
}
self.v4 = (function () {
const buff8 = new Uint8Array(16);
return function v4() {
fill(buff8);
buff8[6] = (buff8[6] & 0x0f) | 0x40;
buff8[8] = (buff8[8] & 0x3f) | 0x80;
return core.uuid(toHexString(buff8));
};
})();
};
function getBigUint64(view, byteOffset, le) {
const a = view.getUint32(byteOffset, le);
@@ -103,17 +92,54 @@ goog.scope(function() {
}
}
self.v8 = (function () {
const buff = new ArrayBuffer(16);
function currentTimestamp(timeRef) {
return BigInt.asUintN(64, "" + (Date.now() - timeRef));
}
const tmpBuff = new ArrayBuffer(8);
const tmpView = new DataView(tmpBuff);
const tmpInt8 = new Uint8Array(tmpBuff);
function nextLong() {
fill(tmpInt8);
return getBigUint64(tmpView, 0, false);
}
self.shortID = (function () {
const buff = new ArrayBuffer(8);
const int8 = new Uint8Array(buff);
const view = new DataView(buff);
const tmpBuff = new ArrayBuffer(8);
const tmpView = new DataView(tmpBuff);
const tmpInt8 = new Uint8Array(tmpBuff);
const base = 0x0000_0000_0000_0000n;
const timeRef = 1640995200000; // ms since 2022-01-01T00:00:00
const maxCs = 0x0000_0000_0000_3fffn; // 14 bits space
return function shortID(ts) {
const tss = currentTimestamp(timeRef);
const msb = (base
| (nextLong() & 0xffff_ffff_0000_0000n)
| (tss & 0x0000_0000_ffff_ffffn));
setBigUint64(view, 0, msb, false);
return encoding.toBase62(int8);
};
})();
self.v4 = (function () {
const arr = new Uint8Array(16);
return function v4() {
fill(arr);
arr[6] = (arr[6] & 0x0f) | 0x40;
arr[8] = (arr[8] & 0x3f) | 0x80;
return core.uuid(encoding.bufferToHex(arr, true));
};
})();
self.v8 = (function () {
const buff = new ArrayBuffer(16);
const int8 = new Uint8Array(buff);
const view = new DataView(buff);
const maxCs = 0x0000_0000_0000_3fffn; // 14 bits space
let countCs = 0n;
let lastRd = 0n;
@@ -122,15 +148,6 @@ goog.scope(function() {
let baseMsb = 0x0000_0000_0000_8000n;
let baseLsb = 0x8000_0000_0000_0000n;
const currentTimestamp = () => {
return BigInt.asUintN(64, "" + (Date.now() - timeRef));
};
const nextLong = () => {
fill(tmpInt8);
return getBigUint64(tmpView, 0, false);
};
lastRd = nextLong() & 0xffff_ffff_ffff_f0ffn;
lastCs = nextLong() & maxCs;
@@ -145,12 +162,12 @@ goog.scope(function() {
setBigUint64(view, 0, msb, false);
setBigUint64(view, 8, lsb, false);
return core.uuid(toHexString(int8));
return core.uuid(encoding.bufferToHex(int8, true));
};
const factory = function v8() {
while (true) {
let ts = currentTimestamp();
let ts = currentTimestamp(timeRef);
// Protect from clock regression
if ((ts - lastTs) < 0) {
@@ -195,6 +212,12 @@ goog.scope(function() {
})();
self.short_v8 = function(uuid) {
const buff = encoding.hexToBuffer(uuid);
const short = new Uint8Array(buff, 4);
return encoding.bufferToBase62(short);
};
self.custom = function formatAsUUID(mostSigBits, leastSigBits) {
const most = mostSigBits.toString("16").padStart(16, "0");
const least = leastSigBits.toString("16").padStart(16, "0");

View File

@@ -324,209 +324,275 @@
interaction-rect (ctsi/set-position-relative-to interaction (:id rect))]
(t/testing "Overlay top-left relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 0))
(t/is (= (:y overlay-pos) 0))))
(t/is (= (:y overlay-pos) 0))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-center relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 0))))
(t/is (= (:y overlay-pos) 0))
(t/is (= snap-v :top))
(t/is (= snap-h :center))))
(t/testing "Overlay top-right relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 70))
(t/is (= (:y overlay-pos) 0))))
(t/is (= (:y overlay-pos) 0))
(t/is (= snap-v :top))
(t/is (= snap-h :right))))
(t/testing "Overlay bottom-left relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 0))
(t/is (= (:y overlay-pos) 80))))
(t/is (= (:y overlay-pos) 80))
(t/is (= snap-v :bottom))
(t/is (= snap-h :left))))
(t/testing "Overlay bottom-center relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 80))))
(t/is (= (:y overlay-pos) 80))
(t/is (= snap-v :bottom))
(t/is (= snap-h :center))))
(t/testing "Overlay bottom-right relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 70))
(t/is (= (:y overlay-pos) 80))))
(t/is (= (:y overlay-pos) 80))
(t/is (= snap-v :bottom))
(t/is (= snap-h :right))))
(t/testing "Overlay center relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 40))))
(t/is (= (:y overlay-pos) 40))
(t/is (= snap-v :center))
(t/is (= snap-h :center))))
(t/testing "Overlay manual relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 40))))
(t/is (= (:y overlay-pos) 40))
(t/is (= snap-v :center))
(t/is (= snap-h :center))))
(t/testing "Overlay manual relative to auto"
(let [i2 (-> interaction-auto
(ctsi/set-overlay-pos-type :manual base-frame objects)
(ctsi/set-overlay-position (gpt/point 12 62)))
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 17))
(t/is (= (:y overlay-pos) 67))))
(t/is (= (:y overlay-pos) 67))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-left relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 5))
(t/is (= (:y overlay-pos) 5))))
(t/is (= (:y overlay-pos) 5))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-center relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 40))
(t/is (= (:y overlay-pos) 5))))
(t/is (= (:y overlay-pos) 5))
(t/is (= snap-v :top))
(t/is (= snap-h :center))))
(t/testing "Overlay top-right relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 75))
(t/is (= (:y overlay-pos) 5))))
(t/is (= (:y overlay-pos) 5))
(t/is (= snap-v :top))
(t/is (= snap-h :right))))
(t/testing "Overlay bottom-left relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 5))
(t/is (= (:y overlay-pos) 85))))
(t/is (= (:y overlay-pos) 85))
(t/is (= snap-v :bottom))
(t/is (= snap-h :left))))
(t/testing "Overlay bottom-center relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 40))
(t/is (= (:y overlay-pos) 85))))
(t/is (= (:y overlay-pos) 85))
(t/is (= snap-v :bottom))
(t/is (= snap-h :center))))
(t/testing "Overlay bottom-right relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 75))
(t/is (= (:y overlay-pos) 85))))
(t/is (= (:y overlay-pos) 85))
(t/is (= snap-v :bottom))
(t/is (= snap-h :right))))
(t/testing "Overlay center relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 40))
(t/is (= (:y overlay-pos) 45))))
(t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :center))
(t/is (= snap-h :center))))
(t/testing "Overlay manual relative to base-frame"
(let [i2 (-> interaction-base-frame
(ctsi/set-overlay-pos-type :manual base-frame objects)
(ctsi/set-overlay-position (gpt/point 12 62)))
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 17))
(t/is (= (:y overlay-pos) 67))))
(t/is (= (:y overlay-pos) 67))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-left relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 15))
(t/is (= (:y overlay-pos) 15))))
(t/is (= (:y overlay-pos) 15))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-center relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 15))))
(t/is (= (:y overlay-pos) 15))
(t/is (= snap-v :top))
(t/is (= snap-h :center))))
(t/testing "Overlay top-right relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 15))))
(t/is (= (:y overlay-pos) 15))
(t/is (= snap-v :top))
(t/is (= snap-h :right))))
(t/testing "Overlay bottom-left relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 15))
(t/is (= (:y overlay-pos) 45))))
(t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :bottom))
(t/is (= snap-h :left))))
(t/testing "Overlay bottom-center relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 45))))
(t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :bottom))
(t/is (= snap-h :center))))
(t/testing "Overlay bottom-right relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 45))))
(t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :bottom))
(t/is (= snap-h :right))))
(t/testing "Overlay center relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 30))))
(t/is (= (:y overlay-pos) 30))
(t/is (= snap-v :center))
(t/is (= snap-h :center))))
(t/testing "Overlay manual relative to popup"
(let [i2 (-> interaction-popup
(ctsi/set-overlay-pos-type :manual base-frame objects)
(ctsi/set-overlay-position (gpt/point 12 62)))
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 27))
(t/is (= (:y overlay-pos) 77))))
(t/is (= (:y overlay-pos) 77))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-left relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 15))
(t/is (= (:y overlay-pos) 15))))
(t/is (= (:y overlay-pos) 15))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-center relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :top-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 15))))
(t/is (= (:y overlay-pos) 15))
(t/is (= snap-v :top))
(t/is (= snap-h :center))))
(t/testing "Overlay top-right relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :top-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 15))))
(t/is (= (:y overlay-pos) 15))
(t/is (= snap-v :top))
(t/is (= snap-h :right))))
(t/testing "Overlay bottom-left relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 15))
(t/is (= (:y overlay-pos) 45))))
(t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :bottom))
(t/is (= snap-h :left))))
(t/testing "Overlay bottom-center relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 45))))
(t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :bottom))
(t/is (= snap-h :center))))
(t/testing "Overlay bottom-right relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 45))))
(t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :bottom))
(t/is (= snap-h :right))))
(t/testing "Overlay center relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 30))))
(t/is (= (:y overlay-pos) 30))
(t/is (= snap-v :center))
(t/is (= snap-h :center))))
(t/testing "Overlay manual relative to rect"
(let [i2 (-> interaction-rect
(ctsi/set-overlay-pos-type :manual base-frame objects)
(ctsi/set-overlay-position (gpt/point 12 62)))
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
[overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 17))
(t/is (= (:y overlay-pos) 67))))))
(t/is (= (:y overlay-pos) 67))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))))
(t/deftest animation-checks

View File

@@ -3,10 +3,10 @@ LABEL maintainer="Andrey Antukh <niwi@niwi.nz>"
ARG DEBIAN_FRONTEND=noninteractive
ENV NODE_VERSION=v18.15.0 \
CLOJURE_VERSION=1.11.1.1257 \
CLJKONDO_VERSION=2023.03.17 \
BABASHKA_VERSION=1.3.176 \
ENV NODE_VERSION=v18.16.1 \
CLOJURE_VERSION=1.11.1.1347 \
CLJKONDO_VERSION=2023.05.26 \
BABASHKA_VERSION=1.3.181 \
LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8
@@ -104,16 +104,12 @@ RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
ESUM='1c4be9aa173cb0deb0d215643d9509c8900e5497290b29eee4bee335fa57984f'; \
BINARY_URL='https://github.com/adoptium/temurin19-binaries/releases/download/jdk-19.0.2%2B7/OpenJDK19U-jdk_aarch64_linux_hotspot_19.0.2_7.tar.gz'; \
;; \
armhf|armv7l) \
ESUM='6a51cb3868b5a3b81848a0d276267230ff3f8639f20ba9ae9ef1d386440bf1fd'; \
BINARY_URL='https://github.com/adoptium/temurin19-binaries/releases/download/jdk-19.0.2%2B7/OpenJDK19U-jdk_arm_linux_hotspot_19.0.2_7.tar.gz'; \
ESUM='b16c0271899de1f0e277dc0398bfff11b54511765f104fa938929ac484dc926d'; \
BINARY_URL='https://github.com/adoptium/temurin20-binaries/releases/download/jdk-20.0.1%2B9/OpenJDK20U-jdk_aarch64_linux_hotspot_20.0.1_9.tar.gz'; \
;; \
amd64|x86_64) \
ESUM='3a3ba7a3f8c3a5999e2c91ea1dca843435a0d1c43737bd2f6822b2f02fc52165'; \
BINARY_URL='https://github.com/adoptium/temurin19-binaries/releases/download/jdk-19.0.2%2B7/OpenJDK19U-jdk_x64_linux_hotspot_19.0.2_7.tar.gz'; \
ESUM='43ad054f135a7894dc87ad5d10ad45d8e82846186515892acdbc17c2c5cd27e4'; \
BINARY_URL='https://github.com/adoptium/temurin20-binaries/releases/download/jdk-20.0.1%2B9/OpenJDK20U-jdk_x64_linux_hotspot_20.0.1_9.tar.gz'; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \

View File

@@ -40,7 +40,10 @@ http {
'' close;
}
# include /etc/nginx/sites-enabled/*;
proxy_cache_path /tmp/cache/ levels=2:2 keys_zone=penpot:20m;
proxy_cache_methods GET HEAD;
proxy_cache_valid any 48h;
proxy_cache_key "$host$request_uri";
server {
listen 3449 default_server;
@@ -89,14 +92,26 @@ http {
error_page 301 302 307 = @handle_redirect;
}
location ~ ^/github/penpot-files/(?<template_file>[a-zA-Z0-9\-\_\.]+) {
proxy_pass https://raw.githubusercontent.com/penpot/penpot-files/main/$template_file;
location /internal/gfonts/css {
proxy_pass https://fonts.googleapis.com/css?$args;
proxy_hide_header Access-Control-Allow-Origin;
proxy_set_header User-Agent "curl/7.74.0";
proxy_set_header Host "raw.githubusercontent.com";
proxy_hide_header Cross-Origin-Resource-Policy;
proxy_hide_header Link;
proxy_hide_header Alt-Svc;
proxy_hide_header Cache-Control;
proxy_hide_header Expires;
proxy_ignore_headers Set-Cookie Vary Cache-Control Expires;
proxy_set_header User-Agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36";
proxy_set_header Host "fonts.googleapis.com";
proxy_set_header Accept "*/*";
proxy_cache penpot;
add_header Access-Control-Allow-Origin $http_origin;
proxy_buffering off;
add_header Cache-Control max-age=86400;
add_header X-Cache-Status $upstream_cache_status;
}
location /internal/assets {
@@ -142,10 +157,53 @@ http {
}
location / {
location ~ ^/github/penpot-files/(?<template_file>[a-zA-Z0-9\-\_\.]+) {
proxy_pass https://raw.githubusercontent.com/penpot/penpot-files/main/$template_file;
proxy_hide_header Access-Control-Allow-Origin;
proxy_set_header User-Agent "curl/7.74.0";
proxy_set_header Host "raw.githubusercontent.com";
proxy_set_header Accept "*/*";
add_header Access-Control-Allow-Origin $http_origin;
proxy_buffering off;
}
location ~ ^/internal/gfonts/font/(?<font_file>.+) {
proxy_pass https://fonts.gstatic.com/s/$font_file;
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Cross-Origin-Resource-Policy;
proxy_hide_header Link;
proxy_hide_header Alt-Svc;
proxy_hide_header Cache-Control;
proxy_hide_header Expires;
proxy_hide_header Cross-Origin-Opener-Policy;
proxy_hide_header Report-To;
proxy_ignore_headers Set-Cookie Vary Cache-Control Expires;
proxy_set_header User-Agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36";
proxy_set_header Host "fonts.gstatic.com";
proxy_set_header Accept "*/*";
proxy_cache penpot;
add_header Access-Control-Allow-Origin $http_origin;
add_header Cache-Control max-age=86400;
add_header X-Cache-Status $upstream_cache_status;
}
location ~ ^/(/|css|fonts|images|js|wasm) {
}
location ~ ^/[^/]+/(.*)$ {
return 301 " /404";
}
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
try_files $uri /index.html$is_args$args =404;
}
}
}

View File

@@ -131,6 +131,15 @@ http {
location / {
add_header Cache-Control "no-cache, max-age=0";
location ~ ^/(/|css|fonts|images|js|wasm) {
}
location ~ ^/[^/]+/(.*)$ {
return 301 " /404";
}
try_files $uri /index.html$is_args$args =404;
}
}
}

View File

@@ -104,7 +104,7 @@ WORKDIR /opt/penpot/exporter
USER penpot:penpot
RUN set -ex; \
yarn; \
yarn run playwright install chromium;
yarn --network-timeout 1000000; \
yarn --network-timeout 1000000 run playwright install chromium;
CMD ["node", "app.js"]

View File

@@ -45,6 +45,11 @@ http {
'' close;
}
proxy_cache_path /tmp/cache/ levels=2:2 keys_zone=penpot:20m;
proxy_cache_methods GET HEAD;
proxy_cache_valid any 48h;
proxy_cache_key "$host$request_uri";
server {
listen 80 default_server;
server_name _;
@@ -88,6 +93,28 @@ http {
error_page 301 302 307 = @handle_redirect;
}
location /internal/gfonts/css {
proxy_pass https://fonts.googleapis.com/css?$args;
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Cross-Origin-Resource-Policy;
proxy_hide_header Link;
proxy_hide_header Alt-Svc;
proxy_hide_header Cache-Control;
proxy_hide_header Expires;
proxy_ignore_headers Set-Cookie Vary Cache-Control Expires;
proxy_set_header User-Agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36";
proxy_set_header Host "fonts.googleapis.com";
proxy_set_header Accept "*/*";
proxy_cache penpot;
add_header Access-Control-Allow-Origin $http_origin;
add_header Cache-Control max-age=86400;
add_header X-Cache-Status $upstream_cache_status;
}
location /internal/assets {
internal;
alias /opt/data/assets;
@@ -109,6 +136,31 @@ http {
}
location / {
location ~ ^/internal/gfonts/font/(?<font_file>.+) {
proxy_pass https://fonts.gstatic.com/s/$font_file;
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Cross-Origin-Resource-Policy;
proxy_hide_header Link;
proxy_hide_header Alt-Svc;
proxy_hide_header Cache-Control;
proxy_hide_header Expires;
proxy_hide_header Cross-Origin-Opener-Policy;
proxy_hide_header Report-To;
proxy_ignore_headers Set-Cookie Vary Cache-Control Expires;
proxy_set_header User-Agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36";
proxy_set_header Host "fonts.gstatic.com";
proxy_set_header Accept "*/*";
proxy_cache penpot;
add_header Access-Control-Allow-Origin $http_origin;
add_header Cache-Control max-age=86400;
add_header X-Cache-Status $upstream_cache_status;
}
location ~* \.(js|css).*$ {
add_header Cache-Control "max-age=86400" always; # 24 hours
}
@@ -116,7 +168,16 @@ http {
location ~* \.(html).*$ {
add_header Cache-Control "no-cache, max-age=0" always;
}
location ~ ^/(/|css|fonts|images|js|wasm) {
}
location ~ ^/[^/]+/(.*)$ {
return 301 " /404";
}
root /var/www/app/;
try_files $uri /index.html$is_args$args =404;
}
}
}

View File

@@ -23,11 +23,15 @@
(declare ^:private assoc-file-name)
(declare prepare-exports)
;; Regex to clean namefiles
(def sanitize-file-regex #"[\\/:*?\"<>|]")
(s/def ::file-id ::us/uuid)
(s/def ::filename ::us/string)
(s/def ::name ::us/string)
(s/def ::object-id ::us/uuid)
(s/def ::page-id ::us/uuid)
(s/def ::share-id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::scale ::us/number)
(s/def ::suffix ::us/string)
@@ -35,7 +39,8 @@
(s/def ::wait ::us/boolean)
(s/def ::export
(s/keys :req-un [::page-id ::file-id ::object-id ::type ::suffix ::scale ::name]))
(s/keys :req-un [::page-id ::file-id ::object-id ::type ::suffix ::scale ::name]
:opt-un [::share-id]))
(s/def ::exports
(s/coll-of ::export :kind vector? :min-count 1))
@@ -89,7 +94,6 @@
proc (-> (rd/render export on-progress)
(p/then (constantly resource))
(p/catch on-error))]
(if wait
(p/then proc #(assoc exchange :response/body (dissoc % :path)))
(assoc exchange :response/body (dissoc resource :path)))))
@@ -133,7 +137,7 @@
:on-progress on-progress)
append (fn [{:keys [filename path] :as object}]
(rsc/add-to-zip! zip path filename))
(rsc/add-to-zip! zip path (str/replace filename sanitize-file-regex "_")))
proc (-> (p/do
(p/loop [exports (seq exports)]
@@ -143,9 +147,7 @@
(p/recur (rest exports)))))
(.finalize zip))
(p/then (constantly resource))
(p/catch on-error))
]
(p/catch on-error))]
(if wait
(p/then proc #(assoc exchange :response/body (dissoc % :path)))
(assoc exchange :response/body (dissoc resource :path)))))
@@ -188,6 +190,7 @@
(process-partition [[part1 :as part]]
{:file-id (:file-id part1)
:page-id (:page-id part1)
:share-id (:share-id part1)
:name (:name part1)
:token token
:type (:type part1)

View File

@@ -18,12 +18,14 @@
(s/def ::type #{:jpeg :png :pdf :svg})
(s/def ::page-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::share-id ::us/uuid)
(s/def ::scale ::us/number)
(s/def ::token ::us/string)
(s/def ::filename ::us/string)
(s/def ::object
(s/keys :req-un [::id ::name ::suffix ::filename]))
(s/keys :req-un [::id ::name ::suffix ::filename]
:opt-un [::share-id]))
(s/def ::objects
(s/coll-of ::object :min-count 1))

View File

@@ -17,7 +17,7 @@
[promesa.core :as p]))
(defn render
[{:keys [file-id page-id token scale type objects] :as params} on-object]
[{:keys [file-id page-id share-id token scale type objects] :as params} on-object]
(letfn [(prepare-options [uri]
#js {:screen #js {:width bw/default-viewport-width
:height bw/default-viewport-height}
@@ -48,9 +48,9 @@
;; take the screnshot of requested objects, one by one
(p/run! (partial render-object page) objects)
nil))]
(p/let [params {:file-id file-id
:page-id page-id
:share-id share-id
:object-id (mapv :id objects)
:route "objects"}
uri (-> (cf/get :public-uri)

View File

@@ -17,7 +17,7 @@
[promesa.core :as p]))
(defn render
[{:keys [file-id page-id token scale type objects] :as params} on-object]
[{:keys [file-id page-id share-id token scale type objects] :as params} on-object]
(letfn [(prepare-options [uri]
#js {:screen #js {:width bw/default-viewport-width
:height bw/default-viewport-height}
@@ -31,6 +31,7 @@
(prepare-uri [base-uri object-id]
(let [params {:file-id file-id
:page-id page-id
:share-id share-id
:object-id object-id
:route "objects"}]
(-> base-uri

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