Compare commits

...

842 Commits

Author SHA1 Message Date
elhombretecla
c565915007 💄 Add new tier two color tokens for ui elements 2025-10-06 09:53:11 +02:00
Eva Marco
2de6b6460e ♻️ Review on switcher component 2025-10-03 15:02:31 +02:00
elhombretecla
f905dfc699 💄 Fix linter errors 2025-10-02 11:05:53 +02:00
elhombretecla
c79f110177 💄 Fix cli errors 2025-10-02 11:05:34 +02:00
elhombretecla
f644b3744a 💄 Fix light theme styles 2025-10-02 11:05:34 +02:00
elhombretecla
0722af3a2f 🎉 Add translation string to switcher label 2025-10-02 11:05:34 +02:00
elhombretecla
b4c6bbb191 💄 Remove css nesting 2025-10-02 11:05:34 +02:00
Eva Marco
cad9d03ca1 🎉 Some fixes 2025-10-02 11:05:34 +02:00
elhombretecla
1d6389a3eb 🎉 Apply fixes and new doc structure 2025-10-02 11:05:34 +02:00
elhombretecla
913a8d3148 🎉 Fix label color and scss variables 2025-10-02 11:05:34 +02:00
elhombretecla
34e3453f24 Use proper classes name convention 2025-10-02 11:05:34 +02:00
elhombretecla
6f362f9211 🎉 Add new component switcher structure 2025-10-02 11:05:34 +02:00
alonso.torres
979b4276ca 🐛 Fix problem with component swapping panel 2025-10-02 09:10:21 +02:00
Elena Torró
a32fe40528 Merge pull request #7409 from penpot/ladybenko-fix-wasm-playwright-ci
🔧 Fix Playwright config in CI to include the wasm build
2025-10-02 09:03:12 +02:00
Natacha
b602df549e Add new shadow icons (#7416)
*  Adds new shadow icons

Signed-off-by: Natacha <natachamenjibar@gmail.com>

*  Add shadow icons

Signed-off-by: Natacha <natachamenjibar@gmail.com>

*  Adds shadow icons

Signed-off-by: Natacha <natachamenjibar@gmail.com>

* 📎 Fix wrong svg

Signed-off-by: Natacha <natachamenjibar@gmail.com>

* 📎 Fix wrong svg

Signed-off-by: Natacha <natachamenjibar@gmail.com>

* 📎 Fix wrong svg

Signed-off-by: Natacha <natachamenjibar@gmail.com>

---------

Signed-off-by: Natacha <natachamenjibar@gmail.com>
2025-10-01 17:11:19 +02:00
Luis de Dios
7f1ab08ec8 🐛 Fix use a pointer cursor for adding variant from the viewport (#7410) 2025-10-01 17:01:07 +02:00
Luis de Dios
1263ea11fa 🐛 Fix order of component menu options in assets tab (#7388)
* 🐛 Reorder component menu options in assets tab

* ♻️ Use new component syntax

* 📚 Add bugfix to changelog

* ♻️ Code restructuring and SCSS improvements
2025-10-01 17:00:27 +02:00
Yamila Moreno
ce26c52b30 👷 Automate docker images creation 2025-10-01 14:54:00 +02:00
Yamila Moreno
5c8b3ac3d6 👷 Automate docker images creation 2025-10-01 13:41:03 +02:00
Aitor Moreno
bd4d576172 Merge pull request #7412 from penpot/elenatorro-fix-loop-all-ancestors
🐛 Break loop when no parent is present
2025-10-01 13:36:16 +02:00
Elena Torro
e10169b3db 🐛 Break loop when no parent is present 2025-10-01 12:43:56 +02:00
Elena Torró
f119a9548d Merge pull request #7411 from penpot/azazeln28-fix-issue-12185-wrong-text-width-height-layout
🐛 Fix wrong text auto width/height layout
2025-10-01 12:40:58 +02:00
Aitor Moreno
c097aef152 🐛 Fix wrong text auto width/height layout 2025-10-01 12:27:38 +02:00
Andrey Antukh
000fa51c73 🐛 Fix zip handling on exporter 2025-10-01 11:56:57 +02:00
Belén Albeza
d815494ffa 🔧 Fix playwright config to do a wasm build 2025-10-01 11:27:13 +02:00
Andrey Antukh
a25ba6b482 📎 Fix incorrect regex for match merge and revert commits 2025-10-01 11:07:21 +02:00
Andrey Antukh
e8434c3370 📎 Update devenv tmux script to start exporter using yarn 2025-10-01 10:59:41 +02:00
Andrey Antukh
7cf4ec2792 ♻️ Make the exporter build as esm module 2025-10-01 10:58:03 +02:00
Andrey Antukh
365ce25996 Merge remote-tracking branch 'origin/staging' into develop 2025-10-01 10:50:19 +02:00
Andrey Antukh
01ef55e4f4 Revert " Add minor improvement to cljs impl logging"
This reverts commit 960b76f760.
2025-10-01 10:48:24 +02:00
Andrey Antukh
3b81c1d750 Revert "♻️ Make the exporter build as esm module"
This reverts commit d0f34f06a9.
2025-10-01 10:47:47 +02:00
Elena Torró
40b34da788 Merge pull request #7269 from penpot/azazeln28-feat-caret-position
🎉 Feat caret position
2025-10-01 09:43:03 +02:00
Aitor Moreno
732c79b7b5 🎉 Add function to retrieve caret position 2025-10-01 09:18:46 +02:00
Andrey Antukh
d0f34f06a9 ♻️ Make the exporter build as esm module 2025-10-01 08:10:37 +02:00
Andrey Antukh
23d5bdd20b 🐛 Add missing poppler-tools dependency on devenv 2025-10-01 08:10:37 +02:00
Andrey Antukh
9f2dc06c95 Add missing srepl helper for disable objects-map feat 2025-10-01 08:10:37 +02:00
Andrey Antukh
62563d28d0 📎 Bump library version to 1.0.9
Mainly fixes dependencies declaration on package.json file
2025-09-30 21:55:17 +02:00
Andrey Antukh
21e2ee9904 🐛 Fix dependencies on library 2025-09-30 21:53:04 +02:00
brian mwenda
e6c418eb9c 🐛 Improve auto-width to fixed conversion logic in layout contexts
Signed-off-by: Brian Mwenda <brian@nathandigital.com>
2025-09-30 21:48:03 +02:00
Luis de Dios
de5ff227d2 🎉 Create variant from the viewport (#7357)
* 🎉 Create variant from the viewport

* ♻️ Use DS styles and new component syntax

* 📎 PR changes
2025-09-30 18:15:17 +02:00
Florian Schroedl
0f67730198 🐛 Dont forward default-value for mismatching tab-type 2025-09-30 14:27:40 +02:00
Florian Schroedl
3da02e2b6b 🐛 Fixes resolved values being prefilled for existing referenced composite token 2025-09-30 14:27:40 +02:00
Florian Schroedl
ab80021fb1 🐛 Fix performance issue on font-family 2025-09-30 14:27:40 +02:00
Xaviju
f31e9b8ac9 🎉 Add blur panel to inspect styles tab (#7397) 2025-09-30 13:08:52 +02:00
Andrey Antukh
7d16515eb7 Add minor enhacements to logging on frontend (#7401)
*  Add logging consistency enhacements on fonts loading

*  Disable data evens ns logging

*  Simplify flags logging on application initialization

*  Improve features logging
2025-09-30 11:59:41 +02:00
Pablo Alba
cd9ba482e3 🐛 Load dependant libraries, and don't allow unload them 2025-09-30 09:55:21 +02:00
David Barragán Merino
dff1ca23d3 📚 Update changelog 2025-09-29 18:08:28 +02:00
Andrey Antukh
c363d4d937 📎 Bump library version 2025-09-29 13:44:14 +02:00
Andrey Antukh
de25a24a6d 🐛 Fix backend repl start issue with jdk 24 2025-09-29 13:35:48 +02:00
Andrey Antukh
accc9a173f Merge remote-tracking branch 'origin/staging' into develop 2025-09-29 13:24:31 +02:00
Andrey Antukh
2d364dde5c Add several minor enhacements to features subsystem
Mainly fixes the team non-inheritable features handling and
removes unnecesary/duplicate checks.
2025-09-29 13:23:16 +02:00
Andrey Antukh
c892a9f254 Integrate objects-map usage on backend and frontend 2025-09-29 13:23:16 +02:00
Andrey Antukh
aaae35fb51 🎉 Add multiplatform impl of ObjectsMap
The new type get influentiated by the ObjectsMap impl on backend
code but with simplier implementation that no longer restricts keys
to UUID type but preserves the same performance characteristics.

This type encodes and decodes correctly both in fressian (backend)
and transit (backend and frontend).

This is an initial implementation and several memory usage
optimizations are still missing.
2025-09-29 13:23:16 +02:00
Andrey Antukh
960b76f760 Add minor improvement to cljs impl logging
Mainly reduce the emmited code, that will contribute to reduce the
bundle size and also adds timestamp to the default output.
2025-09-29 13:23:16 +02:00
Andrey Antukh
d921e7eaa3 📎 Add not-empty generator to schema generator ns 2025-09-29 13:23:16 +02:00
Andrey Antukh
49f06b25fa 📚 Update changelog 2025-09-29 13:23:01 +02:00
Andrey Antukh
5ffb7ae2ec Add warning on using deprecated storage config 2025-09-29 13:23:01 +02:00
Andrey Antukh
27945ace65 Revert deprecated storage config cleaning 2025-09-29 13:23:01 +02:00
María Valderrama
e39bf0b439 Invitations management improvements (#7230)
*  Invitations management improvements

* 📎 Change invite email subject

* 📎 Update icon usage

* ♻️ Fix css file

---------

Co-authored-by: Eva Marco <evamarcod@gmail.com>
2025-09-29 13:18:57 +02:00
Alonso Torres
deee7f7334 Merge pull request #7366 from penpot/niwinz-develop-page-data-type
 Add several enhancements for reduce workspace file load time
2025-09-29 12:43:34 +02:00
Xaviju
20d61cbce2 Create ghost variant for select DS component (#7392) 2025-09-29 12:24:20 +02:00
Andrés Moya
9ad8d3fd08 🔧 Make small improvements from PR comments 2025-09-29 12:16:42 +02:00
Andrés Moya
4c35571336 🔧 Read and modify token themes by id 2025-09-29 12:16:42 +02:00
Andrés Moya
37679b7ec6 🔧 Organize token changes API 2025-09-29 12:16:42 +02:00
Andrés Moya
194eded930 🔧 Unify path name helper functions 2025-09-29 12:16:42 +02:00
Andrés Moya
4e607d8da2 💄 Clarify and reorder interfaces 2025-09-29 12:16:42 +02:00
Andrés Moya
f5fd978a07 🔧 Retrieve tokens from library and not from set 2025-09-29 12:16:42 +02:00
Andrés Moya
b28be62845 🔧 Fix rebase problems 2025-09-29 12:16:42 +02:00
Andrés Moya
d76a5c615c 🔧 Modify token sets by id instead of name and review usage 2025-09-29 12:16:42 +02:00
Andrés Moya
03e05da41e 💄 Normalize some attributes of changes 2025-09-29 12:16:42 +02:00
Andrés Moya
5f886e141a 💄 Minor changes 2025-09-29 12:16:42 +02:00
Andrés Moya
021b8f81ca 🔧 Read token sets by id instead of name 2025-09-29 12:16:42 +02:00
Andrey Antukh
f32112544e Make deleted fonts fixer to run with more granular stragegy
Instead of running it on all the file, only run it to local library
and the current page, reducing considerably the overhead of analyzing
the whole file on each file load.

It stills executes for page each time the page is loaded, and add
some kind of local cache for not doing repeated work each time page
loads is pending to be implemented in other commit.
2025-09-29 12:07:49 +02:00
Andrey Antukh
27e311277a Add logging to frontend repo namespace 2025-09-29 12:07:49 +02:00
Andrey Antukh
b9030fcc73 Add better workspace file indexing strategy
Improve file indexes initialization on workspace.

Instead of initialize indexes for all pages only initialize
indexes for the loaded page.
2025-09-29 12:07:49 +02:00
Andrey Antukh
e1519f0ee4 Integrate objects-map usage on backend and frontend 2025-09-29 12:07:48 +02:00
Andrey Antukh
7fefe6dbc8 🎉 Add multiplatform impl of ObjectsMap
The new type get influentiated by the ObjectsMap impl on backend
code but with simplier implementation that no longer restricts keys
to UUID type but preserves the same performance characteristics.

This type encodes and decodes correctly both in fressian (backend)
and transit (backend and frontend).

This is an initial implementation and several memory usage
optimizations are still missing.
2025-09-29 12:06:56 +02:00
Andrey Antukh
fdf70ae9c1 Fix docstring on common.weak ns function 2025-09-29 12:06:56 +02:00
Andrey Antukh
528315b75c 📎 Add not-empty generator to schema generator ns 2025-09-29 12:06:56 +02:00
Andrey Antukh
42d03a0325 📎 Add several missing imports on repl related namespaces 2025-09-29 12:06:56 +02:00
Andrey Antukh
0346c48b03 Add several minor enhacements to features subsystem
Mainly fixes the team non-inheritable features handling and
removes unnecesary/duplicate checks.
2025-09-29 12:06:56 +02:00
Andrey Antukh
1d54fe2e24 Add support for emit messages without waiting response on worker 2025-09-29 12:06:56 +02:00
Andrey Antukh
255f5af2e3 Add several enhacements to buffer namespace
The changes are just for completenes.
2025-09-29 12:06:56 +02:00
Andrey Antukh
eea65b12dd Add minor improvement to cljs impl logging
Mainly reduce the emmited code, that will contribute to reduce the
bundle size and also adds timestamp to the default output.
2025-09-29 12:06:56 +02:00
Andrey Antukh
d4b7f231c7 🔧 Add missing config for on commit checker 2025-09-29 12:05:09 +02:00
Andrey Antukh
473066cf5c 🔧 Add missing config for on commit checker 2025-09-29 12:04:37 +02:00
Xaviju
5e84bda404 🎉 Add SVG panel to inspect styles tab (#7373) 2025-09-29 09:53:15 +02:00
Andrey Antukh
c1058c7fdb ♻️ Add minor refactor for internal concurrency model
Replace general usage of virtual threads with platform threads
and use virtual threads for lightweight procs such that websocket
connections. This decision is made mainly because virtual threads
does not appear on thread dumps in an easy way so debugging issues
becomes very difficult.

The threads requirement of penpot for serving http requests
is not very big so having so this decision does not really affects
the resource usage.
2025-09-26 14:35:06 +02:00
Andrey Antukh
9d907071aa ⬆️ Update dependencies (#7330)
* ⬆️ Update to JDK25 on the devenv

* ⬆️ Update dependencies

* 🔥 Remove unused flag from devenv backend startup scripts

*  Enable shenandoah gc on backend scripts/repl
2025-09-26 13:43:43 +02:00
Elena Torró
c32b94abcf Merge pull request #7343 from penpot/elenatorro-12118-support-large-svg-files
🐛 Fix parsing large paths with multiple subpaths
2025-09-26 13:35:17 +02:00
Elena Torro
9d8ad0ea6e 🐛 Fix parsing large paths with multiple subpaths 2025-09-26 13:04:47 +02:00
Yamila Moreno
2b1e107a44 Merge pull request #7390 from penpot/yms-add-curl-dependency
🐳 Add curl to the backend image
2025-09-26 11:40:42 +02:00
Yamila Moreno
2196318cfc 🐳 Add curl to the backend image 2025-09-26 11:23:02 +02:00
Yamila Moreno
b3d1701698 Merge pull request #7355 from penpot/yms-docker-update-nginx-entrypoint
🐳 Improve Docker nginx
2025-09-26 10:49:24 +02:00
Yamila Moreno
042bd03beb 🐳 Improve Docker nginx 2025-09-26 10:31:23 +02:00
Juan de la Cruz
a39a127f03 🐛 Fix underline text in template card at carrusel 2025-09-26 09:56:05 +02:00
Pablo Alba
bd665f70bf 💄 Add new library modal UI tweaks 2025-09-25 22:56:27 +02:00
Eva Marco
e184a9a8b9 🐛 Fix context menu on spacing tokens (#7382) 2025-09-25 17:28:46 +02:00
Elena Torró
9b90236b72 Merge pull request #7385 from penpot/elenatorro-improve-image-load-performance
🔧 Improve image parsing performance
2025-09-25 17:20:49 +02:00
Elena Torro
bf6cdf729d 🔧 Improve image parsing performance 2025-09-25 17:17:42 +02:00
Belén Albeza
361bdb4a04 ♻️ Decouple serialization from text/layout models" (#7360)
* ♻️ Move text serialization code to wasm module

* ♻️ Add serializer for TextAlign

* ♻️ Add serializers for TextDirection and TextDecoration

* ♻️ Add serializer for TextTransform

* ♻️ Remove unused font_style from TextLeaf model

* ♻️ Refactor parsing of TextLeaf from bytes

* ♻️ Decouple tight serialization of Paragraph
2025-09-25 16:54:07 +02:00
Elena Torró
3827aa6bd4 Merge pull request #7344 from penpot/elenatorro-11542-truncate-long-font-names-on-fonts-menu
🔧 Use two lines text ellipsis on custom font names
2025-09-25 15:25:50 +02:00
Xaviju
adf7b0df50 🎉 Add visibility panel to inspect styles tab (#7362) 2025-09-25 12:52:43 +02:00
Elena Torro
97b4491a27 🔧 Use two lines text ellipsis on custom font names 2025-09-25 12:49:33 +02:00
Xavier Julian
015bd9e453 🎉 Inspect styles tab: fill panel 2025-09-25 11:31:15 +02:00
Belén Albeza
49d5987b15 💄 Add deprecated namespace and fix import for remaining scss files (#7379) 2025-09-25 11:27:10 +02:00
Belén Albeza
a5e4de97e3 💄 Use deprecated prefix for deprecated scss vars and mixins (#7375) 2025-09-25 09:22:25 +02:00
Alonso Torres
378be9473d 🐛 Fix problem with export size (#7374) 2025-09-25 08:50:31 +02:00
Juan de la Cruz
412cf61d7d 🐛 Remove translations form inspect tab text properties (#7369) 2025-09-25 08:48:41 +02:00
Juan de la Cruz
754a1b6fa2 🐛 Fix loading tips wording (#7368) 2025-09-25 08:48:10 +02:00
Eva Marco
a4ada6dc8a 🐛 Add default flags for tokens (#7367) 2025-09-25 08:47:04 +02:00
Elenzakaleidos
ec94d08f4a 🎉 Update README.md with Variants (#7353)
Update the Readme with new text and image that include Variants as feature

Signed-off-by: Elenzakaleidos <elena.scilinguo@kaleidos.net>
2025-09-25 08:46:02 +02:00
Alonso Torres
b6b2d28464 🐛 Fix problem with flow not being deleted (#7371) 2025-09-24 18:06:26 +02:00
Elena Torro
32770c685a 🐛 Do not add shadows on hidden children 2025-09-24 14:42:57 +02:00
Eva Marco
441dc33e38 ♻️ Add shortcut to scss import paths (#7364)
* 🎉 Add config for shortcut imports

* ♻️ Change import paths
2025-09-24 11:18:34 +02:00
Eva Marco
3f87e768a7 ♻️ Fix color token reviews (#7322)
* ♻️ Fix some review changes

* 🐛 Fix more errors

* 🎉 Create token from colorpicker fixed

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-09-24 11:13:52 +02:00
David Barragán Merino
09e9340ba6 💄 Fix a description and remove an unused event 2025-09-24 09:38:42 +02:00
David Barragán Merino
d5ff7b4144 📎 Add DevEnv builder workflow 2025-09-23 23:26:11 +02:00
David Barragán Merino
ef0aee0a09 📎 Automatically publish github release and docker images with final version tags 2025-09-23 23:25:52 +02:00
Andrey Antukh
1e9682376e Merge remote-tracking branch 'origin/staging' into develop 2025-09-23 12:20:49 +02:00
Andrey Antukh
11b75408fe 🐛 Fix regression on importing binfile-v1 files (#7359) 2025-09-23 11:38:33 +02:00
Andrey Antukh
59f7ede4ff 🐛 Add migration for properly decode all position data on text shapes 2025-09-23 11:34:24 +02:00
Pablo Alba
c9b61745a0 🎉 Switch several variant copies at the same time 2025-09-23 11:31:57 +02:00
Andrés Moya
8954b05d76 🐛 Fix error exporting a file with deleted tokens (#7356) 2025-09-22 17:41:31 +02:00
Aitor Moreno
974b76d7bd Merge pull request #7267 from penpot/azazeln28-feat-text-layout
🎉 Add internal TextContent layout data
2025-09-22 16:21:06 +02:00
Aitor Moreno
f505fcfa0d 🎉 Add internal TextContent layout data 2025-09-22 16:01:23 +02:00
Belén Albeza
e4d610d503 ♻️ Decouple shapes serialization from model (rust) (#7328)
* ♻️ Move shape type serialization to wasm module

* ♻️ Refactor serialization of constraints and vertical alignment into wasm module

* ♻️ Refactor serialization and model of shape blur

* ♻️ Refactor bool serialization to the wasm module

* ♻️ Split wasm::layout into submodules

* ♻️ Refactor serialization of AlignItems, AlignContent, JustifyItems and JustifyContent

* ♻️ Refactor serialization of WrapType and FlexDirection

* ♻️ Refactor serialization of JustifySelf

* ♻️ Refactor serialization of GridCell

* ♻️ Refactor serialization of AlignSelf

* 🐛 Fix AlignSelf not being serialized

* ♻️ Refactor handling of None variants in Raw* enums

* ♻️ Refactor serialization of grid direction

* ♻️ Refactor serialization of GridTrack and GridTrackType

* ♻️ Refactor serialization of Sizing

* ♻️ Refactor serialization of ShadowStyle

* ♻️ Refactor serialization of StrokeCap and StrokeStyle

* ♻️ Refactor serialization of BlendMode

* ♻️ Refactor serialization of FontStyle

* ♻️ Refactor serialization of GrowType
2025-09-22 13:47:54 +02:00
Andrey Antukh
cb4c155b32 📎 Uncomment previously commented migrations 2025-09-22 11:38:52 +02:00
Andrey Antukh
0b346e02ff 🐛 Fix incorrect options pass on decode-file 2025-09-22 11:30:42 +02:00
Madalena Melo
5c23a678cc Merge pull request #7342 from penpot/madalenapmelo-kp-patch-1
📚 Add reference to the Teams section on the Dashboard section
2025-09-22 11:23:29 +02:00
Andrey Antukh
946f641917 📎 Disable possible problematic migrations 2025-09-22 11:12:43 +02:00
David Barragán Merino
fb3923924b 📎 Change the name of some action workflows 2025-09-22 09:58:26 +02:00
Florian Schroedl
c882e8347a Add line-height to composite typography token 2025-09-22 09:52:56 +02:00
Pablo Alba
c1fd1a3b42 📚 Add variants doc for SDK (#7351)
* 📚 Add variants doc for SDK

* 📚 Spelling & style improvements

---------

Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net>
2025-09-21 22:15:27 +02:00
Eva Marco
b1fe32baea ♻️ Remove deprecated @import from scss files (#7347)
* 🐛 Fix import warnings 1 of 2

* 🐛 Fix import warnings 2 of 2

* 🐛 Fix visual tests and format files

* 🐛 Fix mixed declarations on scss
2025-09-19 11:50:08 +02:00
Andrey Antukh
fb7a7d02da Merge pull request #7205 from penpot/niwinz-measures-tokens-backup
♻️ Replace numeric inputs on measure options
2025-09-19 11:44:17 +02:00
Eva Marco
20dfc2a216 🐛 Fix typo on event name (#7350) 2025-09-19 11:40:53 +02:00
Eva Marco
d7d2d36e0a ♻️ Replace measure inputs for numeric input component 2025-09-19 11:28:22 +02:00
Andrey Antukh
07904bcc5d ♻️ Add needed changes to get tokens from sidebar
This reverts commit afe149f702148d86d1dea6cb6a537917ce7202aa.
2025-09-19 10:26:29 +02:00
Andrey Antukh
9686075104 🐛 Fix translations 2025-09-18 12:02:45 +02:00
María Valderrama
436e0e847d 🐛 Fix current version on sidebar 2025-09-18 11:56:47 +02:00
Eva Marco
d50b070a64 🎉 Add usefull mixins to DS (#7340) 2025-09-18 10:47:55 +02:00
Andrey Antukh
80cb48fd6a Merge remote-tracking branch 'origin/staging' into develop 2025-09-18 10:44:21 +02:00
Andrey Antukh
e88039e46a 🐛 Fix future linter issues on wasm shape impl 2025-09-17 16:53:02 +02:00
Andrey Antukh
3c45a8d0b4 Allow delete subscriptions on profile deletion request 2025-09-17 16:53:02 +02:00
Aitor Moreno
c9d71f3b2d 🐛 Fix conflicting shortcuts (text alignment) (#7339) 2025-09-17 16:52:44 +02:00
Madalena Melo
49c6efbc22 📚 Add reference to the Teams section on the Dashboard section
https://tree.taiga.io/project/penpot/task/11806

Signed-off-by: Madalena Melo <madalena.melo@kaleidos.net>
2025-09-17 16:17:24 +02:00
Andrey Antukh
9f37175775 🐛 Fix incorrect path data content initialization on pluings api 2025-09-17 15:19:41 +02:00
andrés gonzález
5ed870cc6e 📚 Update shortcuts docs (#7341) 2025-09-17 14:13:00 +02:00
Andrey Antukh
4fb1c7a630 Merge remote-tracking branch 'origin/staging' into develop 2025-09-17 13:46:49 +02:00
Pablo Alba
2a3d7e470d 📚 Update changelog with variants info (#7335) 2025-09-17 13:45:59 +02:00
Eva Marco
f654eb2dcd 🐛 Fix font weight input placehoder (#7338) 2025-09-17 13:44:11 +02:00
Belén Albeza
c21d705143 🐛 Remove shortcuts for inc/dec line height and letter spacing (#7337) 2025-09-17 12:35:39 +02:00
Andrey Antukh
85c1750706 🐛 Fix backend last migration naming (#7333) 2025-09-17 10:47:14 +02:00
Luis de Dios
e2151409bf 🐛 Fix wrong number of components in the library modal (#7332) 2025-09-17 09:25:23 +02:00
Luis de Dios
4fe6cfc57a 🐛 Fix focus the first property value when creating a variant (#7329) 2025-09-16 23:25:18 +02:00
David Barragán Merino
fd37fdde93 📎 Add release action workflow 2025-09-16 18:06:06 +02:00
David Barragán Merino
66b1d5b7bd Merge remote-tracking branch 'origin/staging' into develop 2025-09-16 16:26:23 +02:00
Andrés Moya
2eed7444b7 🔧 Add migration to automatically fix validation errors 2025-09-16 16:11:58 +02:00
Xavier Julian
2bf7a9dd5f ♻️ Remove unneeded fn parameters 2025-09-16 14:17:14 +02:00
Xavier Julian
7bacd8fbca ♻️ Refactor defmulti fn into case switches 2025-09-16 14:17:14 +02:00
Luis de Dios
ef376fbb7b Add shortcut for creating variant to the shortcuts panel (#7319)
*  Add shortcut for creating variant to the shortcuts panel

* ♻️ Update components to new rumext syntax

* 🐛 Fix unique "key" prop error for each child in a list

* ♻️ Remove deprecated icons and CSS cleanup

* 📎 PR changes
2025-09-16 14:06:47 +02:00
Aitor Moreno
b883882a32 🐛 Fix onboarding select keyboard interaction (#7295) 2025-09-16 13:59:15 +02:00
Pablo Alba
18d5b84b00 🐛 Fix variants events (#7320)
* 🐛 Add missing event add-component-to-variant

* 🐛 Fix event apply-tokens, param applied-to-variant

* 🐛 Fix missing case on event "add new variant"

* 🐛 Fix event combine-as-variants

* 🐛 Fix event variant-edit-property-name

* 🐛 On variants events, change trigger for origin

* 🐛 Split combine-as-variants to not have an optional first parameter
2025-09-16 13:09:23 +02:00
Belén Albeza
e5e11b6383 🔧 Autogenerate serialization values for wasm enums (#7296)
* 🔧 Autogenerate serialization values for wasm enums

* 🔧 Add serializer values to the wasm api

*  Avoid converting to a clojure map the serializer js object

* 🔧 Update watch script for autoserialized enums

* 🐛 Fix missing serializer values
2025-09-16 12:29:14 +02:00
Eva Marco
01e963ae35 🐛 Fix font name hot update (#7316) 2025-09-16 12:23:41 +02:00
Eva Marco
90a80c4b63 🐛 Fix Uppercase on add token button (#7314) 2025-09-16 12:05:55 +02:00
Andrey Antukh
b56f237780 Merge remote-tracking branch 'origin/staging' into develop 2025-09-16 11:38:58 +02:00
Xavier Julian
4970ae3eb4 💄 Align tokens panel vertically to the top 2025-09-16 11:38:33 +02:00
Aitor Moreno
c62fadac47 🐛 Fix fast move with distance (#7302)
* 🐛 Fix fast move with distance

* 📎 Remove duplicated shourtcuts

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-09-16 11:36:21 +02:00
Andrés Moya
a264f84e6c 🔧 Deactivate validation temporarily 2025-09-16 11:34:38 +02:00
Elena Torró
2e21f084fc 🐛 Fix boolean operations on rotated shapes (#7309) 2025-09-15 14:46:56 +02:00
Xavier Julian
55513b9ae5 🎉 Inspect styles tab: layout element panel 2025-09-15 13:39:00 +02:00
Luis de Dios
9311ee4c87 🐛 Fix show in assets panel option for component of variants (#7311) 2025-09-15 13:13:12 +02:00
Eva Marco
07d0062645 🐛 Fix sets shown without color tokens (#7312) 2025-09-15 10:38:06 +02:00
Xavier Julian
f4b38af649 Display border-radius as logical properties in inspect tab 2025-09-15 09:46:01 +02:00
Andrey Antukh
6e7bcd1243 Merge remote-tracking branch 'origin/staging' into develop 2025-09-12 16:55:25 +02:00
Andrey Antukh
b8c8579ff5 Merge pull request #7310 from penpot/niwinz-staging-update-jdk
⬆️ Update node and jdk (patch)
2025-09-12 16:54:49 +02:00
Andrey Antukh
82295c79d4 ⬆️ Update jdk to 24.0.2+12 2025-09-12 16:47:25 +02:00
Andrey Antukh
5174591058 ⬆️ Update nodejs to v22.19.0 2025-09-12 16:44:26 +02:00
Andrés Moya
ed3fc5b8b2 🐛 Fix detaching a nested copy inside a main component (#7304)
* 🐛 Fix detaching a nested copy inside a main component

* 💄 Rename functions for more semantic precission
2025-09-12 16:00:01 +02:00
Pablo Alba
f5f9157786 🐛 Fix paste behavior according to the selected element 2025-09-12 15:17:26 +02:00
Juan de la Cruz
658303fa36 🎉 Add 2.10 release slides (#7293) 2025-09-12 15:06:18 +02:00
Andrey Antukh
6cb0cb7f98 Merge remote-tracking branch 'origin/staging' into develop 2025-09-12 14:49:52 +02:00
Andrey Antukh
84013c21fa Merge pull request #7308 from penpot/niwinz-staging-update-deps
⬆️ Update dependencies
2025-09-12 14:47:53 +02:00
Andrey Antukh
f3062ade39 ⬆️ Update jakarta.mail dependency 2025-09-12 14:28:14 +02:00
Andrey Antukh
ca19d4deeb ⬆️ Update postgresql jdbc dependency 2025-09-12 14:28:14 +02:00
Andrey Antukh
dfceccca3d ⬆️ Update aws s3 sdk version
This transitivelly updates the netty library version
that comes with fixes for several security issues
2025-09-12 14:28:14 +02:00
Luis de Dios
9e2d3b1fa1 🐛 Fix position of annotation for variants (#7306) 2025-09-12 14:05:36 +02:00
Florian Schroedl
4dc0f3b4eb 🐛 Fix make-token throwing because of error in name, keep preview value 2025-09-12 13:49:25 +02:00
andrés gonzález
215288b6b4 📚 Update Design Tokens doc (#7265) 2025-09-12 11:15:23 +02:00
andrés gonzález
2e2c3e7bac 📚 Add doc for variants (#7258) 2025-09-12 11:15:03 +02:00
Xavier Julian
0210b310b7 🎉 Inspect styles tab: layout panel 2025-09-12 10:27:41 +02:00
Eva Marco
ce1e44eda4 ♻️ Refactor set titles (#7301) 2025-09-12 08:46:05 +02:00
Marina López
48825e1e59 Show current penpot version 2025-09-11 13:18:42 +02:00
Florian Schroedl
61cfe2d142 🐛 Fix font-family being split up when restoring from backup value 2025-09-11 12:33:26 +02:00
Eva Marco
2d68f4dfd3 🐛 Fix icons (#7299) 2025-09-11 09:42:11 +02:00
Elena Torró
1e23937aa5 Merge pull request #7291 from penpot/superalex-fix-boolean-and-group-shadows
🐛 Fix boolean and group shadows
2025-09-11 09:27:56 +02:00
Eva Marco
aecaf51953 Add color token on colorpicker (#7197)
*  Add token aplication to colorpicker

* 🐛 Change fn name

* 🐛 Change scss from file

* 🐛 Change color for direct-color

* 🐛 Remove vector from fns

* 🐛 Fix CI

* 🐛 Change color-option name

* 🐛 Fix comments

* 🐛 Remove sets without color tokens
2025-09-11 09:13:43 +02:00
Alejandro Alonso
da05d6b67d 🐛 Fix boolean and group shadows 2025-09-10 15:59:39 +02:00
Alejandro Alonso
99a100ad63 Merge pull request #7264 from penpot/elenatorro-12002-draw-shadows-and-blurs-on-texts-on-surfaces
🐛 Fix text shadows and blur and refactor text rendering
2025-09-10 15:50:33 +02:00
Elena Torró
bd3bcb4b18 Merge pull request #7284 from penpot/superalex-fix-blend-mode
🐛 Fix updating blend mode for shapes
2025-09-10 15:03:17 +02:00
Elena Torró
534c7864fc Merge pull request #7285 from penpot/superalex-fix-cornder-radius
🐛 Fix corner radius
2025-09-10 14:59:06 +02:00
Elena Torro
4bd2eba573 🐛 Fix text shadows and blur and refactor text rendering 2025-09-10 14:20:24 +02:00
Xavier Julian
563f608255 🐛 Display token themes as a string 2025-09-10 13:55:54 +02:00
Alejandro Alonso
382b5e7e3a Merge remote-tracking branch 'origin/staging' into develop 2025-09-10 12:33:54 +02:00
Eva Marco
a503f8ae93 ♻️ Refactor composite token UI (#7287)
* ♻️ Refactor composite token UI

* 🐛 Fix comments
2025-09-10 12:16:39 +02:00
Xavier Julian
e1935fb3fb 🎉 Inspect styles tab: geometry panel 2025-09-10 11:01:19 +02:00
Alejandro Alonso
7ac44009d5 Merge pull request #7288 from penpot/luis-12042-context-menu-variant
🐛 Fix create a variant using the contextual menu
2025-09-09 16:27:35 +02:00
Luis de Dios
f22eef5bf6 🐛 Fix create a variant using the contextual menu 2025-09-09 15:59:04 +02:00
Florian Schroedl
b3763dec3f Typography import-export 2025-09-09 13:30:38 +02:00
Alejandro Alonso
7c61049103 Merge pull request #7257 from penpot/azazeln28-fix-issue-11992-cannot-move-elements-up-or-down
🐛 Fix moving elements up or down while pressing alt
2025-09-09 11:07:58 +02:00
Alejandro Alonso
fe819c6ec4 Merge pull request #7286 from penpot/azazeln28-fix-text-editor-v1-paste
🐛 Fix text editor v1 paste HTML
2025-09-09 11:05:17 +02:00
Aitor Moreno
1a4594a615 🐛 Fix text editor v1 paste HTML 2025-09-09 10:48:15 +02:00
Alejandro Alonso
41751d60d2 🐛 Fix corner radius 2025-09-09 10:24:56 +02:00
Yamila Moreno
8bd0edca46 Merge pull request #7282 from penpot/yms-update-ci
📎 Update CI
2025-09-09 09:30:18 +02:00
David Barragán Merino
d2bff2853f Merge pull request #7283 from penpot/bameda-upgrade-docker-images-dependencies
🐳 Update the version of node and nginx-unprivileged
2025-09-09 09:29:36 +02:00
Alejandro Alonso
e2f22b86c7 🐛 Fix updating blend mode for shapes 2025-09-09 09:19:09 +02:00
Aitor Moreno
ff96f7be85 🐛 Fix moving elements up or down while pressing alt 2025-09-09 09:19:00 +02:00
Alejandro Alonso
108b5ab225 🐛 Fix missing filter-icon 2025-09-09 09:05:42 +02:00
Alejandro Alonso
a403af7ebd 🐛 Fix plugin installation link 2025-09-09 08:47:09 +02:00
Alejandro Alonso
43a238a896 Merge remote-tracking branch 'origin/staging' into develop 2025-09-09 08:40:35 +02:00
Alejandro Alonso
e3c9588c1c Merge pull request #7279 from penpot/palba-variants-events2
🎉 Add "advanced" events to variants
2025-09-09 07:03:22 +02:00
Alejandro Alonso
25b63e5675 Merge pull request #7280 from penpot/palba-fix-variants-duplicate
🐛 Fix bad selection after variant duplicate
2025-09-09 06:49:34 +02:00
David Barragán Merino
6c59d633cd 🐳 Update the version of node and nginx-unprivileged 2025-09-08 18:36:31 +02:00
Yamila Moreno
daa408e291 📎 Update CI 2025-09-08 16:51:05 +02:00
Andrés Moya
bb0a891638 📚 Update changelog 2025-09-08 16:49:27 +02:00
Florian Schrödl
8aed47dad3 Allow references to other typography tokens (#7251) 2025-09-08 16:45:18 +02:00
Pablo Alba
c5bd183f73 🐛 Fix bad selection after variant duplicate 2025-09-08 16:33:38 +02:00
Pablo Alba
06441063f2 Add "advanced" events to variants 2025-09-08 15:33:14 +02:00
Elena Torró
0e23c9f6ab Merge pull request #7278 from penpot/superalex-fix-fill-stroke-opacity-shouldnt-affect-shadows
🐛 Fix fills and strokes opacity shouldn't affect shadows
2025-09-08 14:08:20 +02:00
Alejandro Alonso
8fff9afee6 🐛 Fix fills and strokes opacity shouldn't affect shadows 2025-09-08 13:04:52 +02:00
Alejandro Alonso
9f11a2cb32 🐛 Fix context menu shape ids (#7277)
This reverts commit 1929ee36ed.
2025-09-08 11:59:54 +02:00
Xavier Julian
ff55318c04 🎉 Inspect styles tab: variants panel 2025-09-08 11:59:33 +02:00
Elena Torró
41b7957eff Merge pull request #7274 from penpot/superalex-refactor-drop-shadows
🐛 Fixing nested shadows
2025-09-08 11:38:19 +02:00
Alejandro Alonso
053b2c6248 Merge pull request #7253 from penpot/marina-payments-incorrect-date-plan
🐛 Fix incorrect date displayed for support plan
2025-09-08 11:22:48 +02:00
Alejandro Alonso
7e52aadb98 🐛 Fixing nested shadows 2025-09-08 11:20:03 +02:00
Marina López
fad058ee59 🐛 Fix incorrect date displayed for support plan 2025-09-08 11:06:57 +02:00
Alejandro Alonso
69f41c300f Merge pull request #7199 from penpot/elenatorro-11844-fix-font-long-names
🐛 Fix custom font-long names overflow
2025-09-08 10:48:54 +02:00
Elena Torro
18c5e0b9a8 🐛 Fix font long name overflow 2025-09-08 10:31:35 +02:00
Alejandro Alonso
568c2fd9d7 Merge pull request #7271 from penpot/eva-bugfixing-release
🐛 Fix several bugs
2025-09-08 10:23:05 +02:00
Luis de Dios
794eb78aca ♻️ Refactor icon namespaces (#7262)
* ♻️ Rename old icons as deprecated

* ♻️ Take icons from the namespace for the icon component

* ♻️ Take icons from the namespace for the icon-button component
2025-09-08 10:02:33 +02:00
Florian Schrödl
e761bcac85 🐛 Fix text-transform not unapplyng text-case token (#7272) 2025-09-08 09:34:58 +02:00
Eva Marco
1929ee36ed 🐛 Fix ungroup option missing 2025-09-08 09:18:43 +02:00
Eva Marco
84cccd1b79 🐛 Fix alignment row on single shape selection 2025-09-05 13:54:46 +02:00
Eva Marco
e66d44ca81 🐛 Fix wrong icon on path layer 2025-09-05 13:54:19 +02:00
Eva Marco
2f3b464715 🎉 Add base font size flag (#7270) 2025-09-05 13:36:32 +02:00
Pablo Alba
286e477ad5 🐛 Change variants nesting error texts 2025-09-05 12:34:16 +02:00
Andrés Moya
6e6749f42e 🔧 Add unit tests to apply layout tokens 2025-09-05 11:11:48 +02:00
Andrés Moya
7b6aa0c12a 🐛 Unapply layout item tokens when moving out of a layout 2025-09-05 11:11:48 +02:00
Pablo Alba
409f95ac17 Add basic variants events (#7249)
*  Add basic variants events

*  MR changes
2025-09-04 17:00:09 +02:00
Florian Schroedl
25950be077 🐛 Fix when font-weight is a computed int (math resolver) 2025-09-04 13:06:57 +02:00
Florian Schroedl
5230d54551 🐛 Fix when font-weight is a computed int (math resolver) 2025-09-04 12:23:43 +02:00
Alejandro Alonso
a79be05261 🐛 Fix selection and devtools problem (#7259) 2025-09-04 09:29:38 +02:00
Alejandro Alonso
9eda1d0d78 Merge pull request #7256 from penpot/ladybenko-8371-fix-iconsistent-naming
🐛 Fix inconsistent naming for Flatten
2025-09-04 07:34:10 +02:00
Belén Albeza
f6c4f800c4 📚 Update changelog 2025-09-04 07:13:30 +02:00
Belén Albeza
f363d6a801 Add integration test for Flatten menu option 2025-09-04 07:13:16 +02:00
Belén Albeza
e88ce0d52f 🐛 Unify flatten naming 2025-09-04 07:13:16 +02:00
Alejandro Alonso
fe5fe7a933 Merge pull request #7252 from penpot/mavalroot-typos
🐛 Fix typos
2025-09-04 07:05:37 +02:00
Belén Albeza
9c77296858 🔧 Make the watch script to compile the debug css when not in production env (#7250) 2025-09-03 13:45:11 +02:00
Xavier Julian
34da6b64df 🎉 Inspect styles tab tokens panel 2025-09-03 13:01:38 +02:00
María Valderrama
699cc147b5 🐛 Fix typos 2025-09-03 11:20:12 +02:00
Luis de Dios
b1d792a757 🐛 Fix icons do not appear in swap panel and annotations (#7240)
* 🐛 Fix icons do not appear in swap panel and annotations

* 📎 PR changes
2025-09-03 10:57:47 +02:00
Florian Schroedl
18e6842e35 ♻️ Revert trigger interactive via actionize and propagation 2025-09-03 10:10:34 +02:00
Florian Schroedl
c4481be39f ♻️ Revert trigger interactive via actionize and propagation 2025-09-03 09:42:40 +02:00
Andrés Moya
0df420d353 🐛 Fix setting shape size to zero 2025-09-03 08:57:26 +02:00
Elena Torró
f60b6a4869 Merge pull request #7247 from penpot/ladybenko-11983-textlayout-module
♻️ Refactor into new textlayout module
2025-09-02 17:17:12 +02:00
Belén Albeza
3e02dc550f ♻️ Create type alias for ParagraphBuilderGroup 2025-09-02 15:32:10 +02:00
Belén Albeza
1cf0de395c ♻️ Rename get_children to children (Paragraph) 2025-09-02 15:30:54 +02:00
Belén Albeza
d40b68c004 ♻️ Refactor and rename ParagraphBuilder instantiating from TextContent 2025-09-02 15:22:05 +02:00
Belén Albeza
50b9e8c6e6 ♻️ Rename TextContent::get_width to TextContent::width 2025-09-02 15:07:13 +02:00
Belén Albeza
d25f9cd4bd ♻️ Move auto_width and auto_height to their own textlayout module 2025-09-02 15:03:46 +02:00
Pablo Alba
dac2d31b35 🐛 Don't allow a variant switch when that will provoke a components loop 2025-09-02 15:03:38 +02:00
Florian Schroedl
bedb98ad9f Add context menu for typography 2025-09-02 13:19:45 +02:00
Elena Torró
5f37601122 🐛 Fix different fonts on texts shadows (#7214)
* 🐛 Fix different fonts on texts shadows

* 🔧 Refactor text rendering and move text-decoration logic outside

* 🔧 Use transparency correctly
2025-09-02 12:56:07 +02:00
Pablo Alba
1d3a1a094a 🐛 Missing component copy options on the context menu 2025-09-02 12:49:53 +02:00
Andrés Moya
9652996f07 🐛 Add validation for text shapes with wrong register of overrides 2025-09-02 12:49:41 +02:00
Pablo Alba
796aaed11e 🐛 Fix prop creation on variants move layer 2025-09-02 10:01:30 +02:00
Alejandro Alonso
1da69cfa38 📎 Add next release entries to the changelog 2025-09-01 11:10:09 +02:00
Luis de Dios
2a2735cd67 💄 Adjust design details of some components (#7225) 2025-09-01 11:02:36 +02:00
Elena Torró
0552b6e713 Merge pull request #7189 from penpot/azazeln28-feat-text-playground-fonts
🎉 Add multiple fonts to text editor WASM playground
2025-09-01 10:54:40 +02:00
Alejandro Alonso
30e655b1da Merge pull request #7236 from penpot/eva-fix-padding-sidebar
🐛 Fix wrong spacing
2025-09-01 10:35:21 +02:00
Alejandro Alonso
0aeecc6268 Merge pull request #7234 from penpot/palba-variants-activate-for-everyone
🎉 Activate variants for everyone
2025-09-01 10:27:44 +02:00
Eva Marco
5cec006a76 🐛 Fix wrong spacing 2025-09-01 10:15:52 +02:00
Pablo Alba
307e06372b 🎉 Activate variants for everyone 2025-09-01 09:58:14 +02:00
Xaviju
9f24e76c27 🌐 Convert translation single word to label (#7220) 2025-09-01 08:44:06 +02:00
Xavier Julian
7954eaf529 🎉 Inspect styles tab attributes container box 2025-08-31 14:58:11 +02:00
Eva Marco
fe406b577e 📚 Add comment to not translated string 2025-08-29 14:26:52 +02:00
Eva Marco
76c03af024 ♻️ Fix how files are used on scss files (#7208) 2025-08-29 13:55:50 +02:00
Andrey Antukh
fe5cdcbdc7 🐛 Do not render assets sidebar context menu if it is not open (#7222)
This commit is a workaround to an issue that happens when you performing
multiple selection of several shapes (including components) and an
exception is raised of max depth of updates. The issue is still not
solved, we justo do not render the context menu when user performs
the selection on the workspace.

That issue/exception happens only we have context-menu* component
rendered independently of its visibility and dev-tools open
2025-08-29 13:53:55 +02:00
Florian Schroedl
2479a06f9a 🐛 Fix token create not disabled when creating token without value 2025-08-29 13:41:38 +02:00
Florian Schroedl
fc5e4a821b ♻️ Use interactive update functions only on user actions 2025-08-29 13:38:41 +02:00
Florian Schroedl
3cdbc27de9 Unapply font-weight token when changing font-family 2025-08-29 13:38:41 +02:00
Florian Schroedl
a8ed1371d4 Find closest variant when updating font family 2025-08-29 13:38:41 +02:00
Andrey Antukh
e7bac41c37 Merge pull request #7202 from penpot/niwinz-develop-sidebar-refactor-2
♻️ Refactor right sidebar state management
2025-08-29 12:10:51 +02:00
Andrey Antukh
dede2a8f8e 💄 Fix JS files formatting issues 2025-08-29 11:25:58 +02:00
Andrey Antukh
e94abad3eb Add efficiency refactor for layer-menu* 2025-08-29 11:25:58 +02:00
Andrey Antukh
f8bc6e12a9 Improve efficiency of border radius menu 2025-08-29 11:25:58 +02:00
Andrey Antukh
a2c3208af9 🐛 Fix regression on not updating measures ui on moving frames 2025-08-29 11:25:58 +02:00
Andrey Antukh
a303df9c34 ♻️ Refactor right sidebar state management
Also removing duplicated refs and improve efficiency of
several other refs used on sidebar.
2025-08-29 11:25:58 +02:00
Kelp
bda24f3829 📎 Update Changelog to add font weight token (#7216)
Add font weight to changes.md

Signed-off-by: Kelp <5446186+NatachaMenjibar@users.noreply.github.com>
2025-08-29 11:04:36 +02:00
Elena Torró
94fd3119e8 Merge pull request #7175 from penpot/superalex-fix-clipping
🐛 Fix clipping
2025-08-29 11:03:07 +02:00
María Valderrama
c8091b42a7 🐛 Fix navigation arrows in Libraries & Templates carousel (#7217) 2025-08-29 10:51:00 +02:00
Xaviju
41a859b444 🌐 Recover lost variant translation (#7215) 2025-08-29 09:50:33 +02:00
Aitor Moreno
1cf20f7604 Merge pull request #7203 from penpot/elenatorro-fix-text-decoration-leaf-position
🐛 Fix line height on texts and improve text decoration rendering
2025-08-28 16:21:40 +02:00
Andrey Antukh
629541bc9d 🐛 Fix incorrect recursion on looking boolean parent (#7212) 2025-08-28 16:13:14 +02:00
Alejandro Alonso
44245d1b5f 🐛 Fix position-data validator (#7194) 2025-08-28 15:46:23 +02:00
Luis de Dios
a8692c72c6 Show create variant shortcut also for stand-alone components (#7195) 2025-08-28 15:45:53 +02:00
Elena Torró
7b7da59ca9 Merge pull request #7200 from penpot/ladybenko-11552-fix-font-styles
🐛 Fix new inline text styles not being applied correctly
2025-08-28 15:34:54 +02:00
Elena Torro
b78e3159d1 📚 Add wasm playground internal docs 2025-08-28 15:10:56 +02:00
Luis de Dios
2106028350 🐛 Fix color of variant property names in the design tab (#7204) 2025-08-28 12:51:04 +02:00
Andrey Antukh
c2e7f9dc42 ⬆️ Update base deps (#7207) 2025-08-28 12:50:39 +02:00
Andrey Antukh
8568098c5e 🌐 Validate and rehash translation files 2025-08-28 12:49:35 +02:00
VKing9
c8bc1ef1d2 🌐 Add translations for: Hindi
Currently translated at 94.8% (1816 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2025-08-28 12:46:50 +02:00
Stephan Paternotte
59bd434d05 🌐 Add translations for: Dutch
Currently translated at 97.5% (1869 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2025-08-28 12:46:50 +02:00
Edgars Andersons
3cf1e53340 🌐 Add translations for: Latvian
Currently translated at 96.0% (1839 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2025-08-28 12:46:50 +02:00
Denys Kisil
24f0956630 🌐 Add translations for: Ukrainian (ukr_UA)
Currently translated at 95.8% (1835 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/
2025-08-28 12:46:49 +02:00
Nicola Bortoletto
91991d5b32 🌐 Add translations for: Italian
Currently translated at 95.3% (1826 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2025-08-28 12:46:49 +02:00
Yaron Shahrabani
447b6ed1ab 🌐 Add translations for: Hebrew
Currently translated at 97.3% (1864 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2025-08-28 12:46:49 +02:00
Stas Haas
336486fecd 🌐 Add translations for: German
Currently translated at 89.2% (1710 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2025-08-28 12:46:48 +02:00
Jun Fang
bbd417c119 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 91.8% (1758 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-08-28 12:46:48 +02:00
Ingrid Pigueron
e3ffd45a18 🌐 Add translations for: French
Currently translated at 96.4% (1847 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-08-28 12:46:48 +02:00
Unreal Vision
52a4a46ebd 🌐 Add translations for: French
Currently translated at 96.4% (1847 of 1915 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-08-28 12:46:48 +02:00
Elena Torro
025423a75e 🐛 Fix line height on texts and improve text decoration rendering 2025-08-28 12:40:23 +02:00
Hosted Weblate
a6f17e35dd 🌐 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/
2025-08-28 12:37:27 +02:00
Andrey Antukh
9a8cac3cac Merge remote-tracking branch 'weblate/develop' into develop 2025-08-28 12:36:51 +02:00
Aitor Moreno
884b857d17 🐛 Fix paste RTF crashes text editor (#7196) 2025-08-28 11:53:39 +02:00
Florian Schroedl
a20bbeff79 🔧 Add ff for typography composite token 2025-08-28 10:42:58 +02:00
Eva Marco
2a5f1f870b 🐛 Fix padding of input field component (#7198) 2025-08-28 10:30:33 +02:00
Luis de Dios
cf5303a39c ♻️ Title bar refactor (#7201) 2025-08-28 09:51:53 +02:00
Andrey Antukh
bf1e26c4e6 Merge pull request #7182 from penpot/niwinz-measures-menu-changes
♻️ Add efficiency refactor for sidebar
2025-08-28 08:40:44 +02:00
Andrey Antukh
4713d943d1 ♻️ Add efficiency refactor for workspace sidebars
The main changes are for right sidebar but left sidebar is also
slightly affected beacuse of the move where the active tokes are
resolved.
2025-08-27 17:56:35 +02:00
Eva Marco
df083cb315 🐛 Fix corner case on tooltip positioning 2025-08-27 17:56:35 +02:00
Eva Marco
6401b25964 💄 Format tab-switcher stories jsx file 2025-08-27 17:56:35 +02:00
Andrey Antukh
65f4adc68e Add minor efficiency enhancements to numeric-input* 2025-08-27 17:56:35 +02:00
Andrey Antukh
dfab472522 💄 Add minor cosmetic change to shape layout type helper 2025-08-27 17:56:35 +02:00
Belén Albeza
9759adf747 🐛 Fix new inline text styles not being applied correctly 2025-08-27 16:52:57 +02:00
Andrey Antukh
9ae1a08573 🐛 Make the app.common.time/inst nil safe 2025-08-27 14:17:11 +02:00
Ingrid Pigueron
ddab2cab14 🌐 Add translations for: French
Currently translated at 98.1% (1867 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-08-27 14:15:14 +02:00
Andrey Antukh
cf1a8fff65 Merge remote-tracking branch 'origin/develop' into develop 2025-08-27 13:19:53 +02:00
Andrey Antukh
45be4769d7 Merge remote-tracking branch 'origin/staging' into develop 2025-08-27 13:19:06 +02:00
María Valderrama
a68c41709a 🐛 Fix misleading affordance in saved versions (#7192) 2025-08-27 13:01:54 +02:00
Xaviju
4290bce718 🎉 Inspect tab layout switcher and computed feature (#7166) 2025-08-27 13:01:01 +02:00
Pablo Alba
3dd237002b 🐛 Fix combine as variants from assets tab selects wrong components (#7190) 2025-08-27 13:00:28 +02:00
Andrey Antukh
e0fb112bfb 📎 Update changelog 2025-08-27 12:52:24 +02:00
Andrey Antukh
2629537fd3 Merge branch 'subscriptions-enhancements' into staging 2025-08-27 12:51:34 +02:00
Marina López
cb7711f637 🐛 Fix integration tests 2025-08-27 12:30:48 +02:00
Andrey Antukh
a114e9adb0 Add logging for management update-customer method 2025-08-27 12:02:39 +02:00
Andrey Antukh
5fed5fa158 Add transactions support on management api 2025-08-27 12:00:03 +02:00
Andrey Antukh
6c8873c7f5 🐛 Ensure for-update locking is used on updating profile props 2025-08-27 11:59:35 +02:00
Andrey Antukh
37b0c4adc0 🐛 Add support fror ::db/for-update on sql ns 2025-08-27 11:58:42 +02:00
Florian Schroedl
9106617436 🎉 Add composite typography token 2025-08-27 11:35:56 +02:00
Yamila Moreno
68a95cf0d0 📎 Fix CI inputs 2025-08-27 11:21:34 +02:00
Yamila Moreno
1438632dde 📎 Fix CI inputs 2025-08-27 11:19:42 +02:00
Florian Schroedl
112fa46896 🐛 Fix case-sensitivity and multi word italic in font weight parsing 2025-08-27 11:15:21 +02:00
Yamila Moreno
6da5bbf33a 📎 Fix CI inputs 2025-08-27 11:14:56 +02:00
Yamila Moreno
95faf340c4 📎 Fix CI inputs 2025-08-27 11:13:13 +02:00
Marina López
723ea508df 🐛 Fix missing link for enterprise trial 2025-08-27 10:56:17 +02:00
Yamila Moreno
cbe538261c 📎 Fix CI inputs 2025-08-27 10:42:13 +02:00
Yamila Moreno
1925e6782f 📎 Fix CI inputs 2025-08-27 10:33:17 +02:00
Yamila Moreno
bcd950c141 📎 Fix CI inputs 2025-08-27 10:11:56 +02:00
Aitor Moreno
b215689566 🎉 Add multiple fonts to text editor WASM playground 2025-08-27 09:57:55 +02:00
Pablo Alba
dfe0f64c7c Add variants advanced retrieve tests (#7183) 2025-08-26 16:40:27 +02:00
Andrey Antukh
9de3910526 Add missing impl for Associative on LoadableWeakValueMap (#7188) 2025-08-26 16:39:48 +02:00
Luis de Dios
59eb75d8c3 💄 Style improvements in the component list & grid (#7185) 2025-08-26 16:35:09 +02:00
Florian Schroedl
6670b76ccc Show warning when applying token with non-matching font variant 2025-08-26 16:12:07 +02:00
Florian Schroedl
09b9383a0b Choose closest font weight for token weight when no matching weight is found 2025-08-26 16:12:07 +02:00
Eva Marco
31e37f352d 🐛 Fix token option schema (#7186)
* 🐛 Fix token option schema

* 🐛 Fix numeric-input schema
2025-08-26 14:06:59 +02:00
Yamila Moreno
c5958e4d61 📎 Add storybook to CI bundle 2025-08-26 10:45:08 +02:00
Luis de Dios
f1e7149e88 Add shortcuts for creating variants and properties (#7181)
*  Add shortcuts for creating variants and properties

* 📎 PR changes
2025-08-26 09:32:41 +02:00
Pablo Alba
d80ef17623 🐛 Fix cut pasting a variant into its own parent (#7179) 2025-08-26 09:25:52 +02:00
Andrey Antukh
ffe469ce71 Merge pull request #7159 from penpot/niwinz-develop-migrations-fixes
♻️ Add a better approach for load libraries on file validation and migrations
2025-08-26 09:25:23 +02:00
Andrey Antukh
c35bb6e09a 🎉 Add loadable weak map impl for libraries loading on validation and migration 2025-08-26 09:03:25 +02:00
Alejandro Alonso
8d404d97a1 🐛 Fix clipping 2025-08-26 08:52:42 +02:00
Andrey Antukh
fa2b0bd67c Don't migrate libraries on accessing them on file data migrations
We don't migrate the libraries for avoid cascade migration; it is not ideal
but it reduces the total of the required memory needed for process a single
file migration that requires libraries to be loaded.
2025-08-26 08:15:45 +02:00
Andrey Antukh
9563d1b1f6 Merge pull request #6635 from penpot/eva-add-numeric-input
 Add numeric input component
2025-08-25 19:42:47 +02:00
Eva Marco
33fc578f96 🎉 Add numeric-input component to DS
A new numeric-input impl compatible with tokens
2025-08-25 18:52:39 +02:00
Andrey Antukh
79786dde16 🎉 Add helpers for work with weak references and weak data structs 2025-08-25 18:52:39 +02:00
Marina López
926b2c9cfb 🐛 Fix doble click to submit subscription 2025-08-25 15:42:47 +02:00
Andrés Moya
c1b2aa7628 🐛 Add handler to correctly encode cljs dates to json 2025-08-25 13:52:38 +02:00
Andrey Antukh
991b26b73f 🐛 Fix undo transaction handling on creating a variant from group of components 2025-08-25 11:48:46 +02:00
Andrey Antukh
254a7461b2 Simplify commit-undo-transaction impl 2025-08-25 11:48:46 +02:00
Andrey Antukh
1384ed8aba Remove duplicated execution of check-open-transactions 2025-08-25 11:48:46 +02:00
Andrey Antukh
c9393c0cfb Remove repeated/duplucated lookups on start-undo-transaction 2025-08-25 11:48:46 +02:00
Pablo Alba
6eeb55fb88 🐛 Fix after variant switch children layouts aren't updated (#7177) 2025-08-25 11:35:33 +02:00
Andrey Antukh
c759afc10d 🔥 Remove unnecessary and broken unique-editors field
From the get-teams rpc method response
2025-08-25 11:33:34 +02:00
Marina López
090cb63568 🐛 Fix condition for members warning 2025-08-25 11:07:19 +02:00
Andrey Antukh
f223831766 Refresh subscription info when member role is updated 2025-08-25 10:53:48 +02:00
Marina López
854f286364 ♻️ Fix subscriptions inconsistencies 2025-08-25 10:53:48 +02:00
Andrey Antukh
2846b80cf7 Add rpc methods for obtain editors 2025-08-25 10:53:12 +02:00
Andrey Antukh
ad0ef82ffc 🎉 Add management http api 2025-08-25 10:53:12 +02:00
Elena Torró
3bb547fc45 🐛 Parse rx and ry SVG values correctly (#7176) 2025-08-25 10:44:11 +02:00
Pablo Alba
c3b326d95e 🐛 Fix duplicating a page with variants should duplicate them (#7172)
* 🐛 Fix duplicating a page with variants should duplicate them

*  MR changes
2025-08-25 10:41:03 +02:00
Luis de Dios
8c1a97dac5 Make some improvements to the swap panel (#7174)
* 💄 Visual improvements in swap panel

* ♻️ Refactor search-bar component to use DS icons

* 📎 PR changes
2025-08-25 10:40:20 +02:00
Alejandro Alonso
4053e8c8db Merge pull request #7173 from penpot/elenatorro-11735-fix-text-auto-width
🐛 Fix paragraph layout width on autowidth
2025-08-22 15:16:40 +02:00
Xaviju
ee86a3943d Update code to use design system icon (#7145) 2025-08-22 14:27:11 +02:00
Elena Torro
46b3e174ed 🐛 Fix paragraph layout width on autowidth 2025-08-22 13:55:24 +02:00
Alejandro Alonso
c0c2c9489c Merge pull request #7170 from penpot/elenatorro-fix-text-tests
🔧 Update and fix text tests
2025-08-22 13:34:00 +02:00
Elena Torró
c05c179d67 Merge pull request #7167 from penpot/azazeln28-feat-text-editor-wasm-playground
🎉 Add Text Editor WASM Playground
2025-08-22 12:47:41 +02:00
Aitor Moreno
596193d34d 🎉 Add missing styles on text leaves 2025-08-22 12:22:51 +02:00
Aitor Moreno
15eee0d8d8 🎉 Add Text Editor WASM Playground 2025-08-22 12:22:51 +02:00
Elena Torro
0b7444e8ff 🐛 Use SrcIn only when there is only one inner stroke, otherwise use erode filter 2025-08-22 09:54:15 +02:00
Elena Torro
96a91dc710 🔧 Add missing emoji ranges 2025-08-22 09:54:03 +02:00
Elena Torro
503d431d8e 🔧 Fix and update text use cases 2025-08-22 09:53:50 +02:00
Alejandro Alonso
ed5875f29a Merge pull request #7154 from penpot/niwinz-staging-bug-1
🐛 Fix incorrect show request-access dialog on not-found on viewer
2025-08-22 09:19:47 +02:00
Yamila Moreno
b12d44150b 📎 Fix and improve ci 2025-08-21 17:42:37 +02:00
Andrey Antukh
66f5eb57b9 🐛 Fix incorrect fills coerce to binary type when flag is disabled (#7169) 2025-08-21 15:11:14 +02:00
Pablo Alba
7023880e67 🐛 On a variants switch, keep the value of the rotation and its transformations (#7165) 2025-08-21 13:55:16 +02:00
Andrey Antukh
0e4cf23a93 🐛 Fix incorrect type coerce operations (#7168)
A regression introduced in previous commits that causes
a browser tab totally killed by memory usage.
2025-08-21 13:52:47 +02:00
Elena Torró
3f93b0d44b Merge pull request #7098 from penpot/superalex-fix-big-blur-2
🐛 Fix big blur rendering for wasm render
2025-08-21 09:23:26 +02:00
Elena Torró
4a6c3d6ad3 Merge pull request #7152 from penpot/superalex-update-skia-binaries
🎉 Update skia binares to 0.87.0
2025-08-21 09:10:37 +02:00
Alejandro Alonso
478439f055 🐛 Fix big blur rendering for wasm render 2025-08-21 08:47:25 +02:00
Alejandro Alonso
9ef2454210 🎉 Update skia binares to 0.87.0 2025-08-21 07:38:46 +02:00
Andrey Antukh
a702fee3cd Merge pull request #7143 from penpot/luis-11856-adjust-component-title
 Adjust the appearance of the variant-related elements in the design tab
2025-08-20 17:41:50 +02:00
Pablo Alba
cac639d267 🐛 Fix deleting a variant from assets panel (#7147) 2025-08-20 17:38:11 +02:00
Elena Torró
0daf548773 Merge pull request #7146 from penpot/superlaex-fix-open-path-calculation
🐛 Fix open path calculation
2025-08-20 16:38:17 +02:00
Alejandro Alonso
9249a5d4ea Merge pull request #7155 from penpot/elenatorro-use-erode-image-filter-for-inner-strokes-with-opacity
🐛 Fix inner strokes with opacity using erode instead of multiple blending modes
2025-08-20 15:55:50 +02:00
Andrey Antukh
4c83feaa31 Merge remote-tracking branch 'origin/staging' into develop 2025-08-20 13:56:58 +02:00
Luis de Dios
4d8ad19eea 💄 Improve the alignment, spaces and sizes of the variant elements 2025-08-20 13:51:04 +02:00
Luis de Dios
e7e7d576b2 ♻️ Adapt title-bar component 2025-08-20 13:36:10 +02:00
Luis de Dios
7f2af1c355 Adjust component title 2025-08-20 13:36:10 +02:00
Andrey Antukh
ad38a21053 🐛 Fix incorrect show request-access dialog on not-found on viewer
When a user is not-authenticated
2025-08-20 13:35:20 +02:00
Elena Torro
9bb92277e4 🐛 Fix inner strokes with opacity using erode instead of multiple blending modes 2025-08-20 13:32:20 +02:00
Alejandro Alonso
643621a389 Merge pull request #7119 from penpot/niwinz-develop-type-hints
 Add several performance enhancements
2025-08-20 12:58:43 +02:00
Alejandro Alonso
ef8d2e7418 Merge pull request #7082 from penpot/niwinz-staging-changes-fix
♻️ Refactor schema references and openapi.json output
2025-08-20 12:55:16 +02:00
Andrey Antukh
adffac4eec Merge remote-tracking branch 'origin/main' into staging 2025-08-20 12:49:31 +02:00
Andrey Antukh
575342b3bb ♻️ Use direct schemas instead of references
Only a very common use, basic types schemas should be used as
reference (with namespaced keywords)
2025-08-20 12:33:07 +02:00
Alejandro Alonso
b6ecb4368e Merge pull request #7044 from penpot/niwinz-develop-refactor-versions-sidebar
♻️ Refactor versions sidebar
2025-08-20 12:00:28 +02:00
Pablo Alba
081df7fc03 🐛 Fix combine variants move items 2025-08-20 11:07:51 +02:00
Yamila Moreno
358343b4df 📎 Add build tag workflow and minor changes 2025-08-20 10:51:05 +02:00
Andrey Antukh
d7c19325cc Add better type references naming on openapi output 2025-08-20 10:47:56 +02:00
Andrey Antukh
b472a8ab19 Add general improvement to openapi and js-like doc output 2025-08-20 09:41:01 +02:00
Andrey Antukh
2b83d0d0e9 Add generative test case for openapi json serialization
This will prevent possible regression on introducing schemas without
generators or schema with generators that can't be serialized to json.
2025-08-20 09:38:57 +02:00
Andrey Antukh
911ac263fa 🐛 Use ::sm/any instead of any for get-file-fragment rpc method schema
The usage of `any?` predicate as-is uses the standard any generator that
causes to generate java.lang.Character instances created that are not
properly serialiable to JSON. The `::sm/any` schema delimits the
generator to a commonly known serializable types on json.
2025-08-20 09:38:55 +02:00
Andrey Antukh
545c78eb74 🐛 Add missing generators 2025-08-20 09:38:13 +02:00
Andrey Antukh
124b098c92 🔥 Remove already deprecated change spec 2025-08-20 09:38:13 +02:00
Andrey Antukh
43ed430475 📎 Update .gitignore file 2025-08-20 09:38:13 +02:00
Andrey Antukh
edd3b1512e 🐛 Add missing attrs to add-component change schema 2025-08-20 09:38:13 +02:00
Andrey Antukh
d9623c3c88 🐛 Add proper schema for decoding :obj on add-obj change 2025-08-20 09:38:13 +02:00
Andrey Antukh
f052e31ff0 🐛 Fix incorrect handling of assign operation on changes protocol 2025-08-20 09:38:13 +02:00
Yamila Moreno
73dfe12ec9 📚 Update k8s documentation 2025-08-20 09:04:25 +02:00
Alejandro Alonso
0c3d73745e 🐛 Fix open path calculation 2025-08-20 09:00:04 +02:00
Aitor Moreno
a6ecc4fb3c Merge pull request #7106 from penpot/niwinz-develop-modifiers-enhacements
 Several enhancements on how shape data is written on memory
2025-08-19 17:05:25 +02:00
Denys Kisil
625d9ab188 🌐 Add translations for: Ukrainian (ukr_UA)
Currently translated at 97.9% (1863 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/
2025-08-19 11:03:09 +00:00
Jun Fang
e6cc15e19b 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 93.1% (1771 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-08-19 11:03:07 +00:00
王世阳
0af2cd6413 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 93.1% (1771 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-08-19 11:03:05 +00:00
IsCycleBai
443e0b0206 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 93.1% (1771 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-08-19 11:03:04 +00:00
Jun Fang
a4fa5e9304 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 93.1% (1771 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-08-19 11:03:02 +00:00
IsCycleBai
d94c311a1e 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 93.1% (1771 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-08-19 11:03:01 +00:00
Pablo Alba
c0c8390a7d 🐛 Fix restoring component inside flex 2025-08-19 12:15:30 +02:00
Andrey Antukh
f4be117219 🔥 Remove app.common.time/duration usage on frontend (#7139)
Is broken and not necessary; duration class is no longer
available on frontend code.
2025-08-19 10:36:34 +02:00
Andrey Antukh
7eb590e9fd ♻️ Refactor versions sidebar 2025-08-18 21:57:47 +02:00
Andrey Antukh
6588913141 ♻️ Remove several level of unnecesary allocation on writing text 2025-08-18 21:54:36 +02:00
Andrey Antukh
5c4a60aee7 Make mem write helpers receive offset as first arg 2025-08-18 21:54:36 +02:00
Andrey Antukh
af02e12685 🎉 Add missing write-u32 helper to mem ns 2025-08-18 21:54:36 +02:00
Andrey Antukh
675864ce0b Remove incorrect usage of dm/get-prop 2025-08-18 21:54:36 +02:00
Andrey Antukh
c55f3182d8 💄 Rename text-dimensions to get-text-dimensions 2025-08-18 21:49:00 +02:00
Andrey Antukh
0d6eac7656 💄 Add mainly cosmetic changes to set-shape-shadows
Mainly replace a loop with run! that used reduce as impl.
After measuring there are no real difference between using
the more complex loop and more simplier run!; in parity of
performance we prefer simplier approach.
2025-08-18 21:49:00 +02:00
Andrey Antukh
7acfd199aa 💄 Add mainly cosmetic changes to set-layout-child 2025-08-18 21:49:00 +02:00
Andrey Antukh
33d6f543a1 Remove several not necessary allocations from set-grid-layout-cells 2025-08-18 21:49:00 +02:00
Luis de Dios
4237ef572e 🐛 Fix use ellipsis when property names are too long (#7135) 2025-08-18 21:36:56 +02:00
Pablo Alba
6babea8b12 🐛 Fix alert for bad formula not showing in copies of variants (#7126)
* 🐛 Fix alert for bad formula not showing in copies of variants

*  MR changes
2025-08-18 21:36:29 +02:00
Pablo Alba
6b7f91c671 🐛 Fix weird resizing on combine variants with constraints scale (#7134) 2025-08-18 21:35:04 +02:00
Pablo Alba
b3b183c151 🐛 Fix duplicate a variant when all have bad formulae crashes 2025-08-18 17:09:20 +02:00
Yamila Moreno
59f2ee87e6 📎 Improve github actions 2025-08-18 15:28:15 +02:00
Pablo Alba
6af8940a46 🐛 On variant create do not set sizing 2025-08-18 15:14:36 +02:00
Xavier Julian
e1a1110f06 📎 Update changelog with new typography tokens 2025-08-18 13:50:14 +02:00
Pablo Alba
1dcf1e0b0f 🐛 Fix :show-content wasn't on components sync-attrs 2025-08-18 13:17:09 +02:00
Yamila Moreno
4c3e345c9c 📎 Fix github actions 2025-08-18 13:05:45 +02:00
Andrey Antukh
a3b9a9f07b Remove reflection calls from buffer macros 2025-08-18 13:03:10 +02:00
Andrey Antukh
17ec360720 Add several missing type hints for avoid reflection and autoboxing 2025-08-18 13:03:10 +02:00
Andrey Antukh
89b67d59d5 Fix autoboxing on path type impl 2025-08-18 13:03:10 +02:00
Andrey Antukh
77be00014e Remove reflection on geom rect code 2025-08-18 13:03:10 +02:00
Andrey Antukh
e336f287b6 Remove reflection on geom matrix code 2025-08-18 13:03:10 +02:00
Andrey Antukh
50aa6ff306 Remove reflection calls on binfile v3 code 2025-08-18 13:03:10 +02:00
Alejandro Alonso
56f162f219 Merge pull request #7133 from penpot/superalex-fix-create-variant-doesnt-work-2
🐛 Create variant doesn't work
2025-08-18 13:02:33 +02:00
Pablo Alba
f7a0c4139a 🐛 Fix you can add a rect into a variant container (#7137) 2025-08-18 13:02:27 +02:00
Alejandro Alonso
7c39e321c4 Merge pull request #7136 from penpot/niwinz-develop-fix-inconsistencies-on-text-shortcuts
🐛 Fix several inconsistencies and duplicated shortcuts
2025-08-18 12:57:18 +02:00
Andrey Antukh
ce6a863599 🐛 Fix several inconsistencies and duplicated shortcuts 2025-08-18 12:31:09 +02:00
Alejandro Alonso
832690e71e 🐛 Create variant doesn't work 2025-08-18 11:58:24 +02:00
Pablo Alba
7526cb0d71 🐛 Fix on variants rotation override is not preserving properly (#7120) 2025-08-18 11:57:58 +02:00
andrés gonzález
3292109ab0 🐛 Fix typos in modal about variant connections (#7122) 2025-08-18 11:47:23 +02:00
Elena Torró
be376d2030 Merge pull request #7124 from penpot/ladybenko-11799-fix-remove-layout
🐛 Fix removing layout (wasm)
2025-08-18 09:52:22 +02:00
Alejandro Alonso
4d455b5e9f Merge pull request #7125 from penpot/elenatorro-11842-fix-groups-rendering-on-drag
🐛 Fix Group extrect calculation
2025-08-18 09:36:58 +02:00
Edgars Andersons
50ce28e378 🌐 Add translations for: Latvian
Currently translated at 98.4% (1872 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2025-08-15 21:02:02 +02:00
Yaron Shahrabani
1eee8e2ce8 🌐 Add translations for: Hebrew
Currently translated at 99.7% (1897 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2025-08-15 21:02:00 +02:00
Elena Torro
5e6ce9172f 🔧 Return always a shader on merge_fills for consistency 2025-08-14 16:08:03 +02:00
Elena Torró
03adbc2ae8 🔧 Update multiple emoji test and increase resulting image (#7113) 2025-08-14 15:47:55 +02:00
Belén Albeza
e63a3f76f7 🐛 Fix open/close path detection in wasm (#7110)
* 🐛 Fix open/close path detection in wasm

* 💄 Remove leftover code
2025-08-14 15:45:09 +02:00
Elena Torro
6d42d456fb 🐛 Fix Group extrect calculation 2025-08-14 15:39:45 +02:00
Belén Albeza
c818b6f88f 🐛 Fix layout and constraints not being cleared 2025-08-14 15:38:23 +02:00
Belén Albeza
3f3c7905b4 ♻️ Refactor wasm layout functions to their own submodule 2025-08-14 14:17:49 +02:00
Elena Torró
e8dd13beb2 Merge pull request #7118 from penpot/superalex-fix-extrect-invalidation-for-texts
🐛 Fix extrect invalidation for texts
2025-08-14 13:43:42 +02:00
Elena Torró
fc6b64aa68 Merge pull request #7121 from penpot/superalex-fix-set-corners
🐛 Fix set corners
2025-08-14 13:34:43 +02:00
Alejandro Alonso
5e0a2f66e3 🐛 Fix set corners 2025-08-14 11:50:40 +02:00
Pablo Alba
108d4fabba 🐛 Fix menu entry not showing on assets tab (#7115)
* 🐛 Fix menu entry not showing on assets tab

*  MR changes
2025-08-14 10:39:47 +02:00
Pablo Alba
2e277a39ca 🐛 Fix flick on design tab after variant switch (#7116) 2025-08-14 08:14:06 +02:00
Pablo Alba
814ec43714 🐛 Fix variants nesting loops (#7112)
* 🐛 Fix variants nesting loops

*  MR changes
2025-08-14 08:08:31 +02:00
Andrey Antukh
54bb9ea755 Merge remote-tracking branch 'origin/staging' into develop 2025-08-14 08:06:15 +02:00
Pablo Alba
374e921672 🐛 Fix variants change property name multiple selection 2025-08-13 17:29:03 +02:00
Alejandro Alonso
64e5ea93a0 🐛 Fix extrect invalidation for texts 2025-08-13 14:32:23 +02:00
Pablo Alba
2562d70880 🐛 Fix crash dragging external component into a variant without props (#7111) 2025-08-13 14:29:56 +02:00
Alejandro Alonso
d99ef29152 Merge pull request #7029 from penpot/elenatorro-11691-fix-default-text-fill
🔧 Fix text default color and inner stroke opacity
2025-08-13 12:52:14 +02:00
Edgars Andersons
d5a2cd9cd2 🌐 Add translations for: Latvian
Currently translated at 97.8% (1862 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2025-08-13 12:02:14 +02:00
Elena Torro
82d2889e96 🔧 Improve text strokes blending 2025-08-13 11:50:09 +02:00
Eva Marco
ff2e845f2c 🐛 Fix double click on set name input (#7096) 2025-08-13 09:23:53 +02:00
Florian Schrödl
ccd6ae5ade 🐛 Don't allow letter-spacing value with % (#7100) 2025-08-13 08:31:43 +02:00
Yaron Shahrabani
36bafc0d40 📎 Fix typo on translations 2025-08-12 17:58:10 +02:00
Pablo Alba
f7746b8f94 Add create variants in bulk interactions from assets tab (#7102)
*  Add create variants in bulk interactions from assets tab

*  MR changes
2025-08-12 17:56:47 +02:00
Pablo Alba
537c5ca7b8 🐛 Fix missing selection after swap (#7104) 2025-08-12 17:56:03 +02:00
Pablo Alba
4901a80684 🐛 Fix flex layout everrides are not mantained on variant switch (#7105) 2025-08-12 17:55:29 +02:00
Pablo Alba
03b5d44a7c Merge pull request #7101 from penpot/palba-variants-bulk-root
🐛 Fix bad name on variants bulk when the parent is root
2025-08-12 17:04:13 +02:00
Andrey Antukh
8e51aa8df4 🐛 Fix regression on set-shape-children introduced in prev merge 2025-08-12 16:03:34 +02:00
Andrey Antukh
029a9674ca Merge pull request #7103 from penpot/niwinz-develop-modifiers-enhacements
♻️ Sanitize heap write and read operations
2025-08-12 13:11:02 +02:00
Alejandro Alonso
68cee1b1f1 Merge pull request #7076 from penpot/ladybenko-11755-fix-color-picker
🐛 Fix color picker not working with the new renderer
2025-08-12 11:57:21 +02:00
Aitor Moreno
3f74e230b2 Merge pull request #7092 from penpot/superalex-fix-artifacts-while-panning
🐛 Fix artifacts while panning in wasm render
2025-08-12 11:52:18 +02:00
Elena Torró
6bf1919f8d Merge pull request #7094 from penpot/superalex-fix-ctrl-b-for-editor-v2
🐛 Fix ctrl+b for editor v2
2025-08-12 11:36:24 +02:00
Andrey Antukh
e69d61eaf4 Add facilities for work with dataview with common alases 2025-08-12 11:27:13 +02:00
Alejandro Alonso
2f83f22753 🐛 Fix artifacts while panning in wasm render 2025-08-12 11:23:13 +02:00
Andrey Antukh
f9d757bb85 Move several mem write helpers to mem.heap32 ns
For simplify usage and make it clear the required addressing
is used for that functions
2025-08-12 10:53:02 +02:00
Andrey Antukh
6b6e80f4b8 🐛 Fix regression introduced on the set-grid-layout-cells fn
Incorrect data is used for calcultate the size
2025-08-12 10:33:50 +02:00
Andrey Antukh
f32b92a5b0 Assign defaults on serializers instead on api
For make the operations more efficient
2025-08-12 10:33:08 +02:00
Andrey Antukh
761a0a7009 Improve memory write operations on set-grid-layout-rows 2025-08-12 10:32:35 +02:00
Andrey Antukh
129d3e61fa 🎉 Add missing wrap method on buffer abstraction 2025-08-12 10:30:02 +02:00
Stephan Paternotte
aa94671002 🌐 Add translations for: Dutch
Currently translated at 100.0% (1902 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2025-08-12 10:02:02 +02:00
Nicola Bortoletto
52cf136f84 🌐 Add translations for: Italian
Currently translated at 96.7% (1841 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2025-08-12 10:02:01 +02:00
Yaron Shahrabani
808427795c 🌐 Add translations for: Hebrew
Currently translated at 99.6% (1895 of 1902 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2025-08-12 10:01:59 +02:00
Andrey Antukh
3f71734cb4 Remove unnecessary anon fn allocation on set-grid-layout-data
And remove incorrect use of dm/get prop for non statically known
attributes of shape
2025-08-12 09:59:18 +02:00
Andrey Antukh
9f14edb0d7 Remove unnecessary anonymouns fn allocation from set-flex-layout
And also removes usage of dm/get-prop for props that are known to be
not static
2025-08-12 09:59:18 +02:00
Andrey Antukh
7fa7a806a8 Remove unnecesary allocation of corners on wasm api set-shape 2025-08-12 09:59:18 +02:00
Andrey Antukh
d364f4db62 ♻️ Sanitize heap write and read operations
Mainly improves the offset management making it less
error prone, encapsulating the write operation and offeset
management into write-* operations with proper asserts
for the expected heap type.
2025-08-12 09:59:18 +02:00
Andrey Antukh
f2c431d029 Merge pull request #7041 from penpot/alotor-wasm-bools
 Add wasm boolean calculations
2025-08-12 08:07:18 +02:00
Belén Albeza
6a667c30d6 🐛 Fix color picking sometimes not picking color and/or getting stuck in a react infinite update loop 2025-08-11 17:02:12 +02:00
Alejandro Alonso
de637fcf4e 🐛 Fix ctrl+b for editor v2 2025-08-11 14:56:04 +02:00
Aitor Moreno
132069472c Merge pull request #7067 from penpot/superalex-fix-frames-extrect-calculation
🐛 Fix frames extrect calculation
2025-08-11 13:57:29 +02:00
Elena Torro
172c6ad4b8 🔧 Set fill paint as transparent when there are no fills 2025-08-11 13:52:49 +02:00
Andrey Antukh
73a72ec1c7 💄 Add naming and docstring consistency fixes to wasm api 2025-08-11 12:49:01 +02:00
Andrey Antukh
c39a8d84ac 💄 Abstract call to mem/free on wasm api ns 2025-08-11 10:30:14 +02:00
Andrey Antukh
027e5c64cc Reduce compexity on set-shape-children wasm api method 2025-08-11 10:30:14 +02:00
Andrey Antukh
ba42c9b85e Add improved interop between wasm bool and common code 2025-08-11 10:30:14 +02:00
alonso.torres
cd1be43384 Add support for boolean shapes 2025-08-11 10:30:14 +02:00
Andrey Antukh
6176027263 Import translatiosn from weblate
commit 17905edb9d24c9ae60921d94d1367a6e91df2b51
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Mon Aug 11 09:17:44 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 96.1% (1829 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit b57270851a843c64af8698ea7f8300cab1be75cf
Author: Henrik Allberg <henrik@thexorb.com>
Date:   Mon Aug 11 09:19:56 2025 +0200

    🌐 Add translations for: Swedish

    Currently translated at 84.4% (1607 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/

commit 3aa31a7a52ba54126d3d14f6f24ea493f17ef99e
Author: Црнобог <68vuletic@gmail.com>
Date:   Mon Aug 11 09:19:49 2025 +0200

    🌐 Add translations for: Serbian

    Currently translated at 73.0% (1389 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sr/

commit c451f75888be5d27aac35c716375cf722ccb805a
Author: Alejandro Alonso <alejandro.alonso@kaleidos.net>
Date:   Mon Aug 11 09:20:32 2025 +0200

    🌐 Add translations for: Yoruba

    Currently translated at 62.7% (1193 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/yo/

commit 64d084cfef057cdd635874aad961ad1f42cc16ab
Author: Alejandro Alonso <alejandro.alonso@kaleidos.net>
Date:   Mon Aug 11 09:17:58 2025 +0200

    🌐 Add translations for: Igbo

    Currently translated at 27.2% (518 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ig/

commit afee2e44bb22dfd28d55704cb1c387bf33b271ec
Author: Revenant <mohdmuizz22@yahoo.com>
Date:   Mon Aug 11 09:18:44 2025 +0200

    🌐 Add translations for: Malay

    Currently translated at 35.7% (680 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ms/

commit 27a43f53a486f9794e3d739793ca03cf11888240
Author: Alejandro Alonso <alejandro.alonso@kaleidos.net>
Date:   Mon Aug 11 09:17:27 2025 +0200

    🌐 Add translations for: Hausa

    Currently translated at 66.1% (1259 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ha/

commit 76d634a8da529ca27ff4f50d044ef8077b995b42
Author: Stephan Paternotte <stephan@paternottes.net>
Date:   Mon Aug 11 09:19:06 2025 +0200

    🌐 Add translations for: Dutch

    Currently translated at 96.2% (1830 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/

commit eacdded92d1c8be56117e8d5ca0cf99db0d6b506
Author: Edgars Andersons <Edgars+Weblate@gaitenis.id.lv>
Date:   Mon Aug 11 09:18:35 2025 +0200

    🌐 Add translations for: Latvian

    Currently translated at 96.2% (1830 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/

commit 162163d566ef2ff89c3f96e0dbddfc24ea89bbe0
Author: Ņikita K <nikita.kozlovs@gmail.com>
Date:   Mon Aug 11 09:18:31 2025 +0200

    🌐 Add translations for: Latvian

    Currently translated at 96.2% (1830 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/

commit 01275a3458f485aeef190bf6588e0e45e8fad334
Author: Suhwan Kim <jgk9282@gmail.com>
Date:   Mon Aug 11 09:18:19 2025 +0200

    🌐 Add translations for: Korean

    Currently translated at 11.4% (218 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ko/

commit 03ba18cda687c3738bdcb6f49fd179eb449b50a3
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Aug 11 09:20:29 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 96.2% (1830 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit ec77d8ada6f02abd03676febc1b2974c91bc907c
Author: al0cam <benjaminsikac@gmail.com>
Date:   Mon Aug 11 09:17:46 2025 +0200

    🌐 Add translations for: Croatian

    Currently translated at 85.2% (1621 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/

commit fce74589887b1439e73324fea8e145273a1a9236
Author: Zvonimir Juranko <zjuranko@gmail.com>
Date:   Mon Aug 11 09:17:46 2025 +0200

    🌐 Add translations for: Croatian

    Currently translated at 85.2% (1621 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/

commit e16c2c3118755810d07738671cbd2f7c6452e328
Author: TheScientistPT <joao.ed.reis.gomes@gmail.com>
Date:   Mon Aug 11 09:19:29 2025 +0200

    🌐 Add translations for: Portuguese (Portugal)

    Currently translated at 83.5% (1589 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_PT/

commit 7a394c7d4e3d2d43c93218f2e191919a1c23c864
Author: Dário <dariogomes@gmail.com>
Date:   Mon Aug 11 09:19:29 2025 +0200

    🌐 Add translations for: Portuguese (Portugal)

    Currently translated at 83.5% (1589 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_PT/

commit 32c157b0c377fbcefeb83c1f1559d32ed935367f
Author: Amerey.eu <info@amerey.eu>
Date:   Mon Aug 11 09:15:59 2025 +0200

    🌐 Add translations for: Czech

    Currently translated at 84.9% (1615 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/cs/

commit 3abf6a572727a8a93d6e0fac5f037c9383f213b0
Author: Mikel Larreategi <mlarreategi@codesyntax.com>
Date:   Mon Aug 11 09:16:45 2025 +0200

    🌐 Add translations for: Basque

    Currently translated at 61.4% (1169 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/eu/

commit b9165b23d30672f41324dc0135e6d519c0b704d1
Author: Radek Sawicki <radek@sqrc.pl>
Date:   Mon Aug 11 09:19:09 2025 +0200

    🌐 Add translations for: Polish

    Currently translated at 59.9% (1141 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pl/

commit b02c4cc7df984212eee183f813c7f5f16cd4c9eb
Author: Nicola Bortoletto <nicola.bortoletto@live.com>
Date:   Mon Aug 11 09:18:08 2025 +0200

    🌐 Add translations for: Italian

    Currently translated at 96.2% (1830 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/

commit 4eae1c68c2c17d438282a3d5f246609c97bf0064
Author: Valentina Chapellu <valentina.chapellu@gmail.com>
Date:   Mon Aug 11 09:18:04 2025 +0200

    🌐 Add translations for: Italian

    Currently translated at 96.2% (1830 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/

commit 225484e99e58fdf32dd888f70bb95f3070b16dc2
Author: Ahmad HosseinBor <123hozeifeh@gmail.com>
Date:   Mon Aug 11 09:16:52 2025 +0200

    🌐 Add translations for: Persian

    Currently translated at 41.0% (780 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/

commit 56ad686a1bf5634d52929790a315263fa77f2999
Author: william chen <william.fromtw@gmail.com>
Date:   Mon Aug 11 09:20:46 2025 +0200

    🌐 Add translations for: Chinese (Traditional Han script)

    Currently translated at 85.1% (1620 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/

commit 8d7c4c97556f7e5f001183535df09a1c796cdec1
Author: im424 <424@live.hk>
Date:   Mon Aug 11 09:20:46 2025 +0200

    🌐 Add translations for: Chinese (Traditional Han script)

    Currently translated at 85.1% (1620 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/

commit 00eea4f7f6fb6a02cd4256a5269d76d5ee678c3f
Author: Yaron Shahrabani <sh.yaron@gmail.com>
Date:   Mon Aug 11 09:17:37 2025 +0200

    🌐 Add translations for: Hebrew

    Currently translated at 96.2% (1830 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/

commit 23bed6b374ceee65a2c257ea3c32b9a8726a619d
Author: Linerly <linerly@proton.me>
Date:   Mon Aug 11 09:17:52 2025 +0200

    🌐 Add translations for: Indonesian

    Currently translated at 90.3% (1719 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/

commit ab3fb9f1b6b0596f564a1e4ba580a55b2ee5a556
Author: Mahmoud A. Rabo <Mahmoud@s3geeks.com>
Date:   Mon Aug 11 09:15:34 2025 +0200

    🌐 Add translations for: Arabic

    Currently translated at 58.8% (1120 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/

commit e1dd8905c6290e7f634de0e0e5228ac38cdbce4d
Author: AlexTECPlayz <alextec70@outlook.com>
Date:   Mon Aug 11 09:19:36 2025 +0200

    🌐 Add translations for: Romanian

    Currently translated at 68.1% (1296 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ro/

commit fb3401c258f195d23a0f33461cd596eaf10b8751
Author: George Lemon <george@getvasco.com>
Date:   Mon Aug 11 09:19:35 2025 +0200

    🌐 Add translations for: Romanian

    Currently translated at 68.1% (1296 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ro/

commit b8e351851472436c70ecf51a59f87dfbc038c2cf
Author: Allan Nordhøy <epost@anotheragency.no>
Date:   Mon Aug 11 09:18:56 2025 +0200

    🌐 Add translations for: Norwegian Bokmål

    Currently translated at 8.7% (166 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nb_NO/

commit 322b67dabc5332d11e004d1ddbd1109c612a8460
Author: Stas Haas <stas@girafic.de>
Date:   Mon Aug 11 09:16:14 2025 +0200

    🌐 Add translations for: German

    Currently translated at 90.1% (1714 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit c46e2d73039f3dcb6c7840060a7086829287ff80
Author: Pablo Alba <pablo.alba@kaleidos.net>
Date:   Mon Aug 11 09:16:12 2025 +0200

    🌐 Add translations for: German

    Currently translated at 90.1% (1714 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit fbc774fe28c2e051c5f7926135955ad39146eff4
Author: Eranot <renato.konflanz@unochapeco.edu.br>
Date:   Mon Aug 11 09:19:23 2025 +0200

    🌐 Add translations for: Portuguese (Brazil)

    Currently translated at 67.0% (1276 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/

commit a8f110374dc910462a7e83010b93f01c5ef5c514
Author: 王世阳 <wangshiyangchina@gmail.com>
Date:   Mon Aug 11 09:20:38 2025 +0200

    🌐 Add translations for: Chinese (Simplified Han script)

    Currently translated at 72.0% (1370 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/

commit 9a31a6239be28d43157132bfa9d6d7d2543b26ec
Author: Anonymous <noreply@weblate.org>
Date:   Mon Aug 11 09:20:38 2025 +0200

    🌐 Add translations for: Chinese (Simplified Han script)

    Currently translated at 72.0% (1370 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/

commit 024ff5c9ed52bca1112828512c60ee3049d956c0
Author: Merih Güz <iletisim@merihguz.com>
Date:   Mon Aug 11 09:20:17 2025 +0200

    🌐 Add translations for: Turkish

    Currently translated at 75.6% (1438 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/

commit 5de31af3af03876f5af4bdba91c17b4889386fe9
Author: Çağlar Yeşilyurt <grch@mm.st>
Date:   Mon Aug 11 09:20:17 2025 +0200

    🌐 Add translations for: Turkish

    Currently translated at 75.6% (1438 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/

commit 92ad28a35d98930a895bf9c070aeecce3056a643
Author: The_BadUser <vanjavs41@gmail.com>
Date:   Mon Aug 11 09:19:43 2025 +0200

    🌐 Add translations for: Russian

    Currently translated at 75.8% (1442 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/

commit ed9522d50281d56df8ccf7257f028e1ff317b4bc
Author: Vin <k3kelm4vw@mozmail.com>
Date:   Mon Aug 11 09:19:43 2025 +0200

    🌐 Add translations for: Russian

    Currently translated at 75.8% (1442 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/

commit 52b2837ef77449020f18d5fb4a3c8af539fe477e
Author: Anonymous <noreply@weblate.org>
Date:   Mon Aug 11 09:16:19 2025 +0200

    🌐 Add translations for: Greek

    Currently translated at 27.0% (515 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/el/

commit 15656760a1546bc877149797fd322f489cd9030a
Author: Ingrid Pigueron <ingridp.uxr@gmail.com>
Date:   Mon Aug 11 09:17:17 2025 +0200

    🌐 Add translations for: French

    Currently translated at 96.1% (1829 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit d6952275de5e9a83bfe4d2238147c80bcfa1e35b
Author: Unreal Vision <unrealvisionyt@gmail.com>
Date:   Mon Aug 11 09:17:14 2025 +0200

    🌐 Add translations for: French

    Currently translated at 96.1% (1829 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit 828f535facc0a9d3c586fed027b23443b0698ee2
Author: Louis Chance <contact@louischance.com>
Date:   Mon Aug 11 09:17:13 2025 +0200

    🌐 Add translations for: French

    Currently translated at 96.1% (1829 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit 0656f7836a630f409d9fc575e132f250eec7f5a3
Author: Pablo Alba <pablo.alba@kaleidos.net>
Date:   Mon Aug 11 09:17:13 2025 +0200

    🌐 Add translations for: French

    Currently translated at 96.1% (1829 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit 0497701d1ca4134549c5b8bd4419466f87111fcd
Author: Anonymous <noreply@weblate.org>
Date:   Mon Aug 11 09:16:31 2025 +0200

    🌐 Add translations for: Spanish

    Currently translated at 97.2% (1850 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/

commit 22c77ac2bf5fa9f5945bd680b97fafe5779b7324
Author: Andrey Antukh <niwi@niwi.nz>
Date:   Mon Aug 11 09:16:25 2025 +0200

    🌐 Add translations for: Spanish

    Currently translated at 97.2% (1850 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/

commit cdd3b23d7ccb1cf00fa3b66788df69449f012de7
Author: Aryiu <aryiu@users.noreply.hosted.weblate.org>
Date:   Mon Aug 11 09:15:52 2025 +0200

    🌐 Add translations for: Catalan

    Currently translated at 56.6% (1078 of 1902 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/

commit 41ab7871188c93469c9e94ade3c495a7380450e1
Author: Hosted Weblate <hosted@weblate.org>
Date:   Mon Aug 11 09:15:14 2025 +0200

    🌐 Update translation files

    Updated by "Cleanup translation files" hook in Weblate.

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/

commit 4f46b6b42a9660cdceaf231e9e20615b4e64c8fe
Merge: 58bd7c6bd4 2239711f15
Author: Hosted Weblate <hosted@weblate.org>
Date:   Mon Aug 11 09:15:10 2025 +0200

    🌐 Merge branch 'origin/develop' into Weblate.

commit 2239711f15f62c29cb7eb1981874ae81019d4b3e
Author: Stas Haas <stas@girafic.de>
Date:   Tue Jul 29 07:59:24 2025 +0200

    🌐 Add translations for: German

    Currently translated at 92.6% (1721 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit 836068ca8cbc3e72a96bfa4be1d239ad2d516d32
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 08:41:57 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 1c2958825198a6194bf99db5cc50a3a386df98f6
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:58:53 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit c0f884b12350225c897e0f0843e09a02ea1c6639
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:56:18 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4692c0019c9d09a1019b6605d748d8f3144edf68
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:51:22 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 6f74a30b90e39d1cb998dd7a37931d1a55a1bbfb
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:44:43 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit e657464742dc9f151313d4450025fd3ac57a6732
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:39:28 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit a41e8ed7865083123703710fbdacaa5ee9e506fa
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:34:59 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4d27f8cb303b27f9ddba30d2a2fbc2163c920694
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:27:34 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit b5369fa2380beaa190265df4847c81af713bf348
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:03:29 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4d639b62f29e469abce6f463208079e73f70b146
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:02:50 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit ddb5fe44ee8e12f6592b4cbf3f0c9f5a1be3f695
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 06:49:11 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit e702b556e2ae798e22cc966f6eff421cbc6fda81
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 06:42:32 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit f74f8f96e5af79900df3508ee5551d8f85e63558
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 06:40:51 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit f7db30ac4d1855b646e0ece454a2153aaaaeb309
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 06:33:44 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 07fd2353f670b981601f5d51aca3f013483af9a6
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 09:29:04 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit a8b578cbaa69c6bd1c8319b43a96799267eae98d
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 09:25:49 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 12a9927f7b5b257d2909be6c73af72a58ddfb8b6
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 09:24:52 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 2314eab73fa7fcb7b479c16dfb0551a188a7c46e
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 09:23:25 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 88b721c600ff72a2e8bdc4c301f0745a3174fbd5
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 09:17:17 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 14b4124e4cdf4ced296d234b4dbd76afa5a6166c
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 09:12:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit ccaa8da28c65d0256027f2fbf3e556797ef901fb
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 08:16:45 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit e517e2f0152383549a46ac72b4ebc94752dbdbf3
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 08:12:19 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit cb359962bc2aff200c501d8466db1097e93d074e
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 08:03:00 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 71205252a4f4aadeed2cf0b6da41722a6d2e38a8
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:51:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit fe56a9fdd177c4ca624b9dbb88bfa30dc31a92f4
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:48:33 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 3f2d9bf68f466c21557e045fcce7aab76441d9da
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:22:29 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit e41af1831d5f749e341ce163f520ce2dff5fc7de
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:21:22 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit bb8c9129c9c3cff69ce59b7bfe86560bba977e09
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:20:40 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit efd5849fc1e6e76fdf91b21b0a69be2b7e89cbcf
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:18:52 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 0b2403b8bd2682e4a2cfdfe879b34c83ed5a6913
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:13:43 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 8412b71915047cf0d436279b89f93bc0c77f1c20
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:03:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit fe49d3ccd5697ef830bb71086557f8a249c3ed6e
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:02:20 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 48f39509b7a93e501115c3d4cc5d8bb76361b197
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:00:51 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 634df69814a209dc830ffebe471be4e821b849fa
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 06:57:19 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit f2ddf8266af3f4d56351883f5ad845327834fe8b
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 06:50:16 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 27d24e6438d34ecfea68b87d0322d51b05cfdb68
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:49:01 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit bc0afe1b8053bb0258b7b18905714d3cc070c55f
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:40:07 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4f6e0bd6778e8105d248f298319b9771c99347a1
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:36:54 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit faae4f0c18ee77bd2330936448823f3caa7df881
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:35:38 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 5e43671edccc5786f46afba4b1e5c32de39d05a6
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:29:10 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 6aec58217b608f125e327d9b7048fa163984b8b3
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:24:10 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit a20b28bfe96618e507259b6294f6791bc30e1ed3
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:22:34 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 59ee55aa262c7248b9f85f2fdd5b217132b71975
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:21:42 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 3825a6a464c6015f383e90be9155c81da8a1f2c1
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:17:33 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 7fe83a8f347a65e101ec1e01ef30d8791057718f
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 07:39:05 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 52.2% (970 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 7eab18e15ec35f4e7a4eb33fbec17c8fd61c5a65
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 07:28:07 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 52.2% (970 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 66a42acd37df0eecfa409fd460b279565b7c7292
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 07:23:43 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 52.2% (970 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 9eb17f7172141e366eb338b128bf2229deeb1246
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 07:23:08 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 52.2% (970 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 2626d45eb96da5e47a5b90c5ba4aa13362b8eea2
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Mon Jul 7 11:56:38 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 52.2% (970 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit f3719edaa121a9c880cf5f670ed18bb6f1806378
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Mon Jul 7 11:43:06 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 52.2% (970 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 67001aafa71aa9dba8c5e1e927c9ce305e14070e
Author: Stephan Paternotte <stephan@paternottes.net>
Date:   Mon Jul 7 07:02:49 2025 +0200

    🌐 Add translations for: Dutch

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/

commit 4d4f5265c00c384a9ba7bb4bf49461279ca536fd
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 12:37:46 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit f3cc8ae33cbb6941434cf71d7d4b4e037aa0a594
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 12:19:25 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 2d3c0e4a342e1a051d0bf07a580b99a53c12078a
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 12:09:45 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4e394b39599e4854ff53374f2ea900d3ea0d49f7
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 09:27:07 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit ebef981a652cba41d988e4f93f685c640ecd5efe
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 09:24:00 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 6ca42826654fec0fec38631252547323a2226b93
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 09:23:29 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 5bd8c711b63e967ef2657106e8f2e1498a2596e0
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 09:18:58 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 6356069174f83939690751f965b641709a7a708e
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 08:22:38 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit a3107d059ec8cf8762cca2e258e09595651c7e4c
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 08:09:28 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit c0f2f43b0669a38c05c1bae783359d70bb53957d
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 08:01:49 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit d725f53e9ea0c22302485fccc3289e8546124570
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 07:54:46 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 6a50a0db429792a41861281f41b1bafd2bebd64d
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 07:53:55 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit ee382c0d777c65240fdea46222e8249ae131c538
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 11:12:22 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 181d3083e1e03b7b640545479c666a629c3cdacb
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 11:10:31 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit e898179de59525f259d5ccfae59f4f2fca309f3d
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 11:05:22 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit b16596c7b9c3814478c0149e172329a3aa074dd1
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 10:47:59 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit ae70f146dc1b187d53bcba575e4c365b9077d42a
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:40:00 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4be87babf5605533eed83cee114cf9171c7985cb
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:39:23 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 1f4261a6e5686224cfedfebf385572d565b573c7
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:27:26 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 50db4f8bc8fbac02ef880add7e5e2330ab06dab0
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:17:03 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit d2897327a2aeef26cfc4da4ca63291af7504921e
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:13:15 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 5c7f7e4179ab2e510a6fcabb86f66bd1b2827bd6
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:09:13 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit b1d5bbcd0db8a96967cc067bb29f147b2174eb8a
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:06:52 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 2cea4705c972032fc7be9b4fbc047a5422846b13
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:03:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 44d01ba7054306027f006fcfca9afad18e1f08ac
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:59:32 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 50a2f98ac6573d47237578dd1bce6d848cc83c78
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:58:33 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit cef20ab80a59ca5da6a14be2c6b3b6fc57ea4aab
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:58:15 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 214f9c2bb1e75fe99fe7407e77eb273d437782cd
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:54:59 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4f9f7a38f811b1948fb9a34fc57f4e9d0ddf1c1b
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:46:36 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit be7e929ae70344d6f4349eae569a3012bdfe2e2a
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:12:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4db59803d86f78a35538b089f122291a2577716d
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:06:20 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 14aad9500e7c901573effbb5fdff2a8a0bcca036
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:05:29 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 85014b458caa951d798e45000b02f25b9fdad271
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:02:38 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 002a623606025367f33870c8074e2ea486315b5d
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:01:16 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit b72efd33dfeadefcb9f63e68e59cb16fb6366483
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:57:48 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit ab75e51c330561eb132eef8354908fcbd8b3ec09
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:57:18 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 2bae66951b930e872c06443b78c36c225c564438
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:45:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 1151123aeabfcad65410bd44d6e04685d21ba5ed
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:40:17 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 97a4f34e599d4f1ac6da3029cab3d993ee3ab4da
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:35:28 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit a2e1ec123bce716ea0bcc2542809398ec3f65cb1
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:30:19 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 9e72ab6771857ec0814cd0fa22dac6bf1470901a
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:28:38 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 87849347286683544dba68e9a1cbdc9fc06b7ff3
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:22:13 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 1e359566b91bf23270a34ed398b058a635a7de7b
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 30 14:35:56 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit 312ee3f7036615fce7f88b880accce71605470dc
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 30 14:31:42 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit 7ca4e07d12e419e11b2d0079acb8133beac1315a
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 30 14:29:26 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit 3e1db432d2fef95542ea9d0d5e4c164ab2190e11
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 30 14:22:29 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit f6121a315de283a8a0b2e163aeaff48851a42a25
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 30 14:19:07 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit e1720aae76015fba1754575dc743a3021040e04a
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 30 14:15:08 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit a399deae487af3db9127adc89aa39bd813296ecf
Author: Corentin Noël <tintou@noel.tf>
Date:   Fri Jun 27 14:26:07 2025 +0200

    🌐 Add translations for: French

    Currently translated at 99.8% (1855 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit 1c81b4c9b3bac3bc210b16ba9691cc5e2917c896
Author: Ingrid Pigueron <ingridp.uxr@gmail.com>
Date:   Thu Jun 26 11:31:04 2025 +0200

    🌐 Add translations for: French

    Currently translated at 99.8% (1854 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit 0c756f50747fd9a0031e398d6bba4903d402519a
Author: Stas Haas <stas@girafic.de>
Date:   Sun Jun 22 11:35:49 2025 +0200

    🌐 Add translations for: German

    Currently translated at 92.6% (1720 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit eaa9d3e2bccf4f2fb9aa9f23fee530c7efe5e720
Author: al0cam <benjaminsikac@gmail.com>
Date:   Thu Jun 19 14:48:06 2025 +0200

    🌐 Add translations for: Croatian

    Currently translated at 87.4% (1624 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/

commit 8fc4f74bf8e8e754f780a155c2eb8f6d9c51f3b4
Author: Stas Haas <stas@girafic.de>
Date:   Tue Jun 17 09:06:30 2025 +0200

    🌐 Add translations for: German

    Currently translated at 91.9% (1708 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit 4d396ef7f7b1be2ec5f57d671717ae1dbb9419ab
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 16 21:34:47 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit 28dcfb52785fecca69a4bf22ac7222b330d29f9f
Author: al0cam <benjaminsikac@gmail.com>
Date:   Mon Jun 16 07:54:05 2025 +0200

    🌐 Add translations for: Croatian

    Currently translated at 87.2% (1620 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/

commit 680c9a1a0ebe3b323cf1bc7d3f92d7e0bda53c7a
Author: Yaron Shahrabani <sh.yaron@gmail.com>
Date:   Mon Jun 16 18:51:01 2025 +0200

    🌐 Add translations for: Hebrew

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/

commit 6e3f6ed276b74df94a64af294ea4ca80ac1c43f6
Author: Ingrid Pigueron <ingridp.uxr@gmail.com>
Date:   Sat Jun 14 12:53:48 2025 +0200

    🌐 Add translations for: French

    Currently translated at 99.8% (1854 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit feabe2414490e29db3fbabf20a00d890df9907d8
Author: Nicola Bortoletto <nicola.bortoletto@live.com>
Date:   Fri Jun 13 08:26:35 2025 +0200

    🌐 Add translations for: Italian

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/

commit a3b49ee9510b53ae5b010bfbcfb5022a79f6fe1b
Author: Yaron Shahrabani <sh.yaron@gmail.com>
Date:   Thu Jun 12 08:17:05 2025 +0200

    🌐 Add translations for: Hebrew

    Currently translated at 97.0% (1803 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/

commit c775173ec6f5d22964e9b79cc2a80e3cbd08fccb
Author: Stas Haas <stas@girafic.de>
Date:   Thu Jun 12 10:56:20 2025 +0200

    🌐 Add translations for: German

    Currently translated at 90.7% (1686 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit 249051c087fd679f7d5a5c604a65c1eb6fb377d0
Author: Yaron Shahrabani <sh.yaron@gmail.com>
Date:   Wed Jun 11 11:24:21 2025 +0200

    🌐 Add translations for: Hebrew

    Currently translated at 96.9% (1800 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/

commit d33693bf9f8aa419b7688deed03a26926d7b5338
Author: Stas Haas <stas@girafic.de>
Date:   Tue Jun 10 15:05:19 2025 +0200

    🌐 Add translations for: German

    Currently translated at 90.4% (1680 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit c3d4852c7f61ac398aa505eb0f1b3e0a5e6a6f49
Author: Unreal Vision <unrealvisionyt@gmail.com>
Date:   Tue Jun 10 14:59:59 2025 +0200

    🌐 Add translations for: French

    Currently translated at 99.7% (1853 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit 16e6fadb8dd1c2285c64fde7079d6b33c8a7f6a6
Author: Rudra Harsh <harshrudra020@gmail.com>
Date:   Mon Jun 9 15:46:43 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 1.2% (23 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 7653bc6060bc0f6ad0664549cc46a15d7e72ccd7
Author: Stephan Paternotte <stephan@paternottes.net>
Date:   Tue Jun 10 05:46:17 2025 +0200

    🌐 Add translations for: Dutch

    Currently translated at 99.7% (1853 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/

commit 0f65e960b1de543b9133e6742a732e46967d1f83
Author: Edgars Andersons <Edgars+Weblate@gaitenis.id.lv>
Date:   Tue Jun 10 11:51:03 2025 +0200

    🌐 Add translations for: Latvian

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/

commit 05f12ae1bf59986cdc8f5e98ac14c33d6c0e79e2
Author: Nicola Bortoletto <nicola.bortoletto@live.com>
Date:   Mon Jun 9 23:14:58 2025 +0200

    🌐 Add translations for: Italian

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/

commit 2e16f175f57b757a5de4f387caf58331ec5dc822
Author: Yaron Shahrabani <sh.yaron@gmail.com>
Date:   Mon Jun 9 19:01:14 2025 +0200

    🌐 Add translations for: Hebrew

    Currently translated at 96.6% (1794 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/

commit a202b8b663c3a32af7e21f662907d8365fb587c5
Author: Stas Haas <stas@girafic.de>
Date:   Tue Jun 10 14:57:32 2025 +0200

    🌐 Add translations for: German

    Currently translated at 90.0% (1672 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit ffe9682df9a5a65dc1f582844e566cf3eff32a08
Author: Unreal Vision <unrealvisionyt@gmail.com>
Date:   Tue Jun 10 14:56:54 2025 +0200

    🌐 Add translations for: French

    Currently translated at 98.7% (1833 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit f8f4abe8007491f5392bfb1ab5cbfba618e22700
Author: Ingrid Pigueron <ingridp.uxr@gmail.com>
Date:   Mon Jun 9 19:44:09 2025 +0200

    🌐 Add translations for: French

    Currently translated at 98.7% (1833 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit bb6fee5a9ba86eb823c22fae22a49afdcc36c659
Author: Rudra Harsh <harshrudra020@gmail.com>
Date:   Mon Jun 9 15:21:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 0.5% (11 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 55b4c5c07827d4b6eda2ffcc71caca81e6f36534
Author: Madalena Melo <madalena.melo@kaleidos.net>
Date:   Mon Jun 9 11:52:50 2025 +0200

    🌐  Added translation for: Hindi
2025-08-11 09:21:55 +02:00
Andrey Antukh
58bd7c6bd4 Import translations from weblate
commit 2239711f15f62c29cb7eb1981874ae81019d4b3e
Author: Stas Haas <stas@girafic.de>
Date:   Tue Jul 29 07:59:24 2025 +0200

    🌐 Add translations for: German

    Currently translated at 92.6% (1721 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit 836068ca8cbc3e72a96bfa4be1d239ad2d516d32
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 08:41:57 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 1c2958825198a6194bf99db5cc50a3a386df98f6
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:58:53 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit c0f884b12350225c897e0f0843e09a02ea1c6639
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:56:18 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4692c0019c9d09a1019b6605d748d8f3144edf68
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:51:22 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 6f74a30b90e39d1cb998dd7a37931d1a55a1bbfb
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:44:43 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit e657464742dc9f151313d4450025fd3ac57a6732
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:39:28 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit a41e8ed7865083123703710fbdacaa5ee9e506fa
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:34:59 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4d27f8cb303b27f9ddba30d2a2fbc2163c920694
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:27:34 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit b5369fa2380beaa190265df4847c81af713bf348
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:03:29 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4d639b62f29e469abce6f463208079e73f70b146
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 07:02:50 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit ddb5fe44ee8e12f6592b4cbf3f0c9f5a1be3f695
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 06:49:11 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit e702b556e2ae798e22cc966f6eff421cbc6fda81
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 06:42:32 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit f74f8f96e5af79900df3508ee5551d8f85e63558
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 06:40:51 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit f7db30ac4d1855b646e0ece454a2153aaaaeb309
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 10 06:33:44 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 100.0% (1857 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 07fd2353f670b981601f5d51aca3f013483af9a6
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 09:29:04 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit a8b578cbaa69c6bd1c8319b43a96799267eae98d
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 09:25:49 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 12a9927f7b5b257d2909be6c73af72a58ddfb8b6
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 09:24:52 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 2314eab73fa7fcb7b479c16dfb0551a188a7c46e
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 09:23:25 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 88b721c600ff72a2e8bdc4c301f0745a3174fbd5
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 09:17:17 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 14b4124e4cdf4ced296d234b4dbd76afa5a6166c
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 09:12:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit ccaa8da28c65d0256027f2fbf3e556797ef901fb
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 08:16:45 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit e517e2f0152383549a46ac72b4ebc94752dbdbf3
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 08:12:19 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit cb359962bc2aff200c501d8466db1097e93d074e
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 08:03:00 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 71205252a4f4aadeed2cf0b6da41722a6d2e38a8
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:51:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit fe56a9fdd177c4ca624b9dbb88bfa30dc31a92f4
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:48:33 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 3f2d9bf68f466c21557e045fcce7aab76441d9da
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:22:29 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit e41af1831d5f749e341ce163f520ce2dff5fc7de
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:21:22 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit bb8c9129c9c3cff69ce59b7bfe86560bba977e09
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:20:40 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit efd5849fc1e6e76fdf91b21b0a69be2b7e89cbcf
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:18:52 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 0b2403b8bd2682e4a2cfdfe879b34c83ed5a6913
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:13:43 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 8412b71915047cf0d436279b89f93bc0c77f1c20
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:03:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit fe49d3ccd5697ef830bb71086557f8a249c3ed6e
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:02:20 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 48f39509b7a93e501115c3d4cc5d8bb76361b197
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 07:00:51 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 634df69814a209dc830ffebe471be4e821b849fa
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 06:57:19 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit f2ddf8266af3f4d56351883f5ad845327834fe8b
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Wed Jul 9 06:50:16 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 27d24e6438d34ecfea68b87d0322d51b05cfdb68
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:49:01 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit bc0afe1b8053bb0258b7b18905714d3cc070c55f
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:40:07 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4f6e0bd6778e8105d248f298319b9771c99347a1
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:36:54 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit faae4f0c18ee77bd2330936448823f3caa7df881
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:35:38 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 5e43671edccc5786f46afba4b1e5c32de39d05a6
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:29:10 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 6aec58217b608f125e327d9b7048fa163984b8b3
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:24:10 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit a20b28bfe96618e507259b6294f6791bc30e1ed3
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:22:34 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 59ee55aa262c7248b9f85f2fdd5b217132b71975
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:21:42 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 3825a6a464c6015f383e90be9155c81da8a1f2c1
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 09:17:33 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 82.7% (1536 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 7fe83a8f347a65e101ec1e01ef30d8791057718f
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 07:39:05 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 52.2% (970 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 7eab18e15ec35f4e7a4eb33fbec17c8fd61c5a65
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 07:28:07 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 52.2% (970 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 66a42acd37df0eecfa409fd460b279565b7c7292
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 07:23:43 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 52.2% (970 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 9eb17f7172141e366eb338b128bf2229deeb1246
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Tue Jul 8 07:23:08 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 52.2% (970 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 2626d45eb96da5e47a5b90c5ba4aa13362b8eea2
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Mon Jul 7 11:56:38 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 52.2% (970 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit f3719edaa121a9c880cf5f670ed18bb6f1806378
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Mon Jul 7 11:43:06 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 52.2% (970 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 67001aafa71aa9dba8c5e1e927c9ce305e14070e
Author: Stephan Paternotte <stephan@paternottes.net>
Date:   Mon Jul 7 07:02:49 2025 +0200

    🌐 Add translations for: Dutch

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/

commit 4d4f5265c00c384a9ba7bb4bf49461279ca536fd
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 12:37:46 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit f3cc8ae33cbb6941434cf71d7d4b4e037aa0a594
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 12:19:25 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 2d3c0e4a342e1a051d0bf07a580b99a53c12078a
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 12:09:45 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4e394b39599e4854ff53374f2ea900d3ea0d49f7
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 09:27:07 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit ebef981a652cba41d988e4f93f685c640ecd5efe
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 09:24:00 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 6ca42826654fec0fec38631252547323a2226b93
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 09:23:29 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 5bd8c711b63e967ef2657106e8f2e1498a2596e0
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 09:18:58 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 6356069174f83939690751f965b641709a7a708e
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 08:22:38 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit a3107d059ec8cf8762cca2e258e09595651c7e4c
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 08:09:28 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 40.3% (750 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit c0f2f43b0669a38c05c1bae783359d70bb53957d
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 08:01:49 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit d725f53e9ea0c22302485fccc3289e8546124570
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 07:54:46 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 6a50a0db429792a41861281f41b1bafd2bebd64d
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Fri Jul 4 07:53:55 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit ee382c0d777c65240fdea46222e8249ae131c538
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 11:12:22 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 181d3083e1e03b7b640545479c666a629c3cdacb
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 11:10:31 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit e898179de59525f259d5ccfae59f4f2fca309f3d
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 11:05:22 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit b16596c7b9c3814478c0149e172329a3aa074dd1
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 10:47:59 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit ae70f146dc1b187d53bcba575e4c365b9077d42a
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:40:00 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4be87babf5605533eed83cee114cf9171c7985cb
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:39:23 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 1f4261a6e5686224cfedfebf385572d565b573c7
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:27:26 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 50db4f8bc8fbac02ef880add7e5e2330ab06dab0
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:17:03 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit d2897327a2aeef26cfc4da4ca63291af7504921e
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:13:15 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 5c7f7e4179ab2e510a6fcabb86f66bd1b2827bd6
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:09:13 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit b1d5bbcd0db8a96967cc067bb29f147b2174eb8a
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:06:52 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 2cea4705c972032fc7be9b4fbc047a5422846b13
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 09:03:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 44d01ba7054306027f006fcfca9afad18e1f08ac
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:59:32 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 50a2f98ac6573d47237578dd1bce6d848cc83c78
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:58:33 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit cef20ab80a59ca5da6a14be2c6b3b6fc57ea4aab
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:58:15 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 214f9c2bb1e75fe99fe7407e77eb273d437782cd
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:54:59 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4f9f7a38f811b1948fb9a34fc57f4e9d0ddf1c1b
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:46:36 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit be7e929ae70344d6f4349eae569a3012bdfe2e2a
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:12:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 4db59803d86f78a35538b089f122291a2577716d
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:06:20 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 14aad9500e7c901573effbb5fdff2a8a0bcca036
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:05:29 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 85014b458caa951d798e45000b02f25b9fdad271
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:02:38 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 002a623606025367f33870c8074e2ea486315b5d
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 08:01:16 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit b72efd33dfeadefcb9f63e68e59cb16fb6366483
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:57:48 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit ab75e51c330561eb132eef8354908fcbd8b3ec09
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:57:18 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 2bae66951b930e872c06443b78c36c225c564438
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:45:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 1151123aeabfcad65410bd44d6e04685d21ba5ed
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:40:17 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 97a4f34e599d4f1ac6da3029cab3d993ee3ab4da
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:35:28 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit a2e1ec123bce716ea0bcc2542809398ec3f65cb1
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:30:19 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 9e72ab6771857ec0814cd0fa22dac6bf1470901a
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:28:38 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 87849347286683544dba68e9a1cbdc9fc06b7ff3
Author: VKing9 <vaibhavrathod2282@gmail.com>
Date:   Thu Jul 3 07:22:13 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 27.3% (508 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 1e359566b91bf23270a34ed398b058a635a7de7b
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 30 14:35:56 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit 312ee3f7036615fce7f88b880accce71605470dc
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 30 14:31:42 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit 7ca4e07d12e419e11b2d0079acb8133beac1315a
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 30 14:29:26 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit 3e1db432d2fef95542ea9d0d5e4c164ab2190e11
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 30 14:22:29 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit f6121a315de283a8a0b2e163aeaff48851a42a25
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 30 14:19:07 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit e1720aae76015fba1754575dc743a3021040e04a
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 30 14:15:08 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit a399deae487af3db9127adc89aa39bd813296ecf
Author: Corentin Noël <tintou@noel.tf>
Date:   Fri Jun 27 14:26:07 2025 +0200

    🌐 Add translations for: French

    Currently translated at 99.8% (1855 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit 1c81b4c9b3bac3bc210b16ba9691cc5e2917c896
Author: Ingrid Pigueron <ingridp.uxr@gmail.com>
Date:   Thu Jun 26 11:31:04 2025 +0200

    🌐 Add translations for: French

    Currently translated at 99.8% (1854 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit 0c756f50747fd9a0031e398d6bba4903d402519a
Author: Stas Haas <stas@girafic.de>
Date:   Sun Jun 22 11:35:49 2025 +0200

    🌐 Add translations for: German

    Currently translated at 92.6% (1720 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit eaa9d3e2bccf4f2fb9aa9f23fee530c7efe5e720
Author: al0cam <benjaminsikac@gmail.com>
Date:   Thu Jun 19 14:48:06 2025 +0200

    🌐 Add translations for: Croatian

    Currently translated at 87.4% (1624 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/

commit 8fc4f74bf8e8e754f780a155c2eb8f6d9c51f3b4
Author: Stas Haas <stas@girafic.de>
Date:   Tue Jun 17 09:06:30 2025 +0200

    🌐 Add translations for: German

    Currently translated at 91.9% (1708 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit 4d396ef7f7b1be2ec5f57d671717ae1dbb9419ab
Author: Denys Kisil <ossenjoyer@proton.me>
Date:   Mon Jun 16 21:34:47 2025 +0200

    🌐 Add translations for: Ukrainian (ukr_UA)

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/

commit 28dcfb52785fecca69a4bf22ac7222b330d29f9f
Author: al0cam <benjaminsikac@gmail.com>
Date:   Mon Jun 16 07:54:05 2025 +0200

    🌐 Add translations for: Croatian

    Currently translated at 87.2% (1620 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/

commit 680c9a1a0ebe3b323cf1bc7d3f92d7e0bda53c7a
Author: Yaron Shahrabani <sh.yaron@gmail.com>
Date:   Mon Jun 16 18:51:01 2025 +0200

    🌐 Add translations for: Hebrew

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/

commit 6e3f6ed276b74df94a64af294ea4ca80ac1c43f6
Author: Ingrid Pigueron <ingridp.uxr@gmail.com>
Date:   Sat Jun 14 12:53:48 2025 +0200

    🌐 Add translations for: French

    Currently translated at 99.8% (1854 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit feabe2414490e29db3fbabf20a00d890df9907d8
Author: Nicola Bortoletto <nicola.bortoletto@live.com>
Date:   Fri Jun 13 08:26:35 2025 +0200

    🌐 Add translations for: Italian

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/

commit a3b49ee9510b53ae5b010bfbcfb5022a79f6fe1b
Author: Yaron Shahrabani <sh.yaron@gmail.com>
Date:   Thu Jun 12 08:17:05 2025 +0200

    🌐 Add translations for: Hebrew

    Currently translated at 97.0% (1803 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/

commit c775173ec6f5d22964e9b79cc2a80e3cbd08fccb
Author: Stas Haas <stas@girafic.de>
Date:   Thu Jun 12 10:56:20 2025 +0200

    🌐 Add translations for: German

    Currently translated at 90.7% (1686 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit 249051c087fd679f7d5a5c604a65c1eb6fb377d0
Author: Yaron Shahrabani <sh.yaron@gmail.com>
Date:   Wed Jun 11 11:24:21 2025 +0200

    🌐 Add translations for: Hebrew

    Currently translated at 96.9% (1800 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/

commit d33693bf9f8aa419b7688deed03a26926d7b5338
Author: Stas Haas <stas@girafic.de>
Date:   Tue Jun 10 15:05:19 2025 +0200

    🌐 Add translations for: German

    Currently translated at 90.4% (1680 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit c3d4852c7f61ac398aa505eb0f1b3e0a5e6a6f49
Author: Unreal Vision <unrealvisionyt@gmail.com>
Date:   Tue Jun 10 14:59:59 2025 +0200

    🌐 Add translations for: French

    Currently translated at 99.7% (1853 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit 16e6fadb8dd1c2285c64fde7079d6b33c8a7f6a6
Author: Rudra Harsh <harshrudra020@gmail.com>
Date:   Mon Jun 9 15:46:43 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 1.2% (23 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 7653bc6060bc0f6ad0664549cc46a15d7e72ccd7
Author: Stephan Paternotte <stephan@paternottes.net>
Date:   Tue Jun 10 05:46:17 2025 +0200

    🌐 Add translations for: Dutch

    Currently translated at 99.7% (1853 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/

commit 0f65e960b1de543b9133e6742a732e46967d1f83
Author: Edgars Andersons <Edgars+Weblate@gaitenis.id.lv>
Date:   Tue Jun 10 11:51:03 2025 +0200

    🌐 Add translations for: Latvian

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/

commit 05f12ae1bf59986cdc8f5e98ac14c33d6c0e79e2
Author: Nicola Bortoletto <nicola.bortoletto@live.com>
Date:   Mon Jun 9 23:14:58 2025 +0200

    🌐 Add translations for: Italian

    Currently translated at 99.9% (1856 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/

commit 2e16f175f57b757a5de4f387caf58331ec5dc822
Author: Yaron Shahrabani <sh.yaron@gmail.com>
Date:   Mon Jun 9 19:01:14 2025 +0200

    🌐 Add translations for: Hebrew

    Currently translated at 96.6% (1794 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/

commit a202b8b663c3a32af7e21f662907d8365fb587c5
Author: Stas Haas <stas@girafic.de>
Date:   Tue Jun 10 14:57:32 2025 +0200

    🌐 Add translations for: German

    Currently translated at 90.0% (1672 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/

commit ffe9682df9a5a65dc1f582844e566cf3eff32a08
Author: Unreal Vision <unrealvisionyt@gmail.com>
Date:   Tue Jun 10 14:56:54 2025 +0200

    🌐 Add translations for: French

    Currently translated at 98.7% (1833 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit f8f4abe8007491f5392bfb1ab5cbfba618e22700
Author: Ingrid Pigueron <ingridp.uxr@gmail.com>
Date:   Mon Jun 9 19:44:09 2025 +0200

    🌐 Add translations for: French

    Currently translated at 98.7% (1833 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/

commit bb6fee5a9ba86eb823c22fae22a49afdcc36c659
Author: Rudra Harsh <harshrudra020@gmail.com>
Date:   Mon Jun 9 15:21:41 2025 +0200

    🌐 Add translations for: Hindi

    Currently translated at 0.5% (11 of 1857 strings)

    Translation: Penpot/frontend
    Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/

commit 55b4c5c07827d4b6eda2ffcc71caca81e6f36534
Author: Madalena Melo <madalena.melo@kaleidos.net>
Date:   Mon Jun 9 11:52:50 2025 +0200

    🌐  Added translation for: Hindi
2025-08-11 09:14:05 +02:00
Andrey Antukh
f02667e031 Merge remote-tracking branch 'origin/staging' into develop 2025-08-11 09:12:03 +02:00
Alejandro Alonso
8e0a6e4123 🐛 Fix auto height is fixed in the HTML inspect tab for text elements (#7078) 2025-08-11 09:07:43 +02:00
Marina López
0131cd6f8b Display the total price of the subscription and the cap amount (#7088) 2025-08-11 09:07:24 +02:00
Alejandro Alonso
99b40cecf2 Revert "🐛 Fix big blur rendering for wasm render"
This reverts commit c7a4c67d83.
2025-08-09 08:44:52 +02:00
Alejandro Alonso
c7a4c67d83 🐛 Fix big blur rendering for wasm render 2025-08-09 08:42:55 +02:00
Florian Schrödl
c29a8cb0c4 Implement font-weight token (#7089) 2025-08-08 11:11:18 +02:00
Andrey Antukh
288a7b21d6 Merge tag '2.9.0-RC8' 2025-08-08 09:47:42 +02:00
andrés gonzález
32bd08533d 💄 Remove slide about overrides in the release notes (#7086) 2025-08-08 09:46:40 +02:00
Yamila Moreno
c1aae12327 📎 Improve gh actions 2025-08-07 18:08:25 +02:00
Yamila Moreno
23a6f4b7c1 📎 Improve gh actions 2025-08-07 18:07:47 +02:00
Yamila Moreno
a9f4fe84fa 📎 Improve gh actions 2025-08-07 17:51:20 +02:00
Andrey Antukh
133e6e1e68 Merge tag '2.9.0-RC7' 2025-08-07 16:30:30 +02:00
Andrey Antukh
f7832585dc Add tests for snapshot locking (#7085) 2025-08-07 16:27:43 +02:00
Eva Marco
e34bfb50a8 🐛 Fix font variant names for source sans pro font (#7087) 2025-08-07 16:25:15 +02:00
Andrey Antukh
6abd045273 🐛 Add missing generator for token-set file change operation (#7080)
* 🐛 Add missing generator for token-set file change operation

* 🐛 Use ::sm/any instead of :any for on get-file-data-for-thumbnail rpc method

Mainly because :any will use a very generic generator that can generate
instances of Character that are not directly serializable to JSON
2025-08-07 12:36:14 +02:00
Florian Schrödl
0a106c2604 🐛 Fix import of borderWidth (#7084) 2025-08-07 12:16:18 +02:00
Andrey Antukh
8f5f88743b Normalize font variant naming for google fonts (#7083) 2025-08-07 11:14:40 +02:00
Florian Schrödl
9562d2f1f0 Allow font-families with surrounding quotation marks (#7081) 2025-08-07 11:13:04 +02:00
Andrey Antukh
ea482f16c8 💄 Add minor cosmetic changes to dashboard sidebar components (#7052)
* 💄 Change component decl style of sidebar-team-switch

* 💄 Change component decl style of sidebar-search

* 💄 Add general cosmetic changes to sidebar components
2025-08-07 10:57:46 +02:00
Alejandro Alonso
50634e1a4c 🐛 Fix selection lost when using keyboard 2025-08-07 09:28:17 +02:00
Andrey Antukh
56de96d25b Merge remote-tracking branch 'origin/staging' into develop 2025-08-07 08:04:40 +02:00
Luis de Dios
5d1c20c47c 🐛 Fix focus new added property (#7065) 2025-08-07 07:47:17 +02:00
Marina López
778a608854 🐛 Fix tooltip for icon plans from team dropdown (#7075) 2025-08-07 07:43:49 +02:00
andrés gonzález
7de8e10721 🐛 Fix changelog link (#7070) 2025-08-07 07:43:11 +02:00
andrés gonzález
80f41c4a69 🐛 Fix issue where Alt + arrow keys shortcut interferes with letter-spacing (#7071) 2025-08-07 07:42:33 +02:00
Luis de Dios
a3557a81e4 🐛 Fix add space between the name and the index of new properties (#7068) 2025-08-07 07:41:33 +02:00
Luis de Dios
0a02e526ee Treat empty names as a malformed formula (#7073) 2025-08-07 07:41:07 +02:00
Luis de Dios
db9349e764 💄 Style improvements in the swap panel (#7077) 2025-08-07 07:40:38 +02:00
Belén Albeza
60903f349f 🐛 Fix color picker not working with the new renderer 2025-08-06 18:00:48 +02:00
Florian Schrödl
b91e955486 Text decoration fixes (#7066)
*  Show text more options when apply text decoration token

* 🐛 Fix placeholder
2025-08-06 16:23:38 +02:00
Marina López
a76a9fae41 🐛 Fix an unused translation (#7074) 2025-08-06 13:28:02 +02:00
Andrey Antukh
f7cfbdd229 🐛 Comment the problematic migration 2025-08-05 22:05:52 +02:00
Andrey Antukh
e28d2842f6 🐛 Revert the revert of orientation detection on media
This reverts commit 515cbf7bef.
2025-08-05 22:03:09 +02:00
Andrey Antukh
ccc3ca0948 Disable virtual threads on http server 2025-08-05 20:34:47 +02:00
Andrey Antukh
515cbf7bef 🐛 Revert orientation detection on media 2025-08-05 19:30:01 +02:00
Andrey Antukh
c320cbc47b 🐛 Revert to semaphore based climit impl 2025-08-05 19:17:35 +02:00
Yamila Moreno
6166f45a7f Merge pull request #7069 from penpot/yms-update-k8s-documentation
📚 Update k8s documentation
2025-08-05 15:44:59 +02:00
Yamila Moreno
c103eb86db 📚 Update k8s documentation 2025-08-05 13:55:39 +02:00
Alejandro Alonso
61d93d69b1 Merge pull request #7048 from penpot/elenatorro-11704-fix-symbols-font
 Include symbols support
2025-08-05 13:40:16 +02:00
Belén Albeza
d5abf34538 🐛 Fix text style change not being applied (#7036)
* 🐛 Fix text styles not being applied to current cursor

* 🔧 Add text file for bug 11552

* 📚 Update changelog
2025-08-05 13:35:54 +02:00
Alejandro Alonso
7efc297cd9 Merge pull request #7053 from penpot/ladybenko-11678-compact-keep-ratio-flag
 Compact fill serialization (opacity + flags)
2025-08-05 13:29:49 +02:00
Alejandro Alonso
98522a390e 🐛 Fix frames extrect calculation 2025-08-05 13:25:25 +02:00
Andrey Antukh
46969585ed Disable native buffers usage on xnio
A temporal change for investigate native memory leak
2025-08-04 22:13:08 +02:00
Andrey Antukh
47882c5419 Add missing parameter on climit instance creation 2025-08-04 19:53:56 +02:00
andrés gonzález
019d5e083a 💄 Change copys at the 2.9 release slides (#7063) 2025-08-04 19:53:50 +02:00
Belén Albeza
6fc949844d Use 1 byte to store opacity in gradient fills 2025-08-04 14:13:40 +02:00
Andrey Antukh
97e8c9250a Merge remote-tracking branch 'origin/staging' into develop 2025-08-04 14:10:57 +02:00
Andrey Antukh
85f6cf32ae 🐛 Several bugfixes (#7062)
* 🐛 Fix incorrect status validation on subscription internal api

* 🐛 Make the shortcuts overwritting optional
2025-08-04 13:54:29 +02:00
Marina López
ded8e39e73 🐛 Fix hidden button in subscribe modal when there is a large number of teams (#7061) 2025-08-04 13:16:58 +02:00
Florian Schrödl
551313d3de Text case fixes (#7058)
*  Add placeholder

*  Remove status icon
2025-08-04 12:13:57 +02:00
Andrey Antukh
e730200873 🐛 Fix pinned project ordering on dashboard sidebar (#7060) 2025-08-04 12:07:19 +02:00
Andrey Antukh
433e61bc4e Merge remote-tracking branch 'origin/staging' into develop 2025-08-04 11:52:24 +02:00
Andrei Fëdorov
818b03d8f2 Add text decoration token (#7049) 2025-08-04 10:47:09 +02:00
Belén Albeza
ae3aef8dcc Use existing space for storing image fill flags 2025-08-04 10:42:56 +02:00
Luis de Dios
1b30325640 🐛 Fix adjust focus in select component (#7024) 2025-08-04 10:21:17 +02:00
Yamila Moreno
44d626d578 📎 Fix typo in documentation 2025-08-01 16:32:42 +02:00
Francis Santiago
4501d13961 📚 Clarify OpenShift requirements (#6937)
* 📚 Clarify OpenShift requirements

* 📚 Remove the click for expanding
2025-08-01 16:26:04 +02:00
Elena Torró
c8f5ec4698 ♻️ Refactor dropdown-menu and make dropdown visibility exclusive (#6956)
* 🐛 Fix having multiple dropdown menus opened on dashboard page

* ♻️ Refactor dropdown-menu

Make it follow new standards and make it external api more usable,
not depending on manually provided list of ids.

This also implements the autoclosing of "other" active/open
dropdown-menu (or other similar components).

* 📎 Add PR feedback changes

* 🐛 Fix incorrect event handling on project-menu

* 🐛 Fix unexpected exception

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-08-01 16:14:15 +02:00
Pablo Alba
07b15819d4 🎉 Add the ability to create variants from a selection (#7045)
* 🎉 Add the ability to create variants from a selection

* 📎 Add PR feedback changes

* 💄 Add minor cosmetic changes

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-08-01 15:39:46 +02:00
Elena Torró
f519c6ef46 Center team settings properly (#7021) 2025-08-01 15:17:32 +02:00
Juan de la Cruz
baa1cfb2f8 🎉 Add 2.9 release slides (#7019) 2025-08-01 14:59:11 +02:00
Eva Marco
905699d15a Add info to apply-token events (#7050) 2025-08-01 14:00:30 +02:00
Eva Marco
fe53869308 🐛 Fix small details on number token application (#7051) 2025-08-01 13:52:09 +02:00
Elena Torro
c69ee35e18 Include symbols support 2025-08-01 13:41:12 +02:00
Luis de Dios
8d5ee92f16 🐛 Fix show 'add new property' from menu when a variant is selected (#7042) 2025-08-01 13:21:19 +02:00
Andrey Antukh
e55d184d2b Merge remote-tracking branch 'origin/staging' into develop 2025-08-01 13:13:56 +02:00
Andrey Antukh
50076bac83 Merge remote-tracking branch 'origin/main' into staging 2025-08-01 13:10:52 +02:00
Eva Marco
e976714964 🐛 Fix error on inspect tab with texts (#7032) 2025-08-01 13:03:43 +02:00
Brandon Currell
44bc4b7fa4 🐳 Add missing package in the exporter Docker image (#7026)
penpot-exporter requires poppler-utils for exporting to a PDF, but it is missing.
Added the package to the Dockerfile in the RUN section where dependencies are
being installed.

Signed-off-by: Brandon Currell <brandon+git@currell.pw>
2025-08-01 13:00:51 +02:00
Brandon Currell
ce0d6ffda2 🐳 Add missing package in the exporter Docker image (#7026)
penpot-exporter requires poppler-utils for exporting to a PDF, but it is missing.
Added the package to the Dockerfile in the RUN section where dependencies are
being installed.

Signed-off-by: Brandon Currell <brandon+git@currell.pw>
2025-08-01 12:59:39 +02:00
Yamila Moreno
5c14f486d7 🐳 Update Imagemagick version 2025-08-01 12:49:11 +02:00
Andrey Antukh
0cbd980b68 🎉 Add imagemagick docker image build scripts (#6925)
* 🎉 Add imagemagick docker image build scripts

* 📎 Add PR feedback changes
2025-08-01 12:48:47 +02:00
Andrey Antukh
bc2308f2ce Merge pull request #7011 from penpot/lmcrean-lmcrean-milestones-version-lock
🎉 Add the ability to lock/unlock of file versions
2025-08-01 12:40:07 +02:00
Laurie Crean
0b47a366ab Implement version locking functionality for file snapshots
Signed-off-by: Laurie Crean <lmcrean@gmail.com>
2025-08-01 11:41:30 +02:00
Andrey Antukh
1892fa6782 Merge pull request #7043 from penpot/niwinz-develop-refactor-time-helpers
♻️ Refactor time related namespaces
2025-08-01 11:35:07 +02:00
Andrey Antukh
6f35b7db24 Add reader tag support for tokens related types 2025-08-01 11:20:01 +02:00
Andrey Antukh
4d9e070bcd Add reader tag support for types path data 2025-08-01 11:20:01 +02:00
Andrey Antukh
61fe8e8d8e Add reader tag support for geom matrix 2025-08-01 11:20:01 +02:00
Andrey Antukh
0934095e96 Add reader tag support for geom point 2025-08-01 11:20:01 +02:00
Andrey Antukh
eba2ff7d8d Add impl for Inst protocol for FileTime class 2025-08-01 11:20:01 +02:00
Andrey Antukh
283eb0419c ♻️ Refactor time related namespaces
Mainly removes the custom app.util.time namespace
from frontend and backend and normalize all to use
the app.common.time namespace
2025-08-01 11:20:01 +02:00
Elena Torro
9a0c36c442 🐛 Fix default color when neither fill nor background color is set 2025-07-31 16:17:13 +02:00
luisδμ
ff1d26294a 🐛 Fix create properties with a default value instead of an empty one (#7033) 2025-07-31 15:01:51 +02:00
Eva Marco
63bfbbb3c6 🐛 Fix typography token context menu (#7038) 2025-07-31 15:00:14 +02:00
Eva Marco
95dda2b1af 🐛 Fix stroke width token application (#7039) 2025-07-31 14:59:48 +02:00
Elena Torró
76d725559e Set default new text fill color depending on background color (#6998) 2025-07-31 12:31:54 +02:00
luisδμ
d7ec8ccbc0 🐛 Fix property name cannot be empty (#7030) 2025-07-31 12:27:10 +02:00
Juanfran
6def5e285b 🐛 Apply design review fixes for variant connection help (#11186) (#7016) 2025-07-31 12:26:04 +02:00
Andrey Antukh
5170872961 Merge pull request #7031 from penpot/eva-fix-export-button-width
🐛 Fix export button width on inspect tab
2025-07-31 12:25:03 +02:00
Andrey Antukh
b46e9ee065 Merge remote-tracking branch 'origin/staging' into develop 2025-07-31 12:22:14 +02:00
Andrey Antukh
871ca68e1e 📎 Allow revert commits on github commit checker 2025-07-31 12:14:29 +02:00
Andrey Antukh
0ab896fc76 Revert " Highlight first font in font selector search, apply on Enter/click"
This reverts commit e62567d09e.
2025-07-31 12:14:29 +02:00
Andrey Antukh
6a4b548457 Revert "🐛 Fix font selector highlight inconsistency (#6990)"
This reverts commit 708a40bff1.
2025-07-31 12:14:29 +02:00
Elena Torró
0457ca4fe5 Use 'desvincular' instead of 'desacoplar' (#7020) 2025-07-31 11:50:46 +02:00
Elena Torro
083be7df88 🐛 Fix focus editor check 2025-07-31 10:05:37 +02:00
Eva Marco
695a399941 🐛 Fix export button width on inspect tab 2025-07-31 09:30:46 +02:00
luisδμ
200b69fae2 📚 Improve documentation for combobox and select in the storybook (#7006) 2025-07-31 09:05:54 +02:00
Eva Marco
a32463fada 🐛 Fix tooltip position after several shows and hides (#7022) 2025-07-31 09:00:05 +02:00
luisδμ
3b04cd37ff 🐛 Fix empty values should not have dimmed text (#7015) 2025-07-30 18:06:39 +02:00
Eva Marco
5d44c88988 🐛 Fix token pill not showing position application on dimension token type (#7018) 2025-07-30 14:24:10 +02:00
luisδμ
4d688b1d55 🐛 Fix title for button when trying to remove last variant property (#7017) 2025-07-30 13:28:42 +02:00
Andrey Antukh
e43b6fb0b7 Merge pull request #6992 from penpot/niwinz-artboard-defaults
 Add defaults for artboard drawing
2025-07-30 13:27:54 +02:00
Andrey Antukh
7895f03447 💄 Add minor cosmetic changes 2025-07-30 13:11:28 +02:00
Marina López
1f42b2f72d Show preset name when an option is selected 2025-07-30 13:11:28 +02:00
Andrey Antukh
f4adfe56be Add defaults for artboard drawing 2025-07-30 13:11:28 +02:00
Alejandro Alonso
33a679fbc0 Merge pull request #6940 from penpot/niwinz-develop-inplace-import
🎉 Add support for in-place binfile import
2025-07-30 12:42:37 +02:00
Pablo Alba
9db67cc5e8 🐛 Fix bad swap slot after two swaps (#6962)
* 🐛 Fix bad swap slot after two swaps

*  MR changes
2025-07-30 12:35:27 +02:00
luisδμ
9834f0596b 🐛 Fix move empty variant values to the end when component is selected (#7009)
* 🐛 Move empty variant values to the end when component is selected

* 📎 PR changes
2025-07-30 12:29:51 +02:00
Andrey Antukh
ce87d797d1 Merge pull request #7014 from penpot/niwinz-staging-regression-3
🐛 Fix several issues related to font/text related tokens
2025-07-30 12:25:28 +02:00
Andrey Antukh
37cec8891f 🎉 Add inplace binfile import support 2025-07-30 12:23:40 +02:00
Andrey Antukh
fd62141c04 Disable pointer-map feature (temporary)
Because the upcoming refactor changes several aspects
of that feature and it not make sense to continue have
this active for now, until refactor is merged.
2025-07-30 12:06:41 +02:00
Andrey Antukh
4bdba6894d Add get-with-sql helper to db module 2025-07-30 12:06:41 +02:00
Andrey Antukh
6c7fef29a8 Improve file data type constructor 2025-07-30 12:06:41 +02:00
Andrey Antukh
a77edc5aa2 Add better uri constructor function 2025-07-30 12:06:41 +02:00
Andrey Antukh
7fde1436e1 🐛 Add missing styles to the empty node on editor-v1 2025-07-30 11:45:39 +02:00
Andrey Antukh
e1c5a32fcb 💄 Fix indentation style on generate-unapply-tokens 2025-07-30 11:45:19 +02:00
Andrey Antukh
b262e6a46f 🐛 Fix incorrect condition on checking text shape attrs 2025-07-30 11:44:07 +02:00
Yamila Moreno
31f37a20e3 Merge pull request #7013 from penpot/yms-simplify-gh-actions
 Simplify gh-actions workflows
2025-07-30 11:42:39 +02:00
alonso.torres
06b4ae5c96 🐛 Fix problem with layout update touching geometry 2025-07-30 11:27:15 +02:00
Alejandro Alonso
a3e24785d3 Merge pull request #7003 from penpot/alotor-fix-transform
🐛 Fix wasm transform issues
2025-07-30 11:10:54 +02:00
Yamila Moreno
78102210a5 Simplify gh-actions workflows 2025-07-30 10:45:01 +02:00
Pablo Alba
7553d68100 🐛 Fix corner case of chained switch and libraries (#7008) 2025-07-30 08:44:27 +02:00
Andrey Antukh
2e726b62c3 📎 Update changelog 2025-07-29 20:07:05 +02:00
Andrey Antukh
02acd81c2c 🐛 Add missing profile prop to access style component (#7007)
* 💄 Fix request-access component style

* 🐛 Add missing profile prop to access style component
2025-07-29 16:04:15 +02:00
Andrey Antukh
44daa1cf65 Merge remote-tracking branch 'origin/staging' into develop 2025-07-29 15:22:14 +02:00
Andrey Antukh
bae2de75ff Merge branch 'main' into staging 2025-07-29 15:21:58 +02:00
Andrey Antukh
b68c426cd1 🐛 Fix exception on fills menu when binary-fills flag is active
And multiple shapes are selected.
2025-07-29 15:10:32 +02:00
Andrey Antukh
5161ef15bf 🐛 Fix regression on show access request dialog (#7005) 2025-07-29 14:58:02 +02:00
Andrey Antukh
bdbaa6d597 Merge remote-tracking branch 'origin/staging' into develop 2025-07-29 14:34:35 +02:00
Eva Marco
36d3d94ec9 🐛 Fix X & Y position do not sincronize with tokens (#7004) 2025-07-29 14:32:06 +02:00
Andrey Antukh
0e675a725d 📎 Fix linter issues on frontend
Caused by the merge from staging to develop
2025-07-29 14:15:01 +02:00
Andrey Antukh
17447d7610 Remove restriction of duplicate bindings on mousetrap 2025-07-29 14:14:19 +02:00
Andrey Antukh
2a3046ba2e 📎 Fix linter issue on common 2025-07-29 14:10:49 +02:00
Andrey Antukh
54d76123d0 Merge remote-tracking branch 'origin/staging' into develop 2025-07-29 14:06:53 +02:00
Andrey Antukh
6ffbf08826 Merge pull request #6969 from penpot/andy-show-keyboard-distance
 Show distance between layers while moving them with the keyboard
2025-07-29 13:32:53 +02:00
andrés gonzález
708a40bff1 🐛 Fix font selector highlight inconsistency (#6990)
* 🐛 Fix font selector highlight inconsistency

*  Add minor performance enhancements

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-07-29 13:12:54 +02:00
Andrey Antukh
d84ee8bb65 Optimize mousetrap binding setup 2025-07-29 13:12:28 +02:00
Elena Torró
a16f40cb73 Set page objects once on wasm render(#6994) 2025-07-29 13:00:40 +02:00
Andrey Antukh
02cff2740f Remove restriction of duplicate bindings on mousetrap 2025-07-29 12:51:39 +02:00
Andres Gonzalez
6049d97ed9 Display continously the distances between layers
When a user moves a layer with the keyboard.
2025-07-29 12:51:04 +02:00
Andrey Antukh
3f657a0c04 Merge pull request #6997 from penpot/alotor-fix-wasm-bugs
🐛 Fix wasm problems
2025-07-29 12:42:26 +02:00
Marina López
efaf6573bd 📎 Update monetization texts (#7002) 2025-07-29 12:42:11 +02:00
alonso.torres
4b020dcc1a 🐛 Fix problem when changing size with user input 2025-07-29 12:16:08 +02:00
alonso.torres
223a468bbf 🐛 Fix problem when moving layout with measure input 2025-07-29 12:15:53 +02:00
alonso.torres
ddd0e447f6 🐛 Fix problem when creating shapes after new page 2025-07-29 12:15:27 +02:00
alonso.torres
0c0c81e9a5 🐛 Fix problem with shape to path not working 2025-07-29 12:15:27 +02:00
Yamila Moreno
001bcbce59 Merge pull request #6995 from penpot/yms-update-imagemagick-version
🐳 Update Imagemagick version
2025-07-29 10:58:32 +02:00
Yamila Moreno
c195c07a3f 🐳 Update Imagemagick version 2025-07-29 10:37:11 +02:00
Alejandro Alonso
f5298f51e7 🐛 Fix the context menu always closes after any action (#6944) 2025-07-29 09:50:55 +02:00
Alejandro Alonso
46c440fef2 🐛 Fix remove color button in the gradient editor (#6993) 2025-07-28 17:48:05 +02:00
Aitor Moreno
e6ac2c1159 Merge pull request #6880 from penpot/elenatorro-fix-editor-crash-on-deleting-entire-selection-firefox
🐛 Handle empty paragraph on entire selected text deletion
2025-07-28 17:39:25 +02:00
Florian Schrödl
4c605b8151 Implement text case token (#6978) 2025-07-28 17:36:06 +02:00
Elena Torro
2913899aa5 🐛 Fix auto-format on font 2025-07-28 17:31:36 +02:00
Elena Torro
ecd3245612 🐛 Fix request render after pending calls have finished on set-objects 2025-07-28 17:31:36 +02:00
Xaviju
dadeda4476 🐛 Display stroke properties in inspect tab (#6955) 2025-07-28 16:17:54 +02:00
Elena Torró
d129557f77 Merge pull request #6988 from penpot/superalex-fix-render-wasm-visible-0-width-strokes
🐛 Fix visible 0 width strokes in wasm render
2025-07-28 14:36:07 +02:00
Alejandro Alonso
e77f8b572a Merge pull request #6953 from penpot/superalex-fix-component-changes-not-propagated
🐛 Fix component changes not propagated
2025-07-28 12:53:37 +02:00
Alejandro Alonso
ade5eecf80 🐛 Fix component changes not propagated 2025-07-28 12:38:09 +02:00
Elena Torró
ff7e34e308 Merge pull request #6984 from penpot/superalex-fix-switching-theme-form-wasm-render
🐛 Fix switching theme for wasm render
2025-07-28 11:30:41 +02:00
Yamila Moreno
88055294a2 Reuse github workflows (#6989) 2025-07-28 09:03:47 +02:00
Alejandro Alonso
e473f45048 🐛 Fix visible 0 width strokes in wasm render 2025-07-28 08:46:47 +02:00
Alejandro Alonso
bcee670ac6 🐛 Fix switching theme for wasm render 2025-07-28 07:44:56 +02:00
andrés gonzález
97fc7702b8 📚 Improve and clarify 'Hide and lock layers' section (#6975) 2025-07-25 14:53:32 +02:00
andrés gonzález
54fcd58531 📚 Add doc for resizing text (#6974)
* 📚 Add doc for resizing text

* 📚 Update docs for text resizing

Co-authored-by: Madalena Melo <madalena.melo@kaleidos.net>
Signed-off-by: andrés gonzález <andres.gonzalez79@gmail.com>

---------

Signed-off-by: andrés gonzález <andres.gonzalez79@gmail.com>
Co-authored-by: Madalena Melo <madalena.melo@kaleidos.net>
2025-07-25 13:20:52 +02:00
andrés gonzález
b7a8747f00 📚 Add doc for tokens zip file import option (#6973) 2025-07-25 13:20:39 +02:00
Elena Torró
b93e96a18d Merge pull request #6958 from penpot/superalex-fix-texts-bigger-than-selrects-in-multiple-tiles
🐛 Fix rendering texts bigger than their selrects in mutiple tiles
2025-07-25 13:14:29 +02:00
Alejandro Alonso
b70f6af2df 🐛 Fix rendering texts bigger than their selrects in mutiple tiles 2025-07-25 12:56:57 +02:00
Andrey Antukh
d00de7d5a4 Merge pull request #6960 from penpot/xaviju-remove-image-shape-type-fix
🐛 Remove image type from inspect tab panels
2025-07-25 12:49:57 +02:00
andrés gonzález
5ae4dde222 📚 Add font size token doc (#6972) 2025-07-25 12:30:56 +02:00
Elena Torro
0e20bb6271 🐛 Fix text width calculation 2025-07-25 12:27:26 +02:00
Xaviju
2fbd4b07e0 🐛 Remove image type from inspect tab panels 2025-07-25 12:08:01 +02:00
Elena Torró
bd15ef4618 Merge pull request #6854 from penpot/ladybenko-11522-fix-missing-font
🐛 Fix missing font when pasting text
2025-07-25 11:55:56 +02:00
Belén Albeza
af5b942e05 🐛 Fix copy/paste not working on follow up pastes 2025-07-25 09:53:48 +02:00
Belén Albeza
098fd9fb0f 🐛 Fix not picking up font style / variant in new renderer 2025-07-25 09:48:20 +02:00
Belén Albeza
a242962113 🐛 Fix missing font when pasting text (editor v1) 2025-07-25 09:48:20 +02:00
Elena Torro
2b95e6b7a9 🐛 Fix update canvas background color 2025-07-25 09:19:59 +02:00
Florian Schroedl
58a843ea23 Remove token when applying tyopgraphic asset style 2025-07-24 17:51:53 +02:00
Florian Schroedl
4189d01844 Remove token when applying tyopgraphic asset style 2025-07-24 17:14:04 +02:00
Andrés Moya
57330f53e2 🔧 Use id instead of name for tokens crud 2025-07-24 15:21:18 +02:00
Florian Schroedl
f6b97af148 🐛 Fix spacing menu not available in dimensions token 2025-07-24 15:20:10 +02:00
Florian Schroedl
1c79e726af 🐛 Fix spacing menu not available in dimensions token 2025-07-24 15:16:01 +02:00
Alejandro Alonso
76b7287bf1 Merge pull request #6864 from penpot/niwinz-staging-snapshot-migrations
 Add migrations handling on file snapshots
2025-07-24 11:41:17 +02:00
Andrey Antukh
019bc2f183 Add migrations handling on file snapshots 2025-07-24 11:40:54 +02:00
Florian Schroedl
8c96a617be Add test for spacing token application rules 2025-07-24 11:01:49 +02:00
Florian Schroedl
1f15e9b81e Fix spacing token for frame children 2025-07-24 11:01:49 +02:00
Alejandro Alonso
f7627e515a Merge pull request #6876 from penpot/niwinz-develop-minor-changes-logical-deletion
 Change default status filtering for logical deletion
2025-07-24 10:58:49 +02:00
Andrey Antukh
d08c94d5a6 Change default status filtering for logical deletion 2025-07-24 10:43:45 +02:00
Florian Schroedl
cccea3dc71 Add test for spacing token application rules 2025-07-24 10:42:08 +02:00
Florian Schroedl
c82c39caf3 Fix spacing token for frame children 2025-07-24 10:42:08 +02:00
Xaviju
01896501c1 🐛 Remove image type from inspect tab panels (#6959) 2025-07-24 09:37:38 +02:00
Andrey Antukh
33cf75e933 Merge remote-tracking branch 'origin/staging' into develop 2025-07-24 09:00:29 +02:00
Andrey Antukh
3f9a1525ca Merge pull request #6954 from penpot/alotor-fix-gradient-stroke
🐛 Fix opacity on stroke gradients
2025-07-24 08:59:02 +02:00
alonso.torres
52c1e227d5 🐛 Fix change from gradient to solid color 2025-07-24 08:58:48 +02:00
alonso.torres
955538b12a 🐛 Fix opacity on stroke gradients 2025-07-24 08:58:46 +02:00
Alonso Torres
dfc8a1da4a Fix problem with booleans selection (#6950) 2025-07-24 08:57:02 +02:00
Alonso Torres
8254af27cb 🐛 Fix problem when changing between flex/grid layout (#6949) 2025-07-24 08:54:07 +02:00
Pablo Alba
b477ca0508 🐛 Fix design review bugs on variants advanced retrieve (#6948) 2025-07-24 08:53:26 +02:00
Elena Torró
f76391ecbb 🐛 Enable switch to system theme on options menu (#6946) 2025-07-24 08:43:03 +02:00
Andrés Moya
c49e9fbf18 🐛 Fix last migration of token sets (#6957) 2025-07-24 08:42:16 +02:00
Marina López
122701ee7b 🐛 Fix modal submit button for unpaid or canceled subscriptions (#6947) 2025-07-24 08:41:39 +02:00
Andrés Moya
351362bb50 🐛 Fix migration from tokens lib version 1.2 2025-07-23 15:28:53 +02:00
Andrey Antukh
9a6989d2ca 📎 Fix linter issues introduced on merging staging into develop 2025-07-23 12:27:04 +02:00
Andrey Antukh
8aebe1a41e Merge remote-tracking branch 'origin/staging' into develop 2025-07-23 12:26:09 +02:00
Andrey Antukh
1acf78d57c Merge branch 'main' into staging 2025-07-23 12:09:37 +02:00
Andrey Antukh
523373dfa2 📎 Update .gitignore file 2025-07-23 12:09:15 +02:00
Andrés Moya
f55e7d8165 🐛 Keep shape level groups for token sync later 2025-07-23 12:04:31 +02:00
Andrés Moya
9fdc6be465 🐛 Fix bad touched attributes when applying tokens to text shapes 2025-07-23 12:04:31 +02:00
Alejandro Alonso
9390c1e7be 🐛 Fix "Copy as SVG" generates different code from the Inspect panel (#6945) 2025-07-23 11:46:58 +02:00
Florian Schroedl (aider)
d788a4d252 Implement new token-type :font-families 2025-07-23 11:26:28 +02:00
Eva Marco
b20b272eae 📚 Update changelog 2025-07-23 09:53:49 +02:00
Aitor Moreno
2cddc6fb5b Merge pull request #6583 from penpot/niwinz-fills-binary-type
🎉 Fills as binary type
2025-07-23 09:26:26 +02:00
Alejandro Alonso
d46b519524 🐛 Fix remove color button in the gradient editor (#6942) 2025-07-23 09:04:54 +02:00
Andrey Antukh
4effd375a9 Add several improvements to admin pannel 2025-07-23 08:33:33 +02:00
Andrey Antukh
4e753dc474 💄 Use resolved schemas instead of references
For several schemas on common types
2025-07-23 08:33:28 +02:00
Andrey Antukh
fbf63b98c3 Reuse file data checkers on file validate ns 2025-07-23 08:33:23 +02:00
Marina López
3df557b370 ♻️ Remove the workaround for updating the subscription after subscribing (#6938) 2025-07-23 08:10:20 +02:00
Aitor Moreno
cdb600b081 Remove unused code 2025-07-23 08:03:23 +02:00
Aitor Moreno
ffb688696b 🎉 Add keep-aspect-ratio integration 2025-07-23 08:03:23 +02:00
Andrey Antukh
8bb210e7b6 🎉 Add binary fills integration 2025-07-23 08:03:23 +02:00
Andrey Antukh
9ee488009f ♻️ Add substantial refactor on how types are organized
This mainly affects types related to colors, fills and texts, moving library
based operations from color namespace.
2025-07-23 08:03:23 +02:00
Andrey Antukh
96d9b102b6 Add type hints on config ns 2025-07-23 07:32:11 +02:00
Andrey Antukh
16fba49937 Expose flags for common submodule 2025-07-23 07:32:11 +02:00
Andrey Antukh
af99bd620c Use binary fills to write data to wasm memory 2025-07-23 07:32:11 +02:00
Andrey Antukh
8a58b9d459 Use new write-bool helper on fills metadata 2025-07-23 07:32:11 +02:00
Andrey Antukh
e3c62075b8 Write keep-aspect-ration on fill binary format 2025-07-23 07:32:11 +02:00
Andrey Antukh
22a70eb5b2 🎉 Add write-bool helper to buffer ns helpers 2025-07-23 07:32:11 +02:00
Andrey Antukh
4e2998a366 ♻️ Rename fill to fills namespace 2025-07-23 07:32:11 +02:00
Andrey Antukh
158f759cde Add binary fills initialization on workspace fetch 2025-07-23 07:32:11 +02:00
Aitor Moreno
3e3be95420 Merge pull request #6927 from penpot/elenatorro-test-fix-text-shadows
🐛 Fix text shadows apply text opacity
2025-07-23 06:59:28 +02:00
Elena Torró
b5808701ec Merge pull request #6873 from penpot/niwinz-develop-enhancements-1
 Add improvements for backend admin/debug page
2025-07-22 15:14:08 +02:00
Xaviju
35f3125fff 🐛 Fix null when copying shadow color on inspect tab (#6923)
Co-authored-by: Xavier Julian <xaviju@proton.me>
2025-07-22 14:49:36 +02:00
Elena Torro
5427d207cd 🐛 Fix text shadows apply text opacity 2025-07-22 14:34:10 +02:00
Xaviju
ee23d72d13 🐛 Fix null when copying shadow color on inspect tab (#6923)
Co-authored-by: Xavier Julian <xaviju@proton.me>
2025-07-22 14:06:06 +02:00
Francis Santiago
f22aa606ce 📚 Clarify OpenShift requirements (#6937)
* 📚 Clarify OpenShift requirements

* 📚 Remove the click for expanding
2025-07-22 14:05:02 +02:00
Andrey Antukh
d914314c1c Merge remote-tracking branch 'origin/staging' into develop 2025-07-22 13:04:57 +02:00
Pablo Alba
4aa9f1f62b 🐛 On component swap do not show secondary variants (#6928) 2025-07-22 12:33:37 +02:00
David Barragán Merino
9d288486d7 🐛 Subscription current period dates could be null (#6931)
`current-period-start` and `current-period-end` can be null if the invoice has not yet been created in stripe. This happens after the subscription is created, before the webhook is sent.
2025-07-22 12:32:42 +02:00
Pablo Alba
ea5521485a ♻️ Remove redundant flag on text overrides (#6933) 2025-07-22 12:32:24 +02:00
Marina López
f768ffbdad 🐛 Fix wrong behaviour for unpaid or canceled subscriptions (#6932) 2025-07-22 12:31:45 +02:00
Andrey Antukh
4f0d3660de 🎉 Add imagemagick docker image build scripts (#6925)
* 🎉 Add imagemagick docker image build scripts

* 📎 Add PR feedback changes
2025-07-22 11:51:13 +02:00
Andrey Antukh
fa72bb4adf Add several improvements to admin pannel 2025-07-22 10:06:29 +02:00
Andrey Antukh
ea0044f69a 💄 Use resolved schemas instead of references
For several schemas on common types
2025-07-22 10:06:29 +02:00
Andrey Antukh
7e493376a4 Reuse file data checkers on file validate ns 2025-07-22 10:06:29 +02:00
Andrey Antukh
8c5afe5ab3 📎 Add next release entries to the changelog 2025-07-21 21:20:46 +02:00
Andrey Antukh
7ccb742ef3 Merge remote-tracking branch 'origin/develop' into staging 2025-07-21 21:15:54 +02:00
Andrey Antukh
7bc29c22ed Merge remote-tracking branch 'origin/develop' into staging 2025-07-21 21:07:24 +02:00
Andrey Antukh
1d550eaa18 Merge remote-tracking branch 'origin/staging' into develop 2025-07-21 21:03:19 +02:00
Andrey Antukh
b71ec4bfe0 Simplify docker build script (#6924) 2025-07-21 20:45:03 +02:00
Andrey Antukh
827bbf6a7f Merge pull request #6926 from penpot/juanfran-close-libraries-modal-on-esc
🐛 Fix ESC key not closing Add/Manage Libraries modal
2025-07-21 15:48:40 +02:00
Juanfran
2db0cc0cbf 🐛 Fix ESC key not closing Add/Manage Libraries modal 2025-07-21 15:23:54 +02:00
Andrey Antukh
42ef01b339 Merge pull request #6871 from penpot/niwinz-develop-login-enhancements
 Allow login dialog on settings
2025-07-21 15:19:06 +02:00
Aitor Moreno
fdaef2be69 Merge pull request #6891 from penpot/elenatorro-test-style-decoration-blending
🔧 Add text decoration styles
2025-07-21 15:18:18 +02:00
Pablo Alba
ae3213f5d4 🐛 Fix text override corner case 2025-07-21 12:40:03 +02:00
Andrey Antukh
6dfd05fdd1 Merge remote-tracking branch 'origin/staging' into develop 2025-07-21 12:05:24 +02:00
Andrey Antukh
51107c3fc9 🐛 Fix incorrect event name on event constructor 2025-07-21 11:58:48 +02:00
Andrey Antukh
b6863efb3a Merge pull request #6874 from penpot/xaviju-11355-tokens-import-details-layout
 Improve legibility on import token notification details
2025-07-21 11:54:08 +02:00
Andrey Antukh
799bceb8b7 🐛 Check if profile is logged-in on subscriptions internal redirects 2025-07-21 11:40:31 +02:00
Andrey Antukh
9e573128c1 🐛 Fix incorrect event name on event constructor 2025-07-21 11:40:31 +02:00
Andrey Antukh
1f05511add Allow login dialog on settings 2025-07-21 11:40:30 +02:00
Elena Torro
eeee52a738 🐛 Ensure line height is properly handled on line breaks 2025-07-21 11:37:56 +02:00
Xavier Julian
7f53860296 📎 Add warning on feature flag temporary fix for font-size tokens 2025-07-21 11:23:27 +02:00
Andrey Antukh
16d0077393 Merge pull request #6920 from penpot/mdbenito-feature/wheel-scrolling-for-templates
 Enable wheel scrolling over templates-section in the dashboard
2025-07-21 11:22:47 +02:00
Andrey Antukh
622fed2f0d 💄 Add minor formating enhancements to dashboard templates ui code 2025-07-21 10:39:50 +02:00
Andrey Antukh
d22ade3289 Remove duplicated code 2025-07-21 10:38:18 +02:00
Miguel de Benito Delgado
7febf330ac Enable wheel scrolling over templates-section in the dashboard 2025-07-21 10:34:50 +02:00
Andrey Antukh
75a50ac1ac Merge pull request #6912 from penpot/andy-highlight-font-selector
 Highlight first font in font selector search, apply on Enter/click
2025-07-21 10:33:53 +02:00
Andres Gonzalez
e62567d09e Highlight first font in font selector search, apply on Enter/click
[Taiga #11579](https://tree.taiga.io/project/penpot/issue/11579)

 Highlight first font in font selector search, apply on Enter/click
2025-07-21 10:13:36 +02:00
Andrey Antukh
8d80eebeb1 Merge pull request #6906 from penpot/andy-enhance-text-auto-resize
 Switch auto-width to auto-height on horizontal resize on text shapes
2025-07-21 10:11:35 +02:00
Andres Gonzalez
ee9a42238d Switch auto-width to auto-height on horizontal resize on text shapes 2025-07-21 09:56:45 +02:00
Andrey Antukh
758c76d661 Merge pull request #6905 from penpot/andy-enhance-text-resize-behavior
 Allow double-click on text bounding box to set auto-width/auto-height
2025-07-21 09:55:18 +02:00
Andrey Antukh
1dec46cbfa Merge pull request #6903 from penpot/superalex-fix-page-duplication
🐛 Fix error on validating file referential integrity when duplicating a page
2025-07-21 09:46:12 +02:00
Andrey Antukh
ae25d704c1 📎 Add missing use-fn hook 2025-07-21 09:32:44 +02:00
Andres Gonzalez
e05f8c0329 Improve text layer resize behavior
Text layers now only switch to fixed grow-type on vertical resize, not on horizontal resize, for a more intuitive UX. See #4602.
2025-07-21 09:27:42 +02:00
Alejandro Alonso
ce62e11626 🐛 Fix error on validating file referential integrity when duplicating a page 2025-07-21 09:26:23 +02:00
Andrey Antukh
9f04c2fc1d Merge pull request #6901 from penpot/superalex-hide-bb-when-editing-effects
 Hide bounding box while editing visual effects
2025-07-21 09:23:18 +02:00
Andrey Antukh
05a405a82d Merge pull request #6893 from penpot/xaviju-11144-copy-color-attr
 Keep color data when copying from info tab into CSS
2025-07-21 09:22:57 +02:00
Andrey Antukh
3c8c21c378 Merge pull request #6899 from abedef/patch-1
📚 Fix broken link in self-hosting docs
2025-07-21 09:19:39 +02:00
Xavier Julian
2dbeb884a5 Keep color data when copying from info tab into CSS 2025-07-21 09:07:20 +02:00
Andrey Antukh
931d72b41f Merge pull request #6887 from dfelinto/fix-trackpad-swipe
🐛 Fix touchpad swipe back/forward #4246
2025-07-21 08:58:32 +02:00
Alejandro Alonso
2e3cdd872c Revert " Highlight first found font in font list when searching [Taiga #3204]"
This reverts commit 55a13c3139.
2025-07-17 13:01:24 +02:00
Andres Gonzalez
55a13c3139 Highlight first found font in font list when searching [Taiga #3204]
This enhancement highlights the first found font in the font list when searching, and allows pressing Enter to select it, for a more intuitive font selection experience.

See [Taiga #3204](https://tree.taiga.io/project/penpot/issue/3204).
2025-07-17 12:09:50 +02:00
Andrey Antukh
f63d1c87e3 Merge pull request #6904 from penpot/andy-fix-email-change-message
 Update email change confirmation message for clarity
2025-07-17 11:31:21 +02:00
Alejandro Alonso
abbfd44534 Hide bounding box while editing visual effects 2025-07-17 09:33:10 +02:00
Andres Gonzalez
f772724f9a Update email change confirmation message for clarity 2025-07-16 10:22:53 +02:00
Andrey Antukh
f3abd0f190 Merge pull request #6902 from penpot/andy-clarify-invite-member-message
 Clarify invite member message for existing team members
2025-07-15 15:48:45 +02:00
Andres Gonzalez
5d4042c861 Clarify invite member message for existing team members
Update the English message shown when inviting team members whose emails are already part of the team, as suggested in issue #6785.
2025-07-15 14:05:20 +02:00
Dalai Felinto
1fbcec98fb 🐛 Fix touchpad swipe back/forward #4246
This prevents the browser to take over the trackbad swipe gesture both
for the dashboard and the workspace.

At an early attempt I did get the code to work only for the workspace,
but it is too unreliable and I could every now and then get it to misbehave.

I believe it is better to be safe and always prevent the browser from
going back/forth, regardless of workspace/dashboard.

Signed-off-by: Dalai Felinto <dalai@blender.org>
2025-07-15 00:24:20 +02:00
Alejandro Alonso
abef9f3cf7 Merge pull request #6889 from penpot/niwinz-staging-bugfix
🐛 Fix unexpected exception on processing old texts
2025-07-14 07:08:31 +02:00
Abed Fayyad
6f1958f9f2 📚 Fix broken link in self-hosting docs
Replaced broken Markdown link to the unofficial self-hosting section.

Signed-off-by: Abed Fayyad <yo@abedef.ca>
2025-07-13 09:16:13 -04:00
Andrey Antukh
6b2ce86d5f Merge pull request #6896 from penpot/juanfran-issue-show-main-component-focus
🐛 Fix initialize-page namespace when showing main component
2025-07-11 18:31:37 +02:00
Pablo Alba
0cfd70da2e 🐛 Fix corner cases on variants text overrides 2025-07-11 15:28:55 +02:00
Xavier Julian
4167faf39d 📎 Add blend-mode in code editor feature to CHANGELOG 2025-07-11 15:14:29 +02:00
Pablo Alba
90e6e8c5eb 🐛 Fix double undo on text partial overrides 2025-07-11 15:05:30 +02:00
Elena Torro
b40b1fa2e4 🔧 Refactor ParagraphBuilder and fix auto height 2025-07-11 13:29:22 +02:00
Juanfran
bb1ec109d8 🐛 Fix initialize-page namespace when showing main component 2025-07-11 13:09:20 +02:00
Elena Torro
4c21468850 🔧 Add text decoration styles 2025-07-10 14:26:41 +02:00
Andrey Antukh
40c300fa1a 🐛 Fix unexpected exception on processing old texts 2025-07-10 09:22:00 +02:00
Elena Torro
e2b55d814b 🐛 Fix select all deletion error on Firefox 2025-07-09 14:50:35 +02:00
Xavier Julian
77a47e4b2b Improve legibility on import token notification details 2025-07-08 15:09:50 +02:00
1162 changed files with 121427 additions and 47546 deletions

View File

@@ -226,14 +226,29 @@ jobs:
keys:
- v1-dependencies-{{ checksum "frontend/deps.edn"}}-{{ checksum "frontend/yarn.lock" }}
# Build frontend
- run:
name: "integration tests"
name: "frontend build"
working_directory: "./frontend"
command: |
yarn install
yarn run build:app:assets
yarn run build:app
yarn run build:app:libs
# Build the wasm bundle
- run:
name: "wasm build"
working_directory: "./render-wasm"
command: |
EMSDK_QUIET=1 . /opt/emsdk/emsdk_env.sh
./build release
# Run integration tests
- run:
name: "integration tests"
working_directory: "./frontend"
command: |
yarn run playwright install chromium
yarn run test:e2e -x --workers=4

View File

@@ -45,10 +45,16 @@
:potok/reify-type
{:level :error}
:missing-protocol-method
{:level :off}
:unresolved-namespace
{:level :warning
:exclude [data_readers]}
:unused-value
{:level :off}
:single-key-in
{:level :warning}
@@ -64,6 +70,9 @@
:redundant-nested-call
{:level :off}
:redundant-str-call
{:level :off}
:earmuffed-var-not-dynamic
{:level :off}

89
.github/workflows/build-bundle.yml vendored Normal file
View File

@@ -0,0 +1,89 @@
name: Bundles Builder
on:
# Create bundle from manual action
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch or ref'
type: string
required: true
default: 'develop'
build_wasm:
description: 'BUILD_WASM. Valid values: yes, no'
type: string
required: false
default: 'yes'
build_storybook:
description: 'BUILD_STORYBOOK. Valid values: yes, no'
type: string
required: false
default: 'yes'
workflow_call:
inputs:
gh_ref:
description: 'Name of the branch or ref'
type: string
required: true
default: 'develop'
build_wasm:
description: 'BUILD_WASM. Valid values: yes, no'
type: string
required: false
default: 'yes'
build_storybook:
description: 'BUILD_STORYBOOK. Valid values: yes, no'
type: string
required: false
default: 'yes'
jobs:
build-bundle:
name: Build and Upload Penpot Bundle
runs-on: ubuntu-24.04
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.gh_ref }}
- name: Extract some useful variables
id: vars
run: |
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
- name: Build bundle
env:
BUILD_WASM: ${{ inputs.build_wasm }}
BUILD_STORYBOOK: ${{ inputs.build_storybook }}
run: ./manage.sh build-bundle
- name: Prepare directories for zipping
run: |
mkdir zips
mv bundles penpot
- name: Create zip bundle
run: |
echo "📦 Packaging Penpot bundle..."
zip -r zips/penpot.zip penpot
- name: Upload Penpot bundle to S3
run: |
aws s3 cp zips/penpot.zip s3://${{ secrets.S3_BUCKET }}/penpot-${{ steps.vars.outputs.gh_ref }}.zip
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
TEXT: |
❌ *[PENPOT] Error during the execution of the job*
📄 Triggered from ref: `${{ steps.vars.outputs.gh_ref }}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}

View File

@@ -1,129 +0,0 @@
name: Build and Upload Penpot Bundles non-prod
on:
# Create bundler for every tag
push:
tags:
- '**' # Pattern matched against refs/tags
# Create bundler every hour between 5:00 and 20:00 on working days
schedule:
- cron: '0 5-20 * * 1-5'
# Create bundler from manual action
workflow_dispatch:
inputs:
zip_mode:
# zip_mode defines how the build artifacts are packaged:
# - 'individual': creates one ZIP file per component (frontend, backend, exporter)
# - 'all': creates a single ZIP containing all components
# - null: for the rest of cases (non-manual events)
description: 'Bundle packaging mode'
required: false
default: 'individual'
type: choice
options:
- individual
- all
jobs:
build-bundles:
name: Build and Upload Penpot Bundles
runs-on: ubuntu-24.04
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Extract somer useful variables
id: vars
run: |
echo "commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "gh_branch=${{ github.base_ref || github.ref_name }}" >> $GITHUB_OUTPUT
# Set up Docker Buildx for multi-arch build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Run manage.sh build-bundle from host
run: ./manage.sh build-bundle
- name: Prepare directories for zipping
run: |
mkdir zips
mv bundles penpot
- name: Create zip bundles for zip_mode == 'all'
if: ${{ github.event.inputs.zip_mode == 'all' }}
run: |
echo "📦 Packaging Penpot 'all' bundles..."
zip -r zips/penpot-all-bundles.zip penpot
- name: Create zip bundles for zip_mode != 'all'
if: ${{ github.event.inputs.zip_mode != 'all' }}
run: |
echo "📦 Packaging Penpot 'individual' bundles..."
zip -r zips/penpot-frontend.zip penpot/frontend
zip -r zips/penpot-backend.zip penpot/backend
zip -r zips/penpot-exporter.zip penpot/exporter
- name: Upload unified 'all' bundle
if: ${{ github.event.inputs.zip_mode == 'all' }}
uses: actions/upload-artifact@v4
with:
name: penpot-all-bundles
path: zips/penpot-all-bundles.zip
- name: Upload individual bundles
if: ${{ github.event.inputs.zip_mode != 'all' }}
uses: actions/upload-artifact@v4
with:
name: penpot-individual-bundles
path: |
zips/penpot-frontend.zip
zips/penpot-backend.zip
zips/penpot-exporter.zip
- name: Upload unified 'all' bundle to S3
if: ${{ github.event.inputs.zip_mode == 'all' }}
run: |
aws s3 cp zips/penpot-all-bundles.zip s3://${{ secrets.S3_BUCKET }}/penpot-all-bundles-${{ steps.vars.outputs.gh_branch}}.zip
aws s3 cp zips/penpot-all-bundles.zip s3://${{ secrets.S3_BUCKET }}/penpot-all-bundles-${{ steps.vars.outputs.commit_hash }}.zip
- name: Upload 'individual' bundles to S3
if: ${{ github.event.inputs.zip_mode != 'all' }}
run: |
for name in penpot-frontend penpot-backend penpot-exporter; do
aws s3 cp zips/${name}.zip s3://${{ secrets.S3_BUCKET }}/${name}-${{ steps.vars.outputs.gh_branch }}-latest.zip
aws s3 cp zips/${name}.zip s3://${{ secrets.S3_BUCKET }}/${name}-${{ steps.vars.outputs.commit_hash }}.zip
done
- name: Notify Mattermost about automatic bundles
if: github.event_name == 'pull_request'
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
TEXT: |
📦 *Penpot bundle automatically generated*
📄 PR: ${{ github.event.pull_request.title }}
🔁 From: \`${{ github.head_ref }}\` to \`{{ github.base_ref }}\`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: Notify Mattermost about manual bundles
if: github.event_name == 'workflow_dispatch'
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
TEXT: |
📦 *Penpot bundle manually generated*
📄 Triggered from branch: `${{ github.ref_name}}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: Print artifact summary URL
run: |
echo "📦 Artifacts available at:"
echo "🔗 https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"

21
.github/workflows/build-develop.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: _DEVELOP
on:
schedule:
- cron: '16 5-20 * * 1-5'
jobs:
build-bundle:
uses: ./.github/workflows/build-bundle.yml
secrets: inherit
with:
gh_ref: "develop"
build_wasm: "yes"
build_storybook: "yes"
build-docker:
needs: build-bundle
uses: ./.github/workflows/build-docker.yml
secrets: inherit
with:
gh_ref: "develop"

View File

@@ -0,0 +1,36 @@
name: DevEnv Docker Image Builder
on:
workflow_dispatch:
jobs:
build-and-push:
name: Build and push DevEnv Docker image
environment: release-admins
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
username: ${{ secrets.PUB_DOCKER_USERNAME }}
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
- name: Build and push DevEnv Docker image
uses: docker/build-push-action@v6
env:
DOCKER_IMAGE: 'penpotapp/devenv'
with:
context: ./docker/devenv/
file: ./docker/devenv/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.DOCKER_IMAGE }}:latest
cache-from: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache,mode=max

101
.github/workflows/build-docker.yml vendored Normal file
View File

@@ -0,0 +1,101 @@
name: Docker Images Builder
on:
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch or ref'
type: string
required: true
default: 'develop'
workflow_call:
inputs:
gh_ref:
description: 'Name of the branch or ref'
type: string
required: true
default: 'develop'
jobs:
build-and-push:
name: Build and Push Penpot Docker Images
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.gh_ref }}
- name: Extract some useful variables
id: vars
run: |
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
- name: Download Penpot Bundles
env:
FILE_NAME: penpot-${{ steps.vars.outputs.gh_ref }}.zip
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
run: |
pushd docker/images
aws s3 cp s3://${{ secrets.S3_BUCKET }}/$FILE_NAME .
unzip $FILE_NAME > /dev/null
mv penpot/backend bundle-backend
mv penpot/frontend bundle-frontend
mv penpot/exporter bundle-exporter
popd
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
registry: ${{ secrets.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Backend Docker image
uses: docker/build-push-action@v6
env:
DOCKER_IMAGE: 'backend'
BUNDLE_PATH: './bundle-backend'
with:
context: ./docker/images/
file: ./docker/images/Dockerfile.backend
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.gh_ref }}
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max
- name: Build and push Frontend Docker image
uses: docker/build-push-action@v6
env:
DOCKER_IMAGE: 'frontend'
BUNDLE_PATH: './bundle-frontend'
with:
context: ./docker/images/
file: ./docker/images/Dockerfile.frontend
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.gh_ref }}
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max
- name: Build and push Exporter Docker image
uses: docker/build-push-action@v6
env:
DOCKER_IMAGE: 'exporter'
BUNDLE_PATH: './bundle-exporter'
with:
context: ./docker/images/
file: ./docker/images/Dockerfile.exporter
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.gh_ref }}
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max

21
.github/workflows/build-staging.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: _STAGING
on:
schedule:
- cron: '36 5-20 * * 1-5'
jobs:
build-bundle:
uses: ./.github/workflows/build-bundle.yml
secrets: inherit
with:
gh_ref: "staging"
build_wasm: "yes"
build_storybook: "yes"
build-docker:
needs: build-bundle
uses: ./.github/workflows/build-docker.yml
secrets: inherit
with:
gh_ref: "staging"

30
.github/workflows/build-tag.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: _TAG
on:
push:
tags:
- '*'
jobs:
build-bundle:
uses: ./.github/workflows/build-bundle.yml
secrets: inherit
with:
gh_ref: ${{ github.ref_name }}
build_wasm: "no"
build_storybook: "yes"
build-docker:
needs: build-bundle
uses: ./.github/workflows/build-docker.yml
secrets: inherit
with:
gh_ref: ${{ github.ref_name }}
# publish-final-tag:
# if: ${{ !contains(github.ref_name, '-RC') && !contains(github.ref_name, '-alpha') && !contains(github.ref_name, '-beta') && contains(github.ref_name, '.') }}
# needs: build-docker
# uses: ./.github/workflows/release.yml
# secrets: inherit
# with:
# gh_ref: ${{ github.ref_name }}

View File

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

95
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
name: Release Publisher
on:
workflow_dispatch:
inputs:
gh_ref:
description: 'Tag to release'
type: string
required: true
workflow_call:
inputs:
gh_ref:
description: 'Tag to release'
type: string
required: true
permissions:
contents: write
jobs:
release:
environment: release-admins
runs-on: ubuntu-24.04
outputs:
version: ${{ steps.vars.outputs.gh_ref }}
release_notes: ${{ steps.extract_release_notes.outputs.release_notes }}
steps:
- name: Extract some useful variables
id: vars
run: |
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.vars.outputs.gh_ref }}
# # --- Publicly release the docker images ---
# - name: Login to private registry
# uses: docker/login-action@v3
# with:
# registry: ${{ secrets.DOCKER_REGISTRY }}
# username: ${{ secrets.DOCKER_USERNAME }}
# password: ${{ secrets.DOCKER_PASSWORD }}
# - name: Login to DockerHub
# uses: docker/login-action@v3
# with:
# username: ${{ secrets.PUB_DOCKER_USERNAME }}
# password: ${{ secrets.PUB_DOCKER_PASSWORD }}
# - name: Publish docker images to DockerHub
# env:
# TAG: ${{ steps.vars.outputs.gh_ref }}
# REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
# HUB: ${{ secrets.PUB_DOCKER_HUB }}
# run: |
# IMAGES=("frontend" "backend" "exporter")
# EXTRA_TAGS=("main" "latest")
# for image in "${IMAGES[@]}"; do
# docker pull "$REGISTRY/penpotapp/$image:$TAG"
# docker tag "$REGISTRY/penpotapp/$image:$TAG" "penpotapp/$image:$TAG"
# docker push "penpotapp/$image:$TAG"
# for tag in "${EXTRA_TAGS[@]}"; do
# docker tag "$REGISTRY/penpotapp/$image:$TAG" "penpotapp/$image:$tag"
# docker push "penpotapp/$image:$tag"
# done
# done
# --- Release notes extraction ---
- name: Extract release notes from CHANGES.md
id: extract_release_notes
env:
TAG: ${{ steps.vars.outputs.gh_ref }}
run: |
RELEASE_NOTES=$(awk "/^## $TAG$/{flag=1; next} /^## /{flag=0} flag" CHANGES.md | awk '{$1=$1};1')
if [ -z "$RELEASE_NOTES" ]; then
RELEASE_NOTES="No changes for $TAG according to CHANGES.md"
fi
echo "release_notes<<EOF" >> $GITHUB_OUTPUT
echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# --- Create GitHub release ---
- name: Create GitHub release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.vars.outputs.gh_ref }}
name: ${{ steps.vars.outputs.gh_ref }}
body: ${{ steps.extract_release_notes.outputs.release_notes }}

7
.gitignore vendored
View File

@@ -31,6 +31,7 @@
/.clj-kondo/.cache
/_dump
/notes
/playground/
/backend/*.md
/backend/*.sql
/backend/*.txt
@@ -41,6 +42,7 @@
/backend/resources/public/assets
/backend/resources/public/media
/backend/target/
/backend/experiments
/bundle*
/cd.md
/clj-profiler/
@@ -51,9 +53,6 @@
/exporter/target
/frontend/.storybook/preview-body.html
/frontend/.storybook/preview-head.html
/frontend/cypress/fixtures/validuser.json
/frontend/cypress/videos/*/
/frontend/cypress/videos/*/
/frontend/dist/
/frontend/npm-debug.log
/frontend/out/
@@ -70,6 +69,8 @@
/vendor/svgclean/bundle*.js
/web
/library/target/
/library/*.zip
/external
clj-profiler/
node_modules

2
.nvmrc
View File

@@ -1 +1 @@
v22.13.1
v22.19.0

View File

@@ -1,6 +1,117 @@
# CHANGELOG
## 2.9.0 (Unreleased)
## 2.11.0 (Unreleased)
### :rocket: Epics and highlights
- Deprecated configuration variables with the prefix `PENPOT_ASSETS_*`, and will be
removed in future versions:
- The `PENPOT_ASSETS_STORAGE_BACKEND` becomes `PENPOT_OBJECTS_STORAGE_BACKEND` and its
values passes from (`assets-fs` or `assets-s3`) to (`fs` or `s3`)
- The `PENPOT_STORAGE_ASSETS_FS_DIRECTORY` becomes `PENPOT_OBJECTS_STORAGE_FS_DIRECTORY`
- The `PENPOT_STORAGE_ASSETS_S3_BUCKET` becomes `PENPOT_OBJECTS_STORAGE_S3_BUCKET`
- The `PENPOT_STORAGE_ASSETS_S3_REGION` becomes `PENPOT_OBJECTS_STORAGE_S3_REGION`
- The `PENPOT_STORAGE_ASSETS_S3_ENDPOINT` becomes `PENPOT_OBJECTS_STORAGE_S3_ENDPOINT`
- The `PENPOT_STORAGE_ASSETS_S3_IO_THREADS` replaced (see below)
- Add `PENPOT_NETTY_IO_THREADS` and `PENPOT_EXECUTOR_THREADS` variables to provide the
control over concurrency of the shared resources used by netty. Penpot uses the netty IO
threads for AWS S3 SDK and Redis/Valkey communication, and the EXEC threads to perform
out of HTTP serving threads tasks such that cache invalidation, S3 response completion,
configuration reloading and many other auxiliar tasks. By default they use a half number
if available cpus with a minumum of 2 for both executors. You should not touch that
variables unless you are know what you are doing.
- Replace the `PENPOT_STORAGE_ASSETS_S3_IO_THREADS` with a more general configuration
`PENPOT_NETTY_IO_THREADS` used to configure a shared netty resources across different
services which use netty internally (redis connection, S3 SDK client). This
configuration is not very commonly used so don't expected real impact on any user.
### :heart: Community contributions (Thank you!)
### :sparkles: New features & Enhancements
- Show current Penpot version [Taiga #11603](https://tree.taiga.io/project/penpot/us/11603)
- Switch several variant copies at the same time [Taiga #11411](https://tree.taiga.io/project/penpot/us/11411)
- Invitations management improvements [Taiga #3479](https://tree.taiga.io/project/penpot/us/3479)
- Alternative ways of creating variants - Button Viewport [Taiga #11931](https://tree.taiga.io/project/penpot/us/11931)
### :bug: Bugs fixed
- Fix selection problems when devtools open [Taiga #11950](https://tree.taiga.io/project/penpot/issue/11950)
- Fix long font names overlap [Taiga #11844](https://tree.taiga.io/project/penpot/issue/11844)
- Fix paste behavior according to the selected element [Taiga #11979](https://tree.taiga.io/project/penpot/issue/11979)
- Fix problem with export size [Github #7160](https://github.com/penpot/penpot/issues/7160)
- Fix multi level library dependencies [Taiga #12155](https://tree.taiga.io/project/penpot/issue/12155)
- Fix component context menu options order in assets tab [Taiga #11941](https://tree.taiga.io/project/penpot/issue/11941)
## 2.10.0
### :rocket: Epics and highlights
- Variants
### :boom: Breaking changes & Deprecations
### :heart: Community contributions (Thank you!)
### :sparkles: New features & Enhancements
- Add efficiency enhancements to right sidebar [Github #7182](https://github.com/penpot/penpot/pull/7182)
- Add defaults for artboard drawing [Taiga #494](https://tree.taiga.io/project/penpot/us/494?milestone=465047)
- Continuous display of distances between elements when moving a layer with the keyboard [Taiga #1780](https://tree.taiga.io/project/penpot/us/1780)
- New Number token - unitless values [Taiga #10936](https://tree.taiga.io/project/penpot/us/10936)
- New font-family token [Taiga #10937](https://tree.taiga.io/project/penpot/us/10937)
- New text case token [Taiga #10942](https://tree.taiga.io/project/penpot/us/10942)
- New text-decoration token [Taiga #10941](https://tree.taiga.io/project/penpot/us/10941)
- New letter spacing token [Taiga #10940](https://tree.taiga.io/project/penpot/us/10940)
- New font weight token [Taiga #10939](https://tree.taiga.io/project/penpot/us/10939)
- Upgrade Node to v22.18.0 [Github #7283](https://github.com/penpot/penpot/pull/7283)
- Upgrade the base docker image for penpot frontend to v1.29.1 [Github #7283](https://github.com/penpot/penpot/pull/7283)
- Create variant from an existing component [Taiga #2088](https://tree.taiga.io/project/penpot/us/2088)
- Create variant from an existing variant [Taiga #8282](https://tree.taiga.io/project/penpot/us/8282)
- Actions over a component with variants [Taiga #10503](https://tree.taiga.io/project/penpot/us/10503)
- Create a variant by dragging a component into a component with variants [Taiga #8134](https://tree.taiga.io/project/penpot/us/8134)
- Transform a variant into an individual component [Taiga #8141](https://tree.taiga.io/project/penpot/us/8141)
- Delete variant [Taiga #6890](https://tree.taiga.io/project/penpot/us/6890)
- Restore an orphaned copy of a variant [Taiga #10446](https://tree.taiga.io/project/penpot/us/10446)
- Add, Edit & Delete variant properties name and value [Taiga #6892](https://tree.taiga.io/project/penpot/us/6892)
- Retrieve variants [Taiga #6888](https://tree.taiga.io/project/penpot/us/6888)
- Retrieve variants with nested components [Taiga #10277](https://tree.taiga.io/project/penpot/us/10277)
- Create variants in bulk from existing components [Taiga #7926](https://tree.taiga.io/project/penpot/us/7926)
- Alternative ways of creating variants - Button Design Tab [Taiga #10316](https://tree.taiga.io/project/penpot/us/10316)
- Fix problem with component swapping panel [Taiga #12175](https://tree.taiga.io/project/penpot/issue/12175)
### :bug: Bugs fixed
- Display strokes information in inspect tab [Taiga #11154](https://tree.taiga.io/project/penpot/issue/11154)
- Fix problem with booleans selection [Taiga #11627](https://tree.taiga.io/project/penpot/issue/11627)
- Fix missing font when copy&paste a chunk of text [Taiga #11522](https://tree.taiga.io/project/penpot/issue/11522)
- Fix bad swap slot after two swaps [Taiga #11659](https://tree.taiga.io/project/penpot/issue/11659)
- Fix missing package for the `penpot_exporter` Docker image [GitHub #7205](https://github.com/penpot/penpot/issues/7025)
- Fix issue where multiple dropdown menus could be opened simultaneously on the dashboard page [Taiga #11500](https://tree.taiga.io/project/penpot/issue/11500)
- Fix font size/variant not updated when editing a text [Taiga #11552](https://tree.taiga.io/project/penpot/issue/11552)
- Fix issue where Alt + arrow keys shortcut interferes with letter-spacing when moving text layers [Taiga #11552](https://tree.taiga.io/project/penpot/issue/11771)
- Fix consistency issues on how font variants are visualized [Taiga #11499](https://tree.taiga.io/project/penpot/us/11499)
- Fix parsing rx and ry SVG values for rect radius [Taiga #11861](https://tree.taiga.io/project/penpot/issue/11861)
- Fix misleading affordance in saved versions [Taiga #11887](https://tree.taiga.io/project/penpot/issue/11887)
- Fix pasting RTF text crashes penpot [Taiga #11717](https://tree.taiga.io/project/penpot/issue/11717)
- Fix navigation arrows in Libraries & Templates carousel [Taiga #10609](https://tree.taiga.io/project/penpot/issue/10609)
- Fix applying tokens with zero value to size [Taiga #11618](https://tree.taiga.io/project/penpot/issue/11618)
- Fix typo [Taiga #11969](https://tree.taiga.io/project/penpot/issue/11969)
- Fix typo [Taiga #11970](https://tree.taiga.io/project/penpot/issue/11970)
- Fix typos [Taiga #11971](https://tree.taiga.io/project/penpot/issue/11971)
- Fix inconsistent naming for "Flatten" [Taiga #8371](https://tree.taiga.io/project/penpot/issue/8371)
- Layout item tokens should be unapplied when moving out of a layout [Taiga #11012](https://tree.taiga.io/project/penpot/issue/11012)
- Fix incorrect date displayed for support plan [Taiga #11986](https://tree.taiga.io/project/penpot/issue/11986)
- Fix can't import 'borderWidth' type token [#132](https://github.com/tokens-studio/penpot/issues/132)
- Fix moving elements up or down while pressing alt [Taiga Issue #11992](https://tree.taiga.io/project/penpot/issue/11992)
- Fix conflicting shortcuts (remove dec/inc line height and letter spacing) [Taiga #12102](https://tree.taiga.io/project/penpot/issue/12102)
- Fix conflicting shortcuts (remove text-align shortcuts) [Taiga #12047](https://tree.taiga.io/project/penpot/issue/12047)
- Fix export file with empty tokens library [Taiga #12137](https://tree.taiga.io/project/penpot/issue/12137)
- Fix context menu on spacing tokens [Taiga #12141](https://tree.taiga.io/project/penpot/issue/12141)
## 2.9.0
### :rocket: Epics and highlights
@@ -8,23 +119,67 @@
### :heart: Community contributions (Thank you!)
- Clarify message when inviting existing team members to make it more user-friendly and clear which invitations will be sent. [Taiga #11441](https://tree.taiga.io/project/penpot/issue/11441) by [@iprithvitharun](https://github.com/iprithvitharun)
- Update email change confirmation message for clarity and correct grammar. [GitHub #6786](https://github.com/penpot/penpot/issues/6786) by [@iprithvitharun](https://github.com/iprithvitharun)
### :sparkles: New features & Enhancements
- Add visual indicator for new comments in the workspace [Taiga #11328](https://tree.taiga.io/project/penpot/issue/11328)
- On components overrides, separate the content of the text from the rest of properties [Taiga #7434](https://tree.taiga.io/project/penpot/us/7434)
- Improve dashboard's sidebar [Taiga #10700](https://tree.taiga.io/project/penpot/us/10700)
- Change "Save color" button to primary button [Taiga #9410](https://tree.taiga.io/project/penpot/issue/9410)
- Support for exif rotated images [GitHub #6767](https://github.com/penpot/penpot/issues/6767)
- Display Blend Mode and Layer Opacity properties in the Inspect tab [Taiga #11283](https://tree.taiga.io/project/penpot/issue/11283)
- Provide CSS `mix-blend-mode` property in code editor when present on shape [Taiga #11282](https://tree.taiga.io/project/penpot/issue/11282)
- Add the option to import tokens in a .zip file. [Taiga #11378](https://tree.taiga.io/project/penpot/us/11378)
- New typography token type - font size token [Taiga #10938](https://tree.taiga.io/project/penpot/us/10938)
- Hide bounding box while editing visual effects [Taiga #11576](https://tree.taiga.io/project/penpot/issue/11576)
- Improved text layer resizing: Allow double-click on text bounding box to set auto-width/auto-height [Taiga #11577](https://tree.taiga.io/project/penpot/issue/11577)
- Improve text layer auto-resize: auto-width switches to auto-height on horizontal resize, and only switches to fixed on vertical resize [Taiga #11578](https://tree.taiga.io/project/penpot/issue/11578)
- Add the ability to show login dialog on profile settings [Github #6871](https://github.com/penpot/penpot/pull/6871)
- Improve the application of tokens with object specific tokens [Taiga #10209](https://tree.taiga.io/project/penpot/us/10209)
- Add info to apply-token event [Taiga #11710](https://tree.taiga.io/project/penpot/task/11710)
- Fix double click on set name input [Taiga #11747](https://tree.taiga.io/project/penpot/issue/11747)
### :bug: Bugs fixed
- Copying font size does not copy the unit [Taiga #11143](https://tree.taiga.io/project/penpot/issue/11143)
- Fix text-decoration line-through that displays a wrong property value [Taiga #11145](https://tree.taiga.io/project/penpot/issue/11145)
- Fix display error message on register form [Taiga #11444](https://tree.taiga.io/project/penpot/issue/11444)
- Fix toggle focus mode did not restore viewport and selection upon exit [GitHub #6280](https://github.com/penpot/penpot/issues/6820)
- Fix problem when creating a layout from an existing layout [Taiga #11554](https://tree.taiga.io/project/penpot/issue/11554)
- Fix title button from Title Case to Capitalize [Taiga #11476](https://tree.taiga.io/project/penpot/issue/11476)
- Fix touchpad swipe leading to navigating back/forth [GitHub #4246](https://github.com/penpot/penpot/issues/4246)
- Keep color data when copying from info tab into CSS [Taiga #11144](https://tree.taiga.io/project/penpot/issue/11144)
- Update HSL values to modern syntax as defined in W3C CSS Color Module Level 4 [Taiga #11144](https://tree.taiga.io/project/penpot/issue/11144)
- Fix main component receives focus and is selected when using 'Show Main Component' [Taiga #11402](https://tree.taiga.io/project/penpot/issue/11402)
- Fix UI theme selection from main menu [Taiga #11567](https://tree.taiga.io/project/penpot/issue/11567)
- Fix duplicating pages with mainInstance shapes nested inside groups [Taiga #10774](https://tree.taiga.io/project/penpot/issue/10774)
- Fix ESC key not closing Add/Manage Libraries modal [Taiga #11523](https://tree.taiga.io/project/penpot/issue/11523)
- Fix copying a shadow color from info tab [Taiga #11211](https://tree.taiga.io/project/penpot/issue/11211)
- Fix remove color button in the gradient editor [Taiga #11623](https://tree.taiga.io/project/penpot/issue/11623)
- Fix "Copy as SVG" generates different code from the Inspect panel [Taiga #11519](https://tree.taiga.io/project/penpot/issue/11519)
- Fix overriden tokens in text copies are not preserved [Taiga #11486](https://tree.taiga.io/project/penpot/issue/11486)
- Fix problem when changing between flex/grid layout [Taiga #11625](https://tree.taiga.io/project/penpot/issue/11625)
- Fix opacity on stroke gradients [Taiga #11646](https://tree.taiga.io/project/penpot/issue/11646)
- Fix change from gradient to solid color [Taiga #11648](https://tree.taiga.io/project/penpot/issue/11648)
- Fix the context menu always closes after any action [Taiga #11624](https://tree.taiga.io/project/penpot/issue/11624)
- Fix X & Y position do not sincronize with tokens [Taiga #11617](https://tree.taiga.io/project/penpot/issue/11617)
- Fix tooltip position after first time [Taiga #11688](https://tree.taiga.io/project/penpot/issue/11688)
- Fix inconsistent ordering of pinned projects on dashboard sidebar [Taiga #11674](https://tree.taiga.io/project/penpot/issue/11674)
- Fix export button width on inspect tab [Taiga #11394](https://tree.taiga.io/project/penpot/issue/11394)
- Fix stroke width token application [Taiga #11724](https://tree.taiga.io/project/penpot/issue/11724)
- Fix number token application on shape [Taiga #11331](https://tree.taiga.io/project/penpot/task/11331)
- Fix auto height is fixed in the HTML inspect tab for text elements [Taiga #11680](https://tree.taiga.io/project/penpot/task/11680)
## 2.8.1
### :bug: Bugs fixed
- Fix unexpected exception on processing old texts [Github #6889](https://github.com/penpot/penpot/pull/6889)
- Fix error on inspect tab when selecting multiple shapes [Taiga #11655](https://tree.taiga.io/project/penpot/issue/11655)
- Fix missing package for the penport_exporter Docker image [GitHub #7205](https://github.com/penpot/penpot/issues/7025)
## 2.8.0
@@ -34,7 +189,7 @@
**Penpot Library**
The initial prototype is completly reworked for provide a more consistent API
The initial prototype is completly reworked to provide a more consistent API
and to have proper validation and params decoding. All the details can be found
on [its own changelog](library/CHANGES.md)
@@ -46,6 +201,7 @@ in future versions. Therefore, **migration from Redis to ValKey is recommended f
on-premises instances** that want to keep up to date.
### :heart: Community contributions (Thank you!)
- Add Serbian language [GitHub #5002](https://github.com/penpot/penpot/issues/5002) by [crnobog69](https://github.com/crnobog69)
### :sparkles: New features & Enhancements
@@ -101,7 +257,6 @@ on-premises instances** that want to keep up to date.
- Fix copy in error message [GitHub #6615](https://github.com/penpot/penpot/pull/6615)
- Fix url on invitation link [Taiga #11284](https://tree.taiga.io/project/penpot/issue/11284)
## 2.7.1
### :bug: Bugs fixed
@@ -109,7 +264,6 @@ on-premises instances** that want to keep up to date.
- Fix incorrect handling of strokes with images on importing files
- Fix tokens disappearing after manual additions [Taiga #11063](https://tree.taiga.io/project/penpot/issue/11063)
## 2.7.0
### :rocket: Epics and highlights
@@ -241,7 +395,6 @@ on-premises instances** that want to keep up to date.
- Add character limitation to asset inputs [Taiga #10669](https://tree.taiga.io/project/penpot/issue/10669)
- Fix Storybook link 'list of all available icons' wrong path [Taiga #10705](https://tree.taiga.io/project/penpot/issue/10705)
## 2.5.4
### :heart: Community contributions (Thank you!)
@@ -286,7 +439,7 @@ on-premises instances** that want to keep up to date.
### :boom: Breaking changes & Deprecations
Although this is not a breaking change, we believe its important to highlight it in this
Although this is not a breaking change, we believe it's important to highlight it in this
section:
This release includes a fix for an internal bug in Penpot that caused incorrect handling
@@ -294,9 +447,9 @@ of media assets (e.g., fill images). The issue has been resolved since version 2
no new incorrect references will be generated. However, existing files may still contain
incorrect references.
To address this, weve provided a script to correct these references in existing files.
To address this, we've provided a script to correct these references in existing files.
While having incorrect references generally doesnt result in visible issues, there are
While having incorrect references generally doesn't result in visible issues, there are
rare cases where it can cause problems. For example, if a component library (containing
images) is deleted, and that library is being used in other files, running the FileGC task
(responsible for freeing up space and performing logical deletions) could leave those
@@ -371,7 +524,6 @@ is a number of cores)
- Fix missing methods reference on API Docs
- Fix memory usage issue on file-gc asynchronous task (related to snapshots feature)
## 2.4.1
### :bug: Bugs fixed
@@ -379,7 +531,6 @@ is a number of cores)
- Fix error when importing files with touched components [Taiga #9625](https://tree.taiga.io/project/penpot/issue/9625)
- Fix problem when changing color libraries [Plugins #184](https://github.com/penpot/penpot-plugins/issues/184)
## 2.4.0
### :rocket: Epics and highlights
@@ -433,7 +584,6 @@ is a number of cores)
- Add initial documentation for Kubernetes
## 2.3.1
### :bug: Bugs fixed
@@ -441,7 +591,6 @@ is a number of cores)
- Fix unexpected issue on interaction between plugins sandbox and
internal impl of promise
## 2.3.0
### :rocket: Epics and highlights
@@ -467,7 +616,6 @@ is a number of cores)
You can enable it with the `enable-feature-text-editor-v2` configuration flag.
### :bug: Bugs fixed
- Fix problem with constraints buttons [Taiga #8465](https://tree.taiga.io/project/penpot/issue/8465)
@@ -507,8 +655,8 @@ is a number of cores)
### :boom: Breaking changes & Deprecations
- Removed "merge assets" option when exporting ".svg + .json" files. After the components changes the option wasn't
working properly and we're planning to change the format soon. We think it's better to deprecate the option for the
time being.
working properly and we're planning to change the format soon. We think it's better to deprecate the option for the
time being.
### :heart: Community contributions (Thank you!)
@@ -524,7 +672,7 @@ time being.
freeing up space in the database. It can be enabled with the
`enable-enable-tiered-file-data-storage` flag.
*(On-Premise feature, EXPERIMENTAL).*
_(On-Premise feature, EXPERIMENTAL)._
- **JSON Interoperability for HTTP API** [Taiga #8372](https://tree.taiga.io/project/penpot/us/8372)
@@ -567,7 +715,7 @@ time being.
- **Design System**
We implemented and subbed in new components from our Design System: `loader*` ([Taiga #8355](https://tree.taiga.io/project/penpot/task/8355)) and `tab-switcher*` ([Taiga #8518](https://tree.taiga.io/project/penpot/task/8518)).
We implemented and subbed in new components from our Design System: `loader*` ([Taiga #8355](https://tree.taiga.io/project/penpot/task/8355)) and `tab-switcher*` ([Taiga #8518](https://tree.taiga.io/project/penpot/task/8518)).
- **Storybook** [Taiga #6329](https://tree.taiga.io/project/penpot/us/6329)
@@ -622,11 +770,11 @@ time being.
### :sparkles: New features
- Consolidate templates new order and naming [Taiga #8392](https://tree.taiga.io/project/penpot/task/8392)
- Consolidate templates new order and naming [Taiga #8392](https://tree.taiga.io/project/penpot/task/8392)
### :bug: Bugs fixed
- Fix the search label in translations [Taiga #8402](https://tree.taiga.io/project/penpot/issue/8402)
- Fix the "search" label in translations [Taiga #8402](https://tree.taiga.io/project/penpot/issue/8402)
- Fix pencil loader [Taiga #8348](https://tree.taiga.io/project/penpot/issue/8348)
- Fix several issues on the OIDC.
- Fix regression on the `email-verification` flag [Taiga #8398](https://tree.taiga.io/project/penpot/issue/8398)
@@ -706,22 +854,21 @@ time being.
- Fix color palette sorting [Taiga #7458](https://tree.taiga.io/project/penpot/issue/7458)
- Fix style scoping problem with imported SVG [Taiga #7671](https://tree.taiga.io/project/penpot/issue/7671)
## 2.0.1
### :bug: Bugs fixed
- Fix different issues related to components v2 migrations including [Github #4443](https://github.com/penpot/penpot/issues/4443)
## 2.0.0 - I Just Can't Get Enough
### :rocket: Epics and highlights
- Grid CSS layout [Taiga #4915](https://tree.taiga.io/project/penpot/epic/4915)
- UI redesign [Taiga #4958](https://tree.taiga.io/project/penpot/epic/4958)
- New components System [Taiga #2662](https://tree.taiga.io/project/penpot/epic/2662)
- Swap components [Taiga #1331](https://tree.taiga.io/project/penpot/us/1331)
- Images as fill [Taiga #2983](https://tree.taiga.io/project/penpot/us/2983)
- Images as fill [Taiga #2983](https://tree.taiga.io/project/penpot/us/2983)
- HTML code generation [Taiga #5277](https://tree.taiga.io/project/penpot/us/5277)
- Light and dark themes [Taiga #2287](https://tree.taiga.io/project/penpot/us/2287)
@@ -730,9 +877,9 @@ time being.
- New strokes default to inside border [Taiga #6847](https://tree.taiga.io/project/penpot/issue/6847)
- Change default z ordering on layers in flex layout. The previous behavior was inconsistent with how HTML works and we changed it to be more consistent. Previous layers that overlapped could be hidden, the fastest way to fix this is changing the z-index property but a better way is to change the order of your layers.
### :heart: Community contributions (Thank you!)
- New Hausa, Yoruba and Igbo translations and update translation files (by All For Tech Empowerment Foundation) [Taiga #6950](https://tree.taiga.io/project/penpot/us/6950), [Taiga #6534](https://tree.taiga.io/project/penpot/us/6534)
- New Hausa, Yoruba and Igbo translations and update translation files (by All For Tech Empowerment Foundation) [Taiga #6950](https://tree.taiga.io/project/penpot/us/6950), [Taiga #6534](https://tree.taiga.io/project/penpot/us/6534)
- Hide bounding-box when editing shape (by @VasilevsVV) [#3930](https://github.com/penpot/penpot/pull/3930)
- CTRL + "+" to zoom into canvas instead of browser (by @audriu) [#3848](https://github.com/penpot/penpot/pull/3848)
- Add dev deps.edn in the project root (by @PEZ) [#3794](https://github.com/penpot/penpot/pull/3794)
@@ -741,6 +888,7 @@ time being.
- Typo (by StephanEggermont) [#157](https://github.com/penpot/penpot-docs/pull/157)
### :sparkles: New features
- Send comments with Ctrl+Enter / Cmd + Enter [Taiga #6085](https://tree.taiga.io/project/penpot/issue/6085)
- Select through stroke only rectangle [Taiga #5484](https://tree.taiga.io/project/penpot/issue/5484)
- Stroke default position [Taiga #6847](https://tree.taiga.io/project/penpot/issue/6847)
@@ -808,6 +956,7 @@ time being.
- [REDESIGN] Onboarding slides [Taiga #6678](https://tree.taiga.io/project/penpot/us/6678)
### :bug: Bugs fixed
- Fix pixelated thumbnails [Github #3681](https://github.com/penpot/penpot/issues/3681), [Github #3661](https://github.com/penpot/penpot/issues/3661)
- Fix problem with not applying colors to boards [Github #3941](https://github.com/penpot/penpot/issues/3941)
- Fix problem with path editor undoing changes [Github #3998](https://github.com/penpot/penpot/issues/3998)
@@ -816,7 +965,7 @@ time being.
- Selecting from Color Palette does not work for board when there is no existing fill [Taiga #6464](https://tree.taiga.io/project/penpot/issue/6464)
- Color thumbnails are consistently rounded in the inspect code mode [Taiga #5886](https://tree.taiga.io/project/penpot/issue/5886)
- Adding vector path points before first point of existing open path not working [Taiga #6593](https://tree.taiga.io/project/penpot/issue/6593)
- Some image formats include the extension when importing [Taiga #5485](https://tree.taiga.io/project/penpot/issue/5485)
- Some image formats include the extension when importing [Taiga #5485](https://tree.taiga.io/project/penpot/issue/5485)
- Gradient color tool doesn't work properly with flipped items [Taiga #6485](https://tree.taiga.io/project/penpot/issue/6485)
- [TEXT] Align options are not shown when several text are selected [Taiga #5948](https://tree.taiga.io/project/penpot/issue/5948)
- [VIEW MODE] Comments not working properly on multiple pages [Taiga #6281](https://tree.taiga.io/project/penpot/issue/6281)
@@ -860,7 +1009,7 @@ time being.
### :sparkles: New features
- Improve selected colors [Taiga #5805]( https://tree.taiga.io/project/penpot/us/5805)
- Improve selected colors [Taiga #5805](https://tree.taiga.io/project/penpot/us/5805)
### :bug: Bugs fixed
@@ -895,7 +1044,6 @@ time being.
- 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
@@ -1009,7 +1157,6 @@ time being.
- 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)
@@ -1163,12 +1310,14 @@ time being.
- Fix problem with opacity in imported SVG's [Taiga #4923](https://tree.taiga.io/project/penpot/issue/4923)
### :heart: Community contributions by (Thank you!)
- To @ondrejkonec: for contributing to the code with:
- Refactor CSS variables [Github #2948](https://github.com/penpot/penpot/pull/2948)
## 1.17.3
### :bug: Bugs fixed
- Fix copy and paste very nested inside itself [Taiga #4848](https://tree.taiga.io/project/penpot/issue/4848)
- Fix custom fonts not rendered correctly [Taiga #4874](https://tree.taiga.io/project/penpot/issue/4874)
- Fix problem with shadows and blur on multiple selection
@@ -1201,6 +1350,7 @@ time being.
## 1.17.1
### :bug: Bugs fixed
- Fix components groups items show the component name in list mode [Taiga #4770](https://tree.taiga.io/project/penpot/issue/4770)
- Fix typing CMD+Z on MacOS turns the cursor into a Zoom cursor [Taiga #4778](https://tree.taiga.io/project/penpot/issue/4778)
- Fix white space on small screens [Taiga #4774](https://tree.taiga.io/project/penpot/issue/4774)
@@ -1315,7 +1465,7 @@ time being.
### :boom: Breaking changes & Deprecations
- Removed the support for v2 internal file data blob format. This
- Removed the support for v2 internal file data blob format. This
version has never been documented nor set as default value so
technically this is not a breaking change because we are removing
a "private API".
@@ -1420,7 +1570,6 @@ time being.
- Fix when ungrouping, the items previously grouped should ALWAYS remain selected [Taiga #4064](https://tree.taiga.io/project/penpot/issue/4064)
- Change shortcut for "Clear undo" [#2219](https://github.com/penpot/penpot/issues/2219)
## 1.15.2-beta
### :bug: Bugs fixed
@@ -1504,6 +1653,7 @@ time being.
- Fix bringing complete file data when launching the export dialog [Taiga #4006](https://tree.taiga.io/project/penpot/issue/4006)
### :arrow_up: Deps updates
### :heart: Community contributions by (Thank you!)
## 1.14.2-beta
@@ -1544,10 +1694,10 @@ time being.
- Prototype connection should be under the rules [Taiga #3384](https://tree.taiga.io/project/penpot/issue/3384)
- Fix problem with empty text boxes events [Taiga #3627](https://tree.taiga.io/project/penpot/issue/3627)
## 1.13.5-beta
### :bug: Bugs fixed
- Fix orientation artboard preset not working with differently sized artboards [Taiga #3548](https://tree.taiga.io/project/penpot/issue/3548)
- Fix background on export arboards [Taiga #1991](https://tree.taiga.io/project/penpot/issue/1991)
@@ -1691,6 +1841,7 @@ time being.
- Fix problem when resizing a group with texts with auto-width/height [#3171](https://tree.taiga.io/project/penpot/issue/3171)
### :arrow_up: Deps updates
### :heart: Community contributions by (Thank you!)
## 1.12.4-beta
@@ -1708,7 +1859,7 @@ time being.
### :bug: Bugs fixed
- Fix issue with shift+select to deselect shapes [Taiga #3154](https://tree.taiga.io/project/penpot/issue/3154)
- Fix issue with drag-select shapes [Taiga #3165](https://tree.taiga.io/project/penpot/issue/3165)
- Fix issue with drag-select shapes [Taiga #3165](https://tree.taiga.io/project/penpot/issue/3165)
- Fix issue on password persistence after registration process on private instances
## 1.12.2-beta
@@ -1726,7 +1877,6 @@ time being.
- Fix length of names in sidebar [Taiga #2962](https://tree.taiga.io/project/penpot/issue/2962)
- Fix issues on loki integration
## 1.12.0-beta
### :boom: Breaking changes

View File

@@ -77,17 +77,14 @@ Provide your team or organization with a completely owned collaborative design t
### Integrations ###
Penpot offers integration into the development toolchain, thanks to its support for webhooks and an API accessible through access tokens.
### Whats great for design ###
With Penpot you can design libraries to share and reuse; turn design elements into components and tokens to allow reusability and scalability; and build realistic user flows and interactions.
### Design Tokens ###
With Penpots standardized [design tokens](https://penpot.dev/collaboration/design-tokens) format, you can easily reuse and sync tokens across different platforms, workflows, and disciplines.
### Building Design Systems: design tokens, components and variants ###
Penpot brings design systems to code-minded teams: a single source of truth with native Design Tokens, Components, and Variants for scalable, reusable, and consistent UI across projects and platforms.
<br />
<p align="center">
<img src="https://img.plasmic.app/img-optimizer/v1/img?src=https%3A%2F%2Fimg.plasmic.app%2Fimg-optimizer%2Fv1%2Fimg%2F9dd677c36afb477e9666ccd1d3f009ad.png" alt="Open Source" style="width: 65%;">
<img src="https://github.com/user-attachments/assets/cce75ad6-f783-473f-8803-da9eb8255fef">
</p>
<br />

View File

@@ -3,10 +3,10 @@
:deps
{penpot/common {:local/root "../common"}
org.clojure/clojure {:mvn/version "1.12.1"}
org.clojure/clojure {:mvn/version "1.12.2"}
org.clojure/tools.namespace {:mvn/version "1.5.0"}
com.github.luben/zstd-jni {:mvn/version "1.5.7-3"}
com.github.luben/zstd-jni {:mvn/version "1.5.7-4"}
io.prometheus/simpleclient {:mvn/version "0.16.0"}
io.prometheus/simpleclient_hotspot {:mvn/version "0.16.0"}
@@ -17,7 +17,7 @@
io.prometheus/simpleclient_httpserver {:mvn/version "0.16.0"}
io.lettuce/lettuce-core {:mvn/version "6.7.0.RELEASE"}
io.lettuce/lettuce-core {:mvn/version "6.8.1.RELEASE"}
;; Minimal dependencies required by lettuce, we need to include them
;; explicitly because clojure dependency management does not support
;; yet the BOM format.
@@ -28,29 +28,30 @@
com.google.guava/guava {:mvn/version "33.4.8-jre"}
funcool/yetti
{:git/tag "v11.4"
:git/sha "ce50d42"
{:git/tag "v11.6"
:git/sha "94dc017"
:git/url "https://github.com/funcool/yetti.git"
:exclusions [org.slf4j/slf4j-api]}
com.github.seancorfield/next.jdbc
{:mvn/version "1.3.1002"}
{:mvn/version "1.3.1070"}
metosin/reitit-core {:mvn/version "0.9.1"}
nrepl/nrepl {:mvn/version "1.3.1"}
nrepl/nrepl {:mvn/version "1.4.0"}
org.postgresql/postgresql {:mvn/version "42.7.6"}
org.xerial/sqlite-jdbc {:mvn/version "3.49.1.0"}
org.postgresql/postgresql {:mvn/version "42.7.7"}
org.xerial/sqlite-jdbc {:mvn/version "3.50.3.0"}
com.zaxxer/HikariCP {:mvn/version "6.3.0"}
com.zaxxer/HikariCP {:mvn/version "7.0.2"}
io.whitfin/siphash {:mvn/version "2.0.0"}
buddy/buddy-hashers {:mvn/version "2.0.167"}
buddy/buddy-sign {:mvn/version "3.6.1-359"}
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.0"}
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.2"}
org.jsoup/jsoup {:mvn/version "1.20.1"}
org.jsoup/jsoup {:mvn/version "1.21.2"}
org.im4java/im4java
{:git/tag "1.4.0-penpot-2"
:git/sha "e2b3e16"
@@ -60,12 +61,12 @@
org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"}
dawran6/emoji {:mvn/version "0.1.5"}
markdown-clj/markdown-clj {:mvn/version "1.12.3"}
dawran6/emoji {:mvn/version "0.2.0"}
markdown-clj/markdown-clj {:mvn/version "1.12.4"}
;; Pretty Print specs
pretty-spec/pretty-spec {:mvn/version "0.1.4"}
software.amazon.awssdk/s3 {:mvn/version "2.31.55"}}
software.amazon.awssdk/s3 {:mvn/version "2.33.10"}}
:paths ["src" "resources" "target/classes"]
:aliases
@@ -80,12 +81,14 @@
:build
{:extra-deps
{io.github.clojure/tools.build {:git/tag "v0.10.9" :git/sha "e405aac"}}
{io.github.clojure/tools.build {:mvn/version "0.10.10"}}
:ns-default build}
:test
{:main-opts ["-m" "kaocha.runner"]
:jvm-opts ["-Dlog4j2.configurationFile=log4j2-devenv-repl.xml"]
:jvm-opts ["-Dlog4j2.configurationFile=log4j2-devenv-repl.xml"
"--sun-misc-unsafe-memory-access=allow"
"--enable-native-access=ALL-UNNAMED"]
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}}
:outdated

View File

@@ -6,12 +6,14 @@
(ns user
(:require
[app.binfile.common :as bfc]
[app.common.data :as d]
[app.common.debug :as debug]
[app.common.exceptions :as ex]
[app.common.files.helpers :as cfh]
[app.common.fressian :as fres]
[app.common.geom.matrix :as gmt]
[app.common.json :as json]
[app.common.logging :as l]
[app.common.perf :as perf]
[app.common.pprint :as pp]
@@ -19,8 +21,9 @@
[app.common.schema.desc-js-like :as smdj]
[app.common.schema.desc-native :as smdn]
[app.common.schema.generators :as sg]
[app.common.schema.openapi :as oapi]
[app.common.spec :as us]
[app.common.json :as json]
[app.common.time :as ct]
[app.common.transit :as t]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
@@ -30,9 +33,9 @@
[app.srepl.helpers :as srepl.helpers]
[app.srepl.main :as srepl]
[app.util.blob :as blob]
[app.util.time :as dt]
[clj-async-profiler.core :as prof]
[clojure.contrib.humanize :as hum]
[clojure.datafy :refer [datafy]]
[clojure.java.io :as io]
[clojure.pprint :refer [pprint print-table]]
[clojure.repl :refer :all]

View File

@@ -193,7 +193,7 @@
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div
style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">
Click to the link below to confirm the change:</div>
Click the link below to confirm the change.</div>
</td>
</tr>
<tr>
@@ -217,8 +217,7 @@
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div
style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">
If you received this email by mistake, please consider changing your password for security
reasons.</div>
If you did not request this change, consider changing your password for security reasons.</div>
</td>
</tr>
<tr>

View File

@@ -2,12 +2,11 @@ Hello {{name|abbreviate:25}}!
We received a request to change your current email to {{ pending-email }}.
Click to the link below to confirm the change:
Click the link below to confirm the change.
{{ public-uri }}/#/auth/verify-token?token={{token}}
If you received this email by mistake, please consider changing your password
for security reasons.
If you did not request this change, consider changing your password for security reasons.
Enjoy!
The Penpot team.

View File

@@ -1 +1 @@
Invitation to join {{team}}
{{invited-by|abbreviate:25}} has invited you to join the team “{{ team|abbreviate:25 }}

View File

@@ -17,38 +17,6 @@ Debug Main Page
<desc><a href="/dbg/error">CLICK HERE TO SEE THE ERROR REPORTS</a> </desc>
</fieldset>
<fieldset>
<legend>Download file data:</legend>
<desc>Given an FILE-ID, downloads the file data as file. The file data is encoded using transit.</desc>
<form method="get" action="/dbg/file/data">
<div class="row">
<input type="text" style="width:300px" name="file-id" placeholder="file-id" />
</div>
<div class="row">
<input type="submit" name="download" value="Download" />
<input type="submit" name="clone" value="Clone" />
</div>
</form>
</fieldset>
<fieldset>
<legend>Upload File Data:</legend>
<desc>Create a new file on your draft projects using the file downloaded from the previous section.</desc>
<form method="post" enctype="multipart/form-data" action="/dbg/file/data">
<div class="row">
<input type="file" name="file" value="" />
</div>
<div class="row">
<label>Import with same id?</label>
<input type="checkbox" name="reuseid" />
</div>
<div class="row">
<input type="submit" value="Upload" />
</div>
</form>
</fieldset>
<fieldset>
<legend>Profile Management</legend>
<form method="post" action="/dbg/actions/resend-email-verification">
@@ -81,6 +49,50 @@ Debug Main Page
</section>
<section class="widget">
<fieldset>
<legend>Download RAW file data:</legend>
<desc>Given an FILE-ID, downloads the file AS-IS (no validation
checks, just exports the file data and related objects in raw)
<br/>
<br/>
<b>WARNING: this operation does not performs any checks</b>
</desc>
<form method="get" action="/dbg/actions/file-raw-export-import">
<div class="row">
<input type="text" style="width:300px" name="file-id" placeholder="file-id" />
</div>
<div class="row">
<input type="submit" name="download" value="Download" />
<input type="submit" name="clone" value="Clone" />
</div>
</form>
</fieldset>
<fieldset>
<legend>Upload File Data:</legend>
<desc>Create a new file on your draft projects using the file downloaded from the previous section.
<br/>
<br/>
<b>WARNING: this operation does not performs any checks</b>
</desc>
<form method="post" enctype="multipart/form-data" action="/dbg/actions/file-raw-export-import">
<div class="row">
<input type="file" name="file" value="" />
</div>
<div class="row">
<label>Import with same id?</label>
<input type="checkbox" name="reuseid" />
</div>
<div class="row">
<input type="submit" value="Upload" />
</div>
</form>
</fieldset>
</section>
<section class="widget">
<fieldset>
<legend>Export binfile:</legend>
@@ -88,7 +100,7 @@ Debug Main Page
the related libraries in a single custom formatted binary
file.</desc>
<form method="get" action="/dbg/file/export">
<form method="get" action="/dbg/actions/file-export">
<div class="row set-of-inputs">
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
@@ -116,7 +128,7 @@ Debug Main Page
<legend>Import binfile:</legend>
<desc>Import penpot file in binary format.</desc>
<form method="post" enctype="multipart/form-data" action="/dbg/file/import">
<form method="post" enctype="multipart/form-data" action="/dbg/actions/file-import">
<div class="row">
<input type="file" name="file" value="" />
</div>
@@ -130,79 +142,27 @@ Debug Main Page
<section class="widget">
<fieldset>
<legend>Reset file version</legend>
<desc>Allows reset file data version to a specific number/</desc>
<form method="post" action="/dbg/actions/reset-file-version">
<div class="row">
<input type="text" style="width:300px" name="file-id" placeholder="file-id" />
</div>
<div class="row">
<input type="number" style="width:100px" name="version" placeholder="version" value="32" />
</div>
<div class="row">
<label for="force-version">Are you sure?</label>
<input id="force-version" type="checkbox" name="force" />
<br />
<small>
This is a just a security double check for prevent non intentional submits.
</small>
</div>
<div class="row">
<input type="submit" value="Submit" />
</div>
</form>
</fieldset>
</section>
<section class="widget">
<h2>Feature Flags</h2>
<fieldset>
<legend>Enable</legend>
<legend>Feature Flags for Team</legend>
<desc>Add a feature flag to a team</desc>
<form method="post" action="/dbg/actions/add-team-feature">
<form method="post" action="/dbg/actions/handle-team-features">
<div class="row">
<input type="text" style="width:300px" name="team-id" placeholder="team-id" />
</div>
<div class="row">
<input type="text" style="width:100px" name="feature" placeholder="feature" value="" />
<select type="text" style="width:100px" name="feature">
{% for feature in supported-features %}
<option value="{{feature}}">{{feature}}</option>
{% endfor %}
</select>
</div>
<div class="row">
<label for="check-feature">Skip feature check</label>
<input id="check-feature" type="checkbox" name="skip-check" />
<br />
<small>
Do not check if the feature is supported
</small>
</div>
<div class="row">
<label for="force-version">Are you sure?</label>
<input id="force-version" type="checkbox" name="force" />
<br />
<small>
This is a just a security double check for prevent non intentional submits.
</small>
</div>
<div class="row">
<input type="submit" value="Submit" />
</div>
</form>
</fieldset>
<fieldset>
<legend>Disable</legend>
<desc>Remove a feature flag from a team</desc>
<form method="post" action="/dbg/actions/remove-team-feature">
<div class="row">
<input type="text" style="width:300px" name="team-id" placeholder="team-id" />
</div>
<div class="row">
<input type="text" style="width:100px" name="feature" placeholder="feature" value="" />
<select style="width:100px" name="action">
<option value="">Action...</option>
<option value="show">Show</option>
<option value="enable">Enable</option>
<option value="disable">Disable</option>
</select>
</div>
<div class="row">

View File

@@ -7,7 +7,9 @@ penpot - error list
{% block content %}
<nav>
<div class="title">
<h1>Error reports (last 200)</h1>
<h1>Error reports (last 200)
<a href="/dbg">[GO BACK]</a>
</h1>
</div>
</nav>
<main class="horizontal-list">

View File

@@ -12,7 +12,7 @@ export PENPOT_FLAGS="\
enable-login-with-gitlab \
enable-backend-worker \
enable-backend-asserts \
enable-feature-fdata-pointer-map \
disable-feature-fdata-pointer-map \
enable-feature-fdata-objects-map \
enable-audit-log \
enable-transit-readable-response \
@@ -28,11 +28,10 @@ export PENPOT_FLAGS="\
enable-auto-file-snapshot \
enable-webhooks \
enable-access-tokens \
enable-tiered-file-data-storage \
disable-tiered-file-data-storage \
enable-file-validation \
enable-file-schema-validation \
enable-subscriptions \
enable-subscriptions-old";
enable-subscriptions";
# Default deletion delay for devenv
export PENPOT_DELETION_DELAY="24h"
@@ -78,10 +77,14 @@ export JAVA_OPTS="\
-Dlog4j2.configurationFile=log4j2-devenv-repl.xml \
-Djdk.tracePinnedThreads=full \
-Dim4java.useV7=true \
-XX:+UseShenandoahGC \
-XX:+EnableDynamicAgentLoading \
-XX:-OmitStackTraceInFastThrow \
-XX:+UnlockExperimentalVMOptions \
-XX:+UnlockDiagnosticVMOptions \
-XX:+DebugNonSafepoints \
-XX:ShenandoahGCMode=generational \
-XX:+UseCompactObjectHeaders \
--sun-misc-unsafe-memory-access=allow \
--enable-preview \
--enable-native-access=ALL-UNNAMED";

View File

@@ -13,7 +13,7 @@ export PENPOT_FLAGS="\
enable-login-with-ldap \
enable-transit-readable-response \
enable-demo-users \
enable-feature-fdata-pointer-map \
disable-feature-fdata-pointer-map \
enable-feature-fdata-objects-map \
disable-secure-session-cookies \
enable-rpc-climit \
@@ -21,11 +21,10 @@ export PENPOT_FLAGS="\
enable-quotes \
enable-file-snapshot \
enable-access-tokens \
enable-tiered-file-data-storage \
disable-tiered-file-data-storage \
enable-file-validation \
enable-file-schema-validation \
enable-subscriptions \
enable-subscriptions-old ";
enable-subscriptions";
# Default deletion delay for devenv
export PENPOT_DELETION_DELAY="24h"

View File

@@ -13,6 +13,7 @@
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uri :as u]
[app.config :as cf]
[app.db :as db]
@@ -28,7 +29,6 @@
[app.tokens :as tokens]
[app.util.inet :as inet]
[app.util.json :as json]
[app.util.time :as dt]
[buddy.sign.jwk :as jwk]
[buddy.sign.jwt :as jwt]
[clojure.set :as set]
@@ -514,7 +514,7 @@
[cfg info request]
(let [info (assoc info
:iss :prepared-register
:exp (dt/in-future {:hours 48}))
:exp (ct/in-future {:hours 48}))
params {:token (tokens/generate (::setup/props cfg) info)
:provider (:provider (:path-params request))
@@ -571,7 +571,7 @@
token (or (:invitation-token info)
(tokens/generate (::setup/props cfg)
{:iss :auth
:exp (dt/in-future "15m")
:exp (ct/in-future "15m")
:profile-id (:id profile)}))
props (audit/profile->props profile)
context (d/without-nils {:external-session-id (:external-session-id info)})]
@@ -619,7 +619,7 @@
:invitation-token (:invitation-token params)
:external-session-id esid
:props props
:exp (dt/in-future "4h")}
:exp (ct/in-future "4h")}
state (tokens/generate (::setup/props cfg)
(d/without-nils params))
uri (build-auth-uri cfg state)]

View File

@@ -15,19 +15,21 @@
[app.common.files.migrations :as fmg]
[app.common.files.validate :as fval]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[app.common.weak :as weak]
[app.config :as cf]
[app.db :as db]
[app.db.sql :as sql]
[app.features.fdata :as feat.fdata]
[app.features.file-migrations :as feat.fmigr]
[app.features.fdata :as fdata]
[app.features.file-migrations :as fmigr]
[app.loggers.audit :as-alias audit]
[app.loggers.webhooks :as-alias webhooks]
[app.storage :as sto]
[app.util.blob :as blob]
[app.util.pointer-map :as pmap]
[app.util.time :as dt]
[app.worker :as-alias wrk]
[clojure.set :as set]
[cuerdas.core :as str]
@@ -38,6 +40,7 @@
(def ^:dynamic *state* nil)
(def ^:dynamic *options* nil)
(def ^:dynamic *reference-file* nil)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DEFAULTS
@@ -53,17 +56,12 @@
(* 1024 1024 100))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare get-resolved-file-libraries)
(declare update-file!)
(def file-attrs
#{:id
:name
:migrations
:features
:project-id
:is-shared
:version
:data})
(sm/keys ctf/schema:file))
(defn parse-file-format
[template]
@@ -151,24 +149,35 @@
changes (assoc :changes (blob/decode changes))
data (assoc :data (blob/decode data)))))
(def sql:get-minimal-file
"SELECT f.id,
f.revn,
f.modified_at,
f.deleted_at
FROM file AS f
WHERE f.id = ?")
(defn get-minimal-file
[cfg id & {:as opts}]
(db/get-with-sql cfg [sql:get-minimal-file id] opts))
(defn decode-file
"A general purpose file decoding function that resolves all external
pointers, run migrations and return plain vanilla file map"
[cfg {:keys [id] :as file}]
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
[cfg {:keys [id] :as file} & {:keys [migrate?] :or {migrate? true}}]
(binding [pmap/*load-fn* (partial fdata/load-pointer cfg id)]
(let [file (->> file
(feat.fmigr/resolve-applied-migrations cfg)
(feat.fdata/resolve-file-data cfg))
(fmigr/resolve-applied-migrations cfg)
(fdata/resolve-file-data cfg))
libs (delay (get-resolved-file-libraries cfg file))]
(-> file
(update :features db/decode-pgarray #{})
(update :data blob/decode)
(update :data feat.fdata/process-pointers deref)
(update :data feat.fdata/process-objects (partial into {}))
(update :data fdata/process-pointers deref)
(update :data fdata/process-objects (partial into {}))
(update :data assoc :id id)
(fmg/migrate-file libs)))))
(cond-> migrate? (fmg/migrate-file libs))))))
(defn get-file
"Get file, resolve all features and apply migrations.
@@ -178,9 +187,9 @@
and decoding."
[cfg file-id & {:as opts}]
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
(some->> (db/get* conn :file {:id file-id}
(assoc opts ::db/remove-deleted false))
(decode-file cfg)))))
(when-let [row (db/get* conn :file {:id file-id}
(assoc opts ::db/remove-deleted false))]
(decode-file cfg row opts)))))
(defn clean-file-features
[file]
@@ -421,6 +430,27 @@
(db/exec-one! conn ["SET LOCAL idle_in_transaction_session_timeout = 0"])
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"])))
(defn invalidate-thumbnails
[cfg file-id]
(let [storage (sto/resolve cfg)
sql-1
(str "update file_tagged_object_thumbnail "
" set deleted_at = now() "
" where file_id=? returning media_id")
sql-2
(str "update file_thumbnail "
" set deleted_at = now() "
" where file_id=? returning media_id")]
(run! #(sto/touch-object! storage %)
(sequence
(keep :media-id)
(concat
(db/exec! cfg [sql-1 file-id])
(db/exec! cfg [sql-2 file-id]))))))
(defn process-file
[cfg {:keys [id] :as file}]
(let [libs (delay (get-resolved-file-libraries cfg file))]
@@ -445,77 +475,78 @@
(vary-meta dissoc ::fmg/migrated))))
(defn encode-file
[{:keys [::db/conn] :as cfg} {:keys [id features] :as file}]
(let [file (if (contains? features "fdata/objects-map")
(feat.fdata/enable-objects-map file)
[cfg {:keys [id features] :as file}]
(let [file (if (and (contains? features "fdata/objects-map")
(:data file))
(fdata/enable-objects-map file)
file)
file (if (contains? features "fdata/pointer-map")
(binding [pmap/*tracked* (pmap/create-tracked)]
(let [file (feat.fdata/enable-pointer-map file)]
(feat.fdata/persist-pointers! cfg id)
file (if (and (contains? features "fdata/pointer-map")
(:data file))
(binding [pmap/*tracked* (pmap/create-tracked :inherit true)]
(let [file (fdata/enable-pointer-map file)]
(fdata/persist-pointers! cfg id)
file))
file)]
(-> file
(update :features db/encode-pgarray conn "text")
(update :data blob/encode))))
(d/update-when :features into-array)
(d/update-when :data blob/encode))))
(defn get-params-from-file
(defn- file->params
[file]
(let [params {:has-media-trimmed (:has-media-trimmed file)
:ignore-sync-until (:ignore-sync-until file)
:project-id (:project-id file)
:features (:features file)
:name (:name file)
:is-shared (:is-shared file)
:version (:version file)
:data (:data file)
:id (:id file)
:deleted-at (:deleted-at file)
:created-at (:created-at file)
:modified-at (:modified-at file)
:revn (:revn file)
:vern (:vern file)}]
(-> (d/without-nils params)
(assoc :data-backend nil)
(assoc :data-ref-id nil))))
(-> (select-keys file file-attrs)
(dissoc :team-id)
(dissoc :migrations)))
(defn insert-file!
"Insert a new file into the database table"
"Insert a new file into the database table. Expectes a not-encoded file.
Returns nil."
[{:keys [::db/conn] :as cfg} file & {:as opts}]
(feat.fmigr/upsert-migrations! conn file)
(let [params (-> (encode-file cfg file)
(get-params-from-file))]
(db/insert! conn :file params opts)))
(when (:migrations file)
(fmigr/upsert-migrations! conn file))
(let [file (encode-file cfg file)]
(db/insert! conn :file
(file->params file)
{::db/return-keys false})
nil))
(defn update-file!
"Update an existing file on the database."
[{:keys [::db/conn ::sto/storage] :as cfg} {:keys [id] :as file} & {:as opts}]
(let [file (encode-file cfg file)
params (-> (get-params-from-file file)
(dissoc :id))]
"Update an existing file on the database. Expects not encoded file."
[{:keys [::db/conn] :as cfg} {:keys [id] :as file} & {:as opts}]
;; If file was already offloaded, we touch the underlying storage
;; object for properly trigger storage-gc-touched task
(when (feat.fdata/offloaded? file)
(some->> (:data-ref-id file) (sto/touch-object! storage)))
(if (::reset-migrations opts false)
(fmigr/reset-migrations! conn file)
(fmigr/upsert-migrations! conn file))
(feat.fmigr/upsert-migrations! conn file)
(db/update! conn :file params {:id id} opts)))
(let [file
(encode-file cfg file)
params
(file->params (dissoc file :id))]
(db/update! conn :file params
{:id id}
{::db/return-keys false})
nil))
(defn save-file!
"Applies all the final validations and perist the file, binfile
specific, should not be used outside of binfile domain"
[{:keys [::timestamp] :as cfg} file & {:as opts}]
specific, should not be used outside of binfile domain.
(assert (dt/instant? timestamp) "expected valid timestamp")
Returns nil"
[{:keys [::timestamp] :as cfg} file & {:as opts}]
(assert (ct/inst? timestamp) "expected valid timestamp")
(let [file (-> file
(assoc :created-at timestamp)
(assoc :modified-at timestamp)
(assoc :ignore-sync-until (dt/plus timestamp (dt/duration {:seconds 5})))
(cond-> (not (::overwrite cfg))
(assoc :ignore-sync-until (ct/plus timestamp (ct/duration {:seconds 5}))))
(update :features
(fn [features]
(-> (::features cfg #{})
@@ -532,8 +563,9 @@
(when (ex/exception? result)
(l/error :hint "file schema validation error" :cause result))))
(insert-file! cfg file opts)))
(if (::overwrite cfg)
(update-file! cfg file (assoc opts ::reset-migrations true))
(insert-file! cfg file opts))))
(def ^:private sql:get-file-libraries
"WITH RECURSIVE libs AS (
@@ -558,7 +590,8 @@
l.revn,
l.vern,
l.synced_at,
l.is_shared
l.is_shared,
l.version
FROM libs AS l
INNER JOIN project AS p ON (p.id = l.project_id)
WHERE l.deleted_at IS NULL OR l.deleted_at > now();")
@@ -574,8 +607,18 @@
(db/exec! conn [sql:get-file-libraries file-id])))
(defn get-resolved-file-libraries
"A helper for preload file libraries"
[{:keys [::db/conn] :as cfg} file]
(->> (get-file-libraries conn (:id file))
(into [file] (map #(get-file cfg (:id %))))
(d/index-by :id)))
"Get all file libraries including itself. Returns an instance of
LoadableWeakValueMap that allows do not have strong references to
the loaded libraries and reduce possible memory pressure on having
all this libraries loaded at same time on processing file validation
or file migration.
This still requires at least one library at time to be loaded while
access to it is performed, but it improves considerable not having
the need of loading all the libraries at the same time."
[{:keys [::db/conn] :as cfg} {:keys [id] :as file}]
(let [library-ids (->> (get-file-libraries conn (:id file))
(map :id)
(cons (:id file)))
load-fn #(get-file cfg % :migrate? false)]
(weak/loadable-weak-value-map library-ids load-fn {id file})))

View File

@@ -36,11 +36,6 @@
"fdata/shape-data-type"
nil
;; There is no migration needed, but we don't want to allow
;; copy paste nor import of variant files into no-variant teams
"variants/v1"
nil
(ex/raise :type :internal
:code :no-migration-defined
:hint (str/ffmt "no migation for feature '%' on file importation" feature)

View File

@@ -17,6 +17,7 @@
[app.common.fressian :as fres]
[app.common.logging :as l]
[app.common.spec :as us]
[app.common.time :as ct]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -30,7 +31,6 @@
[app.storage.tmp :as tmp]
[app.tasks.file-gc]
[app.util.events :as events]
[app.util.time :as dt]
[app.worker :as-alias wrk]
[clojure.java.io :as jio]
[clojure.set :as set]
@@ -434,7 +434,7 @@
(defn read-import!
"Do the importation of the specified resource in penpot custom binary
format."
[{:keys [::bfc/input ::bfc/timestamp] :or {timestamp (dt/now)} :as options}]
[{:keys [::bfc/input ::bfc/timestamp] :or {timestamp (ct/now)} :as options}]
(dm/assert!
"expected input stream"
@@ -442,7 +442,7 @@
(dm/assert!
"expected valid instant"
(dt/instant? timestamp))
(ct/inst? timestamp))
(let [version (read-header! input)]
(read-import (assoc options ::version version ::bfc/timestamp timestamp))))
@@ -682,7 +682,7 @@
(io/coercible? output))
(let [id (uuid/next)
tp (dt/tpoint)
tp (ct/tpoint)
ab (volatile! false)
cs (volatile! nil)]
(try
@@ -720,7 +720,7 @@
(satisfies? jio/IOFactory input))
(let [id (uuid/next)
tp (dt/tpoint)
tp (ct/tpoint)
cs (volatile! nil)]
(l/info :hint "import: started" :id (str id))
@@ -742,6 +742,6 @@
(finally
(l/info :hint "import: terminated"
:id (str id)
:elapsed (dt/format-duration (tp))
:elapsed (ct/format-duration (tp))
:error? (some? @cs))))))

View File

@@ -13,6 +13,7 @@
[app.common.data :as d]
[app.common.features :as cfeat]
[app.common.logging :as l]
[app.common.time :as ct]
[app.common.transit :as t]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -23,7 +24,6 @@
[app.storage :as sto]
[app.storage.tmp :as tmp]
[app.util.events :as events]
[app.util.time :as dt]
[app.worker :as-alias wrk]
[clojure.set :as set]
[cuerdas.core :as str]
@@ -344,7 +344,7 @@
(defn export-team!
[cfg team-id]
(let [id (uuid/next)
tp (dt/tpoint)
tp (ct/tpoint)
cfg (create-database cfg)]
(l/inf :hint "start"
@@ -378,15 +378,15 @@
(l/inf :hint "end"
:operation "export"
:id (str id)
:elapsed (dt/format-duration elapsed)))))))
:elapsed (ct/format-duration elapsed)))))))
(defn import-team!
[cfg path]
(let [id (uuid/next)
tp (dt/tpoint)
tp (ct/tpoint)
cfg (-> (create-database cfg path)
(assoc ::bfc/timestamp (dt/now)))]
(assoc ::bfc/timestamp (ct/now)))]
(l/inf :hint "start"
:operation "import"
@@ -434,4 +434,4 @@
(l/inf :hint "end"
:operation "import"
:id (str id)
:elapsed (dt/format-duration elapsed)))))))
:elapsed (ct/format-duration elapsed)))))))

View File

@@ -20,13 +20,14 @@
[app.common.media :as cmedia]
[app.common.schema :as sm]
[app.common.thumbnails :as cth]
[app.common.time :as ct]
[app.common.types.color :as ctcl]
[app.common.types.component :as ctc]
[app.common.types.file :as ctf]
[app.common.types.page :as ctp]
[app.common.types.plugins :as ctpg]
[app.common.types.shape :as cts]
[app.common.types.tokens-lib :as cto]
[app.common.types.tokens-lib :as ctob]
[app.common.types.typography :as cty]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -35,14 +36,15 @@
[app.storage :as sto]
[app.storage.impl :as sto.impl]
[app.util.events :as events]
[app.util.time :as dt]
[clojure.java.io :as jio]
[cuerdas.core :as str]
[datoteka.fs :as fs]
[datoteka.io :as io])
(:import
java.io.File
java.io.InputStream
java.io.OutputStreamWriter
java.lang.AutoCloseable
java.util.zip.ZipEntry
java.util.zip.ZipFile
java.util.zip.ZipOutputStream))
@@ -92,7 +94,7 @@
(defn- default-now
[o]
(or o (dt/now)))
(or o (ct/now)))
;; --- ENCODERS
@@ -103,25 +105,25 @@
(sm/encoder ctp/schema:page sm/json-transformer))
(def encode-shape
(sm/encoder ::cts/shape sm/json-transformer))
(sm/encoder cts/schema:shape sm/json-transformer))
(def encode-media
(sm/encoder ::ctf/media sm/json-transformer))
(sm/encoder ctf/schema:media sm/json-transformer))
(def encode-component
(sm/encoder ::ctc/component sm/json-transformer))
(sm/encoder ctc/schema:component sm/json-transformer))
(def encode-color
(sm/encoder ctcl/schema:library-color sm/json-transformer))
(def encode-typography
(sm/encoder ::cty/typography sm/json-transformer))
(sm/encoder cty/schema:typography sm/json-transformer))
(def encode-tokens-lib
(sm/encoder ::cto/tokens-lib sm/json-transformer))
(sm/encoder ctob/schema:tokens-lib sm/json-transformer))
(def encode-plugin-data
(sm/encoder ::ctpg/plugin-data sm/json-transformer))
(sm/encoder ctpg/schema:plugin-data sm/json-transformer))
(def encode-storage-object
(sm/encoder schema:storage-object sm/json-transformer))
@@ -138,7 +140,7 @@
(sm/decoder ctf/schema:media sm/json-transformer))
(def decode-component
(sm/decoder ::ctc/component sm/json-transformer))
(sm/decoder ctc/schema:component sm/json-transformer))
(def decode-color
(sm/decoder ctcl/schema:library-color sm/json-transformer))
@@ -147,19 +149,19 @@
(sm/decoder schema:file sm/json-transformer))
(def decode-page
(sm/decoder ::ctp/page sm/json-transformer))
(sm/decoder ctp/schema:page sm/json-transformer))
(def decode-shape
(sm/decoder ::cts/shape sm/json-transformer))
(sm/decoder cts/schema:shape sm/json-transformer))
(def decode-typography
(sm/decoder ::cty/typography sm/json-transformer))
(sm/decoder cty/schema:typography sm/json-transformer))
(def decode-tokens-lib
(sm/decoder cto/schema:tokens-lib sm/json-transformer))
(sm/decoder ctob/schema:tokens-lib sm/json-transformer))
(def decode-plugin-data
(sm/decoder ::ctpg/plugin-data sm/json-transformer))
(sm/decoder ctpg/schema:plugin-data sm/json-transformer))
(def decode-storage-object
(sm/decoder schema:storage-object sm/json-transformer))
@@ -173,31 +175,31 @@
(sm/check-fn schema:manifest))
(def validate-file
(sm/check-fn ::ctf/file))
(sm/check-fn ctf/schema:file))
(def validate-page
(sm/check-fn ::ctp/page))
(sm/check-fn ctp/schema:page))
(def validate-shape
(sm/check-fn ::cts/shape))
(sm/check-fn cts/schema:shape))
(def validate-media
(sm/check-fn ::ctf/media))
(sm/check-fn ctf/schema:media))
(def validate-color
(sm/check-fn ctcl/schema:library-color))
(def validate-component
(sm/check-fn ::ctc/component))
(sm/check-fn ctc/schema:component))
(def validate-typography
(sm/check-fn ::cty/typography))
(sm/check-fn cty/schema:typography))
(def validate-tokens-lib
(sm/check-fn ::cto/tokens-lib))
(sm/check-fn ctob/schema:tokens-lib))
(def validate-plugin-data
(sm/check-fn ::ctpg/plugin-data))
(sm/check-fn ctpg/schema:plugin-data))
(def validate-storage-object
(sm/check-fn schema:storage-object))
@@ -251,9 +253,9 @@
(write-entry! output path params)
(with-open [input (sto/get-object-data storage sobject)]
(.putNextEntry output (ZipEntry. (str "objects/" id ext)))
(.putNextEntry ^ZipOutputStream output (ZipEntry. (str "objects/" id ext)))
(io/copy input output :size (:size sobject))
(.closeEntry output))))))
(.closeEntry ^ZipOutputStream output))))))
(defn- export-file
[{:keys [::file-id ::output] :as cfg}]
@@ -284,10 +286,12 @@
(assoc :options (:options data))
:always
(dissoc :data)
(dissoc :data))
file (cond-> file
:always
(encode-file))
path (str "files/" file-id ".json")]
(write-entry! output path file))
@@ -345,7 +349,8 @@
typography (encode-typography object)]
(write-entry! output path typography)))
(when tokens-lib
(when (and tokens-lib
(not (ctob/empty-lib? tokens-lib)))
(let [path (str "files/" file-id "/tokens.json")
encoded-tokens (encode-tokens-lib tokens-lib)]
(write-entry! output path encoded-tokens)))))
@@ -445,7 +450,7 @@
(defn- read-manifest
[^ZipFile input]
(let [entry (get-zip-entry input "manifest.json")]
(with-open [reader (zip-entry-reader input entry)]
(with-open [^AutoCloseable reader (zip-entry-reader input entry)]
(let [manifest (json/read reader :key-fn json/read-kebab-key)]
(decode-manifest manifest)))))
@@ -535,24 +540,27 @@
(defn- read-entry
[^ZipFile input entry]
(with-open [reader (zip-entry-reader input entry)]
(with-open [^AutoCloseable reader (zip-entry-reader input entry)]
(json/read reader :key-fn json/read-kebab-key)))
(defn- read-plain-entry
[^ZipFile input entry]
(with-open [reader (zip-entry-reader input entry)]
(with-open [^AutoCloseable reader (zip-entry-reader input entry)]
(json/read reader)))
(defn- read-file
[{:keys [::bfc/input ::file-id]}]
[{:keys [::bfc/input ::bfc/timestamp]} file-id]
(let [path (str "files/" file-id ".json")
entry (get-zip-entry input path)]
(-> (read-entry input entry)
(decode-file)
(update :revn d/nilv 1)
(update :created-at d/nilv timestamp)
(update :modified-at d/nilv timestamp)
(validate-file))))
(defn- read-file-plugin-data
[{:keys [::bfc/input ::file-id]}]
[{:keys [::bfc/input]} file-id]
(let [path (str "files/" file-id "/plugin-data.json")
entry (get-zip-entry* input path)]
(some->> entry
@@ -561,7 +569,7 @@
(validate-plugin-data))))
(defn- read-file-media
[{:keys [::bfc/input ::file-id ::entries]}]
[{:keys [::bfc/input ::entries]} file-id]
(->> (keep (match-media-entry-fn file-id) entries)
(reduce (fn [result {:keys [id entry]}]
(let [object (->> (read-entry input entry)
@@ -581,7 +589,7 @@
(not-empty)))
(defn- read-file-colors
[{:keys [::bfc/input ::file-id ::entries]}]
[{:keys [::bfc/input ::entries]} file-id]
(->> (keep (match-color-entry-fn file-id) entries)
(reduce (fn [result {:keys [id entry]}]
(let [object (->> (read-entry input entry)
@@ -594,7 +602,7 @@
(not-empty)))
(defn- read-file-components
[{:keys [::bfc/input ::file-id ::entries]}]
[{:keys [::bfc/input ::entries]} file-id]
(let [clean-component-post-decode
(fn [component]
(d/update-when component :objects
@@ -625,7 +633,7 @@
(not-empty))))
(defn- read-file-typographies
[{:keys [::bfc/input ::file-id ::entries]}]
[{:keys [::bfc/input ::entries]} file-id]
(->> (keep (match-typography-entry-fn file-id) entries)
(reduce (fn [result {:keys [id entry]}]
(let [object (->> (read-entry input entry)
@@ -638,14 +646,14 @@
(not-empty)))
(defn- read-file-tokens-lib
[{:keys [::bfc/input ::file-id ::entries]}]
[{:keys [::bfc/input ::entries]} file-id]
(when-let [entry (d/seek (match-tokens-lib-entry-fn file-id) entries)]
(->> (read-plain-entry input entry)
(decode-tokens-lib)
(validate-tokens-lib))))
(defn- read-file-shapes
[{:keys [::bfc/input ::file-id ::page-id ::entries] :as cfg}]
[{:keys [::bfc/input ::entries] :as cfg} file-id page-id]
(->> (keep (match-shape-entry-fn file-id page-id) entries)
(reduce (fn [result {:keys [id entry]}]
(let [object (->> (read-entry input entry)
@@ -659,15 +667,14 @@
(not-empty)))
(defn- read-file-pages
[{:keys [::bfc/input ::file-id ::entries] :as cfg}]
[{:keys [::bfc/input ::entries] :as cfg} file-id]
(->> (keep (match-page-entry-fn file-id) entries)
(keep (fn [{:keys [id entry]}]
(let [page (->> (read-entry input entry)
(decode-page))
page (dissoc page :options)]
(when (= id (:id page))
(let [objects (-> (assoc cfg ::page-id id)
(read-file-shapes))]
(let [objects (read-file-shapes cfg file-id id)]
(assoc page :objects objects))))))
(sort-by :index)
(reduce (fn [result {:keys [id] :as page}]
@@ -675,7 +682,7 @@
(d/ordered-map))))
(defn- read-file-thumbnails
[{:keys [::bfc/input ::file-id ::entries] :as cfg}]
[{:keys [::bfc/input ::entries] :as cfg} file-id]
(->> (keep (match-thumbnail-entry-fn file-id) entries)
(reduce (fn [result {:keys [page-id frame-id tag entry]}]
(let [object (->> (read-entry input entry)
@@ -690,13 +697,13 @@
(not-empty)))
(defn- read-file-data
[cfg]
(let [colors (read-file-colors cfg)
typographies (read-file-typographies cfg)
tokens-lib (read-file-tokens-lib cfg)
components (read-file-components cfg)
plugin-data (read-file-plugin-data cfg)
pages (read-file-pages cfg)]
[cfg file-id]
(let [colors (read-file-colors cfg file-id)
typographies (read-file-typographies cfg file-id)
tokens-lib (read-file-tokens-lib cfg file-id)
components (read-file-components cfg file-id)
plugin-data (read-file-plugin-data cfg file-id)
pages (read-file-pages cfg file-id)]
{:pages (-> pages keys vec)
:pages-index (into {} pages)
:colors colors
@@ -706,11 +713,11 @@
:plugin-data plugin-data}))
(defn- import-file
[{:keys [::bfc/project-id ::file-id ::file-name] :as cfg}]
[{:keys [::bfc/project-id] :as cfg} {file-id :id file-name :name}]
(let [file-id' (bfc/lookup-index file-id)
file (read-file cfg)
media (read-file-media cfg)
thumbnails (read-file-thumbnails cfg)]
file (read-file cfg file-id)
media (read-file-media cfg file-id)
thumbnails (read-file-thumbnails cfg file-id)]
(l/dbg :hint "processing file"
:id (str file-id')
@@ -740,7 +747,7 @@
(vswap! bfc/*state* update :index bfc/update-index (map :media-id thumbnails))
(vswap! bfc/*state* update :thumbnails into thumbnails))
(let [data (-> (read-file-data cfg)
(let [data (-> (read-file-data cfg file-id)
(d/without-nils)
(assoc :id file-id')
(cond-> (:options file)
@@ -757,7 +764,7 @@
file (ctf/check-file file)]
(bfm/register-pending-migrations! cfg file)
(bfc/save-file! cfg file ::db/return-keys false)
(bfc/save-file! cfg file)
file-id')))
@@ -853,7 +860,8 @@
:file-id (str (:file-id params))
::l/sync? true)
(db/insert! conn :file-media-object params))))
(db/insert! conn :file-media-object params
::db/on-conflict-do-nothing? (::bfc/overwrite cfg)))))
(defn- import-file-thumbnails
[{:keys [::db/conn] :as cfg}]
@@ -873,17 +881,77 @@
:media-id (str media-id)
::l/sync? true)
(db/insert! conn :file-tagged-object-thumbnail params))))
(db/insert! conn :file-tagged-object-thumbnail params
{::db/on-conflict-do-nothing? true}))))
(defn- import-files*
[{:keys [::manifest] :as cfg}]
(bfc/disable-database-timeouts! cfg)
(vswap! bfc/*state* update :index bfc/update-index (:files manifest) :id)
(let [files (get manifest :files)
result (reduce (fn [result {:keys [id] :as file}]
(let [name' (get file :name)
name' (if (map? name)
(get name id)
name')
file (assoc file :name name')]
(conj result (import-file cfg file))))
[]
files)]
(import-file-relations cfg)
(import-storage-objects cfg)
(import-file-media cfg)
(import-file-thumbnails cfg)
(bfm/apply-pending-migrations! cfg)
result))
(defn- import-file-and-overwrite*
[{:keys [::manifest ::bfc/file-id] :as cfg}]
(when (not= 1 (count (:files manifest)))
(ex/raise :type :validation
:code :invalid-condition
:hint "unable to perform in-place update with binfile containing more than 1 file"
:manifest manifest))
(bfc/disable-database-timeouts! cfg)
(let [ref-file (bfc/get-minimal-file cfg file-id ::db/for-update true)
file (first (get manifest :files))
cfg (assoc cfg ::bfc/overwrite true)]
(vswap! bfc/*state* update :index assoc (:id file) file-id)
(binding [bfc/*options* cfg
bfc/*reference-file* ref-file]
(import-file cfg file)
(import-storage-objects cfg)
(import-file-media cfg)
(bfc/invalidate-thumbnails cfg file-id)
(bfm/apply-pending-migrations! cfg)
[file-id])))
(defn- import-files
[{:keys [::bfc/timestamp ::bfc/input ::bfc/name] :or {timestamp (dt/now)} :as cfg}]
[{:keys [::bfc/timestamp ::bfc/input] :or {timestamp (ct/now)} :as cfg}]
(assert (instance? ZipFile input) "expected zip file")
(assert (dt/instant? timestamp) "expected valid instant")
(assert (ct/inst? timestamp) "expected valid instant")
(let [manifest (-> (read-manifest input)
(validate-manifest))
entries (read-zip-entries input)]
entries (read-zip-entries input)
cfg (-> cfg
(assoc ::entries entries)
(assoc ::manifest manifest)
(assoc ::bfc/timestamp timestamp))]
(when-not (= "penpot/export-files" (:type manifest))
(ex/raise :type :validation
@@ -891,7 +959,6 @@
:hint "unexpected type on manifest"
:manifest manifest))
;; Check if all files referenced on manifest are present
(doseq [{file-id :id features :features} (:files manifest)]
(let [path (str "files/" file-id ".json")]
@@ -907,35 +974,10 @@
(events/tap :progress {:section :manifest})
(let [index (bfc/update-index (map :id (:files manifest)))
state {:media [] :index index}
cfg (-> cfg
(assoc ::entries entries)
(assoc ::manifest manifest)
(assoc ::bfc/timestamp timestamp))]
(binding [bfc/*state* (volatile! state)]
(db/tx-run! cfg (fn [cfg]
(bfc/disable-database-timeouts! cfg)
(let [ids (->> (:files manifest)
(reduce (fn [result {:keys [id] :as file}]
(let [name' (get file :name)
name' (if (map? name)
(get name id)
name')]
(conj result (-> cfg
(assoc ::file-id id)
(assoc ::file-name name')
(import-file)))))
[]))]
(import-file-relations cfg)
(import-storage-objects cfg)
(import-file-media cfg)
(import-file-thumbnails cfg)
(bfm/apply-pending-migrations! cfg)
ids)))))))
(binding [bfc/*state* (volatile! {:media [] :index {}})]
(if (::bfc/file-id cfg)
(db/tx-run! cfg import-file-and-overwrite*)
(db/tx-run! cfg import-files*)))))
;; --- PUBLIC API
@@ -961,14 +1003,14 @@
"expected instance of jio/IOFactory for `input`")
(let [id (uuid/next)
tp (dt/tpoint)
tp (ct/tpoint)
ab (volatile! false)
cs (volatile! nil)]
(try
(l/info :hint "start exportation" :export-id (str id))
(binding [bfc/*state* (volatile! (bfc/initial-state))]
(with-open [output (io/output-stream output)]
(with-open [output (ZipOutputStream. output)]
(with-open [^AutoCloseable output (io/output-stream output)]
(with-open [^AutoCloseable output (ZipOutputStream. output)]
(let [cfg (assoc cfg ::output output)]
(export-files cfg)
(export-storage-objects cfg)))))
@@ -1007,12 +1049,12 @@
"expected instance of jio/IOFactory for `input`")
(let [id (uuid/next)
tp (dt/tpoint)
tp (ct/tpoint)
cs (volatile! nil)]
(l/info :hint "import: started" :id (str id))
(try
(with-open [input (ZipFile. (fs/file input))]
(with-open [input (ZipFile. ^File (fs/file input))]
(import-files (assoc cfg ::bfc/input input)))
(catch Throwable cause
@@ -1022,11 +1064,11 @@
(finally
(l/info :hint "import: terminated"
:id (str id)
:elapsed (dt/format-duration (tp))
:elapsed (ct/format-duration (tp))
:error? (some? @cs))))))
(defn get-manifest
[path]
(with-open [input (ZipFile. (fs/file path))]
(with-open [^AutoCloseable input (ZipFile. ^File (fs/file path))]
(-> (read-manifest input)
(validate-manifest))))

View File

@@ -12,10 +12,10 @@
[app.common.exceptions :as ex]
[app.common.flags :as flags]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uri :as u]
[app.common.version :as v]
[app.util.overrides]
[app.util.time :as dt]
[clojure.core :as c]
[clojure.java.io :as io]
[cuerdas.core :as str]
@@ -59,10 +59,10 @@
:smtp-default-reply-to "Penpot <no-reply@example.com>"
:smtp-default-from "Penpot <no-reply@example.com>"
:profile-complaint-max-age (dt/duration {:days 7})
:profile-complaint-max-age (ct/duration {:days 7})
:profile-complaint-threshold 2
:profile-bounce-max-age (dt/duration {:days 7})
:profile-bounce-max-age (ct/duration {:days 7})
:profile-bounce-threshold 10
:telemetry-uri "https://telemetry.penpot.app/"
@@ -96,16 +96,16 @@
[:http-server-max-body-size {:optional true} ::sm/int]
[:http-server-max-multipart-body-size {:optional true} ::sm/int]
[:http-server-io-threads {:optional true} ::sm/int]
[:http-server-worker-threads {:optional true} ::sm/int]
[:http-server-max-worker-threads {:optional true} ::sm/int]
[:telemetry-uri {:optional true} :string]
[:telemetry-with-taiga {:optional true} ::sm/boolean] ;; DELETE
[:auto-file-snapshot-every {:optional true} ::sm/int]
[:auto-file-snapshot-timeout {:optional true} ::dt/duration]
[:auto-file-snapshot-timeout {:optional true} ::ct/duration]
[:media-max-file-size {:optional true} ::sm/int]
[:deletion-delay {:optional true} ::dt/duration] ;; REVIEW
[:deletion-delay {:optional true} ::ct/duration] ;; REVIEW
[:telemetry-enabled {:optional true} ::sm/boolean]
[:default-blob-version {:optional true} ::sm/int]
[:allow-demo-users {:optional true} ::sm/boolean]
@@ -148,10 +148,10 @@
[:auth-data-cookie-domain {:optional true} :string]
[:auth-token-cookie-name {:optional true} :string]
[:auth-token-cookie-max-age {:optional true} ::dt/duration]
[:auth-token-cookie-max-age {:optional true} ::ct/duration]
[:registration-domain-whitelist {:optional true} [::sm/set :string]]
[:email-verify-threshold {:optional true} ::dt/duration]
[:email-verify-threshold {:optional true} ::ct/duration]
[:github-client-id {:optional true} :string]
[:github-client-secret {:optional true} :string]
@@ -186,9 +186,9 @@
[:ldap-starttls {:optional true} ::sm/boolean]
[:ldap-user-query {:optional true} :string]
[:profile-bounce-max-age {:optional true} ::dt/duration]
[:profile-bounce-max-age {:optional true} ::ct/duration]
[:profile-bounce-threshold {:optional true} ::sm/int]
[:profile-complaint-max-age {:optional true} ::dt/duration]
[:profile-complaint-max-age {:optional true} ::ct/duration]
[:profile-complaint-threshold {:optional true} ::sm/int]
[:redis-uri {:optional true} ::sm/uri]
@@ -214,20 +214,21 @@
[:media-uri {:optional true} :string]
[:assets-path {:optional true} :string]
;; Legacy, will be removed in 2.5
[:netty-io-threads {:optional true} ::sm/int]
[:executor-threads {:optional true} ::sm/int]
;; DEPRECATED
[:assets-storage-backend {:optional true} :keyword]
[:storage-assets-fs-directory {:optional true} :string]
[:storage-assets-s3-bucket {:optional true} :string]
[:storage-assets-s3-region {:optional true} :keyword]
[:storage-assets-s3-endpoint {:optional true} ::sm/uri]
[:storage-assets-s3-io-threads {:optional true} ::sm/int]
[:objects-storage-backend {:optional true} :keyword]
[:objects-storage-fs-directory {:optional true} :string]
[:objects-storage-s3-bucket {:optional true} :string]
[:objects-storage-s3-region {:optional true} :keyword]
[:objects-storage-s3-endpoint {:optional true} ::sm/uri]
[:objects-storage-s3-io-threads {:optional true} ::sm/int]]))
[:objects-storage-s3-endpoint {:optional true} ::sm/uri]]))
(defn- parse-flags
[config]
@@ -298,7 +299,7 @@
(defn get-deletion-delay
[]
(or (c/get config :deletion-delay)
(dt/duration {:days 7})))
(ct/duration {:days 7})))
(defn get
"A configuration getter. Helps code be more testable."

View File

@@ -10,19 +10,20 @@
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.geom.point :as gpt]
[app.common.json :as json]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.transit :as t]
[app.common.uuid :as uuid]
[app.db.sql :as sql]
[app.metrics :as mtx]
[app.util.json :as json]
[app.util.time :as dt]
[clojure.java.io :as io]
[clojure.set :as set]
[integrant.core :as ig]
[next.jdbc :as jdbc]
[next.jdbc.date-time :as jdbc-dt]
[next.jdbc.prepare :as jdbc.prepare]
[next.jdbc.transaction])
(:import
com.zaxxer.hikari.HikariConfig
@@ -33,6 +34,7 @@
java.io.InputStream
java.io.OutputStream
java.sql.Connection
java.sql.PreparedStatement
java.sql.Savepoint
org.postgresql.PGConnection
org.postgresql.geometric.PGpoint
@@ -377,9 +379,9 @@
(defn is-row-deleted?
[{:keys [deleted-at]}]
(and (dt/instant? deleted-at)
(and (ct/inst? deleted-at)
(< (inst-ms deleted-at)
(inst-ms (dt/now)))))
(inst-ms (ct/now)))))
(defn get*
"Retrieve a single row from database that matches a simple filters. Do
@@ -404,6 +406,24 @@
:hint "database object not found"))
row))
(defn get-with-sql
[ds sql & {:as opts}]
(let [rows (cond->> (exec! ds sql opts)
(::remove-deleted opts true)
(remove is-row-deleted?)
:always
(not-empty))]
(when (and (not rows) (::throw-if-not-exists opts true))
(ex/raise :type :not-found
:code :object-not-found
:hint "database object not found"))
(first rows)))
(def ^:private default-plan-opts
(-> default-opts
(assoc :fetch-size 1000)
@@ -585,7 +605,7 @@
(string? o)
(pginterval o)
(dt/duration? o)
(ct/duration? o)
(interval (inst-ms o))
:else
@@ -599,7 +619,7 @@
val (.getValue o)]
(if (or (= typ "json")
(= typ "jsonb"))
(json/decode val)
(json/decode val :key-fn keyword)
val))))
(defn decode-transit-pgobject
@@ -640,7 +660,7 @@
(when data
(doto (org.postgresql.util.PGobject.)
(.setType "jsonb")
(.setValue (json/encode-str data)))))
(.setValue (json/encode data)))))
;; --- Locks
@@ -686,3 +706,8 @@
[cause]
(and (sql-exception? cause)
(= "40001" (.getSQLState ^java.sql.SQLException cause))))
(extend-protocol jdbc.prepare/SettableParameter
clojure.lang.Keyword
(set-parameter [^clojure.lang.Keyword v ^PreparedStatement s ^long i]
(.setObject s i ^String (d/name v))))

View File

@@ -53,8 +53,15 @@
opts (cond-> opts
(::order-by opts) (assoc :order-by (::order-by opts))
(::columns opts) (assoc :columns (::columns opts))
(::for-update opts) (assoc :suffix "FOR UPDATE")
(::for-share opts) (assoc :suffix "FOR SHARE"))]
(or (::db/for-update opts)
(::for-update opts))
(assoc :suffix "FOR UPDATE")
(or (::db/for-share opts)
(::for-share opts))
(assoc :suffix "FOR SHARE"))]
(sql/for-query table where-params opts))))
(defn update

View File

@@ -12,12 +12,13 @@
[app.common.files.helpers :as cfh]
[app.common.files.migrations :as fmg]
[app.common.logging :as l]
[app.common.types.objects-map :as omap]
[app.common.types.path :as path]
[app.db :as db]
[app.db.sql :as-alias sql]
[app.storage :as sto]
[app.util.blob :as blob]
[app.util.objects-map :as omap]
[app.util.objects-map :as omap.legacy]
[app.util.pointer-map :as pmap]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -36,10 +37,7 @@
[file & _opts]
(let [update-page
(fn [page]
(if (and (pmap/pointer-map? page)
(not (pmap/loaded? page)))
page
(update page :objects omap/wrap)))
(update page :objects omap/wrap))
update-data
(fn [fdata]
@@ -49,6 +47,20 @@
(update :data update-data)
(update :features conj "fdata/objects-map"))))
(defn disable-objects-map
[file & _opts]
(let [update-page
(fn [page]
(update page :objects #(into {} %)))
update-data
(fn [fdata]
(update fdata :pages-index d/update-vals update-page))]
(-> file
(update :data update-data)
(update :features disj "fdata/objects-map"))))
(defn process-objects
"Apply a function to all objects-map on the file. Usualy used for convert
the objects-map instances to plain maps"
@@ -58,7 +70,8 @@
(fn [page]
(update page :objects
(fn [objects]
(if (omap/objects-map? objects)
(if (or (omap/objects-map? objects)
(omap.legacy/objects-map? objects))
(update-fn objects)
objects)))))
fdata))
@@ -81,6 +94,12 @@
(let [data (get-file-data system file)]
(assoc file :data data)))
(defn decode-file-data
[_system {:keys [data] :as file}]
(cond-> file
(bytes? data)
(assoc :data (blob/decode data))))
(defn load-pointer
"A database loader pointer helper"
[system file-id id]

View File

@@ -8,6 +8,7 @@
"Backend specific code for file migrations. Implemented as permanent feature of files."
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.files.migrations :as fmg :refer [xf:map-name]]
[app.db :as db]
[app.db.sql :as-alias sql]))
@@ -26,14 +27,27 @@
(defn upsert-migrations!
"Persist or update file migrations. Return the updated/inserted number
of rows"
[conn {:keys [id] :as file}]
(let [migrations (or (-> file meta ::fmg/migrated)
(-> file :migrations not-empty)
fmg/available-migrations)
[cfg {:keys [id] :as file}]
(let [conn (db/get-connection cfg)
migrations (or (-> file meta ::fmg/migrated)
(-> file :migrations))
columns [:file-id :name]
rows (mapv (fn [name] [id name]) migrations)]
rows (->> migrations
(mapv (fn [name] [id name]))
(not-empty))]
(when-not rows
(ex/raise :type :internal
:code :missing-migrations
:hint "no migrations available on file"))
(-> (db/insert-many! conn :file-migration columns rows
{::db/return-keys false
::sql/on-conflict-do-nothing true})
(db/get-update-count))))
(defn reset-migrations!
"Replace file migrations"
[cfg {:keys [id] :as file}]
(db/delete! cfg :file-migration {:file-id id})
(upsert-migrations! cfg file))

View File

@@ -7,22 +7,23 @@
(ns app.features.logical-deletion
"A code related to handle logical deletion mechanism"
(:require
[app.config :as cf]
[app.util.time :as dt]))
[app.common.time :as ct]
[app.config :as cf]))
(def ^:private canceled-status
#{"canceled" "unpaid"})
(defn get-deletion-delay
"Calculate the next deleted-at for a resource (file, team, etc) in function
of team settings"
[team]
(if-let [subscription (get team :subscription)]
(if-let [{:keys [type status]} (get team :subscription)]
(cond
(and (= (:type subscription) "unlimited")
(= (:status subscription) "active"))
(dt/duration {:days 30})
(and (= "unlimited" type) (not (contains? canceled-status status)))
(ct/duration {:days 30})
(and (= (:type subscription) "enterprise")
(= (:status subscription) "active"))
(dt/duration {:days 90})
(and (= "enterprise" type) (not (contains? canceled-status status)))
(ct/duration {:days 90})
:else
(cf/get-deletion-delay))

View File

@@ -17,6 +17,7 @@
[app.http.awsns :as-alias awsns]
[app.http.debug :as-alias debug]
[app.http.errors :as errors]
[app.http.management :as mgmt]
[app.http.middleware :as mw]
[app.http.session :as session]
[app.http.websocket :as-alias ws]
@@ -26,7 +27,6 @@
[app.rpc.doc :as-alias rpc.doc]
[app.setup :as-alias setup]
[integrant.core :as ig]
[promesa.exec :as px]
[reitit.core :as r]
[reitit.middleware :as rr]
[yetti.adapter :as yt]
@@ -53,6 +53,8 @@
[:map
[::port ::sm/int]
[::host ::sm/text]
[::io-threads {:optional true} ::sm/int]
[::max-worker-threads {:optional true} ::sm/int]
[::max-body-size {:optional true} ::sm/int]
[::max-multipart-body-size {:optional true} ::sm/int]
[::router {:optional true} [:fn r/router?]]
@@ -63,30 +65,41 @@
(assert (sm/check schema:server-params params)))
(defmethod ig/init-key ::server
[_ {:keys [::handler ::router ::host ::port] :as cfg}]
[_ {:keys [::handler ::router ::host ::port ::mtx/metrics] :as cfg}]
(l/info :hint "starting http server" :port port :host host)
(let [options {:http/port port
:http/host host
:http/max-body-size (::max-body-size cfg)
:http/max-multipart-body-size (::max-multipart-body-size cfg)
:xnio/io-threads (or (::io-threads cfg)
(max 3 (px/get-available-processors)))
:xnio/dispatch :virtual
:ring/compat :ring2
:socket/backlog 4069}
(let [on-dispatch
(fn [_ start-at-ns]
(let [timing (- (System/nanoTime) start-at-ns)
timing (int (/ timing 1000000))]
(mtx/run! metrics
:id :http-server-dispatch-timing
:val timing)))
handler (cond
(some? router)
(router-handler router)
options
{:http/port port
:http/host host
:http/max-body-size (::max-body-size cfg)
:http/max-multipart-body-size (::max-multipart-body-size cfg)
:xnio/direct-buffers false
:xnio/io-threads (::io-threads cfg)
:xnio/max-worker-threads (::max-worker-threads cfg)
:ring/compat :ring2
:events/on-dispatch on-dispatch
:socket/backlog 4069}
(some? handler)
handler
handler
(cond
(some? router)
(router-handler router)
:else
(throw (UnsupportedOperationException. "handler or router are required")))
(some? handler)
handler
options (d/without-nils options)
server (yt/server handler options)]
:else
(throw (UnsupportedOperationException. "handler or router are required")))
server
(yt/server handler (d/without-nils options))]
(assoc cfg ::server (yt/start! server))))
@@ -141,6 +154,7 @@
[::debug/routes schema:routes]
[::mtx/routes schema:routes]
[::awsns/routes schema:routes]
[::mgmt/routes schema:routes]
::session/manager
::setup/props
::db/pool])
@@ -168,6 +182,9 @@
["/webhooks"
(::awsns/routes cfg)]
["/management"
(::mgmt/routes cfg)]
(::ws/routes cfg)
["/api" {:middleware [[mw/cors]]}

View File

@@ -9,18 +9,18 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.time :as ct]
[app.common.uri :as u]
[app.db :as db]
[app.storage :as sto]
[app.util.time :as dt]
[integrant.core :as ig]
[yetti.response :as-alias yres]))
(def ^:private cache-max-age
(dt/duration {:hours 24}))
(ct/duration {:hours 24}))
(def ^:private signature-max-age
(dt/duration {:hours 24 :minutes 15}))
(ct/duration {:hours 24 :minutes 15}))
(defn get-id
[{:keys [path-params]}]

View File

@@ -17,11 +17,9 @@
[app.main :as-alias main]
[app.setup :as-alias setup]
[app.tokens :as tokens]
[app.worker :as-alias wrk]
[clojure.data.json :as j]
[cuerdas.core :as str]
[integrant.core :as ig]
[promesa.exec :as px]
[yetti.request :as yreq]
[yetti.response :as-alias yres]))
@@ -40,8 +38,8 @@
[_ cfg]
(letfn [(handler [request]
(let [data (-> request yreq/body slurp)]
(px/run! :vthread (partial handle-request cfg data)))
{::yres/status 200})]
(handle-request cfg data)
{::yres/status 200}))]
["/sns" {:handler handler
:allowed-methods #{:post}}]))

View File

@@ -15,9 +15,12 @@
[app.common.features :as cfeat]
[app.common.logging :as l]
[app.common.pprint :as pp]
[app.common.time :as ct]
[app.common.transit :as t]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.features.file-migrations :as feat.fmig]
[app.http.session :as session]
[app.rpc.commands.auth :as auth]
[app.rpc.commands.files-create :refer [create-file]]
@@ -29,7 +32,6 @@
[app.storage.tmp :as tmp]
[app.util.blob :as blob]
[app.util.template :as tmpl]
[app.util.time :as dt]
[cuerdas.core :as str]
[datoteka.io :as io]
[emoji.core :as emj]
@@ -50,26 +52,26 @@
{::yres/status 200
::yres/headers {"content-type" "text/html"}
::yres/body (-> (io/resource "app/templates/debug.tmpl")
(tmpl/render {:version (:full cf/version)}))})
(tmpl/render {:version (:full cf/version)
:supported-features cfeat/supported-features}))})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; FILE CHANGES
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn prepare-response
[body]
(let [headers {"content-type" "application/transit+json"}]
{::yres/status 200
::yres/body body
::yres/headers headers}))
(defn- get-resolved-file
[cfg file-id]
(some-> (bfc/get-file cfg file-id :migrate? false)
(update :data blob/encode)))
(defn prepare-download-response
[body filename]
(let [headers {"content-disposition" (str "attachment; filename=" filename)
"content-type" "application/octet-stream"}]
{::yres/status 200
::yres/body body
::yres/headers headers}))
(defn prepare-download
[file filename]
{::yres/status 200
::yres/headers
{"content-disposition" (str "attachment; filename=" filename ".json")
"content-type" "application/octet-stream"}
::yres/body
(t/encode file {:type :json-verbose})})
(def sql:retrieve-range-of-changes
"select revn, changes from file_change where file_id=? and revn >= ? and revn <= ? order by revn")
@@ -77,45 +79,51 @@
(def sql:retrieve-single-change
"select revn, changes, data from file_change where file_id=? and revn = ?")
(defn- retrieve-file-data
[{:keys [::db/pool]} {:keys [params ::session/profile-id] :as request}]
(defn- download-file-data
[cfg {:keys [params ::session/profile-id] :as request}]
(let [file-id (some-> params :file-id parse-uuid)
revn (some-> params :revn parse-long)
filename (str file-id)]
(when-not file-id
(ex/raise :type :validation
:code :missing-arguments))
(let [data (if (integer? revn)
(some-> (db/exec-one! pool [sql:retrieve-single-change file-id revn]) :data)
(some-> (db/get-by-id pool :file file-id) :data))]
(when-not data
(ex/raise :type :not-found
:code :enpty-data
:hint "empty response"))
(if-let [file (get-resolved-file cfg file-id)]
(cond
(contains? params :download)
(prepare-download-response data filename)
(prepare-download file filename)
(contains? params :clone)
(let [profile (profile/get-profile pool profile-id)
project-id (:default-project-id profile)]
(db/tx-run! cfg
(fn [{:keys [::db/conn] :as cfg}]
(let [profile (profile/get-profile conn profile-id)
project-id (:default-project-id profile)
file (-> (create-file cfg {:id (uuid/next)
:name (str "Cloned: " (:name file))
:features (:features file)
:project-id project-id
:profile-id profile-id})
(assoc :data (:data file))
(assoc :migrations (:migrations file)))]
(db/run! pool (fn [{:keys [::db/conn] :as cfg}]
(create-file cfg {:id file-id
:name (str "Cloned file: " filename)
:project-id project-id
:profile-id profile-id})
(db/update! conn :file
{:data data}
{:id file-id})
{::yres/status 201
::yres/body "OK CREATED"})))
(feat.fmig/reset-migrations! conn file)
(db/update! conn :file
{:data (:data file)}
{:id (:id file)}
{::db/return-keys false})
{::yres/status 201
::yres/body "OK CLONED"})))
:else
(prepare-response (blob/decode data))))))
(ex/raise :type :validation
:code :invalid-params
:hint "invalid button"))
(ex/raise :type :not-found
:code :enpty-data
:hint "empty response"))))
(defn- is-file-exists?
[pool id]
@@ -123,81 +131,61 @@
(-> (db/exec-one! pool [sql id]) :exists)))
(defn- upload-file-data
[{:keys [::db/pool]} {:keys [::session/profile-id params] :as request}]
[{:keys [::db/pool] :as cfg} {:keys [::session/profile-id params] :as request}]
(let [profile (profile/get-profile pool profile-id)
project-id (:default-project-id profile)
data (some-> params :file :path io/read*)]
file (some-> params :file :path io/read* t/decode)]
(if (and data project-id)
(let [fname (str "Imported file *: " (dt/now))
(if (and file project-id)
(let [fname (str "Imported: " (:name file) "(" (ct/now) ")")
reuse-id? (contains? params :reuseid)
file-id (or (and reuse-id? (ex/ignoring (-> params :file :filename parse-uuid)))
(uuid/next))]
(if (and reuse-id? file-id
(is-file-exists? pool file-id))
(do
(db/update! pool :file
{:data data
:deleted-at nil}
{:id file-id})
{::yres/status 200
::yres/body "OK UPDATED"})
(db/tx-run! cfg
(fn [{:keys [::db/conn] :as cfg}]
(db/update! conn :file
{:data (:data file)
:features (into-array (:features file))
:deleted-at nil}
{:id file-id}
{::db/return-keys false})
(feat.fmig/reset-migrations! conn file)
{::yres/status 200
::yres/body "OK UPDATED"}))
(db/tx-run! cfg
(fn [{:keys [::db/conn] :as cfg}]
(let [file (-> (create-file cfg {:id file-id
:name fname
:features (:features file)
:project-id project-id
:profile-id profile-id})
(assoc :data (:data file))
(assoc :migrations (:migrations file)))]
(db/run! pool (fn [{:keys [::db/conn] :as cfg}]
(create-file cfg {:id file-id
:name fname
:project-id project-id
:profile-id profile-id})
(db/update! conn :file
{:data data}
{:id file-id})
{:data (:data file)}
{:id file-id}
{::db/return-keys false})
(feat.fmig/reset-migrations! conn file)
{::yres/status 201
::yres/body "OK CREATED"}))))
::yres/body "OK CREATED"})))))
{::yres/status 500
::yres/body "ERROR"})))
(ex/raise :type :validation
:code :invalid-params
:hint "invalid file uploaded"))))
(defn file-data-handler
(defn raw-export-import-handler
[cfg request]
(case (yreq/method request)
:get (retrieve-file-data cfg request)
:get (download-file-data cfg request)
:post (upload-file-data cfg request)
(ex/raise :type :http
:code :method-not-found)))
(defn file-changes-handler
[{:keys [::db/pool]} {:keys [params] :as request}]
(letfn [(retrieve-changes [file-id revn]
(if (str/includes? revn ":")
(let [[start end] (->> (str/split revn #":")
(map str/trim)
(map parse-long))]
(some->> (db/exec! pool [sql:retrieve-range-of-changes file-id start end])
(map :changes)
(map blob/decode)
(mapcat identity)
(vec)))
(if-let [revn (parse-long revn)]
(let [item (db/exec-one! pool [sql:retrieve-single-change file-id revn])]
(some-> item :changes blob/decode vec))
(ex/raise :type :validation :code :invalid-arguments))))]
(let [file-id (some-> params :id parse-uuid)
revn (or (some-> params :revn parse-long) "latest")
filename (str file-id)]
(when (or (not file-id) (not revn))
(ex/raise :type :validation
:code :invalid-arguments
:hint "missing arguments"))
(let [data (retrieve-changes file-id revn)]
(if (contains? params :download)
(prepare-download-response data filename)
(prepare-response data))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ERROR BROWSER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -234,7 +222,7 @@
(-> (io/resource "app/templates/error-report.v3.tmpl")
(tmpl/render (-> content
(assoc :id id)
(assoc :created-at (dt/format-instant created-at :rfc1123))))))]
(assoc :created-at (ct/format-inst created-at :rfc1123))))))]
(if-let [report (get-report request)]
(let [result (case (:version report)
@@ -258,7 +246,7 @@
(defn error-list-handler
[{:keys [::db/pool]} _request]
(let [items (->> (db/exec! pool [sql:error-reports])
(map #(update % :created-at dt/format-instant :rfc1123)))]
(map #(update % :created-at ct/format-inst :rfc1123)))]
{::yres/status 200
::yres/body (-> (io/resource "app/templates/error-list.tmpl")
(tmpl/render {:items items}))
@@ -430,49 +418,49 @@
::yres/body "OK"}))
(defn- add-team-feature
[{:keys [params] :as request}]
(let [team-id (some-> params :team-id d/parse-uuid)
feature (some-> params :feature str)
(defn- handle-team-features
[cfg {:keys [params] :as request}]
(let [team-id (some-> params :team-id d/parse-uuid)
feature (some-> params :feature str)
action (some-> params :action)
skip-check (contains? params :skip-check)]
(when-not (contains? params :force)
(ex/raise :type :validation
:code :missing-force
:hint "missing force checkbox"))
(when (nil? team-id)
(ex/raise :type :validation
:code :invalid-team-id
:hint "provided invalid team id"))
(srepl/enable-team-feature! team-id feature :skip-check skip-check)
(if (= action "show")
(let [team (db/run! cfg teams/get-team-info {:id team-id})]
{::yres/status 200
::yres/headers {"content-type" "text/plain"}
::yres/body (apply str "Team features:\n"
(->> (:features team)
(map (fn [feature]
(str "- " feature "\n")))))})
{::yres/status 200
::yres/headers {"content-type" "text/plain"}
::yres/body "OK"}))
(do
(when-not (contains? params :force)
(ex/raise :type :validation
:code :missing-force
:hint "missing force checkbox"))
(defn- remove-team-feature
[{:keys [params] :as request}]
(let [team-id (some-> params :team-id d/parse-uuid)
feature (some-> params :feature str)
skip-check (contains? params :skip-check)]
(cond
(= action "enable")
(srepl/enable-team-feature! team-id feature :skip-check skip-check)
(when-not (contains? params :force)
(ex/raise :type :validation
:code :missing-force
:hint "missing force checkbox"))
(= action "disable")
(srepl/disable-team-feature! team-id feature :skip-check skip-check)
(when (nil? team-id)
(ex/raise :type :validation
:code :invalid-team-id
:hint "provided invalid team id"))
:else
(ex/raise :type :validation
:code :invalid-action
:hint (str "invalid action: " action)))
(srepl/disable-team-feature! team-id feature :skip-check skip-check)
{::yres/status 200
::yres/headers {"content-type" "text/plain"}
::yres/body "OK"}))
{::yres/status 200
::yres/headers {"content-type" "text/plain"}
::yres/body "OK"}))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; OTHER SMALL VIEWS/HANDLERS
@@ -525,6 +513,25 @@
(ex/raise :type :authentication
:code :only-admins-allowed)))))})
(def errors
(letfn [(handle-error [cause]
(when-let [data (ex-data cause)]
(when (= :validation (:type data))
(str "Error: " (or (:hint data) (ex-message cause)) "\n"))))]
{:name ::errors
:compile
(fn [& _params]
(fn [handler]
(fn [request]
(try
(handler request)
(catch Throwable cause
(let [body (or (handle-error cause)
(ex/format-throwable cause))]
{::yres/status 400
::yres/headers {"content-type" "text/plain"}
::yres/body body}))))))}))
(defmethod ig/assert-key ::routes
[_ params]
(assert (db/pool? (::db/pool params)) "expected a valid database pool")
@@ -540,15 +547,14 @@
["/changelog" {:handler (partial changelog-handler cfg)}]
["/error/:id" {:handler (partial error-handler cfg)}]
["/error" {:handler (partial error-list-handler cfg)}]
["/actions/resend-email-verification"
{:handler (partial resend-email-notification cfg)}]
["/actions/reset-file-version"
{:handler (partial reset-file-version cfg)}]
["/actions/add-team-feature"
{:handler (partial add-team-feature)}]
["/actions/remove-team-feature"
{:handler (partial remove-team-feature)}]
["/file/export" {:handler (partial export-handler cfg)}]
["/file/import" {:handler (partial import-handler cfg)}]
["/file/data" {:handler (partial file-data-handler cfg)}]
["/file/changes" {:handler (partial file-changes-handler cfg)}]]])
["/actions" {:middleware [[errors]]}
["/resend-email-verification"
{:handler (partial resend-email-notification cfg)}]
["/reset-file-version"
{:handler (partial reset-file-version cfg)}]
["/handle-team-features"
{:handler (partial handle-team-features cfg)}]
["/file-export" {:handler (partial export-handler cfg)}]
["/file-import" {:handler (partial import-handler cfg)}]
["/file-raw-export-import" {:handler (partial raw-export-import-handler cfg)}]]]])

View File

@@ -0,0 +1,234 @@
;; 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.http.management
"Internal mangement HTTP API"
(:require
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.time :as ct]
[app.db :as db]
[app.main :as-alias main]
[app.rpc.commands.profile :as cmd.profile]
[app.setup :as-alias setup]
[app.tokens :as tokens]
[app.worker :as-alias wrk]
[integrant.core :as ig]
[yetti.response :as-alias yres]))
;; ---- ROUTES
(declare ^:private authenticate)
(declare ^:private get-customer)
(declare ^:private update-customer)
(defmethod ig/assert-key ::routes
[_ params]
(assert (db/pool? (::db/pool params)) "expect valid database pool"))
(def ^:private default-system
{:name ::default-system
:compile
(fn [_ _]
(fn [handler cfg]
(fn [request]
(handler cfg request))))})
(def ^:private transaction
{:name ::transaction
:compile
(fn [data _]
(when (:transaction data)
(fn [handler]
(fn [cfg request]
(db/tx-run! cfg handler request)))))})
(defmethod ig/init-key ::routes
[_ cfg]
["" {:middleware [[default-system cfg]
[transaction]]}
["/authenticate"
{:handler authenticate
:allowed-methods #{:post}}]
["/get-customer"
{:handler get-customer
:transaction true
:allowed-methods #{:post}}]
["/update-customer"
{:handler update-customer
:allowed-methods #{:post}
:transaction true}]])
;; ---- HELPERS
(defn- coercer
[schema & {:as opts}]
(let [decode-fn (sm/decoder schema sm/json-transformer)
check-fn (sm/check-fn schema opts)]
(fn [data]
(-> data decode-fn check-fn))))
;; ---- API: AUTHENTICATE
(defn- authenticate
[cfg request]
(let [token (-> request :params :token)
props (get cfg ::setup/props)
result (tokens/verify props {:token token :iss "authentication"})]
{::yres/status 200
::yres/body result}))
;; ---- API: GET-CUSTOMER
(def ^:private schema:get-customer
[:map [:id ::sm/uuid]])
(def ^:private coerce-get-customer-params
(coercer schema:get-customer
:type :validation
:hint "invalid data provided for `get-customer` rpc call"))
(def ^:private sql:get-customer-slots
"WITH teams AS (
SELECT tpr.team_id AS id,
tpr.profile_id AS profile_id
FROM team_profile_rel AS tpr
WHERE tpr.is_owner IS true
AND tpr.profile_id = ?
), teams_with_slots AS (
SELECT tpr.team_id AS id,
count(*) AS total
FROM team_profile_rel AS tpr
WHERE tpr.team_id IN (SELECT id FROM teams)
AND tpr.can_edit IS true
GROUP BY 1
ORDER BY 2
)
SELECT max(total) AS total FROM teams_with_slots;")
(defn- get-customer-slots
[cfg profile-id]
(let [result (db/exec-one! cfg [sql:get-customer-slots profile-id])]
(:total result)))
(defn- get-customer
[cfg request]
(let [profile-id (-> request :params coerce-get-customer-params :id)
profile (cmd.profile/get-profile cfg profile-id)
result {:id (get profile :id)
:name (get profile :fullname)
:email (get profile :email)
:num-editors (get-customer-slots cfg profile-id)
:subscription (-> profile :props :subscription)}]
{::yres/status 200
::yres/body result}))
;; ---- API: UPDATE-CUSTOMER
(def ^:private schema:timestamp
(sm/type-schema
{:type ::timestamp
:pred ct/inst?
:type-properties
{:title "inst"
:description "The same as :app.common.time/inst but encodes to epoch"
:error/message "should be an instant"
:gen/gen (->> (sg/small-int)
(sg/fmap (fn [v] (ct/inst v))))
:decode/string ct/inst
:encode/string inst-ms
:decode/json ct/inst
:encode/json inst-ms}}))
(def ^:private schema:subscription
[:map {:title "Subscription"}
[:id ::sm/text]
[:customer-id ::sm/text]
[:type [:enum
"unlimited"
"professional"
"enterprise"]]
[:status [:enum
"active"
"canceled"
"incomplete"
"incomplete_expired"
"past_due"
"paused"
"trialing"
"unpaid"]]
[:billing-period [:enum
"month"
"day"
"week"
"year"]]
[:quantity :int]
[:description [:maybe ::sm/text]]
[:created-at schema:timestamp]
[:start-date [:maybe schema:timestamp]]
[:ended-at [:maybe schema:timestamp]]
[:trial-end [:maybe schema:timestamp]]
[:trial-start [:maybe schema:timestamp]]
[:cancel-at [:maybe schema:timestamp]]
[:canceled-at [:maybe schema:timestamp]]
[:current-period-end [:maybe schema:timestamp]]
[:current-period-start [:maybe schema:timestamp]]
[:cancel-at-period-end :boolean]
[:cancellation-details
[:map {:title "CancellationDetails"}
[:comment [:maybe ::sm/text]]
[:reason [:maybe ::sm/text]]
[:feedback [:maybe
[:enum
"customer_service"
"low_quality"
"missing_feature"
"other"
"switched_service"
"too_complex"
"too_expensive"
"unused"]]]]]])
(def ^:private schema:update-customer
[:map
[:id ::sm/uuid]
[:subscription [:maybe schema:subscription]]])
(def ^:private coerce-update-customer-params
(coercer schema:update-customer
:type :validation
:hint "invalid data provided for `update-customer` rpc call"))
(defn- update-customer
[cfg request]
(let [{:keys [id subscription]}
(-> request :params coerce-update-customer-params)
{:keys [props] :as profile}
(cmd.profile/get-profile cfg id ::db/for-update true)
props
(assoc props :subscription subscription)]
(l/dbg :hint "update customer"
:profile-id (str id)
:subscription-type (get subscription :type)
:subscription-status (get subscription :status)
:subscription-quantity (get subscription :quantity))
(db/update! cfg :profile
{:props (db/tjson props)}
{:id id}
{::db/return-keys false})
{::yres/status 201
::yres/body nil}))

View File

@@ -10,6 +10,7 @@
[app.common.data :as d]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uri :as u]
[app.config :as cf]
[app.db :as db]
@@ -18,7 +19,6 @@
[app.main :as-alias main]
[app.setup :as-alias setup]
[app.tokens :as tokens]
[app.util.time :as dt]
[cuerdas.core :as str]
[integrant.core :as ig]
[yetti.request :as yreq]))
@@ -35,10 +35,10 @@
(def default-auth-data-cookie-name "auth-data")
;; Default value for cookie max-age
(def default-cookie-max-age (dt/duration {:days 7}))
(def default-cookie-max-age (ct/duration {:days 7}))
;; Default age for automatic session renewal
(def default-renewal-max-age (dt/duration {:hours 6}))
(def default-renewal-max-age (ct/duration {:hours 6}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PROTOCOLS
@@ -66,7 +66,7 @@
[:map {:title "session-params"}
[:user-agent ::sm/text]
[:profile-id ::sm/uuid]
[:created-at ::sm/inst]])
[:created-at ::ct/inst]])
(def ^:private valid-params?
(sm/validator schema:params))
@@ -95,7 +95,7 @@
params))
(update! [_ params]
(let [updated-at (dt/now)]
(let [updated-at (ct/now)]
(db/update! pool :http-session
{:updated-at updated-at}
{:id (:id params)})
@@ -118,7 +118,7 @@
params))
(update! [_ params]
(let [updated-at (dt/now)]
(let [updated-at (ct/now)]
(swap! cache update (:id params) assoc :updated-at updated-at)
(assoc params :updated-at updated-at)))
@@ -158,7 +158,7 @@
(let [uagent (yreq/get-header request "user-agent")
params {:profile-id profile-id
:user-agent uagent
:created-at (dt/now)}
:created-at (ct/now)}
token (gen-token props params)
session (write! manager token params)]
(l/trace :hint "create" :profile-id (str profile-id))
@@ -203,8 +203,8 @@
(defn- renew-session?
[{:keys [updated-at] :as session}]
(and (dt/instant? updated-at)
(let [elapsed (dt/diff updated-at (dt/now))]
(and (ct/inst? updated-at)
(let [elapsed (ct/diff updated-at (ct/now))]
(neg? (compare default-renewal-max-age elapsed)))))
(defn- wrap-soft-auth
@@ -256,14 +256,14 @@
(defn- assign-auth-token-cookie
[response {token :id updated-at :updated-at}]
(let [max-age (cf/get :auth-token-cookie-max-age default-cookie-max-age)
created-at (or updated-at (dt/now))
renewal (dt/plus created-at default-renewal-max-age)
expires (dt/plus created-at max-age)
created-at (or updated-at (ct/now))
renewal (ct/plus created-at default-renewal-max-age)
expires (ct/plus created-at max-age)
secure? (contains? cf/flags :secure-session-cookies)
strict? (contains? cf/flags :strict-session-cookies)
cors? (contains? cf/flags :cors)
name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)
comment (str "Renewal at: " (dt/format-instant renewal :rfc1123))
comment (str "Renewal at: " (ct/format-inst renewal :rfc1123))
cookie {:path "/"
:http-only true
:expires expires
@@ -279,11 +279,11 @@
domain (cf/get :auth-data-cookie-domain)
cname default-auth-data-cookie-name
created-at (or updated-at (dt/now))
renewal (dt/plus created-at default-renewal-max-age)
expires (dt/plus created-at max-age)
created-at (or updated-at (ct/now))
renewal (ct/plus created-at default-renewal-max-age)
expires (ct/plus created-at max-age)
comment (str "Renewal at: " (dt/format-instant renewal :rfc1123))
comment (str "Renewal at: " (ct/format-inst renewal :rfc1123))
secure? (contains? cf/flags :secure-session-cookies)
strict? (contains? cf/flags :strict-session-cookies)
cors? (contains? cf/flags :cors)
@@ -323,7 +323,7 @@
(defmethod ig/assert-key ::tasks/gc
[_ params]
(assert (db/pool? (::db/pool params)) "expected valid database pool")
(assert (dt/duration? (::tasks/max-age params))))
(assert (ct/duration? (::tasks/max-age params))))
(defmethod ig/expand-key ::tasks/gc
[k v]

View File

@@ -33,7 +33,7 @@
(println "event:" (d/name name))
(println "data:" (t/encode-str data {:type :json-verbose}))
(println))]
(.getBytes data "UTF-8"))
(.getBytes ^String data "UTF-8"))
(catch Throwable cause
(l/err :hint "unexpected error on encoding value on sse stream"
:cause cause)
@@ -54,7 +54,7 @@
::yres/body (yres/stream-body
(fn [_ output]
(let [channel (sp/chan :buf buf :xf (keep encode))
listener (events/start-listener
listener (events/spawn-listener
channel
(partial write! output)
(partial pu/close! output))]

View File

@@ -11,12 +11,12 @@
[app.common.logging :as l]
[app.common.pprint :as pp]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.db :as db]
[app.http.session :as session]
[app.metrics :as mtx]
[app.msgbus :as mbus]
[app.util.time :as dt]
[app.util.websocket :as ws]
[integrant.core :as ig]
[promesa.exec.csp :as sp]
@@ -239,7 +239,7 @@
(defn- on-connect
[{:keys [::mtx/metrics]} {:keys [::ws/id] :as wsp}]
(let [created-at (dt/now)]
(let [created-at (ct/now)]
(l/trace :fn "on-connect" :conn-id id)
(swap! state assoc id wsp)
(mtx/run! metrics
@@ -253,7 +253,7 @@
(mtx/run! metrics :id :websocket-active-connections :dec 1)
(mtx/run! metrics
:id :websocket-session-timing
:val (/ (inst-ms (dt/diff created-at (dt/now))) 1000.0))))))
:val (/ (inst-ms (ct/diff created-at (ct/now))) 1000.0))))))
(defn- on-rcv-message
[{:keys [::mtx/metrics ::profile-id ::session-id]} message]

View File

@@ -11,6 +11,7 @@
[app.common.data.macros :as dm]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@@ -23,7 +24,6 @@
[app.setup :as-alias setup]
[app.util.inet :as inet]
[app.util.services :as-alias sv]
[app.util.time :as dt]
[app.worker :as wrk]
[cuerdas.core :as str]))
@@ -108,9 +108,9 @@
[::ip-addr {:optional true} ::sm/text]
[::props {:optional true} [:map-of :keyword :any]]
[::context {:optional true} [:map-of :keyword :any]]
[::tracked-at {:optional true} ::sm/inst]
[::tracked-at {:optional true} ::ct/inst]
[::webhooks/event? {:optional true} ::sm/boolean]
[::webhooks/batch-timeout {:optional true} ::dt/duration]
[::webhooks/batch-timeout {:optional true} ::ct/duration]
[::webhooks/batch-key {:optional true}
[:or ::sm/fn ::sm/text :keyword]]])
@@ -199,7 +199,7 @@
(defn- handle-event!
[cfg event]
(let [params (event->params event)
tnow (dt/now)]
tnow (ct/now)]
(when (contains? cf/flags :audit-log)
;; NOTE: this operation may cause primary key conflicts on inserts
@@ -273,7 +273,7 @@
(let [event (-> (d/without-nils event)
(check-event))]
(db/run! cfg (fn [cfg]
(let [tnow (dt/now)
(let [tnow (ct/now)
params (-> (event->params event)
(assoc :created-at tnow)
(update :tracked-at #(or % tnow)))]

View File

@@ -9,6 +9,7 @@
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.transit :as t]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -16,7 +17,6 @@
[app.http.client :as http]
[app.setup :as-alias setup]
[app.tokens :as tokens]
[app.util.time :as dt]
[integrant.core :as ig]
[lambdaisland.uri :as u]
[promesa.exec :as px]))
@@ -55,7 +55,7 @@
[{:keys [::uri] :as cfg} events]
(let [token (tokens/generate (::setup/props cfg)
{:iss "authentication"
:iat (dt/now)
:iat (ct/now)
:uid uuid/zero})
body (t/encode {:events events})
headers {"content-type" "application/transit+json"

View File

@@ -10,13 +10,13 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.logging :as l]
[app.common.time :as ct]
[app.common.transit :as t]
[app.common.uri :as uri]
[app.config :as cf]
[app.db :as db]
[app.http.client :as http]
[app.loggers.audit :as audit]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.data.json :as json]
[cuerdas.core :as str]
@@ -124,7 +124,7 @@
{:id (:id whook)})))
(db/update! pool :webhook
{:updated-at (dt/now)
{:updated-at (ct/now)
:error-code nil
:error-count 0}
{:id (:id whook)})))
@@ -132,7 +132,7 @@
(report-delivery! [whook req rsp err]
(db/insert! pool :webhook-delivery
{:webhook-id (:id whook)
:created-at (dt/now)
:created-at (ct/now)
:error-code err
:req-data (db/tjson req)
:rsp-data (db/tjson rsp)}))]
@@ -155,7 +155,7 @@
(let [req {:uri (:uri whook)
:headers {"content-type" (:mtype whook)
"user-agent" (str/ffmt "penpot/%" (:main cf/version))}
:timeout (dt/duration "4s")
:timeout (ct/duration "4s")
:method :post
:body body}]
(try

View File

@@ -11,6 +11,7 @@
[app.auth.oidc.providers :as-alias oidc.providers]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.time :as ct]
[app.config :as cf]
[app.db :as-alias db]
[app.email :as-alias email]
@@ -19,6 +20,7 @@
[app.http.awsns :as http.awsns]
[app.http.client :as-alias http.client]
[app.http.debug :as-alias http.debug]
[app.http.management :as mgmt]
[app.http.session :as-alias session]
[app.http.session.tasks :as-alias session.tasks]
[app.http.websocket :as http.ws]
@@ -38,8 +40,9 @@
[app.storage.gc-touched :as-alias sto.gc-touched]
[app.storage.s3 :as-alias sto.s3]
[app.svgo :as-alias svgo]
[app.util.time :as dt]
[app.util.cron]
[app.worker :as-alias wrk]
[app.worker.executor]
[clojure.test :as test]
[clojure.tools.namespace.repl :as repl]
[cuerdas.core :as str]
@@ -146,23 +149,11 @@
::mdef/labels []
::mdef/type :histogram}
:executors-active-threads
{::mdef/name "penpot_executors_active_threads"
::mdef/help "Current number of threads available in the executor service."
::mdef/labels ["name"]
::mdef/type :gauge}
:executors-completed-tasks
{::mdef/name "penpot_executors_completed_tasks_total"
::mdef/help "Approximate number of completed tasks by the executor."
::mdef/labels ["name"]
::mdef/type :counter}
:executors-running-threads
{::mdef/name "penpot_executors_running_threads"
::mdef/help "Current number of threads with state RUNNING."
::mdef/labels ["name"]
::mdef/type :gauge}})
:http-server-dispatch-timing
{::mdef/name "penpot_http_server_dispatch_timing"
::mdef/help "Histogram of dispatch handler"
::mdef/labels []
::mdef/type :histogram}})
(def system-config
{::db/pool
@@ -174,14 +165,12 @@
::db/max-size (cf/get :database-max-pool-size 60)
::mtx/metrics (ig/ref ::mtx/metrics)}
;; Default thread pool for IO operations
::wrk/executor
{}
;; Default netty IO pool (shared between several services)
::wrk/netty-io-executor
{:threads (cf/get :netty-io-threads)}
::wrk/monitor
{::mtx/metrics (ig/ref ::mtx/metrics)
::wrk/executor (ig/ref ::wrk/executor)
::wrk/name "default"}
::wrk/netty-executor
{:threads (cf/get :executor-threads)}
:app.migrations/migrations
{::db/pool (ig/ref ::db/pool)}
@@ -195,14 +184,19 @@
::rds/redis
{::rds/uri (cf/get :redis-uri)
::mtx/metrics (ig/ref ::mtx/metrics)
::wrk/executor (ig/ref ::wrk/executor)}
::wrk/netty-executor
(ig/ref ::wrk/netty-executor)
::wrk/netty-io-executor
(ig/ref ::wrk/netty-io-executor)}
::mbus/msgbus
{::wrk/executor (ig/ref ::wrk/executor)
{::wrk/executor (ig/ref ::wrk/netty-executor)
::rds/redis (ig/ref ::rds/redis)}
:app.storage.tmp/cleaner
{::wrk/executor (ig/ref ::wrk/executor)}
{::wrk/executor (ig/ref ::wrk/netty-executor)}
::sto.gc-deleted/handler
{::db/pool (ig/ref ::db/pool)
@@ -230,8 +224,10 @@
::http/host (cf/get :http-server-host)
::http/router (ig/ref ::http/router)
::http/io-threads (cf/get :http-server-io-threads)
::http/max-worker-threads (cf/get :http-server-max-worker-threads)
::http/max-body-size (cf/get :http-server-max-body-size)
::http/max-multipart-body-size (cf/get :http-server-max-multipart-body-size)}
::http/max-multipart-body-size (cf/get :http-server-max-multipart-body-size)
::mtx/metrics (ig/ref ::mtx/metrics)}
::ldap/provider
{:host (cf/get :ldap-host)
@@ -271,6 +267,10 @@
::email/blacklist (ig/ref ::email/blacklist)
::email/whitelist (ig/ref ::email/whitelist)}
::mgmt/routes
{::db/pool (ig/ref ::db/pool)
::setup/props (ig/ref ::setup/props)}
:app.http/router
{::session/manager (ig/ref ::session/manager)
::db/pool (ig/ref ::db/pool)
@@ -279,6 +279,7 @@
::setup/props (ig/ref ::setup/props)
::mtx/routes (ig/ref ::mtx/routes)
::oidc/routes (ig/ref ::oidc/routes)
::mgmt/routes (ig/ref ::mgmt/routes)
::http.debug/routes (ig/ref ::http.debug/routes)
::http.assets/routes (ig/ref ::http.assets/routes)
::http.ws/routes (ig/ref ::http.ws/routes)
@@ -298,23 +299,23 @@
:app.http.assets/routes
{::http.assets/path (cf/get :assets-path)
::http.assets/cache-max-age (dt/duration {:hours 24})
::http.assets/cache-max-agesignature-max-age (dt/duration {:hours 24 :minutes 5})
::http.assets/cache-max-age (ct/duration {:hours 24})
::http.assets/cache-max-agesignature-max-age (ct/duration {:hours 24 :minutes 5})
::sto/storage (ig/ref ::sto/storage)}
::rpc/climit
{::mtx/metrics (ig/ref ::mtx/metrics)
::wrk/executor (ig/ref ::wrk/executor)
::wrk/executor (ig/ref ::wrk/netty-executor)
::climit/config (cf/get :rpc-climit-config)
::climit/enabled (contains? cf/flags :rpc-climit)}
:app.rpc/rlimit
{::wrk/executor (ig/ref ::wrk/executor)}
{::wrk/executor (ig/ref ::wrk/netty-executor)}
:app.rpc/methods
{::http.client/client (ig/ref ::http.client/client)
::db/pool (ig/ref ::db/pool)
::wrk/executor (ig/ref ::wrk/executor)
::wrk/executor (ig/ref ::wrk/netty-executor)
::session/manager (ig/ref ::session/manager)
::ldap/provider (ig/ref ::ldap/provider)
::sto/storage (ig/ref ::sto/storage)
@@ -468,45 +469,46 @@
(cf/get :objects-storage-s3-bucket))
::sto.s3/io-threads (or (cf/get :storage-assets-s3-io-threads)
(cf/get :objects-storage-s3-io-threads))
::wrk/executor (ig/ref ::wrk/executor)}
::wrk/netty-io-executor
(ig/ref ::wrk/netty-io-executor)}
:app.storage.fs/backend
{::sto.fs/directory (or (cf/get :storage-assets-fs-directory)
(cf/get :objects-storage-fs-directory))}})
(def worker-config
{::wrk/cron
{::wrk/registry (ig/ref ::wrk/registry)
::db/pool (ig/ref ::db/pool)
::wrk/entries
[{:cron #app/cron "0 0 0 * * ?" ;; daily
[{:cron #penpot/cron "0 0 0 * * ?" ;; daily
:task :session-gc}
{:cron #app/cron "0 0 0 * * ?" ;; daily
{:cron #penpot/cron "0 0 0 * * ?" ;; daily
:task :objects-gc}
{:cron #app/cron "0 0 0 * * ?" ;; daily
{:cron #penpot/cron "0 0 0 * * ?" ;; daily
:task :storage-gc-deleted}
{:cron #app/cron "0 0 0 * * ?" ;; daily
{:cron #penpot/cron "0 0 0 * * ?" ;; daily
:task :storage-gc-touched}
{:cron #app/cron "0 0 0 * * ?" ;; daily
{:cron #penpot/cron "0 0 0 * * ?" ;; daily
:task :tasks-gc}
{:cron #app/cron "0 0 2 * * ?" ;; daily
{:cron #penpot/cron "0 0 2 * * ?" ;; daily
:task :file-gc-scheduler}
{:cron #app/cron "0 30 */3,23 * * ?"
{:cron #penpot/cron "0 30 */3,23 * * ?"
:task :telemetry}
(when (contains? cf/flags :audit-log-archive)
{:cron #app/cron "0 */5 * * * ?" ;; every 5m
{:cron #penpot/cron "0 */5 * * * ?" ;; every 5m
:task :audit-log-archive})
(when (contains? cf/flags :audit-log-gc)
{:cron #app/cron "30 */5 * * * ?" ;; every 5m
{:cron #penpot/cron "30 */5 * * * ?" ;; every 5m
:task :audit-log-gc})]}
::wrk/dispatcher

View File

@@ -14,11 +14,11 @@
[app.common.media :as cm]
[app.common.schema :as sm]
[app.common.schema.openapi :as-alias oapi]
[app.common.time :as ct]
[app.config :as cf]
[app.db :as-alias db]
[app.storage :as-alias sto]
[app.storage.tmp :as tmp]
[app.util.time :as dt]
[buddy.core.bytes :as bb]
[buddy.core.codecs :as bc]
[clojure.java.shell :as sh]
@@ -38,15 +38,13 @@
org.im4java.core.Info))
(def schema:upload
(sm/register!
^{::sm/type ::upload}
[:map {:title "Upload"}
[:filename :string]
[:size ::sm/int]
[:path ::fs/path]
[:mtype {:optional true} :string]
[:headers {:optional true}
[:map-of :string :string]]]))
[:map {:title "Upload"}
[:filename :string]
[:size ::sm/int]
[:path ::fs/path]
[:mtype {:optional true} :string]
[:headers {:optional true}
[:map-of :string :string]]])
(def ^:private schema:input
[:map {:title "Input"}
@@ -118,7 +116,7 @@
(defn- parse-svg
[text]
(let [text (strip-doctype text)]
(dm/with-open [istream (IOUtils/toInputStream text "UTF-8")]
(dm/with-open [istream (IOUtils/toInputStream ^String text "UTF-8")]
(xml/parse istream secure-parser-factory))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -243,7 +241,7 @@
(ex/raise :type :validation
:code :invalid-svg-file
:hint "uploaded svg does not provides dimensions"))
(merge input info {:ts (dt/now)}))
(merge input info {:ts (ct/now)}))
(let [instance (Info. (str path))
mtype' (.getProperty instance "Mime type")]
@@ -263,7 +261,7 @@
(assoc input
:width width
:height height
:ts (dt/now)))))))
:ts (ct/now)))))))
(defmethod process-error org.im4java.core.InfoException
[error]

View File

@@ -438,7 +438,16 @@
:fn (mg/resource "app/migrations/sql/0138-mod-file-data-fragment-table.sql")}
{:name "0139-mod-file-change-table.sql"
:fn (mg/resource "app/migrations/sql/0139-mod-file-change-table.sql")}])
:fn (mg/resource "app/migrations/sql/0139-mod-file-change-table.sql")}
{:name "0140-mod-file-change-table.sql"
:fn (mg/resource "app/migrations/sql/0140-mod-file-change-table.sql")}
{:name "0140-add-locked-by-column-to-file-change-table"
:fn (mg/resource "app/migrations/sql/0140-add-locked-by-column-to-file-change-table.sql")}
{:name "0141-add-idx-to-file-library-rel"
:fn (mg/resource "app/migrations/sql/0141-add-idx-to-file-library-rel.sql")}])
(defn apply-migrations!
[pool name migrations]

View File

@@ -0,0 +1,11 @@
-- Add locked_by column to file_change table for version locking feature
-- This allows users to lock their own saved versions to prevent deletion by others
ALTER TABLE file_change
ADD COLUMN locked_by uuid NULL REFERENCES profile(id) ON DELETE SET NULL DEFERRABLE;
-- Create index for locked versions queries
CREATE INDEX file_change__locked_by__idx ON file_change (locked_by) WHERE locked_by IS NOT NULL;
-- Add comment for documentation
COMMENT ON COLUMN file_change.locked_by IS 'Profile ID of user who has locked this version. Only the creator can lock/unlock their own versions. Locked versions cannot be deleted by others.';

View File

@@ -0,0 +1,2 @@
ALTER TABLE file_change
ADD COLUMN migrations text[];

View File

@@ -0,0 +1,2 @@
CREATE INDEX IF NOT EXISTS file_library_rel__library_file_id__idx
ON file_library_rel (library_file_id);

View File

@@ -10,10 +10,10 @@
[app.common.data :as d]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.transit :as t]
[app.config :as cfg]
[app.redis :as rds]
[app.util.time :as dt]
[app.worker :as wrk]
[integrant.core :as ig]
[promesa.core :as p]
@@ -56,7 +56,7 @@
[k v]
{k (-> (d/without-nils v)
(assoc ::buffer-size 128)
(assoc ::timeout (dt/duration {:seconds 30})))})
(assoc ::timeout (ct/duration {:seconds 30})))})
(def ^:private schema:params
[:map ::rds/redis ::wrk/executor])
@@ -216,8 +216,7 @@
(rds/add-listener sconn (create-listener rcv-ch))
(px/thread
{:name "penpot/msgbus/io-loop"
:virtual true}
{:name "penpot/msgbus"}
(try
(loop []
(let [timeout-ch (sp/timeout-chan 1000)

View File

@@ -12,17 +12,16 @@
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.metrics :as mtx]
[app.redis.script :as-alias rscript]
[app.util.cache :as cache]
[app.util.time :as dt]
[app.worker :as-alias wrk]
[clojure.core :as c]
[clojure.java.io :as io]
[cuerdas.core :as str]
[integrant.core :as ig]
[promesa.core :as p]
[promesa.exec :as px])
[promesa.core :as p])
(:import
clojure.lang.MapEntry
io.lettuce.core.KeyValue
@@ -45,8 +44,10 @@
io.lettuce.core.pubsub.api.sync.RedisPubSubCommands
io.lettuce.core.resource.ClientResources
io.lettuce.core.resource.DefaultClientResources
io.netty.channel.nio.NioEventLoopGroup
io.netty.util.HashedWheelTimer
io.netty.util.Timer
io.netty.util.concurrent.EventExecutorGroup
java.lang.AutoCloseable
java.time.Duration))
@@ -111,21 +112,16 @@
(defmethod ig/expand-key ::redis
[k v]
(let [cpus (px/get-available-processors)
threads (max 1 (int (* cpus 0.2)))]
{k (-> (d/without-nils v)
(assoc ::timeout (dt/duration "10s"))
(assoc ::io-threads (max 3 threads))
(assoc ::worker-threads (max 3 threads)))}))
{k (-> (d/without-nils v)
(assoc ::timeout (ct/duration "10s")))})
(def ^:private schema:redis-params
[:map {:title "redis-params"}
::wrk/executor
::wrk/netty-io-executor
::wrk/netty-executor
::mtx/metrics
[::uri ::sm/uri]
[::worker-threads ::sm/int]
[::io-threads ::sm/int]
[::timeout ::dt/duration]])
[::timeout ::ct/duration]])
(defmethod ig/assert-key ::redis
[_ params]
@@ -141,17 +137,30 @@
(defn- initialize-resources
"Initialize redis connection resources"
[{:keys [::uri ::io-threads ::worker-threads ::wrk/executor ::mtx/metrics] :as params}]
[{:keys [::uri ::mtx/metrics ::wrk/netty-io-executor ::wrk/netty-executor] :as params}]
(l/inf :hint "initialize redis resources"
:uri (str uri)
:io-threads io-threads
:worker-threads worker-threads)
:uri (str uri))
(let [timer (HashedWheelTimer.)
resources (.. (DefaultClientResources/builder)
(ioThreadPoolSize ^long io-threads)
(computationThreadPoolSize ^long worker-threads)
(eventExecutorGroup ^EventExecutorGroup netty-executor)
;; We provide lettuce with a shared event loop
;; group instance instead of letting lettuce to
;; create its own
(eventLoopGroupProvider
(reify io.lettuce.core.resource.EventLoopGroupProvider
(allocate [_ _] netty-io-executor)
(threadPoolSize [_]
(.executorCount ^NioEventLoopGroup netty-io-executor))
(release [_ _ _ _ _]
;; Do nothing
)
(shutdown [_ _ _ _]
;; Do nothing
)))
(timer ^Timer timer)
(build))
@@ -166,7 +175,7 @@
(l/trace :hint "evict connection (cache)" :key key :reason cause)
(some-> val d/close!))
cache (cache/create :executor executor
cache (cache/create :executor netty-executor
:on-remove on-remove
:keepalive "5m")]
(reify
@@ -331,7 +340,7 @@
(p/rejected cause))))
(eval-script [sha]
(let [tpoint (dt/tpoint)]
(let [tpoint (ct/tpoint)]
(->> (.evalsha ^RedisScriptingAsyncCommands cmd
^String sha
^ScriptOutputType ScriptOutputType/MULTI
@@ -346,7 +355,7 @@
:name (name sname)
:sha sha
:params (str/join "," (::rscript/vals script))
:elapsed (dt/format-duration elapsed))
:elapsed (ct/format-duration elapsed))
result)))
(p/merr on-error))))

View File

@@ -12,6 +12,7 @@
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.common.time :as ct]
[app.config :as cf]
[app.db :as db]
[app.http :as-alias http]
@@ -31,7 +32,6 @@
[app.storage :as-alias sto]
[app.util.inet :as inet]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]
@@ -103,7 +103,7 @@
data (-> params
(assoc ::handler-name handler-name)
(assoc ::ip-addr ip-addr)
(assoc ::request-at (dt/now))
(assoc ::request-at (ct/now))
(assoc ::external-session-id session-id)
(assoc ::external-event-origin event-origin)
(assoc ::session/id (::session/id request))
@@ -130,7 +130,7 @@
[{:keys [::mtx/metrics ::metrics-id]} f mdata]
(let [labels (into-array String [(::sv/name mdata)])]
(fn [cfg params]
(let [tp (dt/tpoint)]
(let [tp (ct/tpoint)]
(try
(f cfg params)
(finally

View File

@@ -11,17 +11,16 @@
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.metrics :as mtx]
[app.rpc :as-alias rpc]
[app.util.cache :as cache]
[app.util.services :as-alias sv]
[app.util.time :as dt]
[app.worker :as-alias wrk]
[clojure.edn :as edn]
[clojure.set :as set]
[datoteka.fs :as fs]
[integrant.core :as ig]
[promesa.exec :as px]
[promesa.exec.bulkhead :as pbh])
(:import
clojure.lang.ExceptionInfo
@@ -154,7 +153,7 @@
:id limit-id
:label limit-label
:queue queue
:elapsed (some-> elapsed dt/format-duration)
:elapsed (some-> elapsed ct/format-duration)
:params @limit-params)))
(def ^:private idseq (AtomicLong. 0))
@@ -171,19 +170,19 @@
mlabels (into-array String [(id->str limit-id)])
limit-id (id->str limit-id limit-key)
limiter (cache/get cache limit-id (partial create-limiter config))
tpoint (dt/tpoint)
tpoint (ct/tpoint)
req-id (.incrementAndGet ^AtomicLong idseq)]
(try
(let [stats (pbh/get-stats limiter)]
(measure metrics mlabels stats nil)
(log "enqueued" req-id stats limit-id limit-label limit-params nil))
(px/invoke! limiter (fn []
(let [elapsed (tpoint)
stats (pbh/get-stats limiter)]
(measure metrics mlabels stats elapsed)
(log "acquired" req-id stats limit-id limit-label limit-params elapsed)
(handler))))
(pbh/invoke! limiter (fn []
(let [elapsed (tpoint)
stats (pbh/get-stats limiter)]
(measure metrics mlabels stats elapsed)
(log "acquired" req-id stats limit-id limit-label limit-params elapsed)
(handler))))
(catch ExceptionInfo cause
(let [{:keys [type code]} (ex-data cause)]
@@ -289,13 +288,9 @@
(get-limits cfg)))
(defn invoke!
"Run a function in context of climit.
Intended to be used in virtual threads."
[{:keys [::executor ::rpc/climit] :as cfg} f params]
"Run a function in context of climit."
[{:keys [::rpc/climit] :as cfg} f params]
(let [f (if climit
(let [f (if (some? executor)
(fn [cfg params] (px/await! (px/submit! executor (fn [] (f cfg params)))))
f)]
(build-exec-chain cfg f))
(build-exec-chain cfg f)
f)]
(f cfg params)))

View File

@@ -7,6 +7,7 @@
(ns app.rpc.commands.access-token
(:require
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.db :as db]
[app.main :as-alias main]
@@ -15,8 +16,7 @@
[app.rpc.quotes :as quotes]
[app.setup :as-alias setup]
[app.tokens :as tokens]
[app.util.services :as sv]
[app.util.time :as dt]))
[app.util.services :as sv]))
(defn- decode-row
[row]
@@ -24,13 +24,13 @@
(defn create-access-token
[{:keys [::db/conn ::setup/props]} profile-id name expiration]
(let [created-at (dt/now)
(let [created-at (ct/now)
token-id (uuid/next)
token (tokens/generate props {:iss "access-token"
:tid token-id
:iat created-at})
expires-at (some-> expiration dt/in-future)
expires-at (some-> expiration ct/in-future)
token (db/insert! conn :access-token
{:id token-id
:name name
@@ -49,7 +49,7 @@
(def ^:private schema:create-access-token
[:map {:title "create-access-token"}
[:name [:string {:max 250 :min 1}]]
[:expiration {:optional true} ::dt/duration]])
[:expiration {:optional true} ::ct/duration]])
(sv/defmethod ::create-access-token
{::doc/added "1.18"

View File

@@ -10,6 +10,7 @@
[app.common.data :as d]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@@ -20,8 +21,7 @@
[app.rpc.doc :as-alias doc]
[app.rpc.helpers :as rph]
[app.util.inet :as inet]
[app.util.services :as sv]
[app.util.time :as dt]))
[app.util.services :as sv]))
(def ^:private event-columns
[:id
@@ -49,7 +49,7 @@
(defn- adjust-timestamp
[{:keys [timestamp created-at] :as event}]
(let [margin (inst-ms (dt/diff timestamp created-at))]
(let [margin (inst-ms (ct/diff timestamp created-at))]
(if (or (neg? margin)
(> margin 3600000))
;; If event is in future or lags more than 1 hour, we reasign
@@ -63,7 +63,7 @@
[{:keys [::db/pool]} {:keys [::rpc/profile-id events] :as params}]
(let [request (-> params meta ::http/request)
ip-addr (inet/parse-request request)
tnow (dt/now)
tnow (ct/now)
xform (comp
(map (fn [event]
(-> event

View File

@@ -6,12 +6,14 @@
(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.features :as cfeat]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@@ -30,7 +32,6 @@
[app.setup.welcome-file :refer [create-welcome-file]]
[app.tokens :as tokens]
[app.util.services :as sv]
[app.util.time :as dt]
[app.worker :as wrk]
[cuerdas.core :as str]))
@@ -42,7 +43,7 @@
(defn- elapsed-verify-threshold?
[profile]
(let [elapsed (dt/diff (:modified-at profile) (dt/now))
(let [elapsed (ct/diff (:modified-at profile) (ct/now))
verify-threshold (cf/get :email-verify-threshold)]
(pos? (compare elapsed verify-threshold))))
@@ -62,7 +63,7 @@
(ex/raise :type :validation
:code :account-without-password
:hint "the current account does not have password")
(let [result (profile/verify-password cfg password (:password profile))]
(let [result (auth/verify-password password (:password profile))]
(when (:update result)
(l/trc :hint "updating profile password"
:id (str (:id profile))
@@ -85,7 +86,7 @@
(ex/raise :type :validation
:code :wrong-credentials))
(when-let [deleted-at (:deleted-at profile)]
(when (dt/is-after? (dt/now) deleted-at)
(when (ct/is-after? (ct/now) deleted-at)
(ex/raise :type :validation
:code :wrong-credentials)))
@@ -156,7 +157,7 @@
(:profile-id tdata)))
(update-password [conn profile-id]
(let [pwd (profile/derive-password cfg password)]
(let [pwd (auth/derive-password password)]
(db/update! conn :profile {:password pwd :is-active true} {:id profile-id})
nil))]
@@ -244,7 +245,7 @@
:backend "penpot"
:iss :prepared-register
:profile-id (:id profile)
:exp (dt/in-future {:days 7})
:exp (ct/in-future {:days 7})
:props {:newsletter-updates (or accept-newsletter-updates false)}}
params (d/without-nils params)
@@ -344,7 +345,7 @@
[{:keys [::db/conn] :as cfg} profile]
(let [vtoken (tokens/generate (::setup/props cfg)
{:iss :verify-email
:exp (dt/in-future "72h")
:exp (ct/in-future "72h")
:profile-id (:id profile)
:email (:email profile)})
;; NOTE: this token is mainly used for possible complains
@@ -352,7 +353,7 @@
ptoken (tokens/generate (::setup/props cfg)
{:iss :profile-identity
:profile-id (:id profile)
:exp (dt/in-future {:days 30})})]
:exp (ct/in-future {:days 30})})]
(eml/send! {::eml/conn conn
::eml/factory eml/register
:public-uri (cf/get :public-uri)
@@ -378,7 +379,7 @@
(not (contains? cf/flags :email-verification)))
params (-> params
(assoc :is-active is-active)
(update :password #(profile/derive-password cfg %)))
(update :password auth/derive-password))
profile (->> (create-profile! conn params)
(create-profile-rels! conn))]
(vary-meta profile assoc :created true))))
@@ -466,7 +467,7 @@
(when (= action "resend-email-verification")
(db/update! conn :profile
{:modified-at (dt/now)}
{:modified-at (ct/now)}
{:id (:id profile)})
(send-email-verification! cfg profile))
@@ -495,7 +496,7 @@
(letfn [(create-recovery-token [{:keys [id] :as profile}]
(let [token (tokens/generate (::setup/props cfg)
{:iss :password-recovery
:exp (dt/in-future "15m")
:exp (ct/in-future "15m")
:profile-id id})]
(assoc profile :token token)))
@@ -503,7 +504,7 @@
(let [ptoken (tokens/generate (::setup/props cfg)
{:iss :profile-identity
:profile-id (:id profile)
:exp (dt/in-future {:days 30})})]
:exp (ct/in-future {:days 30})})]
(eml/send! {::eml/conn conn
::eml/factory eml/password-recovery
:public-uri (cf/get :public-uri)
@@ -544,7 +545,7 @@
:else
(do
(db/update! conn :profile
{:modified-at (dt/now)}
{:modified-at (ct/now)}
{:id (:id profile)})
(->> profile
(create-recovery-token)

View File

@@ -13,6 +13,7 @@
[app.common.features :as cfeat]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.config :as cf]
[app.db :as db]
[app.http.sse :as sse]
@@ -26,9 +27,7 @@
[app.rpc.doc :as-alias doc]
[app.tasks.file-gc]
[app.util.services :as sv]
[app.util.time :as dt]
[app.worker :as-alias wrk]
[promesa.exec :as px]
[yetti.response :as yres]))
(set! *warn-on-reflection* true)
@@ -94,7 +93,7 @@
;; --- Command: import-binfile
(defn- import-binfile
[{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [profile-id project-id version name file]}]
[{:keys [::db/pool] :as cfg} {:keys [profile-id project-id version name file]}]
(let [team (teams/get-team pool
:profile-id profile-id
:project-id project-id)
@@ -105,16 +104,12 @@
(assoc ::bfc/name name)
(assoc ::bfc/input (:path file)))
;; NOTE: the importation process performs some operations that are
;; not very friendly with virtual threads, and for avoid
;; unexpected blocking of other concurrent operations we dispatch
;; that operation to a dedicated executor.
result (case (int version)
1 (px/invoke! executor (partial bf.v1/import-files! cfg))
3 (px/invoke! executor (partial bf.v3/import-files! cfg)))]
1 (bf.v1/import-files! cfg)
3 (bf.v3/import-files! cfg))]
(db/update! pool :project
{:modified-at (dt/now)}
{:modified-at (ct/now)}
{:id project-id}
{::db/return-keys false})
@@ -125,21 +120,35 @@
[:name [:or [:string {:max 250}]
[:map-of ::sm/uuid [:string {:max 250}]]]]
[:project-id ::sm/uuid]
[:file-id {:optional true} ::sm/uuid]
[:version {:optional true} ::sm/int]
[:file ::media/upload]])
[:file media/schema:upload]])
(sv/defmethod ::import-binfile
"Import a penpot file in a binary format."
"Import a penpot file in a binary format. If `file-id` is provided,
an in-place import will be performed instead of creating a new file.
The in-place imports are only supported for binfile-v3 and when a
.penpot file only contains one penpot file.
"
{::doc/added "1.15"
::doc/changes ["1.20" "Add file-id param for in-place import"
"1.20" "Set default version to 3"]
::webhooks/event? true
::sse/stream? true
::sm/params schema:import-binfile}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id project-id version file] :as params}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id project-id version file-id file] :as params}]
(projects/check-edition-permissions! pool profile-id project-id)
(let [version (or version 1)
(let [version (or version 3)
params (-> params
(assoc :profile-id profile-id)
(assoc :version version))
cfg (cond-> cfg
(uuid? file-id)
(assoc ::bfc/file-id file-id))
manifest (case (int version)
1 nil
3 (bf.v3/get-manifest (:path file)))]
@@ -147,5 +156,6 @@
(with-meta
(sse/response (partial import-binfile cfg params))
{::audit/props {:file nil
:file-id file-id
:generated-by (:generated-by manifest)
:referer (:referer manifest)}})))

View File

@@ -11,6 +11,7 @@
[app.common.exceptions :as ex]
[app.common.geom.point :as gpt]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uri :as uri]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -29,7 +30,6 @@
[app.rpc.retry :as rtry]
[app.util.pointer-map :as pmap]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.set :as set]
[cuerdas.core :as str]))
@@ -222,7 +222,7 @@
(defn upsert-comment-thread-status!
([conn profile-id thread-id]
(upsert-comment-thread-status! conn profile-id thread-id (dt/in-future "1s")))
(upsert-comment-thread-status! conn profile-id thread-id (ct/in-future "1s")))
([conn profile-id thread-id mod-at]
(db/exec-one! conn [sql:upsert-comment-thread-status thread-id profile-id mod-at mod-at])))

View File

@@ -7,16 +7,16 @@
(ns app.rpc.commands.demo
"A demo specific mutations."
(:require
[app.auth :refer [derive-password]]
[app.common.exceptions :as ex]
[app.common.time :as ct]
[app.config :as cf]
[app.db :as db]
[app.loggers.audit :as audit]
[app.rpc :as-alias rpc]
[app.rpc.commands.auth :as auth]
[app.rpc.commands.profile :as profile]
[app.rpc.doc :as-alias doc]
[app.util.services :as sv]
[app.util.time :as dt]
[buddy.core.codecs :as bc]
[buddy.core.nonce :as bn]))
@@ -45,15 +45,13 @@
params {:email email
:fullname fullname
:is-active true
:deleted-at (dt/in-future (cf/get-deletion-delay))
:password (profile/derive-password cfg password)
:props {}}]
(let [profile (db/tx-run! cfg (fn [{:keys [::db/conn]}]
(->> (auth/create-profile! conn params)
(auth/create-profile-rels! conn))))]
(with-meta {:email email
:password password}
{::audit/profile-id (:id profile)}))))
:deleted-at (ct/in-future (cf/get-deletion-delay))
:password (derive-password password)
:props {}}
profile (db/tx-run! cfg (fn [{:keys [::db/conn]}]
(->> (auth/create-profile! conn params)
(auth/create-profile-rels! conn))))]
(with-meta {:email email
:password password}
{::audit/profile-id (:id profile)})))

View File

@@ -16,6 +16,7 @@
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.schema.desc-js-like :as-alias smdj]
[app.common.time :as ct]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
[app.common.uri :as uri]
@@ -37,10 +38,8 @@
[app.util.blob :as blob]
[app.util.pointer-map :as pmap]
[app.util.services :as sv]
[app.util.time :as dt]
[app.worker :as wrk]
[cuerdas.core :as str]
[promesa.exec :as px]))
[cuerdas.core :as str]))
;; --- FEATURES
@@ -52,7 +51,7 @@
;; --- HELPERS
(def long-cache-duration
(dt/duration {:days 7}))
(ct/duration {:days 7}))
(defn decode-row
[{:keys [data changes features] :as row}]
@@ -78,6 +77,7 @@
;; --- FILE PERMISSIONS
(def ^:private sql:file-permissions
"select fpr.is_owner,
fpr.is_admin,
@@ -187,15 +187,15 @@
[:name [:string {:max 250}]]
[:revn [::sm/int {:min 0}]]
[:vern [::sm/int {:min 0}]]
[:modified-at ::dt/instant]
[:modified-at ::ct/inst]
[:is-shared ::sm/boolean]
[:project-id ::sm/uuid]
[:created-at ::dt/instant]
[:created-at ::ct/inst]
[:data {:optional true} ::sm/any]])
(def schema:permissions-mixin
[:map {:title "PermissionsMixin"}
[:permissions ::perms/permissions]])
[:permissions perms/schema:permissions]])
(def schema:file-with-permissions
[:merge {:title "FileWithPermissions"}
@@ -250,7 +250,7 @@
(feat.fmigr/resolve-applied-migrations cfg file))))))
(defn get-file
[{:keys [::db/conn ::wrk/executor] :as cfg} id
[{:keys [::db/conn] :as cfg} id
& {:keys [project-id
migrate?
include-deleted?
@@ -272,13 +272,8 @@
::db/remove-deleted (not include-deleted?)
::sql/for-update lock-for-update?})
(feat.fmigr/resolve-applied-migrations cfg)
(feat.fdata/resolve-file-data cfg))
;; NOTE: we perform the file decoding in a separate thread
;; because it has heavy and synchronous operations for
;; decoding file body that are not very friendly with virtual
;; threads.
file (px/invoke! executor #(decode-row file))
(feat.fdata/resolve-file-data cfg)
(decode-row))
file (if (and migrate? (fmg/need-migration? file))
(migrate-file cfg file options)
@@ -304,7 +299,7 @@
(defn get-file-etag
[{:keys [::rpc/profile-id]} {:keys [modified-at revn vern permissions]}]
(str profile-id "/" revn "/" vern "/" (hash fmg/available-migrations) "/"
(dt/format-instant modified-at :iso)
(ct/format-inst modified-at :iso)
"/"
(uri/map->query-string permissions)))
@@ -341,14 +336,24 @@
(cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features 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.
(if (and (contains? (:features file) "fdata/pointer-map")
(not (contains? (:features params) "fdata/pointer-map")))
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
(update file :data feat.fdata/process-pointers deref))
file))))
(as-> file 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
(if (and (contains? (:features file) "fdata/pointer-map")
(not (contains? (:features params) "fdata/pointer-map")))
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
(update file :data feat.fdata/process-pointers deref))
file)
;; This operation is needed for backward comapatibility with
;; frontends that does not support objects-map mechanism; this
;; just converts all objects map instaces to plain maps
(if (and (contains? (:features file) "fdata/objects-map")
(not (contains? (:features params) "fdata/objects-map")))
(update file :data feat.fdata/process-objects (partial into {}))
file)))))
;; --- COMMAND QUERY: get-file-fragment (by id)
@@ -356,8 +361,8 @@
[:map {:title "FileFragment"}
[:id ::sm/uuid]
[:file-id ::sm/uuid]
[:created-at ::dt/instant]
[:content any?]])
[:created-at ::ct/inst]
[:content ::sm/any]])
(def schema:get-file-fragment
[:map {:title "get-file-fragment"}
@@ -460,8 +465,42 @@
(:has-libraries row)))
;; --- COMMAND QUERY: get-library-usage
(declare get-library-usage)
(def schema:get-library-usage
[:map {:title "get-library-usage"}
[:file-id ::sm/uuid]])
:sample
(sv/defmethod ::get-library-usage
"Gets the number of files that use the specified library."
{::doc/added "2.10.0"
::sm/params schema:get-library-usage
::sm/result ::sm/int}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id]}]
(dm/with-open [conn (db/open pool)]
(check-read-permissions! pool profile-id file-id)
(get-library-usage conn file-id)))
(def ^:private sql:get-library-usage
"SELECT COUNT(*) AS used
FROM file_library_rel AS flr
JOIN file AS fl ON (flr.library_file_id = fl.id)
WHERE flr.library_file_id = ?::uuid
AND (fl.deleted_at IS NULL OR
fl.deleted_at > now())")
(defn- get-library-usage
[conn file-id]
(let [row (db/exec-one! conn [sql:get-library-usage file-id])]
{:used-in (:used row)}))
;; --- QUERY COMMAND: get-page
(defn- prune-objects
"Given the page data and the object-id returns the page data with all
other not needed objects removed from the `:objects` data
@@ -551,8 +590,34 @@
;; --- COMMAND QUERY: get-team-shared-files
(defn- components-and-variants
"Return a set with all the variant-ids, and a list of components, but with
only one component by variant"
[components]
(let [{:keys [variant-ids components]}
(reduce (fn [{:keys [variant-ids components] :as acc} {:keys [variant-id] :as component}]
(cond
(nil? variant-id)
{:variant-ids variant-ids :components (conj components component)}
(contains? variant-ids variant-id)
acc
:else
{:variant-ids (conj variant-ids variant-id) :components (conj components component)}))
{:variant-ids #{} :components []}
components)]
{:components components
:variant-ids variant-ids}))
;;coalesce(string_agg(flr.library_file_id::text, ','), '') as library_file_ids
(def ^:private sql:team-shared-files
"select f.id,
"with file_library_agg as (
select flr.file_id,
coalesce(array_agg(flr.library_file_id) filter (where flr.library_file_id is not null), '{}') as library_file_ids
from file_library_rel flr
group by flr.file_id
)
select f.id,
f.revn,
f.vern,
f.data,
@@ -565,10 +630,12 @@
f.version,
f.is_shared,
ft.media_id,
p.team_id
p.team_id,
fla.library_file_ids
from file as f
inner join project as p on (p.id = f.project_id)
left join file_thumbnail as ft on (ft.file_id = f.id and ft.revn = f.revn and ft.deleted_at is null)
left join file_library_agg as fla on fla.file_id = f.id
where f.is_shared = true
and f.deleted_at is null
and p.deleted_at is null
@@ -584,10 +651,13 @@
:sample (into [] (take limit sorted-assets))}))]
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
(let [load-objects (fn [component]
(ctf/load-component-objects data component))
components-sample (-> (assets-sample (ctkl/components data) 4)
(update :sample #(mapv load-objects %)))]
(let [load-objects (fn [component]
(ctf/load-component-objects data component))
comps-and-variants (components-and-variants (ctkl/components-seq data))
components (into {} (map (juxt :id identity) (:components comps-and-variants)))
components-sample (-> (assets-sample components 4)
(update :sample #(mapv load-objects %))
(assoc :variants-count (-> comps-and-variants :variant-ids count)))]
{:components components-sample
:media (assets-sample (:media data) 3)
:colors (assets-sample (:colors data) 3)
@@ -609,6 +679,8 @@
(dissoc :media-id)
(assoc :thumbnail-id media-id))
(dissoc row :media-id))))
(map (fn [row]
(update row :library-file-ids db/decode-pgarray #{})))
(map #(assoc % :library-summary (get-library-summary cfg %)))
(map #(dissoc % :data))))))
@@ -641,6 +713,7 @@
;; --- COMMAND QUERY: Files that use this File library
(def ^:private sql:library-using-files
"SELECT f.id,
f.name
@@ -713,6 +786,7 @@
;; --- COMMAND QUERY: get-file-summary
(defn- get-file-summary
[{:keys [::db/conn] :as cfg} {:keys [profile-id id project-id] :as params}]
(check-read-permissions! conn profile-id id)
@@ -730,11 +804,13 @@
(cfeat/check-file-features! (:features file)))
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
{:name (:name file)
:components-count (count (ctkl/components-seq (:data file)))
:graphics-count (count (get-in file [:data :media] []))
:colors-count (count (get-in file [:data :colors] []))
:typography-count (count (get-in file [:data :typographies] []))})))
(let [components-and-variants (components-and-variants (ctkl/components-seq (:data file)))]
{:name (:name file)
:components-count (-> components-and-variants :components count)
:variants-count (-> components-and-variants :variant-ids count)
:graphics-count (count (get-in file [:data :media] []))
:colors-count (count (get-in file [:data :colors] []))
:typography-count (count (get-in file [:data :typographies] []))}))))
(sv/defmethod ::get-file-summary
"Retrieve a file summary by its ID. Only authenticated users."
@@ -746,6 +822,7 @@
;; --- COMMAND QUERY: get-file-info
(defn- get-file-info
[{:keys [::db/conn] :as cfg} {:keys [id] :as params}]
(db/get* conn :file
@@ -770,7 +847,7 @@
[conn {:keys [id name]}]
(db/update! conn :file
{:name name
:modified-at (dt/now)}
:modified-at (ct/now)}
{:id id}
{::db/return-keys true}))
@@ -783,8 +860,8 @@
[:id ::sm/uuid]
[:project-id ::sm/uuid]
[:name [:string {:max 250}]]
[:created-at ::dt/instant]
[:modified-at ::dt/instant]]
[:created-at ::ct/inst]
[:modified-at ::ct/inst]]
::sm/params
[:map {:title "RenameFileParams"}
@@ -795,8 +872,8 @@
[:map {:title "SimplifiedFile"}
[:id ::sm/uuid]
[:name [:string {:max 250}]]
[:created-at ::dt/instant]
[:modified-at ::dt/instant]]
[:created-at ::ct/inst]
[:modified-at ::ct/inst]]
::db/transaction true}
[{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id id] :as params}]
@@ -839,7 +916,7 @@
(db/update! cfg :file
{:revn (inc (:revn file))
:data (blob/encode (:data file))
:modified-at (dt/now)
:modified-at (ct/now)
:has-media-trimmed false}
{:id file-id})
@@ -900,7 +977,7 @@
(db/delete! conn :file-library-rel {:library-file-id id})
(db/update! conn :file
{:is-shared false
:modified-at (dt/now)}
:modified-at (ct/now)}
{:id id})
(select-keys file [:id :name :is-shared]))
@@ -909,7 +986,7 @@
(let [file (assoc file :is-shared true)]
(db/update! conn :file
{:is-shared true
:modified-at (dt/now)}
:modified-at (ct/now)}
{:id id})
file)
@@ -945,7 +1022,7 @@
[conn team file-id]
(let [delay (ldel/get-deletion-delay team)
file (db/update! conn :file
{:deleted-at (dt/in-future delay)}
{:deleted-at (ct/in-future delay)}
{:id file-id}
{::db/return-keys [:id :name :is-shared :deleted-at
:project-id :created-at :modified-at]})]
@@ -1000,6 +1077,7 @@
[:library-id ::sm/uuid]])
(sv/defmethod ::link-file-to-library
"Link a file to a library. Returns the recursive list of libraries used by that library"
{::doc/added "1.17"
::webhooks/event? true
::sm/params schema:link-file-to-library}
@@ -1013,7 +1091,8 @@
(fn [{:keys [::db/conn]}]
(check-edition-permissions! conn profile-id file-id)
(check-edition-permissions! conn profile-id library-id)
(link-file-to-library conn params))))
(link-file-to-library conn params)
(bfc/get-libraries cfg [library-id]))))
;; --- MUTATION COMMAND: unlink-file-from-library
@@ -1043,7 +1122,7 @@
(defn update-sync
[conn {:keys [file-id library-id] :as params}]
(db/update! conn :file-library-rel
{:synced-at (dt/now)}
{:synced-at (ct/now)}
{:file-id file-id
:library-file-id library-id}
{::db/return-keys true}))
@@ -1068,14 +1147,14 @@
[conn {:keys [file-id date] :as params}]
(db/update! conn :file
{:ignore-sync-until date
:modified-at (dt/now)}
:modified-at (ct/now)}
{:id file-id}
{::db/return-keys true}))
(def ^:private schema:ignore-file-library-sync-status
[:map {:title "ignore-file-library-sync-status"}
[:file-id ::sm/uuid]
[:date ::dt/instant]])
[:date ::ct/inst]])
;; TODO: improve naming
(sv/defmethod ::ignore-file-library-sync-status

View File

@@ -7,9 +7,9 @@
(ns app.rpc.commands.files-create
(:require
[app.binfile.common :as bfc]
[app.common.data.macros :as dm]
[app.common.features :as cfeat]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.types.file :as ctf]
[app.config :as cf]
[app.db :as db]
@@ -23,13 +23,13 @@
[app.rpc.quotes :as quotes]
[app.util.pointer-map :as pmap]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.set :as set]))
(defn create-file-role!
[conn {:keys [file-id profile-id role]}]
(let [params {:file-id file-id
:profile-id profile-id}]
(->> (perms/assign-role-flags params role)
(db/insert! conn :file-profile-rel))))
@@ -41,9 +41,7 @@
:or {is-shared false revn 0 create-page true}
:as params}]
(dm/assert!
"expected a valid connection"
(db/connection? conn))
(assert (db/connection? conn) "expected a valid connection")
(binding [pmap/*tracked* (pmap/create-tracked)
cfeat/*current* features]
@@ -54,18 +52,18 @@
:is-shared is-shared
:features features
:ignore-sync-until ignore-sync-until
:modified-at modified-at
:created-at modified-at
:deleted-at deleted-at}
{:create-page create-page
:page-id page-id})
file (-> (bfc/insert-file! cfg file)
(bfc/decode-row))]
:page-id page-id})]
(bfc/insert-file! cfg file)
(->> (assoc params :file-id (:id file) :role :owner)
(create-file-role! conn))
(db/update! conn :project
{:modified-at (dt/now)}
{:modified-at (ct/now)}
{:id project-id})
file)))
@@ -114,14 +112,15 @@
;; FIXME: IMPORTANT: this code can have race conditions, because
;; we have no locks for updating team so, creating two files
;; concurrently can lead to lost team features updating
(when-let [features (-> features
(set/difference (:features team))
(set/difference cfeat/no-team-inheritable-features)
(not-empty))]
(let [features (->> features
(set/union (:features team))
(db/create-array conn "text"))]
(let [features (-> features
(set/union (:features team))
(set/difference cfeat/no-team-inheritable-features)
(into-array))]
(db/update! conn :team
{:features features}
{:id (:id team)}

View File

@@ -8,13 +8,16 @@
(:require
[app.binfile.common :as bfc]
[app.common.exceptions :as ex]
[app.common.files.migrations :as fmg]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.db.sql :as-alias sql]
[app.features.fdata :as feat.fdata]
[app.features.file-migrations :refer [reset-migrations!]]
[app.main :as-alias main]
[app.msgbus :as mbus]
[app.rpc :as-alias rpc]
@@ -24,12 +27,18 @@
[app.storage :as sto]
[app.util.blob :as blob]
[app.util.services :as sv]
[app.util.time :as dt]
[cuerdas.core :as str]))
(defn decode-row
[{:keys [migrations] :as row}]
(when row
(cond-> row
(some? migrations)
(assoc :migrations (db/decode-pgarray migrations)))))
(def sql:get-file-snapshots
"WITH changes AS (
SELECT id, label, revn, created_at, created_by, profile_id
SELECT id, label, revn, created_at, created_by, profile_id, locked_by
FROM file_change
WHERE file_id = ?
AND data IS NOT NULL
@@ -60,8 +69,8 @@
(defn- generate-snapshot-label
[]
(let [ts (-> (dt/now)
(dt/format-instant)
(let [ts (-> (ct/now)
(ct/format-inst)
(str/replace #"[T:\.]" "-")
(str/rtrim "Z"))]
(str "snapshot-" ts)))
@@ -74,18 +83,15 @@
(assert (#{:system :user :admin} created-by)
"expected valid keyword for created-by")
(let [conn
(db/get-connection cfg)
created-by
(let [created-by
(name created-by)
deleted-at
(cond
(= deleted-at :default)
(dt/plus (dt/now) (cf/get-deletion-delay))
(ct/plus (ct/now) (cf/get-deletion-delay))
(dt/instant? deleted-at)
(ct/inst? deleted-at)
deleted-at
:else
@@ -101,12 +107,15 @@
(blob/encode (:data file))
features
(db/encode-pgarray (:features file) conn "text")]
(into-array (:features file))
(l/debug :hint "creating file snapshot"
:file-id (str (:id file))
:id (str snapshot-id)
:label label)
migrations
(into-array (:migrations file))]
(l/dbg :hint "creating file snapshot"
:file-id (str (:id file))
:id (str snapshot-id)
:label label)
(db/insert! cfg :file-change
{:id snapshot-id
@@ -114,6 +123,7 @@
:data data
:version (:version file)
:features features
:migrations migrations
:profile-id profile-id
:file-id (:id file)
:label label
@@ -159,7 +169,17 @@
{:file-id file-id
:id snapshot-id}
{::db/for-share true})
(feat.fdata/resolve-file-data cfg))]
(feat.fdata/resolve-file-data cfg)
(decode-row))
;; If snapshot has tracked applied migrations, we reuse them,
;; if not we take a safest set of migrations as starting
;; point. This is because, at the time of implementing
;; snapshots, migrations were not taken into account so we
;; need to make this backward compatible in some way.
file (assoc file :migrations
(or (:migrations snapshot)
(fmg/generate-migrations-from-version 67)))]
(when-not snapshot
(ex/raise :type :not-found
@@ -180,12 +200,16 @@
:label (:label snapshot)
:snapshot-id (str (:id snapshot)))
;; If the file was already offloaded, on restring the snapshot
;; we are going to replace the file data, so we need to touch
;; the old referenced storage object and avoid possible leaks
;; If the file was already offloaded, on restoring the snapshot we
;; are going to replace the file data, so we need to touch the old
;; referenced storage object and avoid possible leaks
(when (feat.fdata/offloaded? file)
(sto/touch-object! storage (:data-ref-id file)))
;; In the same way, on reseting the file data, we need to restore
;; the applied migrations on the moment of taking the snapshot
(reset-migrations! conn file)
(db/update! conn :file
{:data (:data snapshot)
:revn (inc (:revn file))
@@ -253,14 +277,14 @@
:deleted-at nil}
{:id snapshot-id}
{::db/return-keys true})
(dissoc :data :features)))
(dissoc :data :features :migrations)))
(defn- get-snapshot
"Get a minimal snapshot from database and lock for update"
[conn id]
(db/get conn :file-change
{:id id}
{::sql/columns [:id :file-id :created-by :deleted-at]
{::sql/columns [:id :file-id :created-by :deleted-at :profile-id :locked-by]
::db/for-update true}))
(sv/defmethod ::update-file-snapshot
@@ -280,7 +304,7 @@
(defn- delete-file-snapshot!
[conn snapshot-id]
(db/update! conn :file-change
{:deleted-at (dt/now)}
{:deleted-at (ct/now)}
{:id snapshot-id}
{::db/return-keys false})
nil)
@@ -300,4 +324,111 @@
:snapshot-id id
:profile-id profile-id))
;; Check if version is locked by someone else
(when (and (:locked-by snapshot)
(not= (:locked-by snapshot) profile-id))
(ex/raise :type :validation
:code :snapshot-is-locked
:hint "Cannot delete a locked version"
:snapshot-id id
:profile-id profile-id
:locked-by (:locked-by snapshot)))
(delete-file-snapshot! conn id)))))
;;; Lock/unlock version endpoints
(def ^:private schema:lock-file-snapshot
[:map {:title "lock-file-snapshot"}
[:id ::sm/uuid]])
(defn- lock-file-snapshot!
[conn snapshot-id profile-id]
(db/update! conn :file-change
{:locked-by profile-id}
{:id snapshot-id}
{::db/return-keys false})
nil)
(sv/defmethod ::lock-file-snapshot
{::doc/added "1.20"
::sm/params schema:lock-file-snapshot}
[cfg {:keys [::rpc/profile-id id]}]
(db/tx-run! cfg
(fn [{:keys [::db/conn]}]
(let [snapshot (get-snapshot conn id)]
(files/check-edition-permissions! conn profile-id (:file-id snapshot))
(when (not= (:created-by snapshot) "user")
(ex/raise :type :validation
:code :system-snapshots-cant-be-locked
:hint "Only user-created versions can be locked"
:snapshot-id id
:profile-id profile-id))
;; Only the creator can lock their own version
(when (not= (:profile-id snapshot) profile-id)
(ex/raise :type :validation
:code :only-creator-can-lock
:hint "Only the version creator can lock it"
:snapshot-id id
:profile-id profile-id
:creator-id (:profile-id snapshot)))
;; Check if already locked
(when (:locked-by snapshot)
(ex/raise :type :validation
:code :snapshot-already-locked
:hint "Version is already locked"
:snapshot-id id
:profile-id profile-id
:locked-by (:locked-by snapshot)))
(lock-file-snapshot! conn id profile-id)))))
(def ^:private schema:unlock-file-snapshot
[:map {:title "unlock-file-snapshot"}
[:id ::sm/uuid]])
(defn- unlock-file-snapshot!
[conn snapshot-id]
(db/update! conn :file-change
{:locked-by nil}
{:id snapshot-id}
{::db/return-keys false})
nil)
(sv/defmethod ::unlock-file-snapshot
{::doc/added "1.20"
::sm/params schema:unlock-file-snapshot}
[cfg {:keys [::rpc/profile-id id]}]
(db/tx-run! cfg
(fn [{:keys [::db/conn]}]
(let [snapshot (get-snapshot conn id)]
(files/check-edition-permissions! conn profile-id (:file-id snapshot))
(when (not= (:created-by snapshot) "user")
(ex/raise :type :validation
:code :system-snapshots-cant-be-unlocked
:hint "Only user-created versions can be unlocked"
:snapshot-id id
:profile-id profile-id))
;; Only the creator can unlock their own version
(when (not= (:profile-id snapshot) profile-id)
(ex/raise :type :validation
:code :only-creator-can-unlock
:hint "Only the version creator can unlock it"
:snapshot-id id
:profile-id profile-id
:creator-id (:profile-id snapshot)))
;; Check if not locked
(when (not (:locked-by snapshot))
(ex/raise :type :validation
:code :snapshot-not-locked
:hint "Version is not locked"
:snapshot-id id
:profile-id profile-id))
(unlock-file-snapshot! conn id)))))

View File

@@ -10,6 +10,7 @@
[app.common.features :as cfeat]
[app.common.files.changes :as cpc]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@@ -27,7 +28,6 @@
[app.util.blob :as blob]
[app.util.pointer-map :as pmap]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.set :as set]))
;; --- MUTATION COMMAND: create-temp-file
@@ -72,17 +72,16 @@
params
(-> params
(assoc :profile-id profile-id)
(assoc :deleted-at (dt/in-future {:days 1}))
(assoc :deleted-at (ct/in-future {:days 1}))
(assoc :features features))]
(files.create/create-file cfg params)))
;; --- MUTATION COMMAND: update-temp-file
(def ^:private schema:update-temp-file
[:map {:title "update-temp-file"}
[:changes [:vector ::cpc/change]]
[:changes [:vector cpc/schema:change]]
[:revn [::sm/int {:min 0}]]
[:session-id ::sm/uuid]
[:id ::sm/uuid]])
@@ -97,7 +96,7 @@
{:id (uuid/next)
:session-id session-id
:profile-id profile-id
:created-at (dt/now)
:created-at (ct/now)
:file-id id
:revn revn
:data nil

View File

@@ -13,6 +13,7 @@
[app.common.geom.shapes :as gsh]
[app.common.schema :as sm]
[app.common.thumbnails :as thc]
[app.common.time :as ct]
[app.common.types.shape-tree :as ctt]
[app.config :as cf]
[app.db :as db]
@@ -30,13 +31,12 @@
[app.storage :as sto]
[app.util.pointer-map :as pmap]
[app.util.services :as sv]
[app.util.time :as dt]
[cuerdas.core :as str]))
;; --- FEATURES
(def long-cache-duration
(dt/duration {:days 7}))
(ct/duration {:days 7}))
;; --- COMMAND QUERY: get-file-object-thumbnails
@@ -185,7 +185,7 @@
[:map {:title "PartialFile"}
[:id ::sm/uuid]
[:revn {:min 0} ::sm/int]
[:page :any]])
[:page [:map-of :keyword ::sm/any]]])
(sv/defmethod ::get-file-data-for-thumbnail
"Retrieves the data for generate the thumbnail of the file. Used
@@ -247,7 +247,7 @@
(defn- create-file-object-thumbnail!
[{:keys [::sto/storage] :as cfg} file object-id media tag]
(let [file-id (:id file)
timestamp (dt/now)
timestamp (ct/now)
media (persist-thumbnail! storage media timestamp)
[th1 th2] (db/tx-run! cfg (fn [{:keys [::db/conn]}]
(let [th1 (db/exec-one! conn [sql:get-file-object-thumbnail file-id object-id tag])
@@ -271,7 +271,7 @@
[:map {:title "create-file-object-thumbnail"}
[:file-id ::sm/uuid]
[:object-id [:string {:max 250}]]
[:media ::media/upload]
[:media media/schema:upload]
[:tag {:optional true} [:string {:max 50}]]])
(sv/defmethod ::create-file-object-thumbnail
@@ -302,7 +302,7 @@
{::sql/for-update true})]
(sto/touch-object! storage media-id)
(db/update! conn :file-tagged-object-thumbnail
{:deleted-at (dt/now)}
{:deleted-at (ct/now)}
{:file-id file-id
:object-id object-id
:tag tag})))
@@ -338,7 +338,7 @@
hash (sto/calculate-hash path)
data (-> (sto/content path)
(sto/wrap-with-hash hash))
tnow (dt/now)
tnow (ct/now)
media (sto/put-object! storage
{::sto/content data
::sto/deduplicate? true
@@ -381,7 +381,7 @@
[:map {:title "create-file-thumbnail"}
[:file-id ::sm/uuid]
[:revn ::sm/int]
[:media ::media/upload]])
[:media media/schema:upload]])
(sv/defmethod ::create-file-thumbnail
"Creates or updates the file thumbnail. Mainly used for paint the

View File

@@ -15,6 +15,7 @@
[app.common.files.validate :as val]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@@ -36,10 +37,7 @@
[app.util.blob :as blob]
[app.util.pointer-map :as pmap]
[app.util.services :as sv]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.set :as set]
[promesa.exec :as px]))
[clojure.set :as set]))
(declare ^:private get-lagged-changes)
(declare ^:private send-notifications!)
@@ -64,10 +62,10 @@
[:revn {:min 0} ::sm/int]
[:vern {:min 0} ::sm/int]
[:features {:optional true} ::cfeat/features]
[:changes {:optional true} [:vector ::cpc/change]]
[:changes {:optional true} [:vector cpc/schema:change]]
[:changes-with-metadata {:optional true}
[:vector [:map
[:changes [:vector ::cpc/change]]
[:changes [:vector cpc/schema:change]]
[:hint-origin {:optional true} :keyword]
[:hint-events {:optional true} [:vector [:string {:max 250}]]]]]]
[:skip-validate {:optional true} ::sm/boolean]])
@@ -76,7 +74,7 @@
schema:update-file-result
[:vector {:title "update-file-result"}
[:map
[:changes [:vector ::cpc/change]]
[:changes [:vector cpc/schema:change]]
[:file-id ::sm/uuid]
[:id ::sm/uuid]
[:revn {:min 0} ::sm/int]
@@ -123,7 +121,7 @@
[:update-file/global]]
::webhooks/event? true
::webhooks/batch-timeout (dt/duration "2m")
::webhooks/batch-timeout (ct/duration "2m")
::webhooks/batch-key (webhooks/key-fn ::rpc/profile-id :id)
::sm/params schema:update-file
@@ -156,10 +154,9 @@
(assoc :file file)
(assoc :changes changes))
cfg (assoc cfg ::timestamp (dt/now))
tpoint (dt/tpoint)]
cfg (assoc cfg ::timestamp (ct/now))
tpoint (ct/tpoint)]
(when (not= (:vern params)
(:vern file))
@@ -183,15 +180,15 @@
(set/difference (:features team))
(set/difference cfeat/no-team-inheritable-features)
(not-empty))]
(let [features (->> features
(set/union (:features team))
(db/create-array conn "text"))]
(let [features (-> features
(set/union (:features team))
(set/difference cfeat/no-team-inheritable-features)
(into-array))]
(db/update! conn :team
{:features features}
{:id (:id team)}
{::db/return-keys false})))
(mtx/run! metrics {:id :update-file-changes :inc (count changes)})
(binding [l/*context* (some-> (meta params)
@@ -199,7 +196,7 @@
(errors/request->context))]
(-> (update-file* cfg params)
(rph/with-defer #(let [elapsed (tpoint)]
(l/trace :hint "update-file" :time (dt/format-duration elapsed))))))))))
(l/trace :hint "update-file" :time (ct/format-duration elapsed))))))))))
(defn- update-file*
"Internal function, part of the update-file process, that encapsulates
@@ -209,7 +206,7 @@
Follow the inner implementation to `update-file-data!` function.
Only intended for internal use on this module."
[{:keys [::db/conn ::wrk/executor ::timestamp] :as cfg}
[{:keys [::db/conn ::timestamp] :as cfg}
{:keys [profile-id file team features changes session-id skip-validate] :as params}]
(let [;; Retrieve the file data
@@ -222,15 +219,11 @@
;; We create a new lexycal scope for clearly delimit the result of
;; executing this update file operation and all its side effects
(let [file (px/invoke! executor
(fn []
;; Process the file data on separated thread for avoid to do
;; the CPU intensive operation on vthread.
(binding [cfeat/*current* features
cfeat/*previous* (:features file)]
(update-file-data! cfg file
process-changes-and-validate
changes skip-validate))))]
(let [file (binding [cfeat/*current* features
cfeat/*previous* (:features file)]
(update-file-data! cfg file
process-changes-and-validate
changes skip-validate))]
(feat.fmigr/upsert-migrations! conn file)
(persist-file! cfg file)
@@ -244,8 +237,8 @@
:created-at timestamp
:updated-at timestamp
:deleted-at (if (::snapshot-data file)
(dt/plus timestamp (ldel/get-deletion-delay team))
(dt/plus timestamp (dt/duration {:hours 1})))
(ct/plus timestamp (ldel/get-deletion-delay team))
(ct/plus timestamp (ct/duration {:hours 1})))
:file-id (:id file)
:revn (:revn file)
:version (:version file)
@@ -306,7 +299,7 @@
[{:keys [::db/conn ::timestamp]} file]
(let [;; The timestamp can be nil because this function is also
;; intended to be used outside of this module
modified-at (or timestamp (dt/now))]
modified-at (or timestamp (ct/now))]
(db/update! conn :project
{:modified-at modified-at}
@@ -360,7 +353,7 @@
;; TODO: reuse operations if file is migrated
;; TODO: move encoding to a separated thread
file (if (take-snapshot? file)
(let [tpoint (dt/tpoint)
(let [tpoint (ct/tpoint)
snapshot (-> (:data file)
(feat.fdata/process-pointers deref)
(feat.fdata/process-objects (partial into {}))
@@ -372,7 +365,7 @@
:file-id (str (:id file))
:revn (:revn file)
:label label
:elapsed (dt/format-duration elapsed))
:elapsed (ct/format-duration elapsed))
(-> file
(assoc ::snapshot-data snapshot)
@@ -408,7 +401,6 @@
(not skip-validate))
(bfc/get-resolved-file-libraries cfg file))
;; The main purpose of this atom is provide a contextual state
;; for the changes subsystem where optionally some hints can
;; be provided for the changes processing. Right now we are
@@ -452,11 +444,11 @@
(when (contains? cf/flags :auto-file-snapshot)
(let [freq (or (cf/get :auto-file-snapshot-every) 20)
timeout (or (cf/get :auto-file-snapshot-timeout)
(dt/duration {:hours 1}))]
(ct/duration {:hours 1}))]
(or (= 1 freq)
(zero? (mod revn freq))
(> (inst-ms (dt/diff modified-at (dt/now)))
(> (inst-ms (ct/diff modified-at (ct/now)))
(inst-ms timeout))))))
(def ^:private sql:lagged-changes
@@ -496,5 +488,5 @@
:file-id (:id file)
:session-id session-id
:revn (:revn file)
:modified-at (dt/now)
:modified-at (ct/now)
:changes lchanges}))))

View File

@@ -9,6 +9,7 @@
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.db :as db]
[app.db.sql :as-alias sql]
@@ -25,10 +26,7 @@
[app.rpc.helpers :as rph]
[app.rpc.quotes :as quotes]
[app.storage :as sto]
[app.util.services :as sv]
[app.util.time :as dt]
[app.worker :as-alias wrk]
[promesa.exec :as px]))
[app.util.services :as sv]))
(def valid-weight #{100 200 300 400 500 600 700 800 900 950})
(def valid-style #{"normal" "italic"})
@@ -37,14 +35,13 @@
(def ^:private
schema:get-font-variants
[:schema {:title "get-font-variants"}
[:and
[:map
[:team-id {:optional true} ::sm/uuid]
[:file-id {:optional true} ::sm/uuid]
[:project-id {:optional true} ::sm/uuid]
[:share-id {:optional true} ::sm/uuid]]
[::sm/contains-any #{:team-id :file-id :project-id}]]])
[:and
[:map {:title "get-font-variants"}
[:team-id {:optional true} ::sm/uuid]
[:file-id {:optional true} ::sm/uuid]
[:project-id {:optional true} ::sm/uuid]
[:share-id {:optional true} ::sm/uuid]]
[::sm/contains-any #{:team-id :file-id :project-id}]])
(sv/defmethod ::get-font-variants
{::doc/added "1.18"
@@ -106,7 +103,7 @@
(create-font-variant cfg (assoc params :profile-id profile-id)))))
(defn create-font-variant
[{:keys [::sto/storage ::db/conn ::wrk/executor]} {:keys [data] :as params}]
[{:keys [::sto/storage ::db/conn]} {:keys [data] :as params}]
(letfn [(generate-missing! [data]
(let [data (media/run {:cmd :generate-fonts :input data})]
(when (and (not (contains? data "font/otf"))
@@ -124,7 +121,7 @@
content (-> (sto/content resource)
(sto/wrap-with-hash hash))]
{::sto/content content
::sto/touched-at (dt/now)
::sto/touched-at (ct/now)
::sto/deduplicate? true
:content-type mtype
:bucket "team-font-variant"})))
@@ -158,7 +155,7 @@
:otf-file-id (:id otf)
:ttf-file-id (:id ttf)}))]
(let [data (px/invoke! executor (partial generate-missing! data))
(let [data (generate-missing! data)
assets (persist-fonts-files! data)
result (insert-font-variant! assets)]
(vary-meta result assoc ::audit/replace-props (update params :data (comp vec keys))))))
@@ -217,7 +214,7 @@
{::sql/for-update true})
delay (ldel/get-deletion-delay team)
tnow (dt/in-future delay)]
tnow (ct/in-future delay)]
(teams/check-edition-permissions! (:permissions team))
@@ -261,7 +258,7 @@
(teams/check-edition-permissions! (:permissions team))
(db/update! conn :team-font-variant
{:deleted-at (dt/in-future delay)}
{:deleted-at (ct/in-future delay)}
{:id (:id variant)}
{::db/return-keys false})

View File

@@ -13,6 +13,7 @@
[app.common.exceptions :as ex]
[app.common.features :as cfeat]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@@ -27,10 +28,7 @@
[app.setup :as-alias setup]
[app.setup.templates :as tmpl]
[app.storage.tmp :as tmp]
[app.util.services :as sv]
[app.util.time :as dt]
[app.worker :as-alias wrk]
[promesa.exec :as px]))
[app.util.services :as sv]))
;; --- COMMAND: Duplicate File
@@ -104,7 +102,7 @@
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"])
(binding [bfc/*state* (volatile! {:index {file-id (uuid/next)}})]
(duplicate-file (assoc cfg ::bfc/timestamp (dt/now))
(duplicate-file (assoc cfg ::bfc/timestamp (ct/now))
(-> params
(assoc :profile-id profile-id)
(assoc :reset-shared-flag true)))))))
@@ -164,7 +162,7 @@
(db/tx-run! cfg (fn [cfg]
;; Defer all constraints
(db/exec-one! cfg ["SET CONSTRAINTS ALL DEFERRED"])
(-> (assoc cfg ::bfc/timestamp (dt/now))
(-> (assoc cfg ::bfc/timestamp (ct/now))
(duplicate-project (assoc params :profile-id profile-id))))))
(defn duplicate-team
@@ -313,15 +311,14 @@
;; 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}))
(loop [project-ids (into (list project-id) source)
modified-at (ct/now)]
(when-let [project-id (first project-ids)]
(db/update! conn :project
{:modified-at modified-at}
{:id project-id})
(recur (rest project-ids)
(ct/plus modified-at 10))))
nil))
@@ -396,12 +393,7 @@
;; --- COMMAND: Clone Template
(defn clone-template
[{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [project-id profile-id] :as params} template]
;; NOTE: the importation process performs some operations
;; that are not very friendly with virtual threads, and for
;; avoid unexpected blocking of other concurrent operations
;; we dispatch that operation to a dedicated executor.
[{:keys [::db/pool] :as cfg} {:keys [project-id profile-id] :as params} template]
(let [template (tmp/tempfile-from template
:prefix "penpot.template."
:suffix ""
@@ -419,13 +411,13 @@
(assoc ::bfc/features (cfeat/get-team-enabled-features cf/flags team)))
result (if (= format :binfile-v3)
(px/invoke! executor (partial bf.v3/import-files! cfg))
(px/invoke! executor (partial bf.v1/import-files! cfg)))]
(bf.v3/import-files! cfg)
(bf.v1/import-files! cfg))]
(db/tx-run! cfg
(fn [{:keys [::db/conn] :as cfg}]
(db/update! conn :project
{:modified-at (dt/now)}
{:modified-at (ct/now)}
{:id project-id}
{::db/return-keys false})

View File

@@ -10,6 +10,7 @@
[app.common.exceptions :as ex]
[app.common.media :as cm]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@@ -23,11 +24,8 @@
[app.storage :as sto]
[app.storage.tmp :as tmp]
[app.util.services :as sv]
[app.util.time :as dt]
[app.worker :as-alias wrk]
[cuerdas.core :as str]
[datoteka.io :as io]
[promesa.exec :as px]))
[datoteka.io :as io]))
(def default-max-file-size
(* 1024 1024 10)) ; 10 MiB
@@ -48,7 +46,7 @@
[:file-id ::sm/uuid]
[:is-local ::sm/boolean]
[:name [:string {:max 250}]]
[:content ::media/upload]])
[:content media/schema:upload]])
(sv/defmethod ::upload-file-media-object
{::doc/added "1.17"
@@ -67,7 +65,7 @@
mobj (create-file-media-object cfg params)]
(db/update! conn :file
{:modified-at (dt/now)
{:modified-at (ct/now)
:has-media-trimmed false}
{:id file-id}
{::db/return-keys false})
@@ -153,9 +151,9 @@
(assoc ::image (process-main-image info)))))
(defn- create-file-media-object
[{:keys [::sto/storage ::db/conn ::wrk/executor] :as cfg}
[{:keys [::sto/storage ::db/conn] :as cfg}
{:keys [id file-id is-local name content]}]
(let [result (px/invoke! executor (partial process-image content))
(let [result (process-image content)
image (sto/put-object! storage (::image result))
thumb (when-let [params (::thumb result)]
(sto/put-object! storage params))]
@@ -192,7 +190,7 @@
mobj (create-file-media-object-from-url cfg (assoc params :profile-id profile-id))]
(db/update! pool :file
{:modified-at (dt/now)
{:modified-at (ct/now)
:has-media-trimmed false}
{:id file-id}
{::db/return-keys false})

View File

@@ -10,6 +10,7 @@
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.types.plugins :refer [schema:plugin-registry]]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -28,18 +29,14 @@
[app.storage :as sto]
[app.tokens :as tokens]
[app.util.services :as sv]
[app.util.time :as dt]
[app.worker :as wrk]
[cuerdas.core :as str]
[promesa.exec :as px]))
[cuerdas.core :as str]))
(declare check-profile-existence!)
(declare decode-row)
(declare derive-password)
(declare filter-props)
(declare get-profile)
(declare strip-private-attrs)
(declare verify-password)
(def schema:props-notifications
[:map {:title "props-notifications"}
@@ -70,8 +67,8 @@
[:is-blocked {:optional true} ::sm/boolean]
[:is-demo {:optional true} ::sm/boolean]
[:is-muted {:optional true} ::sm/boolean]
[:created-at {:optional true} ::sm/inst]
[:modified-at {:optional true} ::sm/inst]
[:created-at {:optional true} ::ct/inst]
[:modified-at {:optional true} ::ct/inst]
[:default-project-id {:optional true} ::sm/uuid]
[:default-team-id {:optional true} ::sm/uuid]
[:props {:optional true} schema:props]])
@@ -131,9 +128,7 @@
;; NOTE: we need to retrieve the profile independently if we use
;; it or not for explicit locking and avoid concurrent updates of
;; the same row/object.
(let [profile (-> (db/get-by-id conn :profile profile-id ::sql/for-update true)
(decode-row))
(let [profile (get-profile conn profile-id ::db/for-update true)
;; Update the profile map with direct params
profile (-> profile
(assoc :fullname fullname)
@@ -143,9 +138,9 @@
(db/update! conn :profile
{:fullname fullname
:lang lang
:theme theme
:props (db/tjson (:props profile))}
{:id profile-id})
:theme theme}
{:id profile-id}
{::db/return-keys false})
(-> profile
(strip-private-attrs)
@@ -194,7 +189,7 @@
[{:keys [::db/conn] :as cfg} {:keys [profile-id old-password] :as params}]
(let [profile (db/get-by-id conn :profile profile-id ::sql/for-update true)]
(when (and (not= (:password profile) "!")
(not (:valid (verify-password cfg old-password (:password profile)))))
(not (:valid (auth/verify-password old-password (:password profile)))))
(ex/raise :type :validation
:code :old-password-not-match))
profile))
@@ -203,7 +198,7 @@
[{:keys [::db/conn] :as cfg} {:keys [id password] :as profile}]
(when-not (db/read-only? conn)
(db/update! conn :profile
{:password (derive-password cfg password)}
{:password (auth/derive-password password)}
{:id id})
nil))
@@ -228,21 +223,22 @@
(defn- update-notifications!
[{:keys [::db/conn] :as cfg} {:keys [profile-id dashboard-comments email-comments email-invites]}]
(let [profile (get-profile conn profile-id)
(let [profile
(get-profile conn profile-id ::db/for-update true)
notifications
{:dashboard-comments dashboard-comments
:email-comments email-comments
:email-invites email-invites}]
:email-invites email-invites}
(db/update!
conn :profile
{:props
(-> (:props profile)
(assoc :notifications notifications)
(db/tjson))}
{:id (:id profile)})
props
(-> (get profile :props)
(assoc :notifications notifications))]
(db/update! conn :profile
{:props (db/tjson props)}
{:id profile-id}
{::db/return-keys false})
nil))
;; --- MUTATION: Update Photo
@@ -253,7 +249,7 @@
(def ^:private
schema:update-profile-photo
[:map {:title "update-profile-photo"}
[:file ::media/upload]])
[:file media/schema:upload]])
(sv/defmethod ::update-profile-photo
{:doc/added "1.1"
@@ -304,12 +300,11 @@
:content-type (:mtype thumb)}))
(defn upload-photo
[{:keys [::sto/storage ::wrk/executor] :as cfg} {:keys [file] :as params}]
[{:keys [::sto/storage] :as cfg} {:keys [file] :as params}]
(let [params (-> cfg
(assoc ::climit/id [[:process-image/by-profile (:profile-id params)]
[:process-image/global]])
(assoc ::climit/label "upload-photo")
(assoc ::climit/executor executor)
(climit/invoke! generate-thumbnail! file))]
(sto/put-object! storage params)))
@@ -352,13 +347,13 @@
[{:keys [::db/conn] :as cfg} {:keys [profile email] :as params}]
(let [token (tokens/generate (::setup/props cfg)
{:iss :change-email
:exp (dt/in-future "15m")
:exp (ct/in-future "15m")
:profile-id (:id profile)
:email email})
ptoken (tokens/generate (::setup/props cfg)
{:iss :profile-identity
:profile-id (:id profile)
:exp (dt/in-future {:days 30})})]
:exp (ct/in-future {:days 30})})]
(when (not= email (:email profile))
(check-profile-existence! conn params))
@@ -411,7 +406,7 @@
(defn update-profile-props
[{:keys [::db/conn] :as cfg} profile-id props]
(let [profile (get-profile conn profile-id ::sql/for-update true)
(let [profile (get-profile conn profile-id ::db/for-update true)
props (reduce-kv (fn [props k v]
;; We don't accept namespaced keys
(if (simple-ident? k)
@@ -424,16 +419,17 @@
(db/update! conn :profile
{:props (db/tjson props)}
{:id profile-id})
{:id profile-id}
{::db/return-keys false})
(filter-props props)))
(sv/defmethod ::update-profile-props
{::doc/added "1.0"
::sm/params schema:update-profile-props}
::sm/params schema:update-profile-props
::db/transaction true}
[cfg {:keys [::rpc/profile-id props]}]
(db/tx-run! cfg (fn [cfg]
(update-profile-props cfg profile-id props))))
(update-profile-props cfg profile-id props))
;; --- MUTATION: Delete Profile
@@ -444,7 +440,7 @@
::db/transaction true}
[{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id] :as params}]
(let [teams (get-owned-teams conn profile-id)
deleted-at (dt/now)]
deleted-at (ct/now)]
;; If we found owned teams with participants, we don't allow
;; delete profile until the user properly transfer ownership or
@@ -471,6 +467,26 @@
(-> (rph/wrap nil)
(rph/with-transform (session/delete-fn cfg)))))
(def sql:get-subscription-editors
"SELECT DISTINCT
p.id,
p.fullname AS name,
p.email AS email
FROM team_profile_rel AS tpr1
JOIN team_profile_rel AS tpr2
ON (tpr1.team_id = tpr2.team_id)
JOIN profile AS p
ON (tpr2.profile_id = p.id)
WHERE tpr1.profile_id = ?
AND tpr1.is_owner IS true
AND tpr2.can_edit IS true")
(sv/defmethod ::get-subscription-usage
{::doc/added "2.9"}
[cfg {:keys [::rpc/profile-id]}]
(let [editors (db/exec! cfg [sql:get-subscription-editors profile-id])]
{:editors editors}))
;; --- HELPERS
(def sql:owned-teams
@@ -528,15 +544,6 @@
[props]
(into {} (filter (fn [[k _]] (simple-ident? k))) props))
(defn derive-password
[{:keys [::wrk/executor]} password]
(when password
(px/invoke! executor (partial auth/derive-password password))))
(defn verify-password
[{:keys [::wrk/executor]} password password-data]
(px/invoke! executor (partial auth/verify-password password password-data)))
(defn decode-row
[{:keys [props] :as row}]
(cond-> row

View File

@@ -9,6 +9,7 @@
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.db :as db]
[app.db.sql :as-alias sql]
[app.features.logical-deletion :as ldel]
@@ -21,7 +22,6 @@
[app.rpc.permissions :as perms]
[app.rpc.quotes :as quotes]
[app.util.services :as sv]
[app.util.time :as dt]
[app.worker :as wrk]))
;; --- Check Project Permissions
@@ -218,7 +218,7 @@
(sv/defmethod ::update-project-pin
{::doc/added "1.18"
::sm/params schema:update-project-pin
::webhooks/batch-timeout (dt/duration "5s")
::webhooks/batch-timeout (ct/duration "5s")
::webhooks/batch-key (webhooks/key-fn ::rpc/profile-id :id)
::webhooks/event? true
::db/transaction true}
@@ -257,7 +257,7 @@
[conn team project-id]
(let [delay (ldel/get-deletion-delay team)
project (db/update! conn :project
{:deleted-at (dt/in-future delay)}
{:deleted-at (ct/in-future delay)}
{:id project-id}
{::db/return-keys true})]

View File

@@ -11,7 +11,8 @@
[app.common.exceptions :as ex]
[app.common.features :as cfeat]
[app.common.schema :as sm]
[app.common.types.team :as tt]
[app.common.time :as ct]
[app.common.types.team :as types.team]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@@ -30,7 +31,6 @@
[app.setup :as-alias setup]
[app.storage :as sto]
[app.util.services :as sv]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.set :as set]))
@@ -78,9 +78,10 @@
(defn decode-row
[{:keys [features subscription] :as row}]
(cond-> row
(some? features) (assoc :features (db/decode-pgarray features #{}))
(some? subscription) (assoc :subscription (db/decode-transit-pgobject subscription))))
(when row
(cond-> row
(some? features) (assoc :features (db/decode-pgarray features #{}))
(some? subscription) (assoc :subscription (db/decode-transit-pgobject subscription)))))
;; FIXME: move
@@ -461,11 +462,12 @@
;; --- COMMAND QUERY: get-team-info
(defn- get-team-info
(defn get-team-info
[{:keys [::db/conn] :as cfg} {:keys [id] :as params}]
(db/get* conn :team
{:id id}
{::sql/columns [:id :is-default]}))
(-> (db/get* conn :team
{:id id}
{::sql/columns [:id :is-default :features]})
(decode-row)))
(sv/defmethod ::get-team-info
"Retrieve minimal team info by its ID."
@@ -501,7 +503,7 @@
(let [features (-> (cfeat/get-enabled-features cf/flags)
(set/difference cfeat/frontend-only-features)
(cfeat/check-client-features! (:features params)))
(set/difference cfeat/no-team-inheritable-features))
params (-> params
(assoc :profile-id profile-id)
(assoc :features features))
@@ -627,7 +629,7 @@
;; assign owner role to new profile
(db/update! conn :team-profile-rel
(get tt/permissions-for-role :owner)
(get types.team/permissions-for-role :owner)
{:team-id id :profile-id reassign-to}))
;; and finally, if all other conditions does not match and the
@@ -664,7 +666,7 @@
(let [delay (ldel/get-deletion-delay team)
team (db/update! conn :team
{:deleted-at (dt/in-future delay)}
{:deleted-at (ct/in-future delay)}
{:id id}
{::db/return-keys true})]
@@ -740,7 +742,7 @@
:team-id team-id
:role role})
(let [params (get tt/permissions-for-role role)]
(let [params (get types.team/permissions-for-role role)]
;; Only allow single owner on team
(when (= role :owner)
(db/update! conn :team-profile-rel
@@ -758,7 +760,7 @@
[:map {:title "update-team-member-role"}
[:team-id ::sm/uuid]
[:member-id ::sm/uuid]
[:role ::tt/role]])
[:role types.team/schema:role]])
(sv/defmethod ::update-team-member-role
{::doc/added "1.17"
@@ -808,7 +810,7 @@
(def ^:private schema:update-team-photo
[:map {:title "update-team-photo"}
[:team-id ::sm/uuid]
[:file ::media/upload]])
[:file media/schema:upload]])
(sv/defmethod ::update-team-photo
{::doc/added "1.17"

View File

@@ -12,6 +12,7 @@
[app.common.features :as cfeat]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.types.team :as types.team]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -29,7 +30,6 @@
[app.setup :as-alias setup]
[app.tokens :as tokens]
[app.util.services :as sv]
[app.util.time :as dt]
[cuerdas.core :as str]))
;; --- Mutation: Create Team Invitation
@@ -62,7 +62,7 @@
(tokens/generate (::setup/props cfg)
{:iss :profile-identity
:profile-id profile-id
:exp (dt/in-future {:days 30})}))
:exp (ct/in-future {:days 30})}))
(def ^:private schema:create-invitation
[:map {:title "params:create-invitation"}
@@ -75,7 +75,7 @@
[:map
[:id ::sm/uuid]
[:fullname :string]]]
[:role ::types.team/role]
[:role types.team/schema:role]
[:email ::sm/email]])
(def ^:private check-create-invitation-params
@@ -126,7 +126,7 @@
(teams/check-email-spam conn email true)
(let [id (uuid/next)
expire (dt/in-future "168h") ;; 7 days
expire (ct/in-future "168h") ;; 7 days
invitation (db/exec-one! conn [sql:upsert-team-invitation id
(:id team) (str/lower email)
(:id profile)
@@ -224,62 +224,112 @@
(def ^:private xf:map-email (map :email))
(defn- create-team-invitations
[{:keys [::db/conn] :as cfg} {:keys [profile team role emails] :as params}]
(let [emails (set emails)
"Unified function to handle both create and resend team invitations.
Accepts either:
- emails (set) + role (single role for all emails)
- invitations (vector of {:email :role} maps)"
[{:keys [::db/conn] :as cfg} {:keys [profile team role emails invitations] :as params}]
(let [;; Normalize input to a consistent format: [{:email :role}]
invitation-data (cond
;; Case 1: emails + single role (create invitations style)
(and emails role)
(map (fn [email] {:email email :role role}) emails)
join-requests (->> (get-valid-access-request-profiles conn (:id team))
(d/index-by :email))
;; Case 2: invitations with individual roles (resend invitations style)
(some? invitations)
invitations
team-members (into #{} xf:map-email
(teams/get-team-members conn (:id team)))
:else
(throw (ex-info "Invalid parameters: must provide either emails+role or invitations" {})))
invitations (into #{}
(comp
;; We don't re-send inviation to
;; already existing members
(remove team-members)
invitation-emails (into #{} (map :email) invitation-data)
join-requests (->> (get-valid-access-request-profiles conn (:id team))
(d/index-by :email))
team-members (into #{} xf:map-email
(teams/get-team-members conn (:id team)))
invitations (into #{}
(comp
;; We don't re-send invitations to
;; already existing members
(remove #(contains? team-members (:email %)))
;; We don't send invitations to
;; join-requested members
(remove join-requests)
(map (fn [email] (assoc params :email email)))
(keep (partial create-invitation cfg)))
emails)]
(remove #(contains? join-requests (:email %)))
(map (fn [{:keys [email role]}]
(create-invitation cfg
(-> params
(assoc :email email)
(assoc :role role)))))
(remove nil?))
invitation-data)]
;; For requested invitations, do not send invitation emails, add
;; the user directly to the team
(->> join-requests
(filter #(contains? emails (key %)))
(map val)
(run! (partial add-member-to-team conn profile team role)))
(filter #(contains? invitation-emails (key %)))
(map (fn [[email member]]
(let [role (:role (first (filter #(= (:email %) email) invitation-data)))]
(add-member-to-team conn profile team role member))))
(doall))
invitations))
(def ^:private schema:create-team-invitations
[:map {:title "create-team-invitations"}
[:team-id ::sm/uuid]
[:role ::types.team/role]
[:emails [::sm/set ::sm/email]]])
[:and
[:map {:title "create-team-invitations"}
[:team-id ::sm/uuid]
;; Support both formats:
;; 1. emails (set) + role (single role for all)
;; 2. invitations (vector of {:email :role} maps)
[:emails {:optional true} [::sm/set ::sm/email]]
[:role {:optional true} types.team/schema:role]
[:invitations {:optional true} [:vector [:map
[:email ::sm/email]
[:role types.team/schema:role]]]]]
;; Ensure exactly one format is provided
[:fn (fn [params]
(let [has-emails-role (and (contains? params :emails)
(contains? params :role))
has-invitations (contains? params :invitations)]
(and (or has-emails-role has-invitations)
(not (and has-emails-role has-invitations)))))]])
(def ^:private max-invitations-by-request-threshold
"The number of invitations can be sent in a single rpc request"
25)
(sv/defmethod ::create-team-invitations
"A rpc call that allow to send a single or multiple invitations to
join the team."
"A rpc call that allows to send single or multiple invitations to join the team.
Supports two parameter formats:
1. emails (set) + role (single role for all emails)
2. invitations (vector of {:email :role} maps for individual roles)"
{::doc/added "1.17"
::doc/module :teams
::sm/params schema:create-team-invitations}
[cfg {:keys [::rpc/profile-id team-id emails] :as params}]
[cfg {:keys [::rpc/profile-id team-id role emails] :as params}]
(let [perms (teams/get-permissions cfg profile-id team-id)
profile (db/get-by-id cfg :profile profile-id)
emails (into #{} (map profile/clean-email) emails)]
;; Determine which format is being used
using-emails-format? (and emails role)
;; Handle both parameter formats
emails (if using-emails-format?
(into #{} (map profile/clean-email) emails)
#{})
;; Calculate total invitation count for both formats
invitation-count (if using-emails-format?
(count emails)
(count (:invitations params)))]
(when-not (:is-admin perms)
(ex/raise :type :validation
:code :insufficient-permissions))
(when (> (count emails) max-invitations-by-request-threshold)
(when (> invitation-count max-invitations-by-request-threshold)
(ex/raise :type :validation
:code :max-invitations-by-request
:hint "the maximum of invitation on single request is reached"
@@ -288,7 +338,7 @@
(-> cfg
(assoc ::quotes/profile-id profile-id)
(assoc ::quotes/team-id team-id)
(assoc ::quotes/incr (count emails))
(assoc ::quotes/incr invitation-count)
(quotes/check! {::quotes/id ::quotes/invitations-per-team}
{::quotes/id ::quotes/profiles-per-team}))
@@ -304,7 +354,12 @@
(-> params
(assoc :profile profile)
(assoc :team team)
(assoc :emails emails)))]
;; Pass parameters in the correct format for the unified function
(cond-> using-emails-format?
;; If using emails+role format, ensure both are present
(assoc :emails emails :role role)
;; If using invitations format, the :invitations key is already in params
(not using-emails-format?) identity)))]
(with-meta {:total (count invitations)
:invitations invitations}
@@ -318,7 +373,7 @@
[:features {:optional true} ::cfeat/features]
[:id {:optional true} ::sm/uuid]
[:emails [::sm/set ::sm/email]]
[:role ::types.team/role]])
[:role types.team/schema:role]])
(sv/defmethod ::create-team-with-invitations
{::doc/added "1.17"
@@ -403,7 +458,7 @@
[:map {:title "update-team-invitation-role"}
[:team-id ::sm/uuid]
[:email ::sm/email]
[:role ::types.team/role]])
[:role types.team/schema:role]])
(sv/defmethod ::update-team-invitation-role
{::doc/added "1.17"
@@ -418,7 +473,7 @@
:code :insufficient-permissions))
(db/update! conn :team-invitation
{:role (name role) :updated-at (dt/now)}
{:role (name role) :updated-at (ct/now)}
{:team-id team-id :email-to (profile/clean-email email)})
nil))
@@ -471,7 +526,7 @@
(when-let [request (db/get* conn :team-access-request
{:team-id team-id
:requester-id profile-id})]
(when (dt/is-after? (:valid-until request) (dt/now))
(when (ct/is-after? (:valid-until request) (ct/now))
(ex/raise :type :validation
:code :request-already-sent
:hint "you have already made a request to join this team less than 24 hours ago"))))
@@ -487,8 +542,8 @@
"Create or update team access request for provided team and profile-id"
[conn team-id requester-id]
(check-existing-team-access-request conn team-id requester-id)
(let [valid-until (dt/in-future {:hours 24})
auto-join-until (dt/in-future {:days 7})
(let [valid-until (ct/in-future {:hours 24})
auto-join-until (ct/in-future {:days 7})
request-id (uuid/next)]
(db/exec-one! conn [sql:upsert-team-access-request
request-id team-id requester-id

View File

@@ -8,6 +8,7 @@
(:require
[app.common.exceptions :as ex]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.types.team :as types.team]
[app.config :as cf]
[app.db :as db]
@@ -23,8 +24,7 @@
[app.setup :as-alias setup]
[app.tokens :as tokens]
[app.tokens.spec.team-invitation :as-alias spec.team-invitation]
[app.util.services :as sv]
[app.util.time :as dt]))
[app.util.services :as sv]))
(defmulti process-token (fn [_ _ claims] (:iss claims)))
@@ -126,9 +126,9 @@
(def schema:team-invitation-claims
[:map {:title "TeamInvitationClaims"}
[:iss :keyword]
[:exp ::dt/instant]
[:exp ::ct/inst]
[:profile-id ::sm/uuid]
[:role ::types.team/role]
[:role types.team/schema:role]
[:team-id ::sm/uuid]
[:member-email ::sm/email]
[:member-id {:optional true} ::sm/uuid]])

View File

@@ -9,6 +9,7 @@
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uri :as u]
[app.common.uuid :as uuid]
[app.db :as db]
@@ -19,7 +20,6 @@
[app.rpc.doc :as-alias doc]
[app.rpc.permissions :as perms]
[app.util.services :as sv]
[app.util.time :as dt]
[cuerdas.core :as str]))
(defn get-webhooks-permissions
@@ -54,7 +54,7 @@
(http/req! cfg
{:method :head
:uri (str (:uri params))
:timeout (dt/duration "3s")}
:timeout (ct/duration "3s")}
{:sync? true}))]
(if (ex/exception? response)
(if-let [hint (webhooks/interpret-exception response)]

View File

@@ -166,9 +166,6 @@
:servers [{:url (str/ffmt "%/api/rpc" (cf/get :public-uri))
;; :description "penpot backend"
}]
:security
{:api_key []}
:paths paths
:components {:schemas @definitions}}))

View File

@@ -10,15 +10,14 @@
[app.common.exceptions :as ex]
[app.common.schema :as sm]))
(sm/register!
^{::sm/type ::permissions}
[:map {:title "Permissions"}
[:type {:gen/elements [:membership :share-link]} :keyword]
[:is-owner ::sm/boolean]
[:is-admin ::sm/boolean]
[:can-edit ::sm/boolean]
[:can-read ::sm/boolean]
[:is-logged ::sm/boolean]])
(def schema:permissions
[:map {:title "Permissions"}
[:type {:gen/elements [:membership :share-link]} :keyword]
[:is-owner ::sm/boolean]
[:is-admin ::sm/boolean]
[:can-edit ::sm/boolean]
[:can-read ::sm/boolean]
[:is-logged ::sm/boolean]])
(def valid-roles
#{:admin :owner :editor :viewer})

View File

@@ -10,9 +10,9 @@
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.config :as cf]
[app.db :as db]
[app.util.time :as dt]
[app.worker :as wrk]
[cuerdas.core :as str]))
@@ -95,7 +95,7 @@
"- Total: ~(::total params) (INCR ~(::incr params 1))\n")]
(wrk/submit! {::db/conn conn
::wrk/task :sendmail
::wrk/delay (dt/duration "30s")
::wrk/delay (ct/duration "30s")
::wrk/max-retries 4
::wrk/priority 200
::wrk/dedupe true

View File

@@ -47,6 +47,7 @@
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uri :as uri]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -58,7 +59,6 @@
[app.rpc.rlimit.result :as-alias lresult]
[app.util.inet :as inet]
[app.util.services :as-alias sv]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.edn :as edn]
[cuerdas.core :as str]
@@ -67,7 +67,7 @@
[promesa.exec :as px]))
(def ^:private default-timeout
(dt/duration 400))
(ct/duration 400))
(def ^:private default-options
{:codec rds/string-codec
@@ -94,6 +94,10 @@
(defmulti parse-limit (fn [[_ strategy _]] strategy))
(defmulti process-limit (fn [_ _ _ o] (::strategy o)))
(defn- ->seconds
[d]
(-> d inst-ms (/ 1000) int))
(sm/register!
{:type ::rpc/rlimit
:pred #(instance? clojure.lang.Agent %)})
@@ -115,7 +119,7 @@
[:map
[::capacity ::sm/int]
[::rate ::sm/int]
[::internal ::dt/duration]
[::internal ::ct/duration]
[::params [::sm/vec :any]]]
[:map
[::nreq ::sm/int]
@@ -157,7 +161,7 @@
(assert (valid-limit-tuple? vlimit) "expected valid limit tuple")
(if-let [[_ capacity rate interval] (re-find bucket-opts-re opts)]
(let [interval (dt/duration interval)
(let [interval (ct/duration interval)
rate (parse-long rate)
capacity (parse-long capacity)]
{::name name
@@ -166,7 +170,7 @@
::rate rate
::interval interval
::opts opts
::params [(dt/->seconds interval) rate capacity]
::params [(->seconds interval) rate capacity]
::key (str "ratelimit.bucket." (d/name name))})
(ex/raise :type :validation
:code :invalid-bucket-limit-opts
@@ -176,7 +180,7 @@
[redis user-id now {:keys [::key ::params ::service ::capacity ::interval ::rate] :as limit}]
(let [script (-> bucket-rate-limit-script
(assoc ::rscript/keys [(str key "." service "." user-id)])
(assoc ::rscript/vals (conj params (dt/->seconds now))))
(assoc ::rscript/vals (conj params (->seconds now))))
result (rds/eval redis script)
allowed? (boolean (nth result 0))
remaining (nth result 1)
@@ -191,16 +195,16 @@
:remaining remaining)
(-> limit
(assoc ::lresult/allowed allowed?)
(assoc ::lresult/reset (dt/plus now reset))
(assoc ::lresult/reset (ct/plus now reset))
(assoc ::lresult/remaining remaining))))
(defmethod process-limit :window
[redis user-id now {:keys [::nreq ::unit ::key ::service] :as limit}]
(let [ts (dt/truncate now unit)
ttl (dt/diff now (dt/plus ts {unit 1}))
(let [ts (ct/truncate now unit)
ttl (ct/diff now (ct/plus ts {unit 1}))
script (-> window-rate-limit-script
(assoc ::rscript/keys [(str key "." service "." user-id "." (dt/format-instant ts))])
(assoc ::rscript/vals [nreq (dt/->seconds ttl)]))
(assoc ::rscript/keys [(str key "." service "." user-id "." (ct/format-inst ts))])
(assoc ::rscript/vals [nreq (->seconds ttl)]))
result (rds/eval redis script)
allowed? (boolean (nth result 0))
remaining (nth result 1)]
@@ -214,7 +218,7 @@
(-> limit
(assoc ::lresult/allowed allowed?)
(assoc ::lresult/remaining remaining)
(assoc ::lresult/reset (dt/plus ts {unit 1})))))
(assoc ::lresult/reset (ct/plus ts {unit 1})))))
(defn- process-limits!
[redis user-id limits now]
@@ -223,7 +227,7 @@
(d/index-by ::name ::lresult/remaining)
(uri/map->query-string))
reset (->> results
(d/index-by ::name (comp dt/->seconds ::lresult/reset))
(d/index-by ::name (comp ->seconds ::lresult/reset))
(uri/map->query-string))
rejected (d/seek (complement ::lresult/allowed) results)]
@@ -261,7 +265,7 @@
(let [redis (rds/get-or-connect redis ::rpc/rlimit default-options)
uid (get-uid params)
;; FIXME: why not clasic try/catch?
result (ex/try! (process-limits! redis uid limits (dt/now)))]
result (ex/try! (process-limits! redis uid limits (ct/now)))]
(l/trc :hint "process-limits"
:service sname
@@ -321,7 +325,7 @@
(sm/check-fn schema:config))
(def ^:private check-refresh
(sm/check-fn ::dt/duration))
(sm/check-fn ::ct/duration))
(def ^:private check-limits
(sm/check-fn schema:limits))
@@ -351,7 +355,7 @@
config)))]
(when-let [config (some->> path slurp edn/read-string check-config)]
(let [refresh (->> config meta :refresh dt/duration check-refresh)
(let [refresh (->> config meta :refresh ct/duration check-refresh)
limits (->> config compile-pass-1 compile-pass-2 check-limits)]
{::refresh refresh
@@ -410,7 +414,7 @@
(l/info :hint "initializing rlimit config reader" :path (str path))
;; Initialize the state with initial refresh value
(send-via executor state (constantly {::refresh (dt/duration "5s")}))
(send-via executor state (constantly {::refresh (ct/duration "5s")}))
;; Force a refresh
(refresh-config (assoc cfg ::path path ::state state)))

View File

@@ -11,11 +11,11 @@
[app.common.exceptions :as ex]
[app.common.json :as json]
[app.common.logging :as l]
[app.common.time :as ct]
[app.config :as cf]
[app.srepl.cli :as cli]
[app.srepl.main]
[app.util.locks :as locks]
[app.util.time :as dt]
[clojure.core :as c]
[clojure.core.server :as ccs]
[clojure.main :as cm]
@@ -77,7 +77,7 @@
(loop []
(when (try
(let [data (read-line)
tpoint (dt/tpoint)]
tpoint (ct/tpoint)]
(l/dbg :hint "received" :data (if (= data ::eof) "EOF" data))

View File

@@ -7,16 +7,17 @@
(ns app.srepl.cli
"PREPL API for external usage (CLI or ADMIN)"
(:require
[app.auth :as auth]
[app.auth :refer [derive-password]]
[app.common.exceptions :as ex]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.db :as db]
[app.rpc.commands.auth :as cmd.auth]
[app.rpc.commands.profile :as cmd.profile]
[app.setup :as-alias setup]
[app.tokens :as tokens]
[app.util.time :as dt]
[cuerdas.core :as str]))
(defn coercer
@@ -53,7 +54,7 @@
(some-> (get-current-system)
(db/tx-run!
(fn [{:keys [::db/conn] :as system}]
(let [password (cmd.profile/derive-password system password)
(let [password (derive-password password)
params {:id (uuid/next)
:email email
:fullname fullname
@@ -73,7 +74,7 @@
(assoc :fullname fullname)
(some? password)
(assoc :password (auth/derive-password password))
(assoc :password (derive-password password))
(some? is-active)
(assoc :is-active is-active))]
@@ -101,7 +102,7 @@
(fn [{:keys [::db/conn] :as system}]
(let [res (if soft
(db/update! conn :profile
{:deleted-at (dt/now)}
{:deleted-at (ct/now)}
{:email email :deleted-at nil})
(db/delete! conn :profile
{:email email}))]
@@ -123,7 +124,7 @@
(defmethod exec-command "derive-password"
[{:keys [password]}]
(auth/derive-password password))
(derive-password password))
(defmethod exec-command "authenticate"
[{:keys [token]}]
@@ -173,6 +174,21 @@
:num-editors (get-customer-slots system id)
:subscription (get props :subscription)})))
(def ^:private schema:timestamp
(sm/type-schema
{:type ::timestamp
:pred ct/inst?
:type-properties
{:title "inst"
:description "The same as :app.common.time/inst but encodes to epoch"
:error/message "should be an instant"
:gen/gen (->> (sg/small-int)
(sg/fmap (fn [v] (ct/inst v))))
:decode/string ct/inst
:encode/string inst-ms
:decode/json ct/inst
:encode/json inst-ms}}))
(def ^:private schema:customer-subscription
[:map {:title "CustomerSubscription"}
[:id ::sm/text]
@@ -186,7 +202,7 @@
"canceled"
"incomplete"
"incomplete_expired"
"pass_due"
"past_due"
"paused"
"trialing"
"unpaid"]]
@@ -198,16 +214,15 @@
"year"]]
[:quantity :int]
[:description [:maybe ::sm/text]]
[:created-at ::sm/timestamp]
[:start-date [:maybe ::sm/timestamp]]
[:ended-at [:maybe ::sm/timestamp]]
[:trial-end [:maybe ::sm/timestamp]]
[:trial-start [:maybe ::sm/timestamp]]
[:cancel-at [:maybe ::sm/timestamp]]
[:canceled-at [:maybe ::sm/timestamp]]
[:current-period-end ::sm/timestamp]
[:current-period-start ::sm/timestamp]
[:created-at schema:timestamp]
[:start-date [:maybe schema:timestamp]]
[:ended-at [:maybe schema:timestamp]]
[:trial-end [:maybe schema:timestamp]]
[:trial-start [:maybe schema:timestamp]]
[:cancel-at [:maybe schema:timestamp]]
[:canceled-at [:maybe schema:timestamp]]
[:current-period-end [:maybe schema:timestamp]]
[:current-period-start [:maybe schema:timestamp]]
[:cancel-at-period-end :boolean]
[:cancellation-details

View File

@@ -12,11 +12,11 @@
[app.common.data :as d]
[app.common.files.migrations :as fmg]
[app.common.files.validate :as cfv]
[app.common.time :as ct]
[app.db :as db]
[app.main :as main]
[app.rpc.commands.files :as files]
[app.rpc.commands.files-snapshot :as fsnap]
[app.util.time :as dt]))
[app.rpc.commands.files-snapshot :as fsnap]))
(def ^:dynamic *system* nil)
@@ -165,7 +165,7 @@
(when (string? label)
(fsnap/create-file-snapshot! system file
{:label label
:deleted-at (dt/in-future {:days 30})
:deleted-at (ct/in-future {:days 30})
:created-by :admin}))
(let [file' (update file' :revn inc)]

View File

@@ -19,6 +19,7 @@
[app.common.pprint :as p]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@@ -38,8 +39,8 @@
[app.srepl.helpers :as h]
[app.util.blob :as blob]
[app.util.pointer-map :as pmap]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.datafy :refer [datafy]]
[clojure.java.io :as io]
[clojure.pprint :refer [print-table]]
[clojure.stacktrace :as strace]
@@ -476,7 +477,7 @@
:max-jobs max-jobs
:max-items max-items)
(let [tpoint (dt/tpoint)
(let [tpoint (ct/tpoint)
factory (px/thread-factory :virtual false :prefix "penpot/file-process/")
executor (px/cached-executor :factory factory)
sjobs (ps/create :permits max-jobs)
@@ -506,7 +507,7 @@
(Thread/sleep (int pause)))
(ps/release! sjobs)
(let [elapsed (dt/format-duration (tpoint))]
(let [elapsed (ct/format-duration (tpoint))]
(l/trc :hint "process:file:end"
:tid thread-id
:file-id (str file-id)
@@ -516,7 +517,7 @@
process-file*
(fn [idx file-id]
(ps/acquire! sjobs)
(px/run! executor (partial process-file file-id idx (dt/tpoint)))
(px/run! executor (partial process-file file-id idx (ct/tpoint)))
(inc idx))
process-files
@@ -542,7 +543,7 @@
(l/dbg :hint "process:error" :cause cause))
(finally
(let [elapsed (dt/format-duration (tpoint))]
(let [elapsed (ct/format-duration (tpoint))]
(l/dbg :hint "process:end"
:rollback rollback?
:elapsed elapsed))))))
@@ -556,7 +557,7 @@
"Mark a project for deletion"
[file-id]
(let [file-id (h/parse-uuid file-id)
tnow (dt/now)]
tnow (ct/now)]
(audit/insert! main/system
{::audit/name "delete-file"
@@ -618,7 +619,7 @@
::audit/props file
::audit/context {:triggered-by "srepl"
:cause "explicit call to restore-file!"}
::audit/tracked-at (dt/now)})
::audit/tracked-at (ct/now)})
(restore-file* system file-id))))))
@@ -626,7 +627,7 @@
"Mark a project for deletion"
[project-id]
(let [project-id (h/parse-uuid project-id)
tnow (dt/now)]
tnow (ct/now)]
(audit/insert! main/system
{::audit/name "delete-project"
@@ -673,7 +674,7 @@
::audit/props project
::audit/context {:triggered-by "srepl"
:cause "explicit call to restore-team!"}
::audit/tracked-at (dt/now)})
::audit/tracked-at (ct/now)})
(restore-project* system project-id))))))
@@ -681,7 +682,7 @@
"Mark a team for deletion"
[team-id]
(let [team-id (h/parse-uuid team-id)
tnow (dt/now)]
tnow (ct/now)]
(audit/insert! main/system
{::audit/name "delete-team"
@@ -733,7 +734,7 @@
::audit/props team
::audit/context {:triggered-by "srepl"
:cause "explicit call to restore-team!"}
::audit/tracked-at (dt/now)})
::audit/tracked-at (ct/now)})
(restore-team* system team-id))))))
@@ -741,7 +742,7 @@
"Mark a profile for deletion."
[profile-id]
(let [profile-id (h/parse-uuid profile-id)
tnow (dt/now)]
tnow (ct/now)]
(audit/insert! main/system
{::audit/name "delete-profile"
@@ -775,7 +776,7 @@
::audit/props (audit/profile->props profile)
::audit/context {:triggered-by "srepl"
:cause "explicit call to restore-profile!"}
::audit/tracked-at (dt/now)})
::audit/tracked-at (ct/now)})
(db/update! system :profile
{:deleted-at nil}
@@ -821,7 +822,7 @@
{:deleted deleted :total total})))]
(let [path (fs/path path)
deleted-at (dt/minus (dt/now) (cf/get-deletion-delay))]
deleted-at (ct/minus (ct/now) (cf/get-deletion-delay))]
(when-not (fs/exists? path)
(throw (ex-info "path does not exists" {:path path})))
@@ -905,7 +906,7 @@
(db/tx-run! main/system
(fn [{:keys [::db/conn] :as cfg}]
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"])
(let [team (-> (assoc cfg ::bfc/timestamp (dt/now))
(let [team (-> (assoc cfg ::bfc/timestamp (ct/now))
(mgmt/duplicate-team :team-id team-id :name name))
rels (db/query conn :team-profile-rel {:team-id team-id})]

View File

@@ -12,13 +12,13 @@
[app.common.data.macros :as dm]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.storage.fs :as sfs]
[app.storage.impl :as impl]
[app.storage.s3 :as ss3]
[app.util.time :as dt]
[cuerdas.core :as str]
[datoteka.fs :as fs]
[integrant.core :as ig])
@@ -27,7 +27,9 @@
(defn get-legacy-backend
[]
(let [name (cf/get :assets-storage-backend)]
(when-let [name (cf/get :assets-storage-backend)]
(l/wrn :hint "using deprecated configuration, please read 2.11 release notes"
:href "https://github.com/penpot/penpot/releases/tag/2.11.0")
(case name
:assets-fs :fs
:assets-s3 :s3
@@ -122,7 +124,7 @@
(dissoc :id))
touched-at (if touch
(or touched-at (dt/now))
(or touched-at (ct/now))
touched-at)
;; NOTE: for now we don't reuse the deleted objects, but in
@@ -224,7 +226,7 @@
(assert (valid-storage? storage))
(let [id (if (impl/object? object-or-id) (:id object-or-id) object-or-id)]
(-> (db/update! connectable :storage-object
{:touched-at (dt/now)}
{:touched-at (ct/now)}
{:id id})
(db/get-update-count)
(pos?))))
@@ -235,7 +237,7 @@
[storage object]
(assert (valid-storage? storage))
(when (or (nil? (:expired-at object))
(dt/is-after? (:expired-at object) (dt/now)))
(ct/is-after? (:expired-at object) (ct/now)))
(-> (impl/resolve-backend storage (:backend object))
(impl/get-object-data object))))
@@ -244,7 +246,7 @@
[storage object]
(assert (valid-storage? storage))
(when (or (nil? (:expired-at object))
(dt/is-after? (:expired-at object) (dt/now)))
(ct/is-after? (:expired-at object) (ct/now)))
(-> (impl/resolve-backend storage (:backend object))
(impl/get-object-bytes object))))
@@ -254,7 +256,7 @@
([storage object options]
(assert (valid-storage? storage))
(when (or (nil? (:expired-at object))
(dt/is-after? (:expired-at object) (dt/now)))
(ct/is-after? (:expired-at object) (ct/now)))
(-> (impl/resolve-backend storage (:backend object))
(impl/get-object-url object options)))))
@@ -266,7 +268,7 @@
(let [backend (impl/resolve-backend storage (:backend object))]
(when (and (= :fs (::type backend))
(or (nil? (:expired-at object))
(dt/is-after? (:expired-at object) (dt/now))))
(ct/is-after? (:expired-at object) (ct/now))))
(-> (impl/get-object-url backend object nil) file-url->path))))
(defn del-object!
@@ -274,7 +276,7 @@
(assert (valid-storage? storage))
(let [id (if (impl/object? object-or-id) (:id object-or-id) object-or-id)
res (db/update! connectable :storage-object
{:deleted-at (dt/now)}
{:deleted-at (ct/now)}
{:id id})]
(pos? (db/get-update-count res))))

View File

@@ -15,10 +15,10 @@
(:require
[app.common.data :as d]
[app.common.logging :as l]
[app.common.time :as ct]
[app.db :as db]
[app.storage :as sto]
[app.storage.impl :as impl]
[app.util.time :as dt]
[integrant.core :as ig]))
(def ^:private sql:lock-sobjects
@@ -106,18 +106,18 @@
(defmethod ig/expand-key ::handler
[k v]
{k (assoc v ::min-age (dt/duration {:hours 2}))})
{k (assoc v ::min-age (ct/duration {:hours 2}))})
(defmethod ig/init-key ::handler
[_ {:keys [::min-age] :as cfg}]
(fn [{:keys [props] :as task}]
(let [min-age (dt/duration (or (:min-age props) min-age))]
(let [min-age (ct/duration (or (:min-age props) min-age))]
(db/tx-run! cfg (fn [cfg]
(let [cfg (assoc cfg ::min-age min-age)
total (clean-deleted! cfg)]
(l/inf :hint "task finished"
:min-age (dt/format-duration min-age)
:min-age (ct/format-duration min-age)
:total total)
{:deleted total}))))))

View File

@@ -212,8 +212,8 @@
deleted 0]
(if-let [chunk (get-chunk pool)]
(let [[nfo ndo] (db/tx-run! cfg process-chunk! chunk)]
(recur (+ freezed nfo)
(+ deleted ndo)))
(recur (long (+ freezed nfo))
(long (+ deleted ndo))))
(do
(l/inf :hint "task finished"
:to-freeze freezed

View File

@@ -12,11 +12,11 @@
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uri :as u]
[app.storage :as-alias sto]
[app.storage.impl :as impl]
[app.storage.tmp :as tmp]
[app.util.time :as dt]
[app.worker :as-alias wrk]
[clojure.java.io :as io]
[datoteka.fs :as fs]
@@ -31,13 +31,13 @@
java.time.Duration
java.util.Collection
java.util.Optional
java.util.concurrent.atomic.AtomicLong
org.reactivestreams.Subscriber
software.amazon.awssdk.core.ResponseBytes
software.amazon.awssdk.core.async.AsyncRequestBody
software.amazon.awssdk.core.async.AsyncResponseTransformer
software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody
software.amazon.awssdk.core.client.config.ClientAsyncConfiguration
software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption
software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient
software.amazon.awssdk.http.nio.netty.SdkEventLoopGroup
software.amazon.awssdk.regions.Region
@@ -69,7 +69,7 @@
20000)
(def default-timeout
(dt/duration {:seconds 30}))
(ct/duration {:seconds 30}))
(declare put-object)
(declare get-object-bytes)
@@ -87,12 +87,11 @@
(def ^:private schema:config
[:map {:title "s3-backend-config"}
::wrk/executor
::wrk/netty-io-executor
[::region {:optional true} :keyword]
[::bucket {:optional true} ::sm/text]
[::prefix {:optional true} ::sm/text]
[::endpoint {:optional true} ::sm/uri]
[::io-threads {:optional true} ::sm/int]])
[::endpoint {:optional true} ::sm/uri]])
(defmethod ig/expand-key ::backend
[k v]
@@ -110,6 +109,7 @@
presigner (build-s3-presigner params)]
(assoc params
::sto/type :s3
::counter (AtomicLong. 0)
::client @client
::presigner presigner
::close-fn #(.close ^java.lang.AutoCloseable client)))))
@@ -121,7 +121,7 @@
(defmethod ig/halt-key! ::backend
[_ {:keys [::close-fn]}]
(when (fn? close-fn)
(px/run! close-fn)))
(close-fn)))
(def ^:private schema:backend
[:map {:title "s3-backend"}
@@ -198,19 +198,16 @@
(Region/of (name region)))
(defn- build-s3-client
[{:keys [::region ::endpoint ::io-threads ::wrk/executor]}]
[{:keys [::region ::endpoint ::wrk/netty-io-executor]}]
(let [aconfig (-> (ClientAsyncConfiguration/builder)
(.advancedOption SdkAdvancedAsyncClientOption/FUTURE_COMPLETION_EXECUTOR executor)
(.build))
sconfig (-> (S3Configuration/builder)
(cond-> (some? endpoint) (.pathStyleAccessEnabled true))
(.build))
thr-num (or io-threads (min 16 (px/get-available-processors)))
hclient (-> (NettyNioAsyncHttpClient/builder)
(.eventLoopGroupBuilder (-> (SdkEventLoopGroup/builder)
(.numberOfThreads (int thr-num))))
(.eventLoopGroup (SdkEventLoopGroup/create netty-io-executor))
(.connectionAcquisitionTimeout default-timeout)
(.connectionTimeout default-timeout)
(.readTimeout default-timeout)
@@ -262,7 +259,7 @@
(.close ^InputStream input))))
(defn- make-request-body
[executor content]
[counter content]
(let [size (impl/get-size content)]
(reify
AsyncRequestBody
@@ -272,16 +269,19 @@
(^void subscribe [_ ^Subscriber subscriber]
(let [delegate (AsyncRequestBody/forBlockingInputStream (long size))
input (io/input-stream content)]
(px/run! executor (partial write-input-stream delegate input))
(px/thread-call (partial write-input-stream delegate input)
{:name (str "penpot/storage/" (.getAndIncrement ^AtomicLong counter))})
(.subscribe ^BlockingInputStreamAsyncRequestBody delegate
^Subscriber subscriber))))))
(defn- put-object
[{:keys [::client ::bucket ::prefix ::wrk/executor]} {:keys [id] :as object} content]
[{:keys [::client ::bucket ::prefix ::counter]} {:keys [id] :as object} content]
(let [path (dm/str prefix (impl/id->path id))
mdata (meta object)
mtype (:content-type mdata "application/octet-stream")
rbody (make-request-body executor content)
rbody (make-request-body counter content)
request (.. (PutObjectRequest/builder)
(bucket bucket)
(contentType mtype)
@@ -338,11 +338,11 @@
(p/fmap #(.asByteArray ^ResponseBytes %)))))
(def default-max-age
(dt/duration {:minutes 10}))
(ct/duration {:minutes 10}))
(defn- get-object-url
[{:keys [::presigner ::bucket ::prefix]} {:keys [id]} {:keys [max-age] :or {max-age default-max-age}}]
(assert (dt/duration? max-age) "expected valid duration instance")
(assert (ct/duration? max-age) "expected valid duration instance")
(let [gor (.. (GetObjectRequest/builder)
(bucket bucket)

View File

@@ -12,8 +12,8 @@
(:require
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.util.time :as dt]
[app.worker :as wrk]
[datoteka.fs :as fs]
[datoteka.io :as io]
@@ -38,13 +38,13 @@
(defmethod ig/expand-key ::cleaner
[k v]
{k (assoc v ::min-age (dt/duration "60m"))})
{k (assoc v ::min-age (ct/duration "60m"))})
(defmethod ig/init-key ::cleaner
[_ cfg]
(fs/create-dir default-tmp-dir)
(px/fn->thread (partial io-loop cfg)
{:name "penpot/storage/tmp-cleaner" :virtual true}))
{:name "penpot/storage/tmp-cleaner"}))
(defmethod ig/halt-key! ::cleaner
[_ thread]
@@ -52,13 +52,13 @@
(defn- io-loop
[{:keys [::min-age] :as cfg}]
(l/inf :hint "started tmp cleaner" :default-min-age (dt/format-duration min-age))
(l/inf :hint "started tmp cleaner" :default-min-age (ct/format-duration min-age))
(try
(loop []
(when-let [[path min-age'] (sp/take! queue)]
(let [min-age (or min-age' min-age)]
(l/dbg :hint "schedule tempfile deletion" :path path
:expires-at (dt/plus (dt/now) min-age))
:expires-at (ct/plus (ct/now) min-age))
(px/schedule! (inst-ms min-age) (partial remove-temp-file cfg path))
(recur))))
(catch InterruptedException _
@@ -87,7 +87,7 @@
path (fs/join default-tmp-dir (str prefix (uuid/next) suffix))
path (Files/createFile path attrs)]
(fs/delete-on-exit! path)
(sp/offer! queue [path (some-> min-age dt/duration)])
(sp/offer! queue [path (some-> min-age ct/duration)])
path))
(defn tempfile-from

View File

@@ -8,10 +8,10 @@
"A generic task for object deletion cascade handling"
(:require
[app.common.logging :as l]
[app.common.time :as ct]
[app.db :as db]
[app.rpc.commands.files :as files]
[app.rpc.commands.profile :as profile]
[app.util.time :as dt]
[integrant.core :as ig]))
(def ^:dynamic *team-deletion* false)
@@ -23,7 +23,7 @@
[{:keys [::db/conn] :as cfg} {:keys [id deleted-at]}]
(when-let [file (db/get* conn :file {:id id} {::db/remove-deleted false})]
(l/trc :hint "marking for deletion" :rel "file" :id (str id)
:deleted-at (dt/format-instant deleted-at))
:deleted-at (ct/format-inst deleted-at))
(db/update! conn :file
{:deleted-at deleted-at}
@@ -62,7 +62,7 @@
(defmethod delete-object :project
[{:keys [::db/conn] :as cfg} {:keys [id deleted-at]}]
(l/trc :hint "marking for deletion" :rel "project" :id (str id)
:deleted-at (dt/format-instant deleted-at))
:deleted-at (ct/format-inst deleted-at))
(db/update! conn :project
{:deleted-at deleted-at}
@@ -79,7 +79,7 @@
(defmethod delete-object :team
[{:keys [::db/conn] :as cfg} {:keys [id deleted-at]}]
(l/trc :hint "marking for deletion" :rel "team" :id (str id)
:deleted-at (dt/format-instant deleted-at))
:deleted-at (ct/format-inst deleted-at))
(db/update! conn :team
{:deleted-at deleted-at}
{:id id}
@@ -101,7 +101,7 @@
(defmethod delete-object :profile
[{:keys [::db/conn] :as cfg} {:keys [id deleted-at]}]
(l/trc :hint "marking for deletion" :rel "profile" :id (str id)
:deleted-at (dt/format-instant deleted-at))
:deleted-at (ct/format-inst deleted-at))
(db/update! conn :profile
{:deleted-at deleted-at}

View File

@@ -16,6 +16,7 @@
[app.common.files.validate :as cfv]
[app.common.logging :as l]
[app.common.thumbnails :as thc]
[app.common.time :as ct]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
[app.common.types.shape-tree :as ctt]
@@ -23,7 +24,6 @@
[app.db :as db]
[app.features.fdata :as feat.fdata]
[app.storage :as sto]
[app.util.time :as dt]
[app.worker :as wrk]
[integrant.core :as ig]))
@@ -282,7 +282,7 @@
(defmethod ig/init-key ::handler
[_ cfg]
(fn [{:keys [props] :as task}]
(let [min-age (dt/duration (or (:min-age props)
(let [min-age (ct/duration (or (:min-age props)
(cf/get-deletion-delay)))
file-id (get props :file-id)
cfg (-> cfg

View File

@@ -8,9 +8,9 @@
"A maintenance task that is responsible of properly scheduling the
file-gc task for all files that matches the eligibility threshold."
(:require
[app.common.time :as ct]
[app.config :as cf]
[app.db :as db]
[app.util.time :as dt]
[app.worker :as wrk]
[integrant.core :as ig]))
@@ -53,7 +53,7 @@
(defmethod ig/init-key ::handler
[_ cfg]
(fn [{:keys [props] :as task}]
(let [min-age (dt/duration (or (:min-age props) (::min-age cfg)))]
(let [min-age (ct/duration (or (:min-age props) (::min-age cfg)))]
(-> cfg
(assoc ::db/rollback (:rollback? props))
(assoc ::min-age min-age)

View File

@@ -9,9 +9,9 @@
of deleted or unreachable objects."
(:require
[app.common.logging :as l]
[app.common.time :as ct]
[app.db :as db]
[app.storage :as sto]
[app.util.time :as dt]
[integrant.core :as ig]))
(def ^:private sql:get-profiles
@@ -53,7 +53,7 @@
(l/trc :hint "permanently delete"
:rel "team"
:id (str id)
:deleted-at (dt/format-instant deleted-at))
:deleted-at (ct/format-inst deleted-at))
;; Mark as deleted the storage object
(some->> photo-id (sto/touch-object! storage))
@@ -82,7 +82,7 @@
:rel "team-font-variant"
:id (str id)
:team-id (str team-id)
:deleted-at (dt/format-instant deleted-at))
:deleted-at (ct/format-inst deleted-at))
;; Mark as deleted the all related storage objects
(some->> (:woff1-file-id font) (sto/touch-object! storage))
@@ -114,7 +114,7 @@
:rel "project"
:id (str id)
:team-id (str team-id)
:deleted-at (dt/format-instant deleted-at))
:deleted-at (ct/format-inst deleted-at))
;; And finally, permanently delete the project.
(db/delete! conn :project {:id id})
@@ -140,7 +140,7 @@
:rel "file"
:id (str id)
:project-id (str project-id)
:deleted-at (dt/format-instant deleted-at))
:deleted-at (ct/format-inst deleted-at))
(when (= "objects-storage" (:data-backend file))
(sto/touch-object! storage (:data-ref-id file)))
@@ -169,7 +169,7 @@
:rel "file-thumbnail"
:file-id (str file-id)
:revn revn
:deleted-at (dt/format-instant deleted-at))
:deleted-at (ct/format-inst deleted-at))
;; Mark as deleted the storage object
(some->> media-id (sto/touch-object! storage))
@@ -198,7 +198,7 @@
:rel "file-tagged-object-thumbnail"
:file-id (str file-id)
:object-id object-id
:deleted-at (dt/format-instant deleted-at))
:deleted-at (ct/format-inst deleted-at))
;; Mark as deleted the storage object
(some->> media-id (sto/touch-object! storage))
@@ -227,7 +227,7 @@
:rel "file-data-fragment"
:id (str id)
:file-id (str file-id)
:deleted-at (dt/format-instant deleted-at))
:deleted-at (ct/format-inst deleted-at))
(some->> data-ref-id (sto/touch-object! storage))
(db/delete! conn :file-data-fragment {:file-id file-id :id id})
@@ -253,7 +253,7 @@
:rel "file-media-object"
:id (str id)
:file-id (str file-id)
:deleted-at (dt/format-instant deleted-at))
:deleted-at (ct/format-inst deleted-at))
;; Mark as deleted the all related storage objects
(some->> (:media-id fmo) (sto/touch-object! storage))
@@ -282,7 +282,7 @@
:rel "file-change"
:id (str id)
:file-id (str file-id)
:deleted-at (dt/format-instant deleted-at))
:deleted-at (ct/format-inst deleted-at))
(when (= "objects-storage" (:data-backend xlog))
(sto/touch-object! storage (:data-ref-id xlog)))
@@ -313,7 +313,7 @@
(db/exec-one! conn ["SET LOCAL rules.deletion_protection TO off"])
(proc-fn cfg)))]
(if (pos? result)
(recur (+ total result))
(recur (long (+ total result)))
total))))
(defmethod ig/assert-key ::handler
@@ -328,14 +328,14 @@
(defmethod ig/init-key ::handler
[_ cfg]
(fn [{:keys [props] :as task}]
(let [threshold (dt/duration (get props :deletion-threshold 0))
(let [threshold (ct/duration (get props :deletion-threshold 0))
cfg (assoc cfg ::deletion-threshold (db/interval threshold))]
(loop [procs (map deref deletion-proc-vars)
total 0]
(if-let [proc-fn (first procs)]
(let [result (execute-proc! cfg proc-fn)]
(recur (rest procs)
(+ total result)))
(long (+ total result))))
(do
(l/inf :hint "task finished" :deleted total)
{:processed total}))))))

View File

@@ -10,8 +10,8 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.time :as ct]
[app.common.transit :as t]
[app.util.time :as dt]
[buddy.sign.jwe :as jwe]))
(defn generate
@@ -22,7 +22,7 @@
(bytes? tokens-key))
(let [payload (-> claims
(assoc :iat (dt/now))
(assoc :iat (ct/now))
(d/without-nils)
(t/encode))]
(jwe/encrypt payload tokens-key {:alg :a256kw :enc :a256gcm})))
@@ -35,8 +35,8 @@
(defn verify
[sprops {:keys [token] :as params}]
(let [claims (decode sprops token)]
(when (and (dt/instant? (:exp claims))
(dt/is-before? (:exp claims) (dt/now)))
(when (and (ct/inst? (:exp claims))
(ct/is-before? (:exp claims) (ct/now)))
(ex/raise :type :validation
:code :invalid-token
:reason :token-expired

View File

@@ -9,7 +9,7 @@
(:refer-clojure :exclude [get])
(:require
[app.common.schema :as sm]
[app.util.time :as dt]
[app.common.time :as ct]
[promesa.exec :as px])
(:import
com.github.benmanes.caffeine.cache.AsyncCache
@@ -51,7 +51,7 @@
(let [cache (as-> (Caffeine/newBuilder) builder
(if (fn? on-remove) (.removalListener builder (create-listener on-remove)) builder)
(if executor (.executor builder ^Executor (px/resolve-executor executor)) builder)
(if keepalive (.expireAfterAccess builder ^Duration (dt/duration keepalive)) builder)
(if keepalive (.expireAfterAccess builder ^Duration (ct/duration keepalive)) builder)
(if (int? max-size) (.maximumSize builder (long max-size)) builder)
(.recordStats builder)
(.buildAsync builder))

View File

@@ -0,0 +1,138 @@
;; 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.util.cron
(:require
[app.common.exceptions :as ex])
(:import
java.time.Instant
java.util.Date
org.apache.logging.log4j.core.util.CronExpression))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Cron Expression
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Cron expressions are comprised of 6 required fields and one
;; optional field separated by white space. The fields respectively
;; are described as follows:
;;
;; Field Name Allowed Values Allowed Special Characters
;; Seconds 0-59 , - * /
;; Minutes 0-59 , - * /
;; Hours 0-23 , - * /
;; Day-of-month 1-31 , - * ? / L W
;; Month 0-11 or JAN-DEC , - * /
;; Day-of-Week 1-7 or SUN-SAT , - * ? / L #
;; Year (Optional) empty, 1970-2199 , - * /
;;
;; The '*' character is used to specify all values. For example, "*"
;; in the minute field means "every minute".
;;
;; The '?' character is allowed for the day-of-month and day-of-week
;; fields. It is used to specify 'no specific value'. This is useful
;; when you need to specify something in one of the two fields, but
;; not the other.
;;
;; The '-' character is used to specify ranges For example "10-12" in
;; the hour field means "the hours 10, 11 and 12".
;;
;; The ',' character is used to specify additional values. For
;; example "MON,WED,FRI" in the day-of-week field means "the days
;; Monday, Wednesday, and Friday".
;;
;; The '/' character is used to specify increments. For example "0/15"
;; in the seconds field means "the seconds 0, 15, 30, and
;; 45". And "5/15" in the seconds field means "the seconds 5, 20, 35,
;; and 50". Specifying '*' before the '/' is equivalent to specifying
;; 0 is the value to start with. Essentially, for each field in the
;; expression, there is a set of numbers that can be turned on or
;; off. For seconds and minutes, the numbers range from 0 to 59. For
;; hours 0 to 23, for days of the month 0 to 31, and for months 0 to
;; 11 (JAN to DEC). The "/" character simply helps you turn on
;; every "nth" value in the given set. Thus "7/6" in the month field
;; only turns on month "7", it does NOT mean every 6th month, please
;; note that subtlety.
;;
;; The 'L' character is allowed for the day-of-month and day-of-week
;; fields. This character is short-hand for "last", but it has
;; different meaning in each of the two fields. For example, the
;; value "L" in the day-of-month field means "the last day of the
;; month" - day 31 for January, day 28 for February on non-leap
;; years. If used in the day-of-week field by itself, it simply
;; means "7" or "SAT". But if used in the day-of-week field after
;; another value, it means "the last xxx day of the month" - for
;; example "6L" means "the last friday of the month". You can also
;; specify an offset from the last day of the month, such as "L-3"
;; which would mean the third-to-last day of the calendar month. When
;; using the 'L' option, it is important not to specify lists, or
;; ranges of values, as you'll get confusing/unexpected results.
;;
;; The 'W' character is allowed for the day-of-month field. This
;; character is used to specify the weekday (Monday-Friday) nearest
;; the given day. As an example, if you were to specify "15W" as the
;; value for the day-of-month field, the meaning is: "the nearest
;; weekday to the 15th of the month". So if the 15th is a Saturday,
;; the trigger will fire on Friday the 14th. If the 15th is a Sunday,
;; the trigger will fire on Monday the 16th. If the 15th is a Tuesday,
;; then it will fire on Tuesday the 15th. However if you specify "1W"
;; as the value for day-of-month, and the 1st is a Saturday, the
;; trigger will fire on Monday the 3rd, as it will not 'jump' over the
;; boundary of a month's days. The 'W' character can only be specified
;; when the day-of-month is a single day, not a range or list of days.
;;
;; The 'L' and 'W' characters can also be combined for the
;; day-of-month expression to yield 'LW', which translates to "last
;; weekday of the month".
;;
;; The '#' character is allowed for the day-of-week field. This
;; character is used to specify "the nth" XXX day of the month. For
;; example, the value of "6#3" in the day-of-week field means the
;; third Friday of the month (day 6 = Friday and "#3" = the 3rd one in
;; the month). Other examples: "2#1" = the first Monday of the month
;; and "4#5" = the fifth Wednesday of the month. Note that if you
;; specify "#5" and there is not 5 of the given day-of-week in the
;; month, then no firing will occur that month. If the '#' character
;; is used, there can only be one expression in the day-of-week
;; field ("3#1,6#3" is not valid, since there are two expressions).
;;
;; The legal characters and the names of months and days of the week
;; are not case sensitive.
(defn cron
"Creates an instance of CronExpression from string."
[s]
(try
(CronExpression. s)
(catch java.text.ParseException e
(ex/raise :type :parse
:code :invalid-cron-expression
:cause e
:context {:expr s}))))
(defn cron-expr?
[v]
(instance? CronExpression v))
(defn next-valid-instant-from
[^CronExpression cron ^Instant now]
(assert (cron-expr? cron))
(.toInstant (.getNextValidTimeAfter cron (Date/from now))))
(defn get-next
[cron tnow]
(let [nt (next-valid-instant-from cron tnow)]
(cons nt (lazy-seq (get-next cron nt)))))
(defmethod print-method CronExpression
[o w]
(print-dup o w))
(defmethod print-dup CronExpression
[mv ^java.io.Writer writer]
;; Do not delete this comment
;; (print-ctor o (fn [o w] (print-dup (.toString ^CronExpression o) w)) w)
(.write writer (str "#penpot/cron \"" (.toString ^CronExpression mv) "\"")))

View File

@@ -27,7 +27,7 @@
(sp/put! channel [type data])
nil)))
(defn start-listener
(defn spawn-listener
[channel on-event on-close]
(assert (sp/chan? channel) "expected active events channel")
@@ -51,7 +51,7 @@
[f on-event]
(binding [*channel* (sp/chan :buf 32)]
(let [listener (start-listener *channel* on-event (constantly nil))]
(let [listener (spawn-listener *channel* on-event (constantly nil))]
(try
(f)
(finally

View File

@@ -37,9 +37,9 @@
(:require
[app.common.fressian :as fres]
[app.common.time :as ct]
[app.common.transit :as t]
[app.common.uuid :as uuid]
[app.util.time :as dt]
[clojure.core :as c]
[clojure.data.json :as json])
(:import
@@ -61,8 +61,10 @@
(declare create)
(defn create-tracked
[]
(atom {}))
[& {:keys [inherit]}]
(if inherit
(atom (if *tracked* @*tracked* {}))
(atom {})))
(defprotocol IPointerMap
(get-id [_])
@@ -102,7 +104,7 @@
(clone [this]
(when-not loaded? (load! this))
(let [mdata (assoc mdata :created-at (dt/now))
(let [mdata (assoc mdata :created-at (ct/now))
id (uuid/next)
pmap (PointerMap. id
mdata
@@ -177,7 +179,7 @@
(let [odata' (assoc odata key val)]
(if (identical? odata odata')
this
(let [mdata (assoc mdata :created-at (dt/now))
(let [mdata (assoc mdata :created-at (ct/now))
id (if modified? id (uuid/next))
pmap (PointerMap. id
mdata
@@ -195,7 +197,7 @@
(let [odata' (dissoc odata key)]
(if (identical? odata odata')
this
(let [mdata (assoc mdata :created-at (dt/now))
(let [mdata (assoc mdata :created-at (ct/now))
id (if modified? id (uuid/next))
pmap (PointerMap. id
mdata
@@ -218,7 +220,7 @@
(defn create
([]
(let [id (uuid/next)
mdata (assoc *metadata* :created-at (dt/now))
mdata (assoc *metadata* :created-at (ct/now))
pmap (PointerMap. id mdata {} true true)]
(some-> *tracked* (swap! assoc id pmap))
pmap))
@@ -237,7 +239,7 @@
(do
(some-> *tracked* (swap! assoc (get-id data) data))
data)
(let [mdata (assoc (meta data) :created-at (dt/now))
(let [mdata (assoc (meta data) :created-at (ct/now))
id (uuid/next)
pmap (PointerMap. id
mdata

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