Compare commits

..

2 Commits

Author SHA1 Message Date
Bastien Guerry eb26641374 📝(docs) ortho/typo fixes in docs/env.md 2025-05-06 09:43:12 +02:00
Bastien Guerry 561a185363 📝(docs) sort environment variables alphabetically 2025-05-06 09:39:57 +02:00
69 changed files with 552 additions and 1547 deletions
+1 -3
View File
@@ -79,9 +79,7 @@ jobs:
context: .
file: ./src/frontend/Dockerfile
target: frontend-production
build-args: |
DOCKER_USER=${{ env.DOCKER_USER }}:-1000
PUBLISH_AS_MIT=false
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+8 -27
View File
@@ -8,50 +8,28 @@ and this project adheres to
## [Unreleased]
## Added
## Fixed
- ✨ Add a custom callout block to the editor #892
- 🚩(frontend) version MIT only #911
## Changed
- 📝(frontend) Update documentation
- ✅(frontend) Improve tests coverage
## [3.2.1] - 2025-05-06
## Fixed
- 🐛(frontend) fix list copy paste #943
- 📝(doc) update contributing policy (commit signatures are now mandatory) #895
## [3.2.0] - 2025-05-05
- 🔒(frontend) enhance file download security #889
## Added
- 🚸(backend) make document search on title accent-insensitive #874
- 🚩 add homepage feature flag #861
- 📝(doc) update contributing policy (commit signatures are now mandatory) #895
- ✨(settings) Allow configuring PKCE for the SSO #886
- 🌐(i18n) activate chinese and spanish languages #884
- 🔧(backend) allow overwriting the data directory #893
- (backend) add `django-lasuite` dependency #839
- ✨(frontend) advanced table features #908
## Changed
- ⚡️(frontend) reduce unblocking time for config #867
- ♻️(frontend) bind UI with ability access #900
- ♻️(frontend) use built-in Quote block #908
## Fixed
- 🐛(nginx) fix 404 when accessing a doc #866
- 🔒️(drf) disable browsable HTML API renderer #919
- 🔒(frontend) enhance file download security #889
- 🐛(backend) race condition create doc #633
- 🐛(frontend) fix breaklines in custom blocks #908
## [3.1.0] - 2025-04-07
@@ -180,6 +158,11 @@ and this project adheres to
- ♻️(frontend) improve table pdf rendering
- 🐛(email) invitation emails in receivers language
## Fixed
- 🐛(backend) race condition create doc #633
## [2.2.0] - 2025-02-10
## Added
@@ -568,9 +551,7 @@ and this project adheres to
- ✨(frontend) Coming Soon page (#67)
- 🚀 Impress, project to manage your documents easily and collaboratively.
[unreleased]: https://github.com/numerique-gouv/impress/compare/v3.2.1...main
[v3.2.1]: https://github.com/numerique-gouv/impress/releases/v3.2.1
[v3.2.0]: https://github.com/numerique-gouv/impress/releases/v3.2.0
[unreleased]: https://github.com/numerique-gouv/impress/compare/v3.1.0...main
[v3.1.0]: https://github.com/numerique-gouv/impress/releases/v3.1.0
[v3.0.0]: https://github.com/numerique-gouv/impress/releases/v3.0.0
[v2.6.0]: https://github.com/numerique-gouv/impress/releases/v2.6.0
+7 -21
View File
@@ -2,16 +2,16 @@
Thank you for taking the time to contribute! Please follow these guidelines to ensure a smooth and productive workflow. 🚀🚀🚀
To get started with the project, please refer to the [README.md](https://github.com/suitenumerique/docs/blob/main/README.md) for detailed instructions on how to run Docs locally.
To get started with the project, please refer to the [README.md](https://github.com/suitenumerique/docs/blob/main/README.md) for detailed instructions.
Contributors are required to sign off their commits with `git commit --signoff`: this confirms that they have read and accepted the [Developer's Certificate of Origin 1.1](https://developercertificate.org/). For security reasons we also require [signing your commits with your SSH or GPG key](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) with `git commit -S`.
Contributors are required to sign off their commits with `git commit --sign-off`: this confirms that they have read and accepted the [Developer's Certificate of Origin 1.1](https://developercertificate.org/).
Please also check out our [dev handbook](https://suitenumerique.gitbook.io/handbook) to learn our best practices.
## Help us with translations
You can help us with translations on [Crowdin](https://crowdin.com/project/lasuite-docs).
Your language is not there? Request it on our Crowdin page 😊 or ping us on [Matrix](https://matrix.to/#/#docs-official:matrix.org) and let us know if you can help with translations and/or proofreading.
Your language is not there? Request it on our Crowdin page 😊.
## Creating an Issue
@@ -35,14 +35,10 @@ All commit messages must adhere to the following format:
`<gitmoji>(type) title description`
* <**gitmoji**>: Use a gitmoji to represent the purpose of the commit. For example, ✨ for adding a new feature or 🔥 for removing something, see the list [here](https://gitmoji.dev/).
* <**gitmoji**>: Use a gitmoji to represent the purpose of the commit. For example, ✨ for adding a new feature or 🔥 for removing something, see the list here: <https://gitmoji.dev/>.
* **(type)**: Describe the type of change. Common types include `backend`, `frontend`, `CI`, `docker` etc...
* **title**: A short, descriptive title for the change (*)
* **blank line after the commit title
* **description**: Include additional details on why you made the changes (**).
(*) ⚠️ **Make sure you add no space between the emoji and the (type) but add a space after the closing parenthesis of the type and use no caps!**
(**) ⚠️ **Commit description message is mandatory and shouldn't be too long**
* **title**: A short, descriptive title for the change.
* **description**: Include additional details about what was changed and why.
### Example Commit Message
@@ -70,9 +66,7 @@ Please add a line to the changelog describing your development. The changelog en
It is nice to add information about the purpose of the pull request to help reviewers understand the context and intent of the changes. If you can, add some pictures or a small video to show the changes.
### Don't forget to:
- signoff your commits
- sign your commits with your key (SSH, GPG etc.)
- check your commits (see warnings above)
- check your commits
- check the linting: `make lint && make frontend-lint`
- check the tests: `make test`
- add a changelog entry
@@ -92,11 +86,3 @@ Make sure that all new features or fixes have corresponding tests. Run the test
If you need any help while contributing, feel free to open a discussion or ask for guidance in the issue tracker. We are more than happy to assist!
Thank you for your contributions! 👍
## Contribute to BlockNote
We use [BlockNote](https://www.blocknotejs.org/) for the text editing features of Docs.
If you find and issue with the editor you can [report it](https://github.com/TypeCellOS/BlockNote/issues) directly on their repository.
Please consider contributing to BlockNotejs, as a library, it's useful to many projects not just Docs.
The project is licended with Mozilla Public License Version 2.0 but be aware that [XL packages](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-docx-exporter/LICENSE) are dual licenced with GNU AFFERO GENERAL PUBLIC LICENCE Version 3 and proprietary licence if you are [sponsor](https://www.blocknotejs.org/pricing).
+6 -1
View File
@@ -155,7 +155,8 @@ services:
target: frontend-production
args:
API_ORIGIN: "http://localhost:8071"
PUBLISH_AS_MIT: "false"
Y_PROVIDER_URL: "ws://localhost:4444"
MEDIA_URL: "http://localhost:8083"
SW_DEACTIVATED: "true"
image: impress:frontend-development
ports:
@@ -184,11 +185,15 @@ services:
context: .
dockerfile: ./src/frontend/servers/y-provider/Dockerfile
target: y-provider
command: ["yarn", "workspace", "server-y-provider", "run", "dev"]
working_dir: /app/frontend
restart: unless-stopped
env_file:
- env.d/development/common
ports:
- "4444:4444"
volumes:
- ./src/frontend/:/app/frontend
kc_postgresql:
image: postgres:14.3
+80 -80
View File
@@ -4,97 +4,97 @@ Here we describe all environment variables that can be set for the docs applicat
## impress-backend container
These are the environmental variables you can set for the impress-backend container.
These are the environment variables you can set for the impress-backend container.
| Option | Description | default |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| DJANGO_ALLOWED_HOSTS | allowed hosts | [] |
| DJANGO_SECRET_KEY | secret key | |
| DJANGO_SERVER_TO_SERVER_API_TOKENS | | [] |
| DB_ENGINE | engine to use for database connections | django.db.backends.postgresql_psycopg2 |
| DB_NAME | name of the database | impress |
| DB_USER | user to authenticate with | dinum |
| DB_PASSWORD | password to authenticate with | pass |
| DB_HOST | host of the database | localhost |
| DB_PORT | port of the database | 5432 |
| MEDIA_BASE_URL | | |
| STORAGES_STATICFILES_BACKEND | | whitenoise.storage.CompressedManifestStaticFilesStorage |
|-------------------------------------------------|-----------------------------------------------------------------------------------------------|---------------------------------------------------------|
| AI_ALLOW_REACH_FROM | Users that can use AI must be this level. options are "public", "authenticated", "restricted" | authenticated |
| AI_API_KEY | Ai key to be used for AI Base url | |
| AI_BASE_URL | Openai compatible AI base url | |
| AI_MODEL | Ai Model to use | |
| ALLOW_LOGOUT_GET_METHOD | Allow get logout method | true |
| API_USERS_LIST_LIMIT | Limit on API users | 5 |
| API_USERS_LIST_THROTTLE_RATE_BURST | Throttle rate for api on burst | 30/minute |
| API_USERS_LIST_THROTTLE_RATE_SUSTAINED | Throttle rate for api | 180/hour |
| AWS_S3_ACCESS_KEY_ID | Access id for s3 endpoint | |
| AWS_S3_ENDPOINT_URL | S3 endpoint | |
| AWS_S3_ACCESS_KEY_ID | access id for s3 endpoint | |
| AWS_S3_SECRET_ACCESS_KEY | access key for s3 endpoint | |
| AWS_S3_REGION_NAME | region name for s3 endpoint | |
| AWS_STORAGE_BUCKET_NAME | bucket name for s3 endpoint | impress-media-storage |
| DOCUMENT_IMAGE_MAX_SIZE | maximum size of document in bytes | 10485760 |
| LANGUAGE_CODE | default language | en-us |
| API_USERS_LIST_THROTTLE_RATE_SUSTAINED | throttle rate for api | 180/hour |
| API_USERS_LIST_THROTTLE_RATE_BURST | throttle rate for api on burst | 30/minute |
| SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK | | false |
| TRASHBIN_CUTOFF_DAYS | trashbin cutoff | 30 |
| DJANGO_EMAIL_BACKEND | email backend library | django.core.mail.backends.smtp.EmailBackend |
| DJANGO_EMAIL_BRAND_NAME | brand name for email | |
| DJANGO_EMAIL_HOST | host name of email | |
| DJANGO_EMAIL_HOST_USER | user to authenticate with on the email host | |
| DJANGO_EMAIL_HOST_PASSWORD | password to authenticate with on the email host | |
| DJANGO_EMAIL_LOGO_IMG | logo for the email | |
| DJANGO_EMAIL_PORT | port used to connect to email host | |
| DJANGO_EMAIL_USE_TLS | use tls for email host connection | false |
| DJANGO_EMAIL_USE_SSL | use sstl for email host connection | false |
| DJANGO_EMAIL_FROM | email adress used as sender | from@example.com |
| DJANGO_CORS_ALLOW_ALL_ORIGINS | allow all CORS origins | true |
| DJANGO_CORS_ALLOWED_ORIGINS | list of origins allowed for CORS | [] |
| DJANGO_CORS_ALLOWED_ORIGIN_REGEXES | list of origins allowed for CORS using regulair expressions | [] |
| SENTRY_DSN | sentry host | |
| COLLABORATION_API_URL | collaboration api host | |
| COLLABORATION_SERVER_SECRET | collaboration api secret | |
| COLLABORATION_WS_URL | collaboration websocket url | |
| AWS_S3_REGION_NAME | Region name for s3 endpoint | |
| AWS_S3_SECRET_ACCESS_KEY | Access key for s3 endpoint | |
| AWS_STORAGE_BUCKET_NAME | Bucket name for s3 endpoint | impress-media-storage |
| CACHES_DEFAULT_TIMEOUT | Cache default timeout | 30 |
| COLLABORATION_API_URL | Collaboration api host | |
| COLLABORATION_SERVER_SECRET | Collaboration api secret | |
| COLLABORATION_WS_URL | Collaboration websocket url | |
| CONVERSION_API_CONTENT_FIELD | Conversion api content field | content |
| CONVERSION_API_ENDPOINT | Conversion API endpoint | convert-markdown |
| CONVERSION_API_SECURE | Require secure conversion api | false |
| CONVERSION_API_TIMEOUT | Conversion api timeout | 30 |
| CRISP_WEBSITE_ID | Crisp website id for support | |
| DB_ENGINE | Engine to use for database connections | django.db.backends.postgresql_psycopg2 |
| DB_HOST | Host of the database | localhost |
| DB_NAME | Name of the database | impress |
| DB_PASSWORD | Password to authenticate with | pass |
| DB_PORT | Port of the database | 5432 |
| DB_USER | User to authenticate with | dinum |
| DJANGO_ALLOWED_HOSTS | Allowed hosts | [] |
| DJANGO_CELERY_BROKER_TRANSPORT_OPTIONS | Celery broker transport options | {} |
| DJANGO_CELERY_BROKER_URL | Celery broker url | redis://redis:6379/0 |
| DJANGO_CORS_ALLOW_ALL_ORIGINS | Allow all CORS origins | true |
| DJANGO_CORS_ALLOWED_ORIGIN_REGEXES | List of origins allowed for CORS using regulair expressions | [] |
| DJANGO_CORS_ALLOWED_ORIGINS | List of origins allowed for CORS | [] |
| DJANGO_CSRF_TRUSTED_ORIGINS | CSRF trusted origins | [] |
| DJANGO_EMAIL_BACKEND | Email backend library | django.core.mail.backends.smtp.EmailBackend |
| DJANGO_EMAIL_BRAND_NAME | Brand name for email | |
| DJANGO_EMAIL_FROM | Email adress used as sender | from@example.com |
| DJANGO_EMAIL_HOST | Host name of email | |
| DJANGO_EMAIL_HOST_PASSWORD | Password to authenticate with on the email host | |
| DJANGO_EMAIL_HOST_USER | User to authenticate with on the email host | |
| DJANGO_EMAIL_LOGO_IMG | Logo for the email | |
| DJANGO_EMAIL_PORT | Port used to connect to email host | |
| DJANGO_EMAIL_USE_SSL | Use sstl for email host connection | false |
| DJANGO_EMAIL_USE_TLS | Use tls for email host connection | false |
| DJANGO_SECRET_KEY | Secret key | |
| DJANGO_SERVER_TO_SERVER_API_TOKENS | | [] |
| DOCUMENT_IMAGE_MAX_SIZE | Maximum size of document in bytes | 10485760 |
| FRONTEND_CSS_URL | To add a external css file to the app | |
| FRONTEND_HOMEPAGE_FEATURE_ENABLED | frontend feature flag to display the homepage | false |
| FRONTEND_FOOTER_FEATURE_ENABLED | frontend feature flag to display the footer | false |
| FRONTEND_FOOTER_FEATURE_ENABLED | Frontend feature flag to display the footer | false |
| FRONTEND_FOOTER_VIEW_CACHE_TIMEOUT | Cache duration of the json footer | 86400 |
| FRONTEND_HOMEPAGE_FEATURE_ENABLED | Frontend feature flag to display the homepage | false |
| FRONTEND_THEME | Frontend theme to use | |
| FRONTEND_URL_JSON_FOOTER | Url with a json to configure the footer | |
| FRONTEND_THEME | frontend theme to use | |
| POSTHOG_KEY | posthog key for analytics | |
| CRISP_WEBSITE_ID | crisp website id for support | |
| DJANGO_CELERY_BROKER_URL | celery broker url | redis://redis:6379/0 |
| DJANGO_CELERY_BROKER_TRANSPORT_OPTIONS | celery broker transport options | {} |
| OIDC_CREATE_USER | create used on OIDC | false |
| OIDC_RP_SIGN_ALGO | verification algorithm used OIDC tokens | RS256 |
| OIDC_RP_CLIENT_ID | client id used for OIDC | impress |
| OIDC_RP_CLIENT_SECRET | client secret used for OIDC | |
| OIDC_OP_JWKS_ENDPOINT | JWKS endpoint for OIDC | |
| LANGUAGE_CODE | Default language | en-us |
| LOGGING_LEVEL_LOGGERS_APP | Application logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO |
| LOGGING_LEVEL_LOGGERS_ROOT | Default logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO |
| LOGIN_REDIRECT_URL | Login redirect url | |
| LOGIN_REDIRECT_URL_FAILURE | Login redirect url on failure | |
| LOGOUT_REDIRECT_URL | Logout redirect url | |
| MEDIA_BASE_URL | | |
| Oidc_ALLOW_DUPLICATE_EMAILS | Allow dupplicate emails | false |
| OIDC_AUTH_REQUEST_EXTRA_PARAMS | OIDC extra auth paramaters | {} |
| OIDC_CREATE_USER | Create used on OIDC | false |
| OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION | Fallback to email for identification | true |
| OIDC_OP_AUTHORIZATION_ENDPOINT | Autorization endpoint for OIDC | |
| OIDC_OP_JWKS_ENDPOINT | JWKS endpoint for OIDC | |
| OIDC_OP_LOGOUT_ENDPOINT | Logout endpoint for OIDC | |
| OIDC_OP_TOKEN_ENDPOINT | Token endpoint for OIDC | |
| OIDC_OP_USER_ENDPOINT | User endpoint for OIDC | |
| OIDC_OP_LOGOUT_ENDPOINT | Logout endpoint for OIDC | |
| OIDC_AUTH_REQUEST_EXTRA_PARAMS | OIDC extra auth paramaters | {} |
| OIDC_RP_SCOPES | scopes requested for OIDC | openid email |
| LOGIN_REDIRECT_URL | login redirect url | |
| LOGIN_REDIRECT_URL_FAILURE | login redirect url on failure | |
| LOGOUT_REDIRECT_URL | logout redirect url | |
| OIDC_USE_NONCE | use nonce for OIDC | true |
| OIDC_REDIRECT_REQUIRE_HTTPS | Require https for OIDC redirect url | false |
| OIDC_REDIRECT_ALLOWED_HOSTS | Allowed hosts for OIDC redirect url | [] |
| OIDC_REDIRECT_REQUIRE_HTTPS | Require https for OIDC redirect url | false |
| OIDC_RP_CLIENT_ID | Client id used for OIDC | impress |
| OIDC_RP_CLIENT_SECRET | Client secret used for OIDC | |
| OIDC_RP_SCOPES | Scopes requested for OIDC | openid email |
| OIDC_RP_SIGN_ALGO | Verification algorithm used OIDC tokens | RS256 |
| OIDC_STORE_ID_TOKEN | Store OIDC token | true |
| OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION | faillback to email for identification | true |
| OIDC_ALLOW_DUPLICATE_EMAILS | Allow dupplicate emails | false |
| USER_OIDC_ESSENTIAL_CLAIMS | essential claims in OIDC token | [] |
| OIDC_USE_NONCE | Use nonce for OIDC | true |
| OIDC_USERINFO_FULLNAME_FIELDS | OIDC token claims to create full name | ["first_name", "last_name"] |
| OIDC_USERINFO_SHORTNAME_FIELD | OIDC token claims to create shortname | first_name |
| ALLOW_LOGOUT_GET_METHOD | Allow get logout method | true |
| AI_API_KEY | AI key to be used for AI Base url | |
| AI_BASE_URL | OpenAI compatible AI base url | |
| AI_MODEL | AI Model to use | |
| AI_ALLOW_REACH_FROM | Users that can use AI must be this level. options are "public", "authenticated", "restricted" | authenticated |
| AI_FEATURE_ENABLED | Enable AI options | false |
| Y_PROVIDER_API_KEY | Y provider API key | |
| POSTHOG_KEY | Posthog key for analytics | |
| REDIS_URL | Cache url | redis://redis:6379/1 |
| SENTRY_DSN | Sentry host | |
| SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK | | false |
| STORAGES_STATICFILES_BACKEND | | whitenoise.storage.CompressedManifestStaticFilesStorage |
| TRASHBIN_CUTOFF_DAYS | Trashbin cutoff | 30 |
| USER_OIDC_ESSENTIAL_CLAIMS | Essential claims in OIDC token | [] |
| Y_PROVIDER_API_BASE_URL | Y Provider url | |
| CONVERSION_API_ENDPOINT | Conversion API endpoint | convert-markdown |
| CONVERSION_API_CONTENT_FIELD | Conversion api content field | content |
| CONVERSION_API_TIMEOUT | Conversion api timeout | 30 |
| CONVERSION_API_SECURE | Require secure conversion api | false |
| LOGGING_LEVEL_LOGGERS_ROOT | default logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO |
| LOGGING_LEVEL_LOGGERS_APP | application logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO |
| API_USERS_LIST_LIMIT | Limit on API users | 5 |
| DJANGO_CSRF_TRUSTED_ORIGINS | CSRF trusted origins | [] |
| REDIS_URL | cache url | redis://redis:6379/1 |
| CACHES_DEFAULT_TIMEOUT | cache default timeout | 30 |
| Y_PROVIDER_API_KEY | Y provider API key | |
+1
View File
@@ -64,5 +64,6 @@ COLLABORATION_WS_URL=ws://localhost:4444/collaboration/ws/
# Frontend
FRONTEND_THEME=default
FRONTEND_HOMEPAGE_FEATURE_ENABLED=True
FRONTEND_FOOTER_FEATURE_ENABLED=True
FRONTEND_URL_JSON_FOOTER=http://frontend:3000/contents/footer-demo.json
+6 -9
View File
@@ -11,16 +11,14 @@
},
{
"groupName": "allowed django versions",
"matchManagers": ["pep621"],
"matchPackageNames": ["Django"],
"matchManagers": [
"pep621"
],
"matchPackageNames": [
"Django"
],
"allowedVersions": "<5.2"
},
{
"groupName": "allowed redis versions",
"matchManagers": ["pep621"],
"matchPackageNames": ["redis"],
"allowedVersions": "<6.0.0"
},
{
"enabled": false,
"groupName": "ignored js dependencies",
@@ -28,7 +26,6 @@
"matchPackageNames": [
"eslint",
"fetch-mock",
"prosemirror-model",
"node",
"node-fetch",
"workbox-webpack-plugin"
@@ -19,6 +19,7 @@ pytestmark = pytest.mark.django_db
COLLABORATION_WS_URL="http://testcollab/",
CRISP_WEBSITE_ID="123",
FRONTEND_CSS_URL="http://testcss/",
FRONTEND_HOMEPAGE_FEATURE_ENABLED=True,
FRONTEND_FOOTER_FEATURE_ENABLED=True,
FRONTEND_THEME="test-theme",
MEDIA_BASE_URL="http://testserver/",
+1 -1
View File
@@ -418,7 +418,7 @@ class Base(Configuration):
None, environ_name="FRONTEND_THEME", environ_prefix=None
)
FRONTEND_HOMEPAGE_FEATURE_ENABLED = values.BooleanValue(
default=True,
default=False,
environ_name="FRONTEND_HOMEPAGE_FEATURE_ENABLED",
environ_prefix=None,
)
+16 -16
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "impress"
version = "3.2.1"
version = "3.1.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -25,20 +25,21 @@ license = { file = "LICENSE" }
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"beautifulsoup4==4.13.4",
"boto3==1.38.9",
"beautifulsoup4==4.13.3",
"boto3==1.37.33",
"Brotli==1.1.0",
"celery[redis]==5.5.2",
"celery[redis]==5.5.1",
"django-configurations==2.5.1",
"django-cors-headers==4.7.0",
"django-countries==7.6.1",
"django-filter==25.1",
"django-lasuite==0.0.7",
"django-parler==2.3",
"redis==5.2.1",
"django-redis==5.4.0",
"django-storages[s3]==1.14.6",
"django-timezone-field>=5.1",
"django==5.1.9",
"django==5.1.8",
"django-treebeard==4.7.1",
"djangorestframework==3.16.0",
"drf_spectacular==0.28.0",
@@ -47,18 +48,17 @@ dependencies = [
"factory_boy==3.3.3",
"gunicorn==23.0.0",
"jsonschema==4.23.0",
"lxml==5.4.0",
"lxml==5.3.2",
"markdown==3.8",
"mozilla-django-oidc==4.0.1",
"nested-multipart-parser==1.5.0",
"openai==1.77.0",
"psycopg[binary]==3.2.7",
"pycrdt==0.12.15",
"openai==1.73.0",
"psycopg[binary]==3.2.6",
"pycrdt==0.12.12",
"PyJWT==2.10.1",
"python-magic==0.4.27",
"redis<6.0.0",
"requests==2.32.3",
"sentry-sdk==2.27.0",
"sentry-sdk==2.25.1",
"whitenoise==6.9.0",
]
@@ -71,21 +71,21 @@ dependencies = [
[project.optional-dependencies]
dev = [
"django-extensions==4.1",
"django-test-migrations==1.5.0",
"drf-spectacular-sidecar==2025.5.1",
"django-test-migrations==1.4.0",
"drf-spectacular-sidecar==2025.4.1",
"freezegun==1.5.1",
"ipdb==0.13.13",
"ipython==9.2.0",
"ipython==9.1.0",
"pyfakefs==5.8.0",
"pylint-django==2.6.1",
"pylint==3.3.7",
"pylint==3.3.6",
"pytest-cov==6.1.1",
"pytest-django==4.11.1",
"pytest==8.3.5",
"pytest-icdiff==0.9",
"pytest-xdist==3.6.1",
"responses==0.25.7",
"ruff==0.11.8",
"ruff==0.11.5",
"types-requests==2.32.0.20250328",
]
-3
View File
@@ -39,9 +39,6 @@ ENV NEXT_PUBLIC_API_ORIGIN=${API_ORIGIN}
ARG SW_DEACTIVATED
ENV NEXT_PUBLIC_SW_DEACTIVATED=${SW_DEACTIVATED}
ARG PUBLISH_AS_MIT
ENV NEXT_PUBLIC_PUBLISH_AS_MIT=${PUBLISH_AS_MIT}
RUN yarn build
# ---- Front-end image ----
@@ -452,41 +452,4 @@ test.describe('Doc Editor', () => {
const svgBuffer = await cs.toBuffer(await download.createReadStream());
expect(svgBuffer.toString()).toContain('Hello svg');
});
test('it checks if callout custom block', async ({ page, browserName }) => {
await createDoc(page, 'doc-toolbar', browserName, 1);
const editor = page.locator('.ProseMirror');
await editor.click();
await page.locator('.bn-block-outer').last().fill('/');
await page.getByText('Add a callout block').click();
const calloutBlock = page
.locator('div[data-content-type="callout"]')
.first();
await expect(calloutBlock).toBeVisible();
await calloutBlock.locator('.inline-content').fill('example text');
await expect(page.locator('.bn-block').first()).toHaveAttribute(
'data-background-color',
'yellow',
);
const emojiButton = calloutBlock.getByRole('button');
await expect(emojiButton).toHaveText('💡');
await emojiButton.click();
await page.locator('button[aria-label="⚠️"]').click();
await expect(emojiButton).toHaveText('⚠️');
await page.locator('.bn-side-menu > button').last().click();
await page.locator('.mantine-Menu-dropdown > button').last().click();
await page.locator('.bn-color-picker-dropdown > button').last().click();
await expect(page.locator('.bn-block').first()).toHaveAttribute(
'data-background-color',
'pink',
);
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "app-e2e",
"version": "3.2.1",
"version": "3.1.0",
"private": true,
"scripts": {
"lint": "eslint . --ext .ts",
-1
View File
@@ -1,3 +1,2 @@
NEXT_PUBLIC_API_ORIGIN=
NEXT_PUBLIC_SW_DEACTIVATED=
NEXT_PUBLIC_PUBLISH_AS_MIT=true
@@ -1,3 +1,2 @@
NEXT_PUBLIC_API_ORIGIN=http://localhost:8071
NEXT_PUBLIC_PUBLISH_AS_MIT=false
NEXT_PUBLIC_SW_DEACTIVATED=true
+4 -1
View File
@@ -48,7 +48,10 @@ const nextConfig = {
swDest: '../public/service-worker.js',
include: [
({ asset }) => {
return !!asset.name.match(/.*(static).*/);
if (asset.name.match(/.*(static).*/)) {
return true;
}
return false;
},
],
}),
+8 -11
View File
@@ -1,6 +1,6 @@
{
"name": "app-impress",
"version": "3.2.1",
"version": "3.1.0",
"private": true,
"scripts": {
"dev": "next dev",
@@ -22,29 +22,26 @@
"@blocknote/react": "0.29.1",
"@blocknote/xl-docx-exporter": "0.29.1",
"@blocknote/xl-pdf-exporter": "0.29.1",
"@emoji-mart/data": "1.2.1",
"@emoji-mart/react": "1.1.1",
"@fontsource/material-icons": "5.2.5",
"@gouvfr-lasuite/integration": "1.0.3",
"@gouvfr-lasuite/ui-kit": "0.4.1",
"@hocuspocus/provider": "2.15.2",
"@openfun/cunningham-react": "3.0.0",
"@react-pdf/renderer": "4.3.0",
"@sentry/nextjs": "9.15.0",
"@tanstack/react-query": "5.75.4",
"@sentry/nextjs": "9.14.0",
"@tanstack/react-query": "5.74.9",
"canvg": "4.0.3",
"clsx": "2.1.1",
"cmdk": "1.1.1",
"crisp-sdk-web": "1.0.25",
"docx": "9.4.1",
"emoji-mart": "5.6.0",
"i18next": "25.1.1",
"i18next-browser-languagedetector": "8.1.0",
"i18next": "25.0.2",
"i18next-browser-languagedetector": "8.0.5",
"idb": "8.0.2",
"lodash": "4.17.21",
"luxon": "3.6.1",
"next": "15.3.1",
"posthog-js": "1.239.1",
"posthog-js": "1.236.8",
"react": "*",
"react-aria-components": "1.8.0",
"react-dom": "*",
@@ -55,11 +52,11 @@
"use-debounce": "10.0.4",
"y-protocols": "1.0.6",
"yjs": "*",
"zustand": "5.0.4"
"zustand": "5.0.3"
},
"devDependencies": {
"@svgr/webpack": "8.1.0",
"@tanstack/react-query-devtools": "5.75.4",
"@tanstack/react-query-devtools": "5.74.9",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.3.0",
+1 -29
View File
@@ -1,39 +1,17 @@
/**
* Generic interface for representing an API error structure.
*
* @template T - Optional type of additional data returned with the error.
*/
interface IAPIError<T = unknown> {
/** HTTP status code or API-defined error code */
status: number;
/** Optional list of error causes (e.g., validation issues) */
cause?: string[];
/** Optional extra data provided with the error */
data?: T;
}
/**
* Custom error class for representing API errors.
* Extends the native Error object with additional context such as HTTP status,
* causes, and extra data returned by the API.
*
* @template T - Optional type of the `data` field
*/
export class APIError<T = unknown> extends Error implements IAPIError<T> {
public status: IAPIError['status'];
public cause?: IAPIError['cause'];
public data?: IAPIError<T>['data'];
/**
* Constructs a new APIError instance.
*
* @param message - The human-readable error message.
* @param status - The HTTP status code or equivalent.
* @param cause - (Optional) List of strings describing error causes.
* @param data - (Optional) Any additional data returned by the API.
*/
constructor(message: string, { status, cause, data }: IAPIError<T>) {
super(message);
this.name = 'APIError';
this.status = status;
this.cause = cause;
@@ -41,12 +19,6 @@ export class APIError<T = unknown> extends Error implements IAPIError<T> {
}
}
/**
* Type guard for checking if a value is an instance of APIError.
*
* @param error - The value to check.
* @returns True if the value is an instance of APIError.
*/
export const isAPIError = (error: unknown): error is APIError => {
return error instanceof APIError;
};
@@ -1,36 +0,0 @@
import { APIError, isAPIError } from '@/api';
describe('APIError', () => {
it('should correctly instantiate with required fields', () => {
const error = new APIError('Something went wrong', { status: 500 });
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(APIError);
expect(error.message).toBe('Something went wrong');
expect(error.status).toBe(500);
expect(error.cause).toBeUndefined();
expect(error.data).toBeUndefined();
});
it('should correctly instantiate with all fields', () => {
const details = { field: 'email' };
const error = new APIError('Validation failed', {
status: 400,
cause: ['Invalid email format'],
data: details,
});
expect(error.name).toBe('APIError');
expect(error.status).toBe(400);
expect(error.cause).toEqual(['Invalid email format']);
expect(error.data).toEqual(details);
});
it('should be detected by isAPIError type guard', () => {
const error = new APIError('Unauthorized', { status: 401 });
const notAnError = { message: 'Fake error' };
expect(isAPIError(error)).toBe(true);
expect(isAPIError(notAnError)).toBe(false);
});
});
@@ -1,16 +0,0 @@
import { baseApiUrl } from '@/api';
describe('config', () => {
it('constructs URL with default version', () => {
expect(baseApiUrl()).toBe('http://test.jest/api/v1.0/');
});
it('constructs URL with custom version', () => {
expect(baseApiUrl('2.0')).toBe('http://test.jest/api/v2.0/');
});
it('uses env origin if available', () => {
process.env.NEXT_PUBLIC_API_ORIGIN = 'https://env.example.com';
expect(baseApiUrl('3.0')).toBe('https://env.example.com/api/v3.0/');
});
});
@@ -36,13 +36,4 @@ describe('fetchAPI', () => {
expect(fetchMock.lastUrl()).toEqual('http://test.jest/api/v2.0/some/url');
});
it('removes Content-Type header when withoutContentType is true', async () => {
fetchMock.mock('http://test.jest/api/v1.0/some/url', 200);
await fetchAPI('some/url', { withoutContentType: true });
const options = fetchMock.lastOptions();
expect(options?.headers).not.toHaveProperty('Content-Type');
});
});
@@ -1,59 +0,0 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { renderHook, waitFor } from '@testing-library/react';
import { useAPIInfiniteQuery } from '@/api';
interface DummyItem {
id: number;
}
interface DummyResponse {
results: DummyItem[];
next?: string;
}
const createWrapper = () => {
const queryClient = new QueryClient();
return ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
describe('helpers', () => {
it('fetches and paginates correctly', async () => {
const mockAPI = jest
.fn<Promise<DummyResponse>, [{ page: number; query: string }]>()
.mockResolvedValueOnce({
results: [{ id: 1 }],
next: 'url?page=2',
})
.mockResolvedValueOnce({
results: [{ id: 2 }],
next: undefined,
});
const { result } = renderHook(
() => useAPIInfiniteQuery('test-key', mockAPI, { query: 'test' }),
{ wrapper: createWrapper() },
);
// Wait for first page
await waitFor(() => {
expect(result.current.data?.pages[0].results[0].id).toBe(1);
});
// Fetch next page
await result.current.fetchNextPage();
await waitFor(() => {
expect(result.current.data?.pages.length).toBe(2);
});
await waitFor(() => {
expect(result.current.data?.pages[1].results[0].id).toBe(2);
});
expect(mockAPI).toHaveBeenCalledWith({ query: 'test', page: 1 });
expect(mockAPI).toHaveBeenCalledWith({ query: 'test', page: 2 });
});
});
@@ -1,57 +0,0 @@
import { errorCauses, getCSRFToken } from '@/api';
describe('utils', () => {
describe('errorCauses', () => {
const createMockResponse = (jsonData: any, status = 400): Response => {
return {
status,
json: () => jsonData,
} as unknown as Response;
};
it('parses multiple string causes from error body', async () => {
const mockResponse = createMockResponse(
{
field: ['error message 1', 'error message 2'],
},
400,
);
const result = await errorCauses(mockResponse, { context: 'login' });
expect(result.status).toBe(400);
expect(result.cause).toEqual(['error message 1', 'error message 2']);
expect(result.data).toEqual({ context: 'login' });
});
it('returns undefined causes if no JSON body', async () => {
const mockResponse = createMockResponse(null, 500);
const result = await errorCauses(mockResponse);
expect(result.status).toBe(500);
expect(result.cause).toBeUndefined();
expect(result.data).toBeUndefined();
});
});
describe('getCSRFToken', () => {
it('extracts csrftoken from document.cookie', () => {
Object.defineProperty(document, 'cookie', {
writable: true,
value: 'sessionid=xyz; csrftoken=abc123; theme=dark',
});
expect(getCSRFToken()).toBe('abc123');
});
it('returns undefined if csrftoken is not present', () => {
Object.defineProperty(document, 'cookie', {
writable: true,
value: 'sessionid=xyz; theme=dark',
});
expect(getCSRFToken()).toBeUndefined();
});
});
});
@@ -1,22 +1,6 @@
/**
* Returns the base URL for the backend API.
*
* Priority:
* 1. Uses NEXT_PUBLIC_API_ORIGIN from environment variables if defined.
* 2. Falls back to the browser's window.location.origin if in a browser environment.
* 3. Defaults to an empty string if executed in a non-browser environment without the env variable.
*
* @returns The backend base URL as a string.
*/
export const backendUrl = () =>
process.env.NEXT_PUBLIC_API_ORIGIN ||
(typeof window !== 'undefined' ? window.location.origin : '');
/**
* Constructs the full base API URL, including the versioned path (e.g., `/api/v1.0/`).
*
* @param apiVersion - The version of the API (defaults to '1.0').
* @returns The full versioned API base URL as a string.
*/
export const baseApiUrl = (apiVersion: string = '1.0') =>
`${backendUrl()}/api/v${apiVersion}/`;
@@ -23,9 +23,11 @@ export const fetchAPI = async (
delete headers?.['Content-Type' as keyof typeof headers];
}
return await fetch(apiUrl, {
const response = await fetch(apiUrl, {
...init,
credentials: 'include',
headers,
});
return response;
};
+3 -11
View File
@@ -22,17 +22,9 @@ export type DefinedInitialDataInfiniteOptionsAPI<
>;
/**
* Custom React hook that wraps React Query's `useInfiniteQuery` for paginated API requests.
*
* @template T - Type of the request parameters.
* @template Q - Type of the API response, which must include an optional `next` field for pagination.
*
* @param {string} key - Unique key to identify the query in the cache.
* @param {(props: T & { page: number }) => Promise<Q>} api - Function that fetches paginated data from the API. It receives the params merged with a page number.
* @param {T} param - Static parameters to send with every API request (excluding the page number).
* @param {DefinedInitialDataInfiniteOptionsAPI<Q>} [queryConfig] - Optional configuration passed to `useInfiniteQuery` (e.g., stale time, cache time).
*
* @returns Return value of `useInfiniteQuery`, including data, loading state, fetchNextPage, etc.
* @param param Used for infinite scroll pagination
* @param queryConfig
* @returns
*/
export const useAPIInfiniteQuery = <T, Q extends { next?: APIList<Q>['next'] }>(
key: string,
@@ -1,20 +1,6 @@
/**
* Generic interface representing a paginated API response.
*
* Commonly used for endpoints that return list results with pagination metadata.
*
* @template T - The type of items in the `results` array.
*/
export interface APIList<T> {
/** Total number of items across all pages */
count: number;
/** URL to the next page of results, if available (can be null or undefined) */
next?: string | null;
/** URL to the previous page of results, if available (can be null or undefined) */
previous?: string | null;
/** The list of items for the current page */
results: T[];
}
+1 -18
View File
@@ -1,16 +1,3 @@
/**
* Extracts error information from an HTTP `Response` object.
*
* This is typically used to parse structured error responses from an API
* and normalize them into a consistent format with `status`, `cause`, and optional `data`.
*
* @param response - The HTTP response object from `fetch()`.
* @param data - Optional custom data to include with the error output.
* @returns An object containing:
* - `status`: HTTP status code from the response
* - `cause`: A flattened list of error messages, or undefined if no body
* - `data`: The optional data passed in
*/
export const errorCauses = async (response: Response, data?: unknown) => {
const errorsBody = (await response.json()) as Record<
string,
@@ -31,11 +18,7 @@ export const errorCauses = async (response: Response, data?: unknown) => {
};
/**
* Retrieves the CSRF token from the browser's cookies.
*
* Assumes the CSRF token is stored as a cookie named "csrftoken".
*
* @returns The CSRF token string if found, otherwise `undefined`.
* Retrieves the CSRF token from the document's cookies.
*/
export function getCSRFToken() {
return document.cookie
@@ -35,7 +35,8 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
);
useEffect(() => {
return initializeResizeListener();
const cleanupResizeListener = initializeResizeListener();
return cleanupResizeListener;
}, [initializeResizeListener]);
useEffect(() => {
@@ -53,11 +53,13 @@ export function useConfig() {
const cachedData = getCachedTranslation();
const oneHour = 1000 * 60 * 60;
return useQuery<ConfigResponse, APIError, ConfigResponse>({
const response = useQuery<ConfigResponse, APIError, ConfigResponse>({
queryKey: [KEY_CONFIG],
queryFn: () => getConfig(),
initialData: cachedData,
staleTime: oneHour,
initialDataUpdatedAt: Date.now() - oneHour, // Force initial data to be considered stale
});
return response;
}
-1
View File
@@ -20,7 +20,6 @@ declare module '*.svg?url' {
namespace NodeJS {
interface ProcessEnv {
NEXT_PUBLIC_API_ORIGIN?: string;
NEXT_PUBLIC_PUBLISH_AS_MIT?: string;
NEXT_PUBLIC_SW_DEACTIVATED?: string;
}
}
@@ -27,13 +27,12 @@ import { randomColor } from '../utils';
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar';
import { CalloutBlock, DividerBlock } from './custom-blocks';
import { DividerBlock } from './custom-blocks';
export const blockNoteSchema = withPageBreak(
BlockNoteSchema.create({
blockSpecs: {
...defaultBlockSpecs,
callout: CalloutBlock,
divider: DividerBlock,
},
}),
@@ -11,10 +11,7 @@ import { useTranslation } from 'react-i18next';
import { DocsBlockSchema } from '../types';
import {
getCalloutReactSlashMenuItems,
getDividerReactSlashMenuItems,
} from './custom-blocks';
import { getDividerReactSlashMenuItems } from './custom-blocks';
export const BlockNoteSuggestionMenu = () => {
const editor = useBlockNoteEditor<DocsBlockSchema>();
@@ -28,7 +25,6 @@ export const BlockNoteSuggestionMenu = () => {
combineByGroup(
getDefaultReactSlashMenuItems(editor),
getPageBreakReactSlashMenuItems(editor),
getCalloutReactSlashMenuItems(editor, t, basicBlocksName),
getDividerReactSlashMenuItems(editor, t, basicBlocksName),
),
query,
@@ -6,12 +6,9 @@ import {
useDictionary,
} from '@blocknote/react';
import React, { JSX, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useConfig } from '@/core/config/api';
import { getCalloutFormattingToolbarItems } from '../custom-blocks';
import { AIGroupButton } from './AIButton';
import { FileDownloadButton } from './FileDownloadButton';
import { MarkdownButton } from './MarkdownButton';
@@ -21,13 +18,11 @@ export const BlockNoteToolbar = () => {
const dict = useDictionary();
const [confirmOpen, setIsConfirmOpen] = useState(false);
const [onConfirm, setOnConfirm] = useState<() => void | Promise<void>>();
const { t } = useTranslation();
const { data: conf } = useConfig();
const toolbarItems = useMemo(() => {
const toolbarItems = getFormattingToolbarItems([
...blockTypeSelectItems(dict),
getCalloutFormattingToolbarItems(t),
]);
const fileDownloadButtonIndex = toolbarItems.findIndex(
(item) =>
@@ -51,7 +46,7 @@ export const BlockNoteToolbar = () => {
}
return toolbarItems as JSX.Element[];
}, [dict, t]);
}, [dict]);
const formattingToolbar = useCallback(() => {
return (
@@ -1,43 +0,0 @@
import data from '@emoji-mart/data';
import Picker from '@emoji-mart/react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Box } from '@/components';
interface EmojiPickerProps {
categories: string[];
custom: {
name: string;
id: string;
emojis: string[];
}[];
onClickOutside: () => void;
onEmojiSelect: ({ native }: { native: string }) => void;
}
export const EmojiPicker = ({
categories,
custom,
onClickOutside,
onEmojiSelect,
}: EmojiPickerProps) => {
const { i18n } = useTranslation();
return (
<Box $position="absolute" $zIndex={1000} $margin="2rem 0 0 0">
<Picker
categories={categories}
custom={custom}
data={data}
locale={i18n.resolvedLanguage}
navPosition="none"
onClickOutside={onClickOutside}
onEmojiSelect={onEmojiSelect}
previewPosition="none"
skinTonePosition="none"
theme="light"
/>
</Box>
);
};
@@ -1,166 +0,0 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { defaultProps, insertOrUpdateBlock } from '@blocknote/core';
import { BlockTypeSelectItem, createReactBlockSpec } from '@blocknote/react';
import { TFunction } from 'i18next';
import React, { useEffect, useState } from 'react';
import { css } from 'styled-components';
import { Box, BoxButton, Icon } from '@/components';
import { DocsBlockNoteEditor } from '../../types';
import { EmojiPicker } from '../EmojiPicker';
const calloutCustom = [
{
name: 'Callout',
id: 'callout',
emojis: [
'bulb',
'point_right',
'point_up',
'ok_hand',
'key',
'construction',
'warning',
'fire',
'pushpin',
'scissors',
'question',
'no_entry',
'no_entry_sign',
'alarm_clock',
'phone',
'rotating_light',
'recycle',
'white_check_mark',
'lock',
'paperclip',
'book',
'speaking_head_in_silhouette',
'arrow_right',
'loudspeaker',
'hammer_and_wrench',
'gear',
],
},
];
const calloutCategories = [
'callout',
'people',
'nature',
'foods',
'activity',
'places',
'flags',
'objects',
'symbols',
];
export const CalloutBlock = createReactBlockSpec(
{
type: 'callout',
propSchema: {
textAlignment: defaultProps.textAlignment,
backgroundColor: defaultProps.backgroundColor,
emoji: { default: '💡' },
},
content: 'inline',
},
{
render: ({ block, editor, contentRef }) => {
const [openEmojiPicker, setOpenEmojiPicker] = useState(false);
const toggleEmojiPicker = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
setOpenEmojiPicker(!openEmojiPicker);
};
const onClickOutside = () => setOpenEmojiPicker(false);
const onEmojiSelect = ({ native }: { native: string }) => {
editor.updateBlock(block, { props: { emoji: native } });
setOpenEmojiPicker(false);
};
// Temporary: sets a yellow background color to a callout block when added by
// the user, while keeping the colors menu on the drag handler usable for
// this custom block.
useEffect(() => {
if (
!block.content.length &&
block.props.backgroundColor === 'default'
) {
editor.updateBlock(block, { props: { backgroundColor: 'yellow' } });
}
}, [block, editor]);
return (
<Box
$padding="1rem"
$gap="0.625rem"
style={{
flexGrow: 1,
flexDirection: 'row',
}}
>
<BoxButton
contentEditable={false}
onClick={toggleEmojiPicker}
$css={css`
font-size: 1.125rem;
&:hover {
background-color: rgba(0, 0, 0, 0.1);
}
`}
$align="center"
$height="28px"
$width="28px"
$radius="4px"
>
{block.props.emoji}
</BoxButton>
{openEmojiPicker && (
<EmojiPicker
categories={calloutCategories}
custom={calloutCustom}
onClickOutside={onClickOutside}
onEmojiSelect={onEmojiSelect}
/>
)}
<Box as="p" className="inline-content" ref={contentRef} />
</Box>
);
},
},
);
export const getCalloutReactSlashMenuItems = (
editor: DocsBlockNoteEditor,
t: TFunction<'translation', undefined>,
group: string,
) => [
{
title: t('Callout'),
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: 'callout',
});
},
aliases: ['callout', 'encadré', 'hervorhebung', 'benadrukken'],
group,
icon: <Icon iconName="lightbulb" $size="18px" />,
subtext: t('Add a callout block'),
},
];
export const getCalloutFormattingToolbarItems = (
t: TFunction<'translation', undefined>,
): BlockTypeSelectItem => ({
name: t('Callout'),
type: 'callout',
icon: () => <Icon iconName="lightbulb" $size="16px" />,
isSelected: (block) => block.type === 'callout',
});
@@ -1,2 +1 @@
export * from './CalloutBlock';
export * from './DividerBlock';
@@ -31,7 +31,7 @@ const useSaveDoc = (docId: string, yDoc: Y.Doc, canSave: boolean) => {
_updatedDoc: Y.Doc,
transaction: Y.Transaction,
) => {
setIsLocalChange(transaction.local);
setIsLocalChange(transaction.local ? true : false);
};
yDoc.on('update', onUpdate);
@@ -61,23 +61,6 @@ export const cssEditor = (readonly: boolean) => css`
height: 38px;
}
/**
* Callout, Paragraph and Heading blocks
*/
.bn-block {
border-radius: var(--c--theme--spacings--3xs);
}
.bn-block-outer {
border-radius: var(--c--theme--spacings--3xs);
}
.bn-block-content[data-content-type='paragraph'],
.bn-block-content[data-content-type='heading'] {
padding: var(--c--theme--spacings--3xs) var(--c--theme--spacings--3xs);
border-radius: var(--c--theme--spacings--3xs);
}
h1 {
font-size: 1.875rem;
}
@@ -1,24 +0,0 @@
import { Paragraph, TextRun } from 'docx';
import { DocsExporterDocx } from '../types';
import { docxBlockPropsToStyles } from '../utils';
export const blockMappingCalloutDocx: DocsExporterDocx['mappings']['blockMapping']['callout'] =
(block, exporter) => {
return new Paragraph({
...docxBlockPropsToStyles(block.props, exporter.options.colors),
spacing: { before: 10, after: 10 },
children: [
new TextRun({
text: ' ',
break: 1,
}),
new TextRun(' ' + block.props.emoji + ' '),
...exporter.transformInlineContent(block.content),
new TextRun({
text: ' ',
break: 1,
}),
],
});
};
@@ -1,35 +0,0 @@
import { StyleSheet, Text, View } from '@react-pdf/renderer';
import { DocsExporterPDF } from '../types';
const styles = StyleSheet.create({
wrapper: {
width: '100%',
display: 'flex',
flexDirection: 'row',
padding: 8,
gap: 4,
},
emoji: {
fontSize: 16,
},
text: {
maxWidth: '94%',
paddingTop: 2,
},
});
export const blockMappingCalloutPDF: DocsExporterPDF['mappings']['blockMapping']['callout'] =
(block, exporter) => {
return (
<View wrap={false} style={styles.wrapper}>
<Text debug={false} style={styles.emoji}>
{block.props.emoji}
</Text>
<Text debug={false} style={styles.text}>
{' '}
{exporter.transformInlineContent(block.content)}{' '}
</Text>
</View>
);
};
@@ -1,5 +1,3 @@
export * from './calloutDocx';
export * from './calloutPDF';
export * from './dividerDocx';
export * from './dividerPDF';
export * from './headingPDF';
@@ -1,7 +1,6 @@
import { docxDefaultSchemaMappings } from '@blocknote/xl-docx-exporter';
import {
blockMappingCalloutDocx,
blockMappingDividerDocx,
blockMappingImageDocx,
blockMappingQuoteDocx,
@@ -12,7 +11,6 @@ export const docxDocsSchemaMappings: DocsExporterDocx['mappings'] = {
...docxDefaultSchemaMappings,
blockMapping: {
...docxDefaultSchemaMappings.blockMapping,
callout: blockMappingCalloutDocx,
divider: blockMappingDividerDocx,
quote: blockMappingQuoteDocx,
image: blockMappingImageDocx,
@@ -1,7 +1,6 @@
import { pdfDefaultSchemaMappings } from '@blocknote/xl-pdf-exporter';
import {
blockMappingCalloutPDF,
blockMappingDividerPDF,
blockMappingHeadingPDF,
blockMappingImagePDF,
@@ -15,7 +14,6 @@ export const pdfDocsSchemaMappings: DocsExporterPDF['mappings'] = {
...pdfDefaultSchemaMappings,
blockMapping: {
...pdfDefaultSchemaMappings.blockMapping,
callout: blockMappingCalloutPDF,
heading: blockMappingHeadingPDF,
image: blockMappingImagePDF,
paragraph: blockMappingParagraphPDF,
@@ -19,16 +19,9 @@ export function downloadFile(blob: Blob, filename: string) {
}
/**
* Converts an SVG string into a PNG image and returns it as a data URL.
*
* This function creates a canvas, parses the SVG, calculates the appropriate height
* to preserve the aspect ratio, and renders the SVG onto the canvas using Canvg.
*
* @param {string} svgText - The raw SVG markup to convert.
* @param {number} width - The desired width of the output PNG (height is auto-calculated to preserve aspect ratio).
* @returns {Promise<string>} A Promise that resolves to a PNG image encoded as a base64 data URL.
*
* @throws Will throw an error if the canvas context cannot be initialized.
* Convert SVG to PNG
* @param svgText - The SVG text to convert
* @returns The PNG data URL
*/
export async function convertSvgToPng(svgText: string, width: number) {
// Create a canvas and render the SVG onto it
@@ -42,7 +42,6 @@ const doc = {
beforeEach(() => {
Analytics.clearAnalytics();
process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false';
});
describe('DocToolBox "Copy as HTML" option', () => {
@@ -52,9 +51,7 @@ describe('DocToolBox "Copy as HTML" option', () => {
render(<DocToolBox doc={doc as any} />, {
wrapper: AppWrapper,
});
const optionsButton = await screen.findByLabelText(
'Open the document options',
);
const optionsButton = screen.getByLabelText('Open the document options');
await userEvent.click(optionsButton);
expect(await screen.findByText('Copy as HTML')).toBeInTheDocument();
});
@@ -1,28 +0,0 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { AppWrapper } from '@/tests/utils';
import { DocToolBox } from '../components/DocToolBox';
const doc = {
nb_accesses: 1,
abilities: {
versions_list: true,
destroy: true,
},
};
jest.mock('@/features/docs/doc-export/', () => ({
ModalExport: () => <span>ModalExport</span>,
}));
it('DocToolBox dynamic import: loads DocToolBox when NEXT_PUBLIC_PUBLISH_AS_MIT is false', async () => {
process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false';
render(<DocToolBox doc={doc as any} />, {
wrapper: AppWrapper,
});
expect(await screen.findByText('download')).toBeInTheDocument();
});
@@ -1,35 +0,0 @@
import { render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import { AppWrapper } from '@/tests/utils';
const doc = {
nb_accesses: 1,
abilities: {
versions_list: true,
destroy: true,
},
};
jest.mock('@/features/docs/doc-export/', () => ({
ModalExport: () => <span>ModalExport</span>,
}));
it('DocToolBox dynamic import: loads DocToolBox when NEXT_PUBLIC_PUBLISH_AS_MIT is true', async () => {
process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'true';
const { DocToolBox } = await import('../components/DocToolBox');
render(<DocToolBox doc={doc as any} />, {
wrapper: AppWrapper,
});
await waitFor(
() => {
expect(screen.queryByText('download')).not.toBeInTheDocument();
},
{
timeout: 1000,
},
);
});
@@ -1,47 +1,170 @@
import { Button, useModal } from '@openfun/cunningham-react';
import {
Button,
VariantType,
useModal,
useToastProvider,
} from '@openfun/cunningham-react';
import { useQueryClient } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, Icon } from '@/components';
import {
Box,
DropdownMenu,
DropdownMenuOption,
Icon,
IconOptions,
} from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Doc } from '@/docs/doc-management';
import { KEY_LIST_DOC_VERSIONS } from '@/docs/doc-versioning';
import { useEditorStore } from '@/docs/doc-editor/';
import { ModalExport } from '@/docs/doc-export/';
import {
Doc,
KEY_DOC,
KEY_LIST_DOC,
ModalRemoveDoc,
useCopyDocLink,
useCreateFavoriteDoc,
useDeleteFavoriteDoc,
} from '@/docs/doc-management';
import { DocShareModal } from '@/docs/doc-share';
import {
KEY_LIST_DOC_VERSIONS,
ModalSelectVersion,
} from '@/docs/doc-versioning';
import { useAnalytics } from '@/libs';
import { useResponsiveStore } from '@/stores';
interface DocToolBoxProps {
doc: Doc;
}
const DocToolBoxLicence = dynamic(() =>
process.env.NEXT_PUBLIC_PUBLISH_AS_MIT === 'false'
? import('./DocToolBoxLicenceAGPL').then((mod) => mod.DocToolBoxLicenceAGPL)
: import('./DocToolBoxLicenceMIT').then((mod) => mod.DocToolBoxLicenceMIT),
);
export const DocToolBox = ({ doc }: DocToolBoxProps) => {
const { t } = useTranslation();
const hasAccesses = doc.nb_accesses_direct > 1 && doc.abilities.accesses_view;
const queryClient = useQueryClient();
const { spacingsTokens } = useCunninghamTheme();
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const modalHistory = useModal();
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
const [isModalExportOpen, setIsModalExportOpen] = useState(false);
const selectHistoryModal = useModal();
const modalShare = useModal();
const { isSmallMobile } = useResponsiveStore();
const { isSmallMobile, isDesktop } = useResponsiveStore();
const { editor } = useEditorStore();
const { toast } = useToastProvider();
const copyDocLink = useCopyDocLink(doc.id);
const { isFeatureFlagActivated } = useAnalytics();
const removeFavoriteDoc = useDeleteFavoriteDoc({
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
});
const makeFavoriteDoc = useCreateFavoriteDoc({
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
});
const options: DropdownMenuOption[] = [
...(isSmallMobile
? [
{
label: t('Share'),
icon: 'group',
callback: modalShare.open,
},
{
label: t('Export'),
icon: 'download',
callback: () => {
setIsModalExportOpen(true);
},
},
{
label: t('Copy link'),
icon: 'add_link',
callback: copyDocLink,
},
]
: []),
{
label: doc.is_favorite ? t('Unpin') : t('Pin'),
icon: 'push_pin',
callback: () => {
if (doc.is_favorite) {
removeFavoriteDoc.mutate({ id: doc.id });
} else {
makeFavoriteDoc.mutate({ id: doc.id });
}
},
testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`,
},
{
label: t('Version history'),
icon: 'history',
disabled: !doc.abilities.versions_list,
callback: () => {
selectHistoryModal.open();
},
show: isDesktop,
},
{
label: t('Copy as {{format}}', { format: 'Markdown' }),
icon: 'content_copy',
callback: () => {
void copyCurrentEditorToClipboard('markdown');
},
},
{
label: t('Copy as {{format}}', { format: 'HTML' }),
icon: 'content_copy',
callback: () => {
void copyCurrentEditorToClipboard('html');
},
show: isFeatureFlagActivated('CopyAsHTML'),
},
{
label: t('Delete document'),
icon: 'delete',
disabled: !doc.abilities.destroy,
callback: () => {
setIsModalRemoveOpen(true);
},
},
];
const copyCurrentEditorToClipboard = async (
asFormat: 'html' | 'markdown',
) => {
if (!editor) {
toast(t('Editor unavailable'), VariantType.ERROR, { duration: 3000 });
return;
}
try {
const editorContentFormatted =
asFormat === 'html'
? await editor.blocksToHTMLLossy()
: await editor.blocksToMarkdownLossy();
await navigator.clipboard.writeText(editorContentFormatted);
toast(t('Copied to clipboard'), VariantType.SUCCESS, { duration: 3000 });
} catch (error) {
console.error(error);
toast(t('Failed to copy to clipboard'), VariantType.ERROR, {
duration: 3000,
});
}
};
useEffect(() => {
if (modalHistory.isOpen) {
if (selectHistoryModal.isOpen) {
return;
}
void queryClient.resetQueries({
queryKey: [KEY_LIST_DOC_VERSIONS],
});
}, [modalHistory.isOpen, queryClient]);
}, [selectHistoryModal.isOpen, queryClient]);
return (
<Box
@@ -99,12 +222,55 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
</>
)}
<DocToolBoxLicence
doc={doc}
modalHistory={modalHistory}
modalShare={modalShare}
/>
{!isSmallMobile && (
<Button
color="tertiary-text"
icon={
<Icon iconName="download" $theme="primary" $variation="800" />
}
onClick={() => {
setIsModalExportOpen(true);
}}
size={isSmallMobile ? 'small' : 'medium'}
/>
)}
<DropdownMenu options={options}>
<IconOptions
isHorizontal
$theme="primary"
$padding={{ all: 'xs' }}
$css={css`
border-radius: 4px;
&:hover {
background-color: ${colorsTokens['greyscale-100']};
}
${isSmallMobile
? css`
padding: 10px;
border: 1px solid ${colorsTokens['greyscale-300']};
`
: ''}
`}
aria-label={t('Open the document options')}
/>
</DropdownMenu>
</Box>
{modalShare.isOpen && (
<DocShareModal onClose={() => modalShare.close()} doc={doc} />
)}
{isModalExportOpen && (
<ModalExport onClose={() => setIsModalExportOpen(false)} doc={doc} />
)}
{isModalRemoveOpen && (
<ModalRemoveDoc onClose={() => setIsModalRemoveOpen(false)} doc={doc} />
)}
{selectHistoryModal.isOpen && (
<ModalSelectVersion
onClose={() => selectHistoryModal.close()}
doc={doc}
/>
)}
</Box>
);
};
@@ -1,192 +0,0 @@
import { Button, useModal } from '@openfun/cunningham-react';
import { useQueryClient } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import {
DropdownMenu,
DropdownMenuOption,
Icon,
IconOptions,
} from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { ModalExport } from '@/docs/doc-export/';
import {
Doc,
KEY_DOC,
KEY_LIST_DOC,
ModalRemoveDoc,
useCopyDocLink,
useCreateFavoriteDoc,
useDeleteFavoriteDoc,
} from '@/docs/doc-management';
import {
KEY_LIST_DOC_VERSIONS,
ModalSelectVersion,
} from '@/docs/doc-versioning';
import { useAnalytics } from '@/libs';
import { useResponsiveStore } from '@/stores';
import { DocShareModal } from '../../doc-share';
import { useCopyCurrentEditorToClipboard } from '../hooks/useCopyCurrentEditorToClipboard';
type ModalType = ReturnType<typeof useModal>;
interface DocToolBoxLicenceProps {
doc: Doc;
modalHistory: ModalType;
modalShare: ModalType;
}
export const DocToolBoxLicenceAGPL = ({
doc,
modalHistory,
modalShare,
}: DocToolBoxLicenceProps) => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const { colorsTokens } = useCunninghamTheme();
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
const [isModalExportOpen, setIsModalExportOpen] = useState(false);
const { isSmallMobile, isDesktop } = useResponsiveStore();
const copyDocLink = useCopyDocLink(doc.id);
const { isFeatureFlagActivated } = useAnalytics();
const removeFavoriteDoc = useDeleteFavoriteDoc({
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
});
const makeFavoriteDoc = useCreateFavoriteDoc({
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
});
const copyCurrentEditorToClipboard = useCopyCurrentEditorToClipboard();
const options: DropdownMenuOption[] = [
...(isSmallMobile
? [
{
label: t('Share'),
icon: 'group',
callback: modalShare.open,
},
{
label: t('Export'),
icon: 'download',
callback: () => {
setIsModalExportOpen(true);
},
},
{
label: t('Copy link'),
icon: 'add_link',
callback: copyDocLink,
},
]
: []),
{
label: doc.is_favorite ? t('Unpin') : t('Pin'),
icon: 'push_pin',
callback: () => {
if (doc.is_favorite) {
removeFavoriteDoc.mutate({ id: doc.id });
} else {
makeFavoriteDoc.mutate({ id: doc.id });
}
},
testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`,
},
{
label: t('Version history'),
icon: 'history',
disabled: !doc.abilities.versions_list,
callback: () => {
modalHistory.open();
},
show: isDesktop,
},
{
label: t('Copy as {{format}}', { format: 'Markdown' }),
icon: 'content_copy',
callback: () => {
void copyCurrentEditorToClipboard('markdown');
},
},
{
label: t('Copy as {{format}}', { format: 'HTML' }),
icon: 'content_copy',
callback: () => {
void copyCurrentEditorToClipboard('html');
},
show: isFeatureFlagActivated('CopyAsHTML'),
},
{
label: t('Delete document'),
icon: 'delete',
disabled: !doc.abilities.destroy,
callback: () => {
setIsModalRemoveOpen(true);
},
},
];
useEffect(() => {
if (modalHistory.isOpen) {
return;
}
void queryClient.resetQueries({
queryKey: [KEY_LIST_DOC_VERSIONS],
});
}, [modalHistory.isOpen, queryClient]);
return (
<>
{!isSmallMobile && (
<Button
color="tertiary-text"
icon={<Icon iconName="download" $theme="primary" $variation="800" />}
onClick={() => {
setIsModalExportOpen(true);
}}
size={isSmallMobile ? 'small' : 'medium'}
/>
)}
<DropdownMenu options={options}>
<IconOptions
isHorizontal
$theme="primary"
$padding={{ all: 'xs' }}
$css={css`
border-radius: 4px;
&:hover {
background-color: ${colorsTokens['greyscale-100']};
}
${isSmallMobile
? css`
padding: 10px;
border: 1px solid ${colorsTokens['greyscale-300']};
`
: ''}
`}
aria-label={t('Open the document options')}
/>
</DropdownMenu>
{modalShare.isOpen && (
<DocShareModal onClose={() => modalShare.close()} doc={doc} />
)}
{isModalExportOpen && (
<ModalExport onClose={() => setIsModalExportOpen(false)} doc={doc} />
)}
{isModalRemoveOpen && (
<ModalRemoveDoc onClose={() => setIsModalRemoveOpen(false)} doc={doc} />
)}
{modalHistory.isOpen && (
<ModalSelectVersion onClose={() => modalHistory.close()} doc={doc} />
)}
</>
);
};
@@ -1,165 +0,0 @@
import { useModal } from '@openfun/cunningham-react';
import { useQueryClient } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { DropdownMenu, DropdownMenuOption, IconOptions } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import {
Doc,
KEY_DOC,
KEY_LIST_DOC,
ModalRemoveDoc,
useCopyDocLink,
useCreateFavoriteDoc,
useDeleteFavoriteDoc,
} from '@/docs/doc-management';
import {
KEY_LIST_DOC_VERSIONS,
ModalSelectVersion,
} from '@/docs/doc-versioning';
import { useAnalytics } from '@/libs';
import { useResponsiveStore } from '@/stores';
import { DocShareModal } from '../../doc-share';
import { useCopyCurrentEditorToClipboard } from '../hooks/useCopyCurrentEditorToClipboard';
type ModalType = ReturnType<typeof useModal>;
interface DocToolBoxLicenceProps {
doc: Doc;
modalHistory: ModalType;
modalShare: ModalType;
}
export const DocToolBoxLicenceMIT = ({
doc,
modalHistory,
modalShare,
}: DocToolBoxLicenceProps) => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const { colorsTokens } = useCunninghamTheme();
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
const { isSmallMobile, isDesktop } = useResponsiveStore();
const copyDocLink = useCopyDocLink(doc.id);
const { isFeatureFlagActivated } = useAnalytics();
const removeFavoriteDoc = useDeleteFavoriteDoc({
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
});
const makeFavoriteDoc = useCreateFavoriteDoc({
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
});
const copyCurrentEditorToClipboard = useCopyCurrentEditorToClipboard();
const options: DropdownMenuOption[] = [
...(isSmallMobile
? [
{
label: t('Share'),
icon: 'group',
callback: modalShare.open,
},
{
label: t('Copy link'),
icon: 'add_link',
callback: copyDocLink,
},
]
: []),
{
label: doc.is_favorite ? t('Unpin') : t('Pin'),
icon: 'push_pin',
callback: () => {
if (doc.is_favorite) {
removeFavoriteDoc.mutate({ id: doc.id });
} else {
makeFavoriteDoc.mutate({ id: doc.id });
}
},
testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`,
},
{
label: t('Version history'),
icon: 'history',
disabled: !doc.abilities.versions_list,
callback: () => {
modalHistory.open();
},
show: isDesktop,
},
{
label: t('Copy as {{format}}', { format: 'Markdown' }),
icon: 'content_copy',
callback: () => {
void copyCurrentEditorToClipboard('markdown');
},
},
{
label: t('Copy as {{format}}', { format: 'HTML' }),
icon: 'content_copy',
callback: () => {
void copyCurrentEditorToClipboard('html');
},
show: isFeatureFlagActivated('CopyAsHTML'),
},
{
label: t('Delete document'),
icon: 'delete',
disabled: !doc.abilities.destroy,
callback: () => {
setIsModalRemoveOpen(true);
},
},
];
useEffect(() => {
if (modalHistory.isOpen) {
return;
}
void queryClient.resetQueries({
queryKey: [KEY_LIST_DOC_VERSIONS],
});
}, [modalHistory.isOpen, queryClient]);
return (
<>
<DropdownMenu options={options}>
<IconOptions
isHorizontal
$theme="primary"
$padding={{ all: 'xs' }}
$css={css`
border-radius: 4px;
&:hover {
background-color: ${colorsTokens['greyscale-100']};
}
${isSmallMobile
? css`
padding: 10px;
border: 1px solid ${colorsTokens['greyscale-300']};
`
: ''}
`}
aria-label={t('Open the document options')}
/>
</DropdownMenu>
{modalShare.isOpen && (
<DocShareModal onClose={() => modalShare.close()} doc={doc} />
)}
{isModalRemoveOpen && (
<ModalRemoveDoc onClose={() => setIsModalRemoveOpen(false)} doc={doc} />
)}
{modalHistory.isOpen && (
<ModalSelectVersion onClose={() => modalHistory.close()} doc={doc} />
)}
</>
);
};
@@ -1,31 +0,0 @@
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
import { useTranslation } from 'react-i18next';
import { useEditorStore } from '../../doc-editor';
export const useCopyCurrentEditorToClipboard = () => {
const { editor } = useEditorStore();
const { toast } = useToastProvider();
const { t } = useTranslation();
return async (asFormat: 'html' | 'markdown') => {
if (!editor) {
toast(t('Editor unavailable'), VariantType.ERROR, { duration: 3000 });
return;
}
try {
const editorContentFormatted =
asFormat === 'html'
? await editor.blocksToHTMLLossy()
: await editor.blocksToMarkdownLossy();
await navigator.clipboard.writeText(editorContentFormatted);
toast(t('Copied to clipboard'), VariantType.SUCCESS, { duration: 3000 });
} catch (error) {
console.error(error);
toast(t('Failed to copy to clipboard'), VariantType.ERROR, {
duration: 3000,
});
}
};
};
@@ -38,15 +38,11 @@ setCacheNameDetails({
});
/**
* Chooses the appropriate caching strategy based on the environment and request context.
*
* - In **development**, or for **API requests**, or **HTML pages**, it returns a `NetworkFirst` strategy
* to prioritize fresh responses and ease debugging without needing to clear caches.
* - In **production** (for non-API, non-HTML content), it returns a `CacheFirst` strategy
* to favor performance and offline access.
*
* @param {NetworkFirstOptions | StrategyOptions} [options] - Configuration options for the caching strategy.
* @returns {NetworkFirst | CacheFirst} The selected Workbox strategy instance.
* In development, use NetworkFirst strategy, in production use CacheFirst strategy
* We will be able to test the application in development without having to clear the cache.
* @param url
* @param options
* @returns strategy
*/
const getStrategy = (
options?: NetworkFirstOptions | StrategyOptions,
@@ -53,7 +53,6 @@
"Accessible to anyone": "Für alle zugänglich",
"Accessible to authenticated users": "Für authentifizierte Benutzer zugänglich",
"Add": "Hinzufügen",
"Add a callout block": "Hebt schrift hervor",
"Add a horizontal line": "Waagerechte Linie einfügen",
"Address:": "Anschrift:",
"Administrator": "Administrator",
@@ -68,7 +67,6 @@
"Available soon": "Bald verfügbar",
"Banner image": "Bannerbild",
"Beautify": "Verschönern",
"Callout": "Hervorhebung",
"Can't load this page, please check your internet connection.": "Diese Seite kann nicht geladen werden. Bitte überprüfen Sie Ihre Internetverbindung.",
"Cancel": "Abbrechen",
"Close the modal": "Pop up schliessen",
@@ -268,9 +266,7 @@
},
"en": {
"translation": {
"Share with {{count}} users_one": "Share with {{count}} user",
"Shared with {{count}} users_many": "Shared with {{count}} users",
"Shared with {{count}} users_one": "Shared with {{count}} user",
"Shared with {{count}} users_other": "Shared with {{count}} users"
}
},
@@ -286,7 +282,6 @@
"Accessible to anyone": "Accesible para todos",
"Accessible to authenticated users": "Accesible a usuarios autenticados",
"Add": "Añadir",
"Add a callout block": "Resaltar el texto para que destaque",
"Add a horizontal line": "Añadir una línea horizontal",
"Address:": "Dirección:",
"Administrator": "Administrador",
@@ -301,7 +296,6 @@
"Available soon": "Próximamente disponible",
"Banner image": "Imagen de portada",
"Beautify": "Embellecer",
"Callout": "Destacado",
"Can't load this page, please check your internet connection.": "No se puede cargar esta página, por favor compruebe su conexión a Internet.",
"Cancel": "Cancelar",
"Close the modal": "Cerrar modal",
@@ -326,7 +320,7 @@
"Delete document": "Borrar documento",
"Divider": "Separador",
"Doc visibility card": "Accesos al documento",
"Docs": "Docs",
"Docs": "Documentos",
"Docs Logo": "Logo de Docs",
"Docs is already available, log in to use it now.": "Docs ya está disponible, inicia sesión para empezar a usarlo.",
"Docs makes real-time collaboration simple. Invite collaborators - public officials or external partners - with one click to see their changes live, while maintaining precise access control for data security.": "Docs simplifica la colaboración en tiempo real. Invitar colaboradores - funcionarios públicos o socios externos - con un solo clic para ver sus cambios en vivo, manteniendo un control de acceso preciso para la seguridad de los datos.",
@@ -439,11 +433,11 @@
"Share modal": "Modal para compartir el documento",
"Share the document": "Compartir el documento",
"Share with {{count}} users_many": "Compartir con {{count}} usuarios",
"Share with {{count}} users_one": "Comparte con {{count}} usuario",
"Share with {{count}} users_one": "Comparte con {{count}} usuarios",
"Share with {{count}} users_other": "Compartir con {{count}} usuarios",
"Shared with me": "Compartido conmigo",
"Shared with {{count}} users_many": "Compartido con {{count}} usuarios",
"Shared with {{count}} users_one": "Compartido con {{count}} usuario",
"Shared with {{count}} users_one": "Compartido con {{count}} usuarios",
"Shared with {{count}} users_other": "Compartido con {{count}} usuarios",
"Show more": "Mostrar más",
"Simple and secure collaboration.": "Colaboración sencilla y segura.",
@@ -512,7 +506,6 @@
"Accessible to authenticated users": "Accessible aux utilisateurs authentifiés",
"Add": "Ajouter",
"Add a horizontal line": "Ajouter une ligne horizontale",
"Add a callout block": "Faites ressortir du texte pour le mettre en évidence",
"Address:": "Adresse :",
"Administrator": "Administrateur",
"All docs": "Tous les documents",
@@ -526,7 +519,6 @@
"Available soon": "Disponible prochainement",
"Banner image": "Image de la bannière",
"Beautify": "Embellir",
"Callout": "Encadré",
"Can't load this page, please check your internet connection.": "Impossible de charger cette page, veuillez vérifier votre connexion Internet.",
"Cancel": "Annuler",
"Close the modal": "Fermer la modale",
@@ -920,7 +912,6 @@
"Accessible to anyone": "Toegankelijk voor iedereen",
"Accessible to authenticated users": "Toegankelijk voor geauthentiseerde gebruikers",
"Add": "Voeg toe",
"Add a callout block": "Laat je tekst opvallen",
"Add a horizontal line": "Voeg horizontale lijn toe",
"Address:": "Adres:",
"Administrator": "Administrator",
@@ -935,7 +926,6 @@
"Available soon": "Binnenkort beschikbaar",
"Banner image": "Banner afbeelding",
"Beautify": "Maak mooier",
"Callout": "Benadrukken",
"Can't load this page, please check your internet connection.": "Kan deze pagina niet laden. Controleer je internetverbinding.",
"Cancel": "Breek af",
"Close the modal": "Sluit het venster",
@@ -960,8 +950,8 @@
"Delete document": "Verwijder document",
"Divider": "Scheidingslijn",
"Doc visibility card": "Docs zichtbaarheid kaart",
"Docs": "Docs",
"Docs Logo": "Docs logo",
"Docs": "Documenten",
"Docs Logo": "Document logo",
"Docs is already available, log in to use it now.": "Docs is beschikbaar, log in om het te gebruiken.",
"Docs makes real-time collaboration simple. Invite collaborators - public officials or external partners - with one click to see their changes live, while maintaining precise access control for data security.": "Docs maakt real-time samenwerking eenvoudig. Nodig medewerkers - ambtenaren of externe partners - uit met één klik om hun veranderingen live te zien, terwijl de toegangscontrole voor de gegevensbeveiliging wordt gehandhaafd.",
"Docs offers an intuitive writing experience. Its minimalist interface favors content over layout, while offering the essentials: media import, offline mode and keyboard shortcuts for greater efficiency.": "Docs biedt een intuïtieve schrijfervaring. De minimalistische interface geeft voorrang aan de inhoud boven de lay-out, terwijl essentiële zaken worden aangeboden: importeren van media, offline-modus en sneltoetsen voor grotere efficiëntie.",
@@ -1073,11 +1063,9 @@
"Share modal": "Deel model",
"Share the document": "Deel dit document",
"Share with {{count}} users_many": "Gedeeld met {{count}} gebruikers",
"Share with {{count}} users_one": "Delen met {{count}} gebruiker",
"Share with {{count}} users_other": "Gedeeld met {{count}} gebruikers",
"Shared with me": "Gedeeld met mij",
"Shared with {{count}} users_many": "Gedeeld met {{count}} gebruikers",
"Shared with {{count}} users_one": "Gedeeld met {{count}} gebruiker",
"Shared with {{count}} users_other": "Gedeeld met {{count}} gebruikers",
"Show more": "Toon meer",
"Simple and secure collaboration.": "Eenvoudige en veilige samenwerking.",
@@ -1297,8 +1285,8 @@
"Delete document": "删除文档",
"Divider": "分割线",
"Doc visibility card": "文档可见列表",
"Docs": "Docs",
"Docs Logo": "Docs 徽标",
"Docs": "文档",
"Docs Logo": "文档标志",
"Docs is already available, log in to use it now.": "Docs已可用,请登录使用。",
"Docs makes real-time collaboration simple. Invite collaborators - public officials or external partners - with one click to see their changes live, while maintaining precise access control for data security.": "Docs 支持精确权限控制的实时协作,协作者可一键加入并查看实时修改。",
"Docs offers an intuitive writing experience. Its minimalist interface favors content over layout, while offering the essentials: media import, offline mode and keyboard shortcuts for greater efficiency.": "Docs 的极简界面专注于内容创作,支持媒体导入、离线模式及快捷键操作,提升写作效率。",
@@ -25,10 +25,14 @@ export class PostHogAnalytic extends AbstractAnalytic {
}
public isFeatureFlagActivated(flagName: string): boolean {
return !(
if (
posthog.featureFlags.getFlags().includes(flagName) &&
posthog.isFeatureEnabled(flagName) === false
);
) {
return false;
}
return true;
}
}
+5 -1
View File
@@ -40,7 +40,11 @@ export function isSafeUrl(url: string): boolean {
}
// Check for directory traversal attempts
return !(url.includes('..') || url.includes('../') || url.includes('..\\'));
if (url.includes('..') || url.includes('../') || url.includes('..\\')) {
return false;
}
return true;
} catch {
return false;
}
+6 -7
View File
@@ -1,6 +1,6 @@
{
"name": "impress",
"version": "3.2.1",
"version": "3.1.0",
"private": true,
"workspaces": {
"packages": [
@@ -28,13 +28,12 @@
"server:test": "yarn COLLABORATION_SERVER run test"
},
"resolutions": {
"@types/node": "22.15.12",
"@types/react": "19.1.3",
"@types/react-dom": "19.1.3",
"@typescript-eslint/eslint-plugin": "8.32.0",
"@typescript-eslint/parser": "8.32.0",
"@types/node": "22.15.3",
"@types/react": "19.1.2",
"@types/react-dom": "19.1.2",
"@typescript-eslint/eslint-plugin": "8.31.1",
"@typescript-eslint/parser": "8.31.1",
"eslint": "8.57.0",
"prosemirror-model": "1.25.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"typescript": "5.8.3",
@@ -56,7 +56,6 @@ const globalRules = {
'error',
{ varsIgnorePattern: '^_', argsIgnorePattern: '^_' },
],
'no-var': 'error',
'react/jsx-curly-brace-presence': [
'error',
{ props: 'never', children: 'never', propElementValues: 'always' },
@@ -1,6 +1,6 @@
{
"name": "eslint-config-impress",
"version": "3.2.1",
"version": "3.1.0",
"license": "MIT",
"scripts": {
"lint": "eslint --ext .js ."
@@ -17,7 +17,7 @@
"eslint-plugin-jest": "28.11.0",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-playwright": "2.2.0",
"eslint-plugin-prettier": "5.4.0",
"eslint-plugin-prettier": "5.2.6",
"eslint-plugin-testing-library": "7.1.1",
"prettier": "3.5.3"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "packages-i18n",
"version": "3.2.1",
"version": "3.1.0",
"private": true,
"scripts": {
"extract-translation": "yarn extract-translation:impress",
@@ -13,5 +13,5 @@ module.exports = {
rules: {
'@next/next/no-html-link-for-pages': 'off',
},
ignorePatterns: ['node_modules'],
ignorePatterns: ['node_modules', '.eslintrc.js'],
};
@@ -1,4 +1,4 @@
const config = {
var config = {
rootDir: './__tests__',
testEnvironment: 'node',
transform: {
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "server-y-provider",
"version": "3.2.1",
"version": "3.1.0",
"description": "Y.js provider for docs",
"repository": "https://github.com/numerique-gouv/impress",
"license": "MIT",
@@ -18,8 +18,8 @@
"dependencies": {
"@blocknote/server-util": "0.29.1",
"@hocuspocus/server": "2.15.2",
"@sentry/node": "9.15.0",
"@sentry/profiling-node": "9.15.0",
"@sentry/node": "9.14.0",
"@sentry/profiling-node": "9.14.0",
"axios": "1.9.0",
"cors": "2.8.5",
"express": "5.1.0",
@@ -44,8 +44,8 @@
"supertest": "7.1.0",
"ts-jest": "29.3.2",
"ts-node": "10.9.2",
"tsc-alias": "1.8.16",
"tsc-alias": "1.8.15",
"typescript": "*",
"ws": "8.18.2"
"ws": "8.18.1"
}
}
@@ -17,6 +17,8 @@ import { logger } from '../utils';
/**
* init the collaboration server.
*
* @param port - The port on which the server listens.
* @param serverSecret - The secret key for API authentication.
* @returns An object containing the Express app, Hocuspocus server, and HTTP server instance.
*/
export const initServer = () => {
+158 -220
View File
@@ -1238,16 +1238,11 @@
dependencies:
tslib "^2.4.0"
"@emoji-mart/data@1.2.1", "@emoji-mart/data@^1.2.1":
"@emoji-mart/data@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@emoji-mart/data/-/data-1.2.1.tgz#0ad70c662e3bc603e23e7d98413bd1e64c4fcb6c"
integrity sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==
"@emoji-mart/react@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@emoji-mart/react/-/react-1.1.1.tgz#ddad52f93a25baf31c5383c3e7e4c6e05554312a"
integrity sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==
"@emotion/babel-plugin@^11.13.5":
version "11.13.5"
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz#eab8d65dbded74e0ecfd28dc218e75607c4e7bc0"
@@ -1485,13 +1480,6 @@
dependencies:
eslint-visitor-keys "^3.4.3"
"@eslint-community/eslint-utils@^4.7.0":
version "4.7.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a"
integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==
dependencies:
eslint-visitor-keys "^3.4.3"
"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1":
version "4.12.1"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0"
@@ -4429,19 +4417,19 @@
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.11.0.tgz#75dce8e972f90bba488e2b0cc677fb233aa357ab"
integrity sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==
"@sentry-internal/browser-utils@9.15.0":
version "9.15.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-9.15.0.tgz#999f0205fd6dc380f35c4a1133c64454c06a0fc3"
integrity sha512-tIM+9zXCefkInRiNmBkXKgkamRjEOlAcf768cBKlMWVOatfNrSEB0UEV7qkAYqnQGWkbPkHFMbFJxWptydLODw==
"@sentry-internal/browser-utils@9.14.0":
version "9.14.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-9.14.0.tgz#d864b6c6c41fb409baa5df5080443f7c33bf7b86"
integrity sha512-pDk9XUu9zf7lcT9QX0nTObPNp/y0xQyy1Dj+5/8TSB3vAfe0LQcooKGl/D1h7EoIXVHUozZk5JC/dH+gz6BXRg==
dependencies:
"@sentry/core" "9.15.0"
"@sentry/core" "9.14.0"
"@sentry-internal/feedback@9.15.0":
version "9.15.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-9.15.0.tgz#dd3194e93765521559645fea2f2f864e62156134"
integrity sha512-jyN0r57WL8V5ViwZpiNvbIhF9I89jxn6mtIQcyV85EjIXDyzJmeTgxc/FIU0kcDVv6zso3qnGRJUxGK+GvoYZg==
"@sentry-internal/feedback@9.14.0":
version "9.14.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-9.14.0.tgz#d003ad6cddee140f7e82dbf649cf47e40489f6f9"
integrity sha512-D+PiEUWbDT0vqmaTiOs6OzXwVRVFgf7BCkFs48qsN9sAPwUgT+5zh2oo/rU2r0NrmMcvJVtSY+ezwPMk8BgGsg==
dependencies:
"@sentry/core" "9.15.0"
"@sentry/core" "9.14.0"
"@sentry-internal/node-cpu-profiler@^2.0.0":
version "2.1.0"
@@ -4451,37 +4439,37 @@
detect-libc "^2.0.3"
node-abi "^3.73.0"
"@sentry-internal/replay-canvas@9.15.0":
version "9.15.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-9.15.0.tgz#7ec2a8bb8f571b78506cdba208ee6ed00faa7f40"
integrity sha512-a1/oiXwcW5OmILjD7/R2UEsPQWXJBUr0u388uCKDUGeyXLxBBbIJGS5E8oLwVQLVxhVJrODgxvT19z9OVcbn7g==
"@sentry-internal/replay-canvas@9.14.0":
version "9.14.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-9.14.0.tgz#30516c5555c6646401ea97540a459a064910b1c4"
integrity sha512-GhCSqc0oNzRiLhQsi9LCXgUmIwdHdvzVIsX4fihoFYWfgWSSj5YLqeEkb3CMM8htM6vheSFzIbPLlRS8fjCrPQ==
dependencies:
"@sentry-internal/replay" "9.15.0"
"@sentry/core" "9.15.0"
"@sentry-internal/replay" "9.14.0"
"@sentry/core" "9.14.0"
"@sentry-internal/replay@9.15.0":
version "9.15.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-9.15.0.tgz#7c6522b6b9d410f0f4b0b963777d2894fc11967f"
integrity sha512-lv6ENRmfeBuod6Tr1WgLeF0+wIIXlHWNAGofsaNUvm8UKS7USicFsQWKOZPk4UyjTfrEClPp2vx+o7aUcZS6TQ==
"@sentry-internal/replay@9.14.0":
version "9.14.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-9.14.0.tgz#491d12930175e814c097b9b6ba097f47b4f4770c"
integrity sha512-wgt397/PtpfVQ9t779a0L+hGH3JN9doXv3+9Wj98MLWwhymvJBjpjCFUBLScO5iP6imewTbRqQHbq7XS7I+x1A==
dependencies:
"@sentry-internal/browser-utils" "9.15.0"
"@sentry/core" "9.15.0"
"@sentry-internal/browser-utils" "9.14.0"
"@sentry/core" "9.14.0"
"@sentry/babel-plugin-component-annotate@3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.3.1.tgz#baecd89396cbb4659565a4e8efe7f0a71b19262a"
integrity sha512-5GOxGT7lZN+I8A7Vp0rWY+726FDKEw8HnFiebe51rQrMbfGfCu2Aw9uSM0nT9OG6xhV6WvGccIcCszTPs4fUZQ==
"@sentry/browser@9.15.0":
version "9.15.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-9.15.0.tgz#a690b2bd5fb7ca18789887424e4c0773fd8262c8"
integrity sha512-ppHESKFVQFpAb3rQI2ateDkmMytVcvAWsjZrZ3hF9iEnO3iTIIu32ib5nqQUL4KKXZQovYnDrSlDcdv3ZwX/8Q==
"@sentry/browser@9.14.0":
version "9.14.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-9.14.0.tgz#159ca710028891e1e9b160d491ddfb774cf1ed67"
integrity sha512-acxFbFEei3hzKr/IW3OmkzHlwohRaRBG0872nIhLYV2f/BgZmR6eV5zrUoELMmt2cgoLmDYyfp1734OoplfDbw==
dependencies:
"@sentry-internal/browser-utils" "9.15.0"
"@sentry-internal/feedback" "9.15.0"
"@sentry-internal/replay" "9.15.0"
"@sentry-internal/replay-canvas" "9.15.0"
"@sentry/core" "9.15.0"
"@sentry-internal/browser-utils" "9.14.0"
"@sentry-internal/feedback" "9.14.0"
"@sentry-internal/replay" "9.14.0"
"@sentry-internal/replay-canvas" "9.14.0"
"@sentry/core" "9.14.0"
"@sentry/bundler-plugin-core@3.3.1":
version "3.3.1"
@@ -4551,35 +4539,35 @@
"@sentry/cli-win32-i686" "2.42.2"
"@sentry/cli-win32-x64" "2.42.2"
"@sentry/core@9.15.0":
version "9.15.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-9.15.0.tgz#590f16a15596ce01db49d9d80b31cb18048ca9a4"
integrity sha512-lBmo3bzzaYUesdzc2H5K3fajfXyUNuj5koqyFoCAI8rnt9CBl7SUc/P07+E5eipF8mxgiU3QtkI7ALzRQN8pqQ==
"@sentry/core@9.14.0":
version "9.14.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-9.14.0.tgz#4d51bf0ff719a8dee821d31be4abf1afd73f6c6e"
integrity sha512-OLfucnP3LAL5bxVNWc2RVOHCX7fk9Er5bWPCS+O5cPjqNUUz0HQHhVh2Vhei5C0kYZZM4vy4BQit5T9LrlOaNA==
"@sentry/nextjs@9.15.0":
version "9.15.0"
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-9.15.0.tgz#de54a6f47693512b606d20b8caa104ca5734b92d"
integrity sha512-lx/q1Uqe37MtNff8UIBL5G8SaHn48lDlZyQKrsTd+4txBwT2DsAnyR029n/ZQW5bc1/rLM/qebKLy76x+Xq0vA==
"@sentry/nextjs@9.14.0":
version "9.14.0"
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-9.14.0.tgz#ed42410432c17632fea8ce0fcc036c29a7588d1c"
integrity sha512-uQnG7tPs1qX8OZ5lW3mpslAoN2+XiV2ZJ/3T+VtBatx9MFgEga8lMA/qgdkZP+Q139sroGlUe0tcUSDwwIzaVw==
dependencies:
"@opentelemetry/api" "^1.9.0"
"@opentelemetry/semantic-conventions" "^1.30.0"
"@rollup/plugin-commonjs" "28.0.1"
"@sentry-internal/browser-utils" "9.15.0"
"@sentry/core" "9.15.0"
"@sentry/node" "9.15.0"
"@sentry/opentelemetry" "9.15.0"
"@sentry/react" "9.15.0"
"@sentry/vercel-edge" "9.15.0"
"@sentry-internal/browser-utils" "9.14.0"
"@sentry/core" "9.14.0"
"@sentry/node" "9.14.0"
"@sentry/opentelemetry" "9.14.0"
"@sentry/react" "9.14.0"
"@sentry/vercel-edge" "9.14.0"
"@sentry/webpack-plugin" "3.3.1"
chalk "3.0.0"
resolve "1.22.8"
rollup "4.35.0"
stacktrace-parser "^0.1.10"
"@sentry/node@9.15.0":
version "9.15.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-9.15.0.tgz#f43954ed8ccdc1cb7d5c315b7d481c5f46300885"
integrity sha512-K0LdJxIzYbbsbiT+1tKgNq6MUHuDs2DggBDcFEp3T+yXVJYN1AyalUli06Kmxq8yvou6hgLwWL4gjIcB1IQySA==
"@sentry/node@9.14.0":
version "9.14.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-9.14.0.tgz#683d9dacc0d864710fe292e52e839e5fdc39c94f"
integrity sha512-AWPc6O+zAdSgnsiKm6Nb1txyiKCOIBriJEONdXFSslgZgkm8EWAYRRHyS2Hkmnz0/5bEQ3jEffIw22qJuaHN+Q==
dependencies:
"@opentelemetry/api" "^1.9.0"
"@opentelemetry/context-async-hooks" "^1.30.1"
@@ -4612,42 +4600,42 @@
"@opentelemetry/sdk-trace-base" "^1.30.1"
"@opentelemetry/semantic-conventions" "^1.30.0"
"@prisma/instrumentation" "6.6.0"
"@sentry/core" "9.15.0"
"@sentry/opentelemetry" "9.15.0"
"@sentry/core" "9.14.0"
"@sentry/opentelemetry" "9.14.0"
import-in-the-middle "^1.13.0"
"@sentry/opentelemetry@9.15.0":
version "9.15.0"
resolved "https://registry.yarnpkg.com/@sentry/opentelemetry/-/opentelemetry-9.15.0.tgz#1888c8a08e69a49345d6a161a6dbc04fe6c7744f"
integrity sha512-gGOzgSxbuh4B4SlEonL1LFsazmeqL/fn5CIQqRG0UWWxdt6TKAMlj0ThIlGF3jSHW2eXdpvs+4E73uFEaHIqfg==
"@sentry/opentelemetry@9.14.0":
version "9.14.0"
resolved "https://registry.yarnpkg.com/@sentry/opentelemetry/-/opentelemetry-9.14.0.tgz#7454f23a4add2098a1364b8eea966c057503223e"
integrity sha512-NnHJjSQGpWaZ6+0QK9Xn1T3CTOM16Ij07VnSiGmVz3/IMsNC1/jndqc8p9BxEI+67XhZjOUUN0Ogpq1XRY7YeA==
dependencies:
"@sentry/core" "9.15.0"
"@sentry/core" "9.14.0"
"@sentry/profiling-node@9.15.0":
version "9.15.0"
resolved "https://registry.yarnpkg.com/@sentry/profiling-node/-/profiling-node-9.15.0.tgz#04c7588b8f805f521178e2ef859a482ea21b1e80"
integrity sha512-I5lU5XNYOTeULAUgeZg9Un+nKmwkDKCQ9R6B+RUkT73Lz+xI6miriEfPcMXbeOcTwU6XygKHpcm+0Ksi80QJYA==
"@sentry/profiling-node@9.14.0":
version "9.14.0"
resolved "https://registry.yarnpkg.com/@sentry/profiling-node/-/profiling-node-9.14.0.tgz#367dcc184f026f263a6faf0080acc26ad4215635"
integrity sha512-DDbjWdkb/Hh0SOgzONbufUW7oqyTaHQu+oLWQidNaSHk57M3P/Rz8klxIrYRygvlZJQpd5RKJMpT8G+ceUBz0A==
dependencies:
"@sentry-internal/node-cpu-profiler" "^2.0.0"
"@sentry/core" "9.15.0"
"@sentry/node" "9.15.0"
"@sentry/core" "9.14.0"
"@sentry/node" "9.14.0"
"@sentry/react@9.15.0":
version "9.15.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-9.15.0.tgz#1b0f3b79180bd4359afb4c68da1f19fd866ff643"
integrity sha512-8nojSjiEd/EWIgoWVfkNIkBGL2yoFZoVMBUTcYlypsMnUHNko2RJItOBqZs5/DRBxuzfBKVt8PF+gkhQOm6mPg==
"@sentry/react@9.14.0":
version "9.14.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-9.14.0.tgz#1fa021b5c1a4171356ee391b8ef9652bcf076e59"
integrity sha512-0dRfTorcInBjxVnis6Zv0+Jqex2OXFNQf+cQanKuC0IRkAhZyD2+UO2/v39sSmtvrHIcZRQ9fta8qKdhFUXCqg==
dependencies:
"@sentry/browser" "9.15.0"
"@sentry/core" "9.15.0"
"@sentry/browser" "9.14.0"
"@sentry/core" "9.14.0"
hoist-non-react-statics "^3.3.2"
"@sentry/vercel-edge@9.15.0":
version "9.15.0"
resolved "https://registry.yarnpkg.com/@sentry/vercel-edge/-/vercel-edge-9.15.0.tgz#e9e89dd922f5d322bcf42592cfbb47e4c0e5b771"
integrity sha512-Rfc6pDbHMg5DMIgyZHVIO4IeHgxcH3myPBy9HP1hMLtcEqKL/YS8dK3oQrZoUsNP9chjXkrp4bBeKT/phX3pMg==
"@sentry/vercel-edge@9.14.0":
version "9.14.0"
resolved "https://registry.yarnpkg.com/@sentry/vercel-edge/-/vercel-edge-9.14.0.tgz#9e36070f4199161b2c7c31a81f0f905d83297701"
integrity sha512-qcQddIRNFJbDaBuGFfEthwMgR7+wsPfMVwMurQdxS7Axr3GgT03wVG/SPAVS/yhVLeqXrfeHaqJaNg05z0aErA==
dependencies:
"@opentelemetry/api" "^1.9.0"
"@sentry/core" "9.15.0"
"@sentry/core" "9.14.0"
"@sentry/webpack-plugin@3.3.1":
version "3.3.1"
@@ -4881,29 +4869,29 @@
dependencies:
"@typescript-eslint/utils" "^8.18.1"
"@tanstack/query-core@5.75.4":
version "5.75.4"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.75.4.tgz#e05f2fe4145fb5354271ad19e63eec61f6ce3012"
integrity sha512-pcqOUgWG9oGlzkfRQQMMsEFmtQu0wq81A414CtELZGq+ztVwSTAaoB3AZRAXQJs88LmNMk2YpUKuQbrvzNDyRg==
"@tanstack/query-core@5.74.9":
version "5.74.9"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.74.9.tgz#35d5b1075663072bea22aa3ce21508b195306ecd"
integrity sha512-qmjXpWyigDw4SfqdSBy24FzRvpBPXlaSbl92N77lcrL+yvVQLQkf0T6bQNbTxl9IEB/SvVFhhVZoIlQvFnNuuw==
"@tanstack/query-devtools@5.74.7":
version "5.74.7"
resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.74.7.tgz#c9b022b386ac86e6395228b5d6912e6444b3b971"
integrity sha512-nSNlfuGdnHf4yB0S+BoNYOE1o3oAH093weAYZolIHfS2stulyA/gWfSk/9H4ZFk5mAAHb5vNqAeJOmbdcGPEQw==
"@tanstack/react-query-devtools@5.75.4":
version "5.75.4"
resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.75.4.tgz#89614363d63c997ade81ade4a18e90b57512d4d8"
integrity sha512-CSJZWa316EFtLZtr6RQLAnqWb1MESzyZ7j0bMQjuhYas5FDp/3MA7G6RE4hWauqCCDsNIfIm2Rnm1zJTZVye/w==
"@tanstack/react-query-devtools@5.74.9":
version "5.74.9"
resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.74.9.tgz#7d79e27b2c4d949c9c1e269eafdb7c2256f6d493"
integrity sha512-6dMfeK/5OvC9E88/ziwiv1Pggqkgjker8V+pLJFrjh7O7E7S6yXJRNNr/KjA/c+z6d/i7HpDk8FF+oSr7mhYLg==
dependencies:
"@tanstack/query-devtools" "5.74.7"
"@tanstack/react-query@5.75.4":
version "5.75.4"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.75.4.tgz#721e1cdf7debb110671f558dc2b6276f637554a5"
integrity sha512-Vf65pzYRkf8fk9SP1ncIZjvaXszBhtsvpf+h45Y/9kOywOrVZfBGUpCdffdsVzbmBzmz6TCFes9bM0d3pRrIsA==
"@tanstack/react-query@5.74.9":
version "5.74.9"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.74.9.tgz#fdcac4a31cd7f5786eda7526ee70ed6c63d13799"
integrity sha512-F8xCXDQRDgsPzLzX9+d6ycNoITAIU2bycc1idZd06bt/GjN1quEJDjHvEDWZGoVn0A/ZmntVrYv6TE0kR7c7LA==
dependencies:
"@tanstack/query-core" "5.75.4"
"@tanstack/query-core" "5.74.9"
"@tanstack/react-table@8.20.6":
version "8.20.6"
@@ -5387,10 +5375,10 @@
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@22.10.7", "@types/node@22.15.12", "@types/node@^22.7.5":
version "22.15.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.12.tgz#9ce54e51e09536faa94e4ec300c4728ee83bfa85"
integrity sha512-K0fpC/ZVeb8G9rm7bH7vI0KAec4XHEhBam616nVJCV51bKzJ6oA3luG4WdKoaztxe70QaNjS/xBmcDLmr4PiGw==
"@types/node@*", "@types/node@22.10.7", "@types/node@22.15.3", "@types/node@^22.7.5":
version "22.15.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.3.tgz#b7fb9396a8ec5b5dfb1345d8ac2502060e9af68b"
integrity sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==
dependencies:
undici-types "~6.21.0"
@@ -5446,10 +5434,10 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
"@types/react-dom@*", "@types/react-dom@19.1.3":
version "19.1.3"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.3.tgz#3f0c60804441bf34d19f8dd0d44405c0c0e21bfa"
integrity sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==
"@types/react-dom@*", "@types/react-dom@19.1.2":
version "19.1.2"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.2.tgz#bd1fe3b8c28a3a2e942f85314dcfb71f531a242f"
integrity sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==
"@types/react-modal@3.16.3":
version "3.16.3"
@@ -5463,10 +5451,10 @@
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044"
integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==
"@types/react@*", "@types/react@19.1.3":
version "19.1.3"
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.3.tgz#c75a24b775a63280b02c66a55a3cfa04f4022cf7"
integrity sha512-dLWQ+Z0CkIvK1J8+wrDPwGxEYFA4RAyHoZPxHVGspYmFVnwGSNT24cGIhFJrtfRnWVuW8X7NO52gCXmhkVUWGQ==
"@types/react@*", "@types/react@19.1.2":
version "19.1.2"
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.2.tgz#11df86f66f188f212c90ecb537327ec68bfd593f"
integrity sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==
dependencies:
csstype "^3.0.2"
@@ -5576,30 +5564,30 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@*", "@typescript-eslint/eslint-plugin@8.32.0", "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
version "8.32.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.0.tgz#86630dd3084f9d6c4239bbcd6a7ee1a7ee844f7f"
integrity sha512-/jU9ettcntkBFmWUzzGgsClEi2ZFiikMX5eEQsmxIAWMOn4H3D4rvHssstmAHGVvrYnaMqdWWWg0b5M6IN/MTQ==
"@typescript-eslint/eslint-plugin@*", "@typescript-eslint/eslint-plugin@8.31.1", "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
version "8.31.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz#62f1befe59647524994e89de4516d8dcba7a850a"
integrity sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ==
dependencies:
"@eslint-community/regexpp" "^4.10.0"
"@typescript-eslint/scope-manager" "8.32.0"
"@typescript-eslint/type-utils" "8.32.0"
"@typescript-eslint/utils" "8.32.0"
"@typescript-eslint/visitor-keys" "8.32.0"
"@typescript-eslint/scope-manager" "8.31.1"
"@typescript-eslint/type-utils" "8.31.1"
"@typescript-eslint/utils" "8.31.1"
"@typescript-eslint/visitor-keys" "8.31.1"
graphemer "^1.4.0"
ignore "^5.3.1"
natural-compare "^1.4.0"
ts-api-utils "^2.1.0"
ts-api-utils "^2.0.1"
"@typescript-eslint/parser@*", "@typescript-eslint/parser@8.32.0", "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
version "8.32.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.32.0.tgz#fe840ecb2726a82fa9f5562837ec40503ae71caf"
integrity sha512-B2MdzyWxCE2+SqiZHAjPphft+/2x2FlO9YBx7eKE1BCb+rqBlQdhtAEhzIEdozHd55DXPmxBdpMygFJjfjjA9A==
"@typescript-eslint/parser@*", "@typescript-eslint/parser@8.31.1", "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
version "8.31.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.31.1.tgz#e9b0ccf30d37dde724ee4d15f4dbc195995cce1b"
integrity sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q==
dependencies:
"@typescript-eslint/scope-manager" "8.32.0"
"@typescript-eslint/types" "8.32.0"
"@typescript-eslint/typescript-estree" "8.32.0"
"@typescript-eslint/visitor-keys" "8.32.0"
"@typescript-eslint/scope-manager" "8.31.1"
"@typescript-eslint/types" "8.31.1"
"@typescript-eslint/typescript-estree" "8.31.1"
"@typescript-eslint/visitor-keys" "8.31.1"
debug "^4.3.4"
"@typescript-eslint/scope-manager@8.31.1", "@typescript-eslint/scope-manager@^8.15.0":
@@ -5610,34 +5598,21 @@
"@typescript-eslint/types" "8.31.1"
"@typescript-eslint/visitor-keys" "8.31.1"
"@typescript-eslint/scope-manager@8.32.0":
version "8.32.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.32.0.tgz#6be89f652780f0d3d19d58dc0ee107b1a9e3282c"
integrity sha512-jc/4IxGNedXkmG4mx4nJTILb6TMjL66D41vyeaPWvDUmeYQzF3lKtN15WsAeTr65ce4mPxwopPSo1yUUAWw0hQ==
"@typescript-eslint/type-utils@8.31.1":
version "8.31.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz#be0f438fb24b03568e282a0aed85f776409f970c"
integrity sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA==
dependencies:
"@typescript-eslint/types" "8.32.0"
"@typescript-eslint/visitor-keys" "8.32.0"
"@typescript-eslint/type-utils@8.32.0":
version "8.32.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.32.0.tgz#5e0882393e801963f749bea38888e716045fe895"
integrity sha512-t2vouuYQKEKSLtJaa5bB4jHeha2HJczQ6E5IXPDPgIty9EqcJxpr1QHQ86YyIPwDwxvUmLfP2YADQ5ZY4qddZg==
dependencies:
"@typescript-eslint/typescript-estree" "8.32.0"
"@typescript-eslint/utils" "8.32.0"
"@typescript-eslint/typescript-estree" "8.31.1"
"@typescript-eslint/utils" "8.31.1"
debug "^4.3.4"
ts-api-utils "^2.1.0"
ts-api-utils "^2.0.1"
"@typescript-eslint/types@8.31.1":
version "8.31.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.31.1.tgz#478ed6f7e8aee1be7b63a60212b6bffe1423b5d4"
integrity sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==
"@typescript-eslint/types@8.32.0":
version "8.32.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.32.0.tgz#a4a66b8876b8391970cf069b49572e43f1fc957a"
integrity sha512-O5Id6tGadAZEMThM6L9HmVf5hQUXNSxLVKeGJYWNhhVseps/0LddMkp7//VDkzwJ69lPL0UmZdcZwggj9akJaA==
"@typescript-eslint/typescript-estree@8.31.1":
version "8.31.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz#37792fe7ef4d3021c7580067c8f1ae66daabacdf"
@@ -5652,31 +5627,7 @@
semver "^7.6.0"
ts-api-utils "^2.0.1"
"@typescript-eslint/typescript-estree@8.32.0":
version "8.32.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.0.tgz#11d45f47bfabb141206a3da6c7b91a9d869ff32d"
integrity sha512-pU9VD7anSCOIoBFnhTGfOzlVFQIA1XXiQpH/CezqOBaDppRwTglJzCC6fUQGpfwey4T183NKhF1/mfatYmjRqQ==
dependencies:
"@typescript-eslint/types" "8.32.0"
"@typescript-eslint/visitor-keys" "8.32.0"
debug "^4.3.4"
fast-glob "^3.3.2"
is-glob "^4.0.3"
minimatch "^9.0.4"
semver "^7.6.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/utils@8.32.0":
version "8.32.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.32.0.tgz#24570f68cf845d198b73a7f94ca88d8c2505ba47"
integrity sha512-8S9hXau6nQ/sYVtC3D6ISIDoJzS1NsCK+gluVhLN2YkBPX+/1wkwyUiDKnxRh15579WoOIyVWnoyIf3yGI9REw==
dependencies:
"@eslint-community/eslint-utils" "^4.7.0"
"@typescript-eslint/scope-manager" "8.32.0"
"@typescript-eslint/types" "8.32.0"
"@typescript-eslint/typescript-estree" "8.32.0"
"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/utils@^8.15.0", "@typescript-eslint/utils@^8.18.1":
"@typescript-eslint/utils@8.31.1", "@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/utils@^8.15.0", "@typescript-eslint/utils@^8.18.1":
version "8.31.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.31.1.tgz#5628ea0393598a0b2f143d0fc6d019f0dee9dd14"
integrity sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ==
@@ -5694,14 +5645,6 @@
"@typescript-eslint/types" "8.31.1"
eslint-visitor-keys "^4.2.0"
"@typescript-eslint/visitor-keys@8.32.0":
version "8.32.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.0.tgz#0cca2cac046bc71cc40ce8214bac2850d6ecf4a6"
integrity sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w==
dependencies:
"@typescript-eslint/types" "8.32.0"
eslint-visitor-keys "^4.2.0"
"@ungap/structured-clone@^1.0.0", "@ungap/structured-clone@^1.2.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
@@ -7467,7 +7410,7 @@ emittery@^0.13.1:
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad"
integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==
emoji-mart@5.6.0, emoji-mart@^5.6.0:
emoji-mart@^5.6.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-5.6.0.tgz#71b3ed0091d3e8c68487b240d9d6d9a73c27f023"
integrity sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==
@@ -7844,10 +7787,10 @@ eslint-plugin-playwright@2.2.0:
dependencies:
globals "^13.23.0"
eslint-plugin-prettier@5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz#54d4748904e58eaf1ffe26c4bffa4986ca7f952b"
integrity sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA==
eslint-plugin-prettier@5.2.6:
version "5.2.6"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz#be39e3bb23bb3eeb7e7df0927cdb46e4d7945096"
integrity sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==
dependencies:
prettier-linter-helpers "^1.0.0"
synckit "^0.11.0"
@@ -9021,10 +8964,10 @@ hyphen@^1.6.4:
resolved "https://registry.yarnpkg.com/hyphen/-/hyphen-1.10.6.tgz#0e779d280e696102b97d7e42f5ca5de2cc97e274"
integrity sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==
i18next-browser-languagedetector@8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.1.0.tgz#e7fcc1084e9ca8fece329c8c05b000a6e25bd82e"
integrity sha512-mHZxNx1Lq09xt5kCauZ/4bsXOEA2pfpwSoU11/QTJB+pD94iONFwp+ohqi///PwiFvjFOxe1akYCdHyFo1ng5Q==
i18next-browser-languagedetector@8.0.5:
version "8.0.5"
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.5.tgz#6cfdc72820457ce95e69a2788a4f837d1d8f4e9d"
integrity sha512-OstebRKqKiQw8xEvQF5aRyUujsCatanj7Q9eo5iiH2gJpoXGZ7483ol3sVBwfqbobTQPNH1J+NAyJ1aCQoEC+w==
dependencies:
"@babel/runtime" "^7.23.2"
@@ -9051,10 +8994,10 @@ i18next-parser@9.3.0:
vinyl "^3.0.0"
vinyl-fs "^4.0.0"
i18next@25.1.1:
version "25.1.1"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-25.1.1.tgz#113e7773697d300e7e824185f766362eceb8dc61"
integrity sha512-FZcp3vk3PXc8onasbsWYahfeDIWX4LkKr4vd01xeXrmqyNXlVNtVecEIw2K1o8z3xYrHMcd1bwYQub+3g7zqCw==
i18next@25.0.2:
version "25.0.2"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-25.0.2.tgz#3bfd51d11603e130cc4a88ff37038393dc560951"
integrity sha512-xWxgK8GAaPYkV9ia2tdgbtdM+qiC+ysVTBPvXhpCORU/+QkeQe3BSI7Crr+c4ZXULN1PfnXG/HY2n7HGx4KKBg==
dependencies:
"@babel/runtime" "^7.26.10"
@@ -11622,10 +11565,10 @@ postgres-range@^1.1.1:
resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.4.tgz#a59c5f9520909bcec5e63e8cf913a92e4c952863"
integrity sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==
posthog-js@1.239.1:
version "1.239.1"
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.239.1.tgz#871c734a438f2ead1529deba26031d689819d587"
integrity sha512-4cN3A02e9uyyTVQ/9+bGVfvoAmpM1mlVXlq2DsujCQjloMMSbEb+6Uwv6fro3Q5qk7NEU62kQqZHyvqQIqv9zA==
posthog-js@1.236.8:
version "1.236.8"
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.236.8.tgz#0576c6ffc17ba76f869e82dfda82dccaab084479"
integrity sha512-Nmd1LP9lAairJ2gkIIHNHeY8c7F6nO0g6OWaAHe6MfRnKWwnzBoLkDXEX4ptvooQVKnQ3eS7UR0Q+112TAn9qw==
dependencies:
core-js "^3.38.1"
fflate "^0.4.8"
@@ -11806,10 +11749,10 @@ prosemirror-menu@^1.2.4:
prosemirror-history "^1.0.0"
prosemirror-state "^1.0.0"
prosemirror-model@1.25.0, prosemirror-model@^1.0.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.23.0, prosemirror-model@^1.24.1, prosemirror-model@^1.25.0:
version "1.25.0"
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.25.0.tgz#c147113edc0718a14f03881e4c20367d0221f7af"
integrity sha512-/8XUmxWf0pkj2BmtqZHYJipTBMHIdVjuvFzMvEoxrtyGNmfvdhBiRwYt/eFwy2wA9DtBW3RLqvZnjurEkHaFCw==
prosemirror-model@^1.0.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.23.0, prosemirror-model@^1.24.1, prosemirror-model@^1.25.0:
version "1.25.1"
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.25.1.tgz#aeae9f1ec79fcaa76f6fc619800d91fbcf726870"
integrity sha512-AUvbm7qqmpZa5d9fPKMvH1Q5bqYQvAZWOGRvxsB6iFLyycvC9MwNemNVjHVrWgjaoxAfY8XVg7DbvQ/qxvI9Eg==
dependencies:
orderedmap "^2.0.0"
@@ -13887,7 +13830,7 @@ trough@^2.0.0:
resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f"
integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==
ts-api-utils@^2.0.1, ts-api-utils@^2.1.0:
ts-api-utils@^2.0.1:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91"
integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==
@@ -13927,10 +13870,10 @@ ts-node@10.9.2:
v8-compile-cache-lib "^3.0.1"
yn "3.1.1"
tsc-alias@1.8.16:
version "1.8.16"
resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.8.16.tgz#dbc74e797071801c7284f1a478259de920f852d4"
integrity sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==
tsc-alias@1.8.15:
version "1.8.15"
resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.8.15.tgz#7a07a77a4157872f834841a2a1647fad9464884d"
integrity sha512-yKLVx8ddUurRwhVcS6JFF2ZjksOX2ZWDRIdgt+PQhJBDegIdAdilptiHsuAbx9UFxa16GFrxeKQ2kTcGvR6fkQ==
dependencies:
chokidar "^3.5.3"
commander "^9.0.0"
@@ -14918,21 +14861,16 @@ write-file-atomic@^5.0.1:
imurmurhash "^0.1.4"
signal-exit "^4.0.1"
ws@8.18.2:
version "8.18.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.2.tgz#42738b2be57ced85f46154320aabb51ab003705a"
integrity sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==
ws@8.18.1, ws@^8.11.0, ws@^8.17.1, ws@^8.18.0, ws@^8.5.0:
version "8.18.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb"
integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==
ws@^7.4.6:
version "7.5.10"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
ws@^8.11.0, ws@^8.17.1, ws@^8.18.0, ws@^8.5.0:
version "8.18.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb"
integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==
xml-js@^1.6.8:
version "1.6.11"
resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
@@ -15034,10 +14972,10 @@ yoga-layout@^3.2.1:
resolved "https://registry.yarnpkg.com/yoga-layout/-/yoga-layout-3.2.1.tgz#d2d1ba06f0e81c2eb650c3e5ad8b0b4adde1e843"
integrity sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==
zustand@5.0.4:
version "5.0.4"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.4.tgz#33af161f1e337854ccd8b711ef9e92545d6ae53f"
integrity sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==
zustand@5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.3.tgz#b323435b73d06b2512e93c77239634374b0e407f"
integrity sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==
zwitch@^2.0.0, zwitch@^2.0.4:
version "2.0.4"
@@ -50,6 +50,7 @@ backend:
DB_USER: dinum
DB_PASSWORD: pass
DB_PORT: 5432
FRONTEND_HOMEPAGE_FEATURE_ENABLED: true
FRONTEND_FOOTER_FEATURE_ENABLED: true
FRONTEND_URL_JSON_FOOTER: https://impress.127.0.0.1.nip.io/contents/footer-demo.json
POSTGRES_DB: impress
+1 -1
View File
@@ -1,7 +1,7 @@
environments:
dev:
values:
- version: 3.2.1
- version: 3.1.0
---
repositories:
- name: bitnami
+1 -1
View File
@@ -1,5 +1,5 @@
apiVersion: v2
type: application
name: docs
version: 3.2.1
version: 3.1.0
appVersion: latest
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "3.2.1",
"version": "3.1.0",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {