Compare commits

..

47 Commits

Author SHA1 Message Date
Your name a4d968eff3 remise à niveau 2025-02-13 16:29:33 +01:00
Your name 12f7c8b371 after pull 2025-02-12 10:36:03 +01:00
MarineM1 70e3e9e7d4 Merge branch 'main' into accessibility 2025-02-12 10:02:25 +01:00
Anthony LC 296dbb7957 🔖(minor) release 2.2.0
Added:
- 📝(doc) Add security.md and codeofconduct.md
- (frontend) add home page
- (frontend) cursor display on activity
- (frontend) Add export page break

Changed:
- 🔧(backend) make AI feature reach configurable

Fixed:
- 🌐(CI) Fix email partially translated
- 🐛(frontend) fix cursor breakline
- 🐛(frontend) fix style pdf export
2025-02-11 14:16:58 +01:00
Anthony LC 3827f0f799 🩹(frontend) fine tuning v2.2.0
Fix minor issues:
- Add some height to the carret in the editor
- Improve css in mail templates
- Improve images resolution on homepage
2025-02-11 14:16:58 +01:00
Anthony LC d89e3dc6d4 🛂(frontend) display the AI buttons depend abilities
Anybody with edit right could use the AI.
We changed this behavior, now we have to be
authentified with edit right.
We update the UI to display the AI buttons
only if the user has the correct AI ability.
2025-02-11 10:54:55 +01:00
Samuel Paccoud - DINUM 91cf5f9367 🔧(backend) make AI feature reach configurable
We want to be able to define whether AI features are available to
anonymous users who gained editor access on a document, or if we
demand that they be authenticated or even if we demand that they
gained their editor access via a specific document access.

Being authenticated is now the default value. This will change the
default behavior on your existing instance (see UPGRADE.md)
2025-02-11 10:54:55 +01:00
Anthony LC 5cc4b07cf6 💄(frontend) improve caret style
Improve the caret, to looks more like the
google doc caret.
2025-02-10 15:53:03 +01:00
Anthony LC 0cfc242e09 🚚(frontend) move Blocknote styles
Move Blocknote styles to a separate file.
2025-02-10 15:53:03 +01:00
Anthony LC a6b3cfdb0c (frontend) add export page break
Blocknotejs introduced the ability to export a
document with page breaks.
This commit adds the page break feature to the
editor and so to our export feature.
2025-02-10 15:53:03 +01:00
Anthony LC 5ead18c94c 💬(frontend) add lacking buttons open source section
Some buttons were lacking in the open source
section of the home page. This commit adds them.
2025-02-10 15:40:23 +01:00
AntoLC 5eeb8cae5c 🌐(i18n) update translated strings
Update translated files with new translations
2025-02-10 15:40:23 +01:00
MarineM1 e0426ca803 Merge branch 'main' into accessibility 2025-02-10 14:17:20 +01:00
Jacques ROUSSEL 68bf024005 (helm) add pdbs to deployments
In order to avoid a service interruption during a Kubernetes (k8s)
upgrade, we add a Pod Disruption Budget (PDB) to deployments.
2025-02-10 13:05:54 +01:00
Anthony LC fdd1068c90 (frontend) fix test copy html
The recent upgrade of Blocknote has caused the
test to fail.
This commit updates the test to reflect
the new html structure.
2025-02-10 10:50:29 +01:00
Anthony LC ba695bf647 ⬇️(frontend) downgrade to @react-pdf/renderer to 4.1.6
The version 4.2.1 of @react-pdf/renderer
is not compatible @blocknote/xl-pdf-exporter
and @blocknote/xl-docx-exporter.
2025-02-10 10:50:29 +01:00
Anthony LC 27e7aec193 💄(frontend) improve styles export pdf
When exporting a document to PDF, the headings
spacings were too small, the break lines were
not displayed. This commit fixes these issues
by replacing the needed blocks.
2025-02-10 10:50:29 +01:00
Anthony LC 58b712a1de 🐛(frontend) fix cursor breakline
We had breakline issues with the initial
cursor because of some css properties.
We changed the cursor css to not take
any space in the lines,
avoiding the breakline issues.
We keep the new cursor visibility
feature (always, activity).
2025-02-10 10:50:29 +01:00
renovate[bot] 08f9036523 ⬆️(dependencies) update js dependencies 2025-02-10 10:50:29 +01:00
MarineM1 a01790dc0b Merge branch 'main' into accessibility 2025-02-10 08:35:03 +01:00
Your name e14622ff66 ️ (frontend) modal options modified
The modal for document options has been modified to allow navigation
using "tab" and to be closed.
2025-02-06 14:47:04 +01:00
Your name 75fd994a5b (frontend) Focus
The elements focused with "Tab" are visible and consistent with the
defined style.
2025-02-04 16:27:46 +01:00
Your name 239933aef3 Merge branch 'accessibility' of https://github.com/suitenumerique/docs into accessibility 2025-02-03 16:38:13 +01:00
Your name 95c06d68cb ️ (frontend) navigation with tab and other modifications
- In DocGrid, when navigating with tab, we can see where we are
- In Doc, don't read "doc title input" but "rename"
- In docSearch, don't read "search" icon and read "aucun document
  trouvé" with screen reader
2025-02-03 16:27:24 +01:00
MarineM1 0d12278474 Merge branch 'main' into accessibility 2025-02-03 10:58:02 +01:00
Your name 8ce13075c1 (frontend) leftpanel and grid modified
- Read the Tooltip with the number of participants, not the icon
- In LeftPanel, when navigating with tab, we can see where we are
2025-01-31 16:47:37 +01:00
Your name 225f5a8abb erreur du commit précedent
je ne commite que les modifications annoncées dans le commit précédent
2025-01-31 13:23:05 +01:00
Your name 71a8765770 ️ (frontend) update after comments
-modifications after Sylvain's commentary
-modifications after Anto's commentaries
2025-01-31 11:42:32 +01:00
MarineM1 9186a101ec Merge branch 'main' into accessibility 2025-01-31 11:41:31 +01:00
MarineM1 74e816c479 Merge branch 'main' into accessibility 2025-01-30 16:14:09 +01:00
Your name 5d9eb2d694 mise en correspondance avec le main 2025-01-30 15:25:40 +01:00
Your name 33e168ba17 mise en correspondance avec le main 2025-01-30 14:49:13 +01:00
Your name f1c0f6bba0 ️ (frontend) pin icon translated and read
For good accessibility, the pinned icon will be read by the screen reader, but the simple icon will not
2025-01-30 14:26:29 +01:00
Your name 8f4fd15495 Merge branch 'accessibility' of https://github.com/suitenumerique/docs into accessibility 2025-01-30 09:36:00 +01:00
MarineM1 cc6ce4a945 Merge branch 'main' into accessibility 2025-01-30 09:02:51 +01:00
MarineM1 044c8f0bbd Merge branch 'main' into accessibility 2025-01-29 13:18:04 +01:00
Your name 7e62dcf1fc Merge branch 'main' of https://github.com/suitenumerique/docs into accessibility 2025-01-29 13:10:44 +01:00
MarineM1 e7742d914c Merge branch 'main' into accessibility 2025-01-29 10:54:54 +01:00
Your name b1a5c17d75 ️(frontend) update some code html
To improve accessibility, certain parts of the code are modified
 with the help of a screen reader
2025-01-28 15:32:55 +01:00
Your name bd68396e52 commit suite pull du 28 01 2025 2025-01-28 10:32:11 +01:00
Your name 9f1ae58ead commit avant récup du code 2025-01-28 09:15:29 +01:00
Your name f597549e96 Accessibilité - modifications des boutons corporate, group et public pour le lecteur d'écran 2025-01-28 09:14:26 +01:00
Your name 847e120d67 commit après récupération des modif du main 2025-01-27 09:18:37 +01:00
Your name 6682ddafff accessibilité - icons apps,lock et group plus plues 2025-01-27 09:16:26 +01:00
Your name 2f23404003 Accessibilité - modif des boutons home et search 2025-01-22 11:18:24 +01:00
Your name 6d4210d34b essai de modificaation de la couleur des arrows + pull du 20/01/2025 2025-01-20 11:36:24 +01:00
Your name 85f7598be8 Accessibilité - Lecture du Logo supprimée car double 2025-01-16 15:21:38 +01:00
84 changed files with 6796 additions and 443 deletions
+14 -3
View File
@@ -9,15 +9,25 @@ and this project adheres to
## [Unreleased]
## [2.2.0] - 2025-02-10
## Added
- 📝(doc) Add security.md and codeofconduct.md #604
- ✨(frontend) add home page #553
- ✨(frontend) add home page #608
- ✨(frontend) cursor display on activity #609
- ✨(frontend) Add export page break #623
## Changed
- 🔧(backend) make AI feature reach configurable #628
## Fixed
🌐(CI) Fix email partially translated #616
- 🌐(CI) Fix email partially translated #616
- 🐛(frontend) fix cursor breakline #609
- 🐛(frontend) fix style pdf export #609
## [2.1.0] - 2025-01-29
@@ -401,7 +411,8 @@ and this project adheres to
- 🚀 Impress, project to manage your documents easily and collaboratively.
[unreleased]: https://github.com/numerique-gouv/impress/compare/v2.1.0...main
[unreleased]: https://github.com/numerique-gouv/impress/compare/v2.2.0...main
[v2.2.0]: https://github.com/numerique-gouv/impress/releases/v2.2.0
[v2.1.0]: https://github.com/numerique-gouv/impress/releases/v2.1.0
[v2.0.1]: https://github.com/numerique-gouv/impress/releases/v2.0.1
[v2.0.0]: https://github.com/numerique-gouv/impress/releases/v2.0.0
+5
View File
@@ -15,3 +15,8 @@ the following command inside your docker container:
(Note : in your development environment, you can `make migrate`.)
## [Unreleased]
- AI features are now limited to users who are authenticated. Before this release, even anonymous
users who gained editor access on a document with link reach used to get AI feature.
IF you want anonymous users to keep access on AI features, you must now define the
`AI_ALLOW_REACH_FROM` setting to "public".
+1 -1
View File
@@ -15,7 +15,7 @@ services:
- "1081:1080"
minio:
user: ${DOCKER_USER:-1000}
# user: ${DOCKER_USER:-1000}
image: minio/minio
environment:
- MINIO_ROOT_USER=impress
+3853
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
{
"dependencies": {
"@blocknote/core": "^0.23.4",
"next": "^15.1.7"
}
}
+17 -2
View File
@@ -629,6 +629,9 @@ class Document(MP_Node, BaseModel):
# which date to allow them anyway)
# Anonymous users should also not see document accesses
has_access_role = bool(roles) and not is_deleted
can_update_from_access = (
is_owner_or_admin or RoleChoices.EDITOR in roles
) and not is_deleted
# Add roles provided by the document link, taking into account its ancestors
@@ -647,11 +650,23 @@ class Document(MP_Node, BaseModel):
is_owner_or_admin or RoleChoices.EDITOR in roles
) and not is_deleted
ai_allow_reach_from = settings.AI_ALLOW_REACH_FROM
ai_access = any(
[
ai_allow_reach_from == LinkReachChoices.PUBLIC and can_update,
ai_allow_reach_from == LinkReachChoices.AUTHENTICATED
and user.is_authenticated
and can_update,
ai_allow_reach_from == LinkReachChoices.RESTRICTED
and can_update_from_access,
]
)
return {
"accesses_manage": is_owner_or_admin,
"accesses_view": has_access_role,
"ai_transform": can_update,
"ai_translate": can_update,
"ai_transform": ai_access,
"ai_translate": ai_access,
"attachment_upload": can_update,
"children_list": can_get,
"children_create": can_update and user.is_authenticated,
@@ -2,6 +2,7 @@
Test AI transform API endpoint for users in impress's core app.
"""
import random
from unittest.mock import MagicMock, patch
from django.core.cache import cache
@@ -31,6 +32,9 @@ def ai_settings():
yield
@override_settings(
AI_ALLOW_REACH_FROM=random.choice(["public", "authenticated", "restricted"])
)
@pytest.mark.parametrize(
"reach, role",
[
@@ -57,6 +61,7 @@ def test_api_documents_ai_transform_anonymous_forbidden(reach, role):
}
@override_settings(AI_ALLOW_REACH_FROM="public")
@pytest.mark.usefixtures("ai_settings")
@patch("openai.resources.chat.completions.Completions.create")
def test_api_documents_ai_transform_anonymous_success(mock_create):
@@ -93,6 +98,27 @@ def test_api_documents_ai_transform_anonymous_success(mock_create):
)
@override_settings(AI_ALLOW_REACH_FROM=random.choice(["authenticated", "restricted"]))
@pytest.mark.usefixtures("ai_settings")
@patch("openai.resources.chat.completions.Completions.create")
def test_api_documents_ai_transform_anonymous_limited_by_setting(mock_create):
"""
Anonymous users should be able to request AI transform to a document
if the link reach and role permit it.
"""
document = factories.DocumentFactory(link_reach="public", link_role="editor")
answer = '{"answer": "Salut"}'
mock_create.return_value = MagicMock(
choices=[MagicMock(message=MagicMock(content=answer))]
)
url = f"/api/v1.0/documents/{document.id!s}/ai-transform/"
response = APIClient().post(url, {"text": "Hello", "action": "summarize"})
assert response.status_code == 401
@pytest.mark.parametrize(
"reach, role",
[
@@ -2,6 +2,7 @@
Test AI translate API endpoint for users in impress's core app.
"""
import random
from unittest.mock import MagicMock, patch
from django.core.cache import cache
@@ -51,6 +52,9 @@ def test_api_documents_ai_translate_viewset_options_metadata():
}
@override_settings(
AI_ALLOW_REACH_FROM=random.choice(["public", "authenticated", "restricted"])
)
@pytest.mark.parametrize(
"reach, role",
[
@@ -77,6 +81,7 @@ def test_api_documents_ai_translate_anonymous_forbidden(reach, role):
}
@override_settings(AI_ALLOW_REACH_FROM="public")
@pytest.mark.usefixtures("ai_settings")
@patch("openai.resources.chat.completions.Completions.create")
def test_api_documents_ai_translate_anonymous_success(mock_create):
@@ -113,6 +118,27 @@ def test_api_documents_ai_translate_anonymous_success(mock_create):
)
@override_settings(AI_ALLOW_REACH_FROM=random.choice(["authenticated", "restricted"]))
@pytest.mark.usefixtures("ai_settings")
@patch("openai.resources.chat.completions.Completions.create")
def test_api_documents_ai_translate_anonymous_limited_by_setting(mock_create):
"""
Anonymous users should be able to request AI translate to a document
if the link reach and role permit it.
"""
document = factories.DocumentFactory(link_reach="public", link_role="editor")
answer = '{"answer": "Salut"}'
mock_create.return_value = MagicMock(
choices=[MagicMock(message=MagicMock(content=answer))]
)
url = f"/api/v1.0/documents/{document.id!s}/ai-translate/"
response = APIClient().post(url, {"text": "Hello", "language": "es"})
assert response.status_code == 401
@pytest.mark.parametrize(
"reach, role",
[
@@ -28,8 +28,8 @@ def test_api_documents_retrieve_anonymous_public_standalone():
"abilities": {
"accesses_manage": False,
"accesses_view": False,
"ai_transform": document.link_role == "editor",
"ai_translate": document.link_role == "editor",
"ai_transform": False,
"ai_translate": False,
"attachment_upload": document.link_role == "editor",
"children_create": False,
"children_list": True,
@@ -84,8 +84,8 @@ def test_api_documents_retrieve_anonymous_public_parent():
"abilities": {
"accesses_manage": False,
"accesses_view": False,
"ai_transform": grand_parent.link_role == "editor",
"ai_translate": grand_parent.link_role == "editor",
"ai_transform": False,
"ai_translate": False,
"attachment_upload": grand_parent.link_role == "editor",
"children_create": False,
"children_list": True,
+77 -10
View File
@@ -12,6 +12,7 @@ from django.core import mail
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.core.files.storage import default_storage
from django.test.utils import override_settings
from django.utils import timezone
import pytest
@@ -124,6 +125,9 @@ def test_models_documents_soft_delete(depth):
# get_abilities
@override_settings(
AI_ALLOW_REACH_FROM=random.choice(["public", "authenticated", "restricted"])
)
@pytest.mark.parametrize(
"is_authenticated,reach,role",
[
@@ -175,6 +179,9 @@ def test_models_documents_get_abilities_forbidden(
assert document.get_abilities(user) == expected_abilities
@override_settings(
AI_ALLOW_REACH_FROM=random.choice(["public", "authenticated", "restricted"])
)
@pytest.mark.parametrize(
"is_authenticated,reach",
[
@@ -243,8 +250,8 @@ def test_models_documents_get_abilities_editor(
expected_abilities = {
"accesses_manage": False,
"accesses_view": False,
"ai_transform": True,
"ai_translate": True,
"ai_transform": is_authenticated,
"ai_translate": is_authenticated,
"attachment_upload": True,
"children_create": is_authenticated,
"children_list": True,
@@ -271,6 +278,9 @@ def test_models_documents_get_abilities_editor(
assert all(value is False for value in document.get_abilities(user).values())
@override_settings(
AI_ALLOW_REACH_FROM=random.choice(["public", "authenticated", "restricted"])
)
def test_models_documents_get_abilities_owner(django_assert_num_queries):
"""Check abilities returned for the owner of a document."""
user = factories.UserFactory()
@@ -300,12 +310,16 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
}
with django_assert_num_queries(1):
assert document.get_abilities(user) == expected_abilities
document.soft_delete()
document.refresh_from_db()
expected_abilities["move"] = False
assert document.get_abilities(user) == expected_abilities
@override_settings(
AI_ALLOW_REACH_FROM=random.choice(["public", "authenticated", "restricted"])
)
def test_models_documents_get_abilities_administrator(django_assert_num_queries):
"""Check abilities returned for the administrator of a document."""
user = factories.UserFactory()
@@ -335,11 +349,15 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries)
}
with django_assert_num_queries(1):
assert document.get_abilities(user) == expected_abilities
document.soft_delete()
document.refresh_from_db()
assert all(value is False for value in document.get_abilities(user).values())
@override_settings(
AI_ALLOW_REACH_FROM=random.choice(["public", "authenticated", "restricted"])
)
def test_models_documents_get_abilities_editor_user(django_assert_num_queries):
"""Check abilities returned for the editor of a document."""
user = factories.UserFactory()
@@ -369,23 +387,31 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries):
}
with django_assert_num_queries(1):
assert document.get_abilities(user) == expected_abilities
document.soft_delete()
document.refresh_from_db()
assert all(value is False for value in document.get_abilities(user).values())
def test_models_documents_get_abilities_reader_user(django_assert_num_queries):
@pytest.mark.parametrize("ai_access_setting", ["public", "authenticated", "restricted"])
def test_models_documents_get_abilities_reader_user(
ai_access_setting, django_assert_num_queries
):
"""Check abilities returned for the reader of a document."""
user = factories.UserFactory()
document = factories.DocumentFactory(users=[(user, "reader")])
access_from_link = (
document.link_reach != "restricted" and document.link_role == "editor"
)
expected_abilities = {
"accesses_manage": False,
"accesses_view": True,
"ai_transform": access_from_link,
"ai_translate": access_from_link,
# If you get your editor rights from the link role and not your access role
# You should not access AI if it's restricted to users with specific access
"ai_transform": access_from_link and ai_access_setting != "restricted",
"ai_translate": access_from_link and ai_access_setting != "restricted",
"attachment_upload": access_from_link,
"children_create": access_from_link,
"children_list": True,
@@ -404,11 +430,14 @@ def test_models_documents_get_abilities_reader_user(django_assert_num_queries):
"versions_list": True,
"versions_retrieve": True,
}
with django_assert_num_queries(1):
assert document.get_abilities(user) == expected_abilities
document.soft_delete()
document.refresh_from_db()
assert all(value is False for value in document.get_abilities(user).values())
with override_settings(AI_ALLOW_REACH_FROM=ai_access_setting):
with django_assert_num_queries(1):
assert document.get_abilities(user) == expected_abilities
document.soft_delete()
document.refresh_from_db()
assert all(value is False for value in document.get_abilities(user).values())
def test_models_documents_get_abilities_preset_role(django_assert_num_queries):
@@ -446,6 +475,44 @@ def test_models_documents_get_abilities_preset_role(django_assert_num_queries):
}
@override_settings(AI_ALLOW_REACH_FROM="public")
@pytest.mark.parametrize(
"is_authenticated,reach",
[
(True, "public"),
(False, "public"),
(True, "authenticated"),
],
)
def test_models_document_get_abilities_ai_access_authenticated(is_authenticated, reach):
"""Validate AI abilities when AI is available to any anonymous user with editor rights."""
user = factories.UserFactory() if is_authenticated else AnonymousUser()
document = factories.DocumentFactory(link_reach=reach, link_role="editor")
abilities = document.get_abilities(user)
assert abilities["ai_transform"] is True
assert abilities["ai_translate"] is True
@override_settings(AI_ALLOW_REACH_FROM="authenticated")
@pytest.mark.parametrize(
"is_authenticated,reach",
[
(True, "public"),
(False, "public"),
(True, "authenticated"),
],
)
def test_models_document_get_abilities_ai_access_public(is_authenticated, reach):
"""Validate AI abilities when AI is available only to authenticated users with editor rights."""
user = factories.UserFactory() if is_authenticated else AnonymousUser()
document = factories.DocumentFactory(link_reach=reach, link_role="editor")
abilities = document.get_abilities(user)
assert abilities["ai_transform"] == is_authenticated
assert abilities["ai_translate"] == is_authenticated
def test_models_documents_get_versions_slice_pagination(settings):
"""
The "get_versions_slice" method should allow navigating all versions of
+6 -1
View File
@@ -516,7 +516,12 @@ class Base(Configuration):
AI_API_KEY = values.Value(None, environ_name="AI_API_KEY", environ_prefix=None)
AI_BASE_URL = values.Value(None, environ_name="AI_BASE_URL", environ_prefix=None)
AI_MODEL = values.Value(None, environ_name="AI_MODEL", environ_prefix=None)
AI_ALLOW_REACH_FROM = values.Value(
choices=("public", "authenticated", "restricted"),
default="authenticated",
environ_name="AI_ALLOW_REACH_FROM",
environ_prefix=None,
)
AI_DOCUMENT_RATE_THROTTLE_RATES = {
"minute": 5,
"hour": 100,
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-06 15:30+0000\n"
"PO-Revision-Date: 2025-02-06 15:59\n"
"PO-Revision-Date: 2025-02-10 14:14\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-06 15:30+0000\n"
"PO-Revision-Date: 2025-02-06 15:57\n"
"PO-Revision-Date: 2025-02-10 14:14\n"
"Last-Translator: \n"
"Language-Team: English\n"
"Language: en_US\n"
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-06 15:30+0000\n"
"PO-Revision-Date: 2025-02-06 15:59\n"
"PO-Revision-Date: 2025-02-10 14:14\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-06 15:30+0000\n"
"PO-Revision-Date: 2025-02-06 15:57\n"
"PO-Revision-Date: 2025-02-10 14:14\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Language: nl_NL\n"
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "impress"
version = "2.1.0"
version = "2.2.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -1,3 +1,5 @@
/* eslint-disable playwright/no-conditional-expect */
/* eslint-disable playwright/no-conditional-in-test */
import path from 'path';
import { expect, test } from '@playwright/test';
@@ -368,4 +370,77 @@ test.describe('Doc Editor', () => {
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
});
[
{ ai_transform: false, ai_translate: false },
{ ai_transform: true, ai_translate: false },
{ ai_transform: false, ai_translate: true },
].forEach(({ ai_transform, ai_translate }) => {
test(`it checks AI buttons when can transform is at "${ai_transform}" and can translate is at "${ai_translate}"`, async ({
page,
}) => {
await mockedDocument(page, {
accesses: [
{
id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg',
role: 'owner',
user: {
email: 'super@owner.com',
full_name: 'Super Owner',
},
},
],
abilities: {
destroy: true, // Means owner
link_configuration: true,
ai_transform,
ai_translate,
accesses_manage: true,
accesses_view: true,
update: true,
partial_update: true,
retrieve: true,
},
link_reach: 'public',
link_role: 'editor',
created_at: '2021-09-01T09:00:00Z',
});
await goToGridDoc(page);
await verifyDocName(page, 'Mocked document');
await page.locator('.bn-block-outer').last().fill('Hello World');
const editor = page.locator('.ProseMirror');
await editor.getByText('Hello').dblclick();
if (!ai_transform && !ai_translate) {
await expect(page.getByRole('button', { name: 'AI' })).toBeHidden();
return;
}
await page.getByRole('button', { name: 'AI' }).click();
if (ai_transform) {
await expect(
page.getByRole('menuitem', { name: 'Use as prompt' }),
).toBeVisible();
} else {
await expect(
page.getByRole('menuitem', { name: 'Use as prompt' }),
).toBeHidden();
}
if (ai_translate) {
await expect(
page.getByRole('menuitem', { name: 'Language' }),
).toBeVisible();
} else {
await expect(
page.getByRole('menuitem', { name: 'Language' }),
).toBeHidden();
}
});
});
});
@@ -41,8 +41,16 @@ test.describe('Doc Export', () => {
await expect(page.getByRole('button', { name: 'Download' })).toBeVisible();
});
test('it exports the doc to pdf', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
test('it exports the doc with pdf line break', async ({
page,
browserName,
}) => {
const [randomDoc] = await createDoc(
page,
'doc-editor-line-break',
browserName,
1,
);
const downloadPromise = page.waitForEvent('download', (download) => {
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
@@ -50,8 +58,20 @@ test.describe('Doc Export', () => {
await verifyDocName(page, randomDoc);
await page.locator('.ProseMirror.bn-editor').click();
await page.locator('.ProseMirror.bn-editor').fill('Hello World');
const editor = page.locator('.ProseMirror.bn-editor');
await editor.click();
await editor.locator('.bn-block-outer').last().fill('Hello');
await page.keyboard.press('Enter');
await editor.locator('.bn-block-outer').last().fill('/');
await page.getByText('Page Break').click();
await expect(editor.locator('.bn-page-break')).toBeVisible();
await page.keyboard.press('Enter');
await editor.locator('.bn-block-outer').last().fill('World');
await page
.getByRole('button', {
@@ -69,9 +89,10 @@ test.describe('Doc Export', () => {
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
const pdfBuffer = await cs.toBuffer(await download.createReadStream());
const pdfText = (await pdf(pdfBuffer)).text;
const pdfData = await pdf(pdfBuffer);
expect(pdfText).toContain('Hello World'); // This is the doc text
expect(pdfData.numpages).toBe(2);
expect(pdfData.text).toContain('\n\nHello\n\nWorld'); // This is the doc text
});
test('it exports the doc to docx', async ({ page, browserName }) => {
@@ -395,9 +395,7 @@ test.describe('Doc Header', () => {
navigator.clipboard.readText(),
);
const clipboardContent = await handle.jsonValue();
expect(clipboardContent.trim()).toBe(
`<h1 data-level=\"1\">Hello World</h1><p></p>`,
);
expect(clipboardContent.trim()).toBe(`<h1>Hello World</h1><p></p>`);
});
test('it checks the copy link button', async ({ page }) => {
@@ -10,7 +10,7 @@ test.describe('Header', () => {
test('checks all the elements are visible', async ({ page }) => {
const header = page.locator('header').first();
await expect(header.getByAltText('Docs Logo')).toBeVisible();
await expect(header.getByLabel('Docs Logo')).toBeVisible();
await expect(header.locator('h2').getByText('Docs')).toHaveCSS(
'color',
'rgb(0, 0, 145)',
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "app-e2e",
"version": "2.1.0",
"version": "2.2.0",
"private": true,
"scripts": {
"lint": "eslint . --ext .ts",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "app-impress",
"version": "2.1.0",
"version": "2.2.0",
"private": true,
"scripts": {
"dev": "next dev",
@@ -23,7 +23,7 @@
"@gouvfr-lasuite/integration": "1.0.2",
"@hocuspocus/provider": "2.15.2",
"@openfun/cunningham-react": "2.9.4",
"@react-pdf/renderer": "4.2.1",
"@react-pdf/renderer": "4.1.6",
"@sentry/nextjs": "8.54.0",
"@tanstack/react-query": "5.66.0",
"cmdk": "1.0.4",
@@ -1,3 +1,4 @@
import { FocusScope } from '@react-aria/focus';
import {
PropsWithChildren,
ReactNode,
@@ -6,16 +7,16 @@ import {
useState,
} from 'react';
import { Button, Popover } from 'react-aria-components';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
const StyledPopover = styled(Popover)`
background-color: white;
border-radius: 4px;
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
border: 1px solid #dddddd;
transition: opacity 0.2s ease-in-out;
padding: 1rem;
`;
const StyledButton = styled(Button)`
@@ -29,6 +30,10 @@ const StyledButton = styled(Button)`
font-size: 0.938rem;
padding: 0;
text-wrap: nowrap;
&:focus-within {
outline: 2px solid #007bff;
}
`;
export interface DropButtonProps {
@@ -43,15 +48,17 @@ export const DropButton = ({
isOpen = false,
onOpenChange,
children,
label,
}: PropsWithChildren<DropButtonProps>) => {
const { t } = useTranslation();
const [isLocalOpen, setIsLocalOpen] = useState(isOpen);
const triggerRef = useRef(null);
const triggerRef = useRef<HTMLButtonElement>(null);
const firstFocusableRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
setIsLocalOpen(isOpen);
}, [isOpen]);
if (isLocalOpen && firstFocusableRef.current) {
firstFocusableRef.current.focus();
}
}, [isLocalOpen]);
const onOpenChangeHandler = (isOpen: boolean) => {
setIsLocalOpen(isOpen);
@@ -63,18 +70,30 @@ export const DropButton = ({
<StyledButton
ref={triggerRef}
onPress={() => onOpenChangeHandler(true)}
aria-label={label}
aria-haspopup="true"
aria-expanded={isLocalOpen}
aria-label={t('Open the document options')}
>
{button}
<span aria-hidden="true">{button}</span>
</StyledButton>
<StyledPopover
triggerRef={triggerRef}
isOpen={isLocalOpen}
onOpenChange={onOpenChangeHandler}
>
{children}
</StyledPopover>
{isLocalOpen && (
<StyledPopover
triggerRef={triggerRef}
isOpen={isLocalOpen}
onOpenChange={onOpenChangeHandler}
>
<FocusScope contain restoreFocus>
{children}
<button
ref={firstFocusableRef}
onClick={() => setIsLocalOpen(false)}
>
{t('Close the modal')}
</button>
</FocusScope>
</StyledPopover>
)}
</>
);
};
@@ -1,3 +1,4 @@
//import { t } from 'i18next';
import { PropsWithChildren, useState } from 'react';
import { css } from 'styled-components';
@@ -47,7 +47,11 @@ export const QuickSearchInput = ({
$gap={spacing['2xs']}
$padding={{ all: 'base' }}
>
{!loading && <Icon iconName="search" $variation="600" />}
{!loading && (
<span aria-hidden="true">
<Icon iconName="search" $variation="600" />
</span>
)}
{loading && (
<div>
<Loader size="small" />
@@ -203,6 +203,7 @@ input:-webkit-autofill:focus {
.c__select__wrapper .c__select__inner__actions__open:focus {
outline: none;
}
.c__select__wrapper .labelled-box__label.c__offscreen {
@@ -605,3 +606,31 @@ input:-webkit-autofill:focus {
.c__tooltip {
padding: 4px 6px;
}
/**
* lecture ou non des icons
*/
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
[data-icon]:before {
font-family: 'Material Icons';
content: attr(data-icon);
}
button:focus {
background-color: var(
--c--components--button--primary-text--background--color-hover
);
border-radius: var(--c--components--button--border-radius--focus);
box-shadow: 0 0 0 2px var(--c--theme--colors--primary-400)
}
@@ -1,11 +1,10 @@
import { Button } from '@openfun/cunningham-react';
import Image from 'next/image';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { BoxButton } from '@/components';
import ProConnectImg from '../assets/button-proconnect.svg?url';
import ProConnectImg from '../assets/button-proconnect.svg';
import { useAuth } from '../hooks';
import { gotoLogin, gotoLogout } from '../utils';
@@ -41,8 +40,9 @@ export const ProConnectButton = () => {
background-color: var(--c--theme--colors--primary-action);
}
`}
$radius="4px"
>
<Image src={ProConnectImg} alt={t('ProConnect Image')} />
<ProConnectImg />
</BoxButton>
);
};
@@ -92,6 +92,13 @@ export function AIGroupButton() {
return null;
}
const canAITransform = currentDoc.abilities.ai_transform;
const canAITranslate = currentDoc.abilities.ai_translate;
if (!canAITransform && !canAITranslate) {
return null;
}
return (
<Components.Generic.Menu.Root>
<Components.Generic.Menu.Trigger>
@@ -111,79 +118,85 @@ export function AIGroupButton() {
className="bn-menu-dropdown bn-drag-handle-menu"
sub={true}
>
<AIMenuItemTransform
action="prompt"
docId={currentDoc.id}
icon={
<Text $isMaterialIcon $size="s">
text_fields
</Text>
}
>
{t('Use as prompt')}
</AIMenuItemTransform>
<AIMenuItemTransform
action="rephrase"
docId={currentDoc.id}
icon={
<Text $isMaterialIcon $size="s">
refresh
</Text>
}
>
{t('Rephrase')}
</AIMenuItemTransform>
<AIMenuItemTransform
action="summarize"
docId={currentDoc.id}
icon={
<Text $isMaterialIcon $size="s">
summarize
</Text>
}
>
{t('Summarize')}
</AIMenuItemTransform>
<AIMenuItemTransform
action="correct"
docId={currentDoc.id}
icon={
<Text $isMaterialIcon $size="s">
check
</Text>
}
>
{t('Correct')}
</AIMenuItemTransform>
<Components.Generic.Menu.Root position="right" sub={true}>
<Components.Generic.Menu.Trigger sub={false}>
<Components.Generic.Menu.Item
className="bn-menu-item"
subTrigger={true}
>
<Box $direction="row" $gap="0.6rem">
{canAITransform && (
<>
<AIMenuItemTransform
action="prompt"
docId={currentDoc.id}
icon={
<Text $isMaterialIcon $size="s">
translate
text_fields
</Text>
{t('Language')}
</Box>
</Components.Generic.Menu.Item>
</Components.Generic.Menu.Trigger>
<Components.Generic.Menu.Dropdown
sub={true}
className="bn-menu-dropdown"
>
{languages.map((language) => (
<AIMenuItemTranslate
key={language.value}
language={language.value}
docId={currentDoc.id}
}
>
{t('Use as prompt')}
</AIMenuItemTransform>
<AIMenuItemTransform
action="rephrase"
docId={currentDoc.id}
icon={
<Text $isMaterialIcon $size="s">
refresh
</Text>
}
>
{t('Rephrase')}
</AIMenuItemTransform>
<AIMenuItemTransform
action="summarize"
docId={currentDoc.id}
icon={
<Text $isMaterialIcon $size="s">
summarize
</Text>
}
>
{t('Summarize')}
</AIMenuItemTransform>
<AIMenuItemTransform
action="correct"
docId={currentDoc.id}
icon={
<Text $isMaterialIcon $size="s">
check
</Text>
}
>
{t('Correct')}
</AIMenuItemTransform>
</>
)}
{canAITranslate && (
<Components.Generic.Menu.Root position="right" sub={true}>
<Components.Generic.Menu.Trigger sub={false}>
<Components.Generic.Menu.Item
className="bn-menu-item"
subTrigger={true}
>
{language.display_name}
</AIMenuItemTranslate>
))}
</Components.Generic.Menu.Dropdown>
</Components.Generic.Menu.Root>
<Box $direction="row" $gap="0.6rem">
<Text $isMaterialIcon $size="s">
translate
</Text>
{t('Language')}
</Box>
</Components.Generic.Menu.Item>
</Components.Generic.Menu.Trigger>
<Components.Generic.Menu.Dropdown
sub={true}
className="bn-menu-dropdown"
>
{languages.map((language) => (
<AIMenuItemTranslate
key={language.value}
language={language.value}
docId={currentDoc.id}
>
{language.display_name}
</AIMenuItemTranslate>
))}
</Components.Generic.Menu.Dropdown>
</Components.Generic.Menu.Root>
)}
</Components.Generic.Menu.Dropdown>
</Components.Generic.Menu.Root>
);
@@ -1,4 +1,9 @@
import { Dictionary, locales } from '@blocknote/core';
import {
BlockNoteSchema,
Dictionary,
locales,
withPageBreak,
} from '@blocknote/core';
import '@blocknote/core/fonts/inter.css';
import { BlockNoteView } from '@blocknote/mantine';
import '@blocknote/mantine/style.css';
@@ -6,7 +11,6 @@ import { useCreateBlockNote } from '@blocknote/react';
import { HocuspocusProvider } from '@hocuspocus/provider';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import * as Y from 'yjs';
import { Box, TextErrors } from '@/components';
@@ -17,125 +21,13 @@ import { useUploadFile } from '../hook';
import { useHeadings } from '../hook/useHeadings';
import useSaveDoc from '../hook/useSaveDoc';
import { useEditorStore } from '../stores';
import { cssEditor } from '../styles';
import { randomColor } from '../utils';
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
import { BlockNoteToolbar } from './BlockNoteToolbar';
const cssEditor = (readonly: boolean) => css`
&,
& > .bn-container,
& .ProseMirror {
height: 100%;
.collaboration-cursor__label2 {
color: #0d0d0d;
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: normal;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
position: absolute;
transform: translate(0%, -17px);
background-color: #e2b1f2;
padding: 2px 6px;
border-radius: 4px;
white-space: nowrap;
pointer-events: none;
height: 37px;
color: black;
transition: opacity 0.2s ease-in-out;
clip-path: polygon(
0% 0%,
100% 0%,
100% 50%,
50% 50%,
5% 50%,
0 100%,
0% 75%
);
}
.bn-side-menu[data-block-type='heading'][data-level='1'] {
height: 50px;
}
.bn-side-menu[data-block-type='heading'][data-level='2'] {
height: 43px;
}
.bn-side-menu[data-block-type='heading'][data-level='3'] {
height: 35px;
}
h1 {
font-size: 1.875rem;
}
h2 {
font-size: 1.5rem;
}
h3 {
font-size: 1.25rem;
}
a {
color: var(--c--theme--colors--greyscale-500);
cursor: pointer;
}
.bn-block-group
.bn-block-group
.bn-block-outer:not([data-prev-depth-changed]):before {
border-left: none;
}
}
.bn-editor {
color: var(--c--theme--colors--greyscale-700);
}
.bn-block-outer:not(:first-child) {
&:has(h1) {
padding-top: 32px;
}
&:has(h2) {
padding-top: 24px;
}
&:has(h3) {
padding-top: 16px;
}
}
& .bn-inline-content code {
background-color: gainsboro;
padding: 2px;
border-radius: 4px;
}
@media screen and (width <= 560px) {
& .bn-editor {
${readonly && `padding-left: 10px;`}
}
.bn-side-menu[data-block-type='heading'][data-level='1'] {
height: 46px;
}
.bn-side-menu[data-block-type='heading'][data-level='2'] {
height: 40px;
}
.bn-side-menu[data-block-type='heading'][data-level='3'] {
height: 40px;
}
& .bn-editor h1 {
font-size: 1.6rem;
}
& .bn-editor h2 {
font-size: 1.35rem;
}
& .bn-editor h3 {
font-size: 1.2rem;
}
.bn-block-content[data-is-empty-and-focused][data-content-type='paragraph']
.bn-inline-content:has(> .ProseMirror-trailingBreak:only-child)::before {
font-size: 14px;
}
}
`;
export const blockNoteSchema = withPageBreak(BlockNoteSchema.create());
interface BlockNoteEditorProps {
doc: Doc;
@@ -157,6 +49,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
const collabName = readOnly
? 'Reader'
: user?.full_name || user?.email || t('Anonymous');
const showCursorLabels: 'always' | 'activity' | (string & {}) = 'activity';
const editor = useCreateBlockNote(
{
@@ -168,37 +61,50 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
color: randomColor(),
},
/**
* We re-use the blocknote code to render the cursor but we:
* - fix rendering issue with Firefox
* - We don't want to show the cursor when anonymous users
* We render the cursor with a custom element to:
* - fix rendering issue with the default cursor
* - hide the cursor when anonymous users
*/
renderCursor: (user: { color: string; name: string }) => {
const cursor = document.createElement('span');
const cursorElement = document.createElement('span');
if (user.name === 'Reader') {
return cursor;
return cursorElement;
}
cursor.classList.add('collaboration-cursor__caret-new-empty');
cursor.setAttribute('spellcheck', `false`);
cursorElement.classList.add('collaboration-cursor-custom__base');
const caretElement = document.createElement('span');
caretElement.classList.add('collaboration-cursor-custom__caret');
caretElement.setAttribute('spellcheck', `false`);
caretElement.setAttribute('style', `background-color: ${user.color}`);
const label = document.createElement('span');
if (showCursorLabels === 'always') {
cursorElement.setAttribute('data-active', '');
}
label.classList.add('collaboration-cursor__label2');
label.setAttribute('spellcheck', `false`);
label.setAttribute('style', `background-color: ${user.color}`);
label.insertBefore(document.createTextNode(user.name), null);
const labelElement = document.createElement('span');
cursor.insertBefore(document.createTextNode('\u2060'), null); // Non-breaking space
cursor.insertBefore(label, null);
cursor.insertBefore(document.createTextNode('\u2060'), null); // Non-breaking space
labelElement.classList.add('collaboration-cursor-custom__label');
labelElement.setAttribute('spellcheck', `false`);
labelElement.setAttribute(
'style',
`background-color: ${user.color};border: 1px solid ${user.color};`,
);
labelElement.insertBefore(document.createTextNode(user.name), null);
return cursor;
caretElement.insertBefore(labelElement, null);
cursorElement.insertBefore(document.createTextNode('\u2060'), null); // Non-breaking space
cursorElement.insertBefore(caretElement, null);
cursorElement.insertBefore(document.createTextNode('\u2060'), null); // Non-breaking space
return cursorElement;
},
showCursorLabels: 'activity',
showCursorLabels: showCursorLabels as 'always' | 'activity',
},
dictionary: locales[lang as keyof typeof locales] as Dictionary,
uploadFile,
schema: blockNoteSchema,
},
[collabName, lang, provider, uploadFile],
);
@@ -231,10 +137,12 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
<BlockNoteView
editor={editor}
formattingToolbar={false}
slashMenu={false}
editable={!readOnly}
theme="light"
>
<BlockNoteToolbar />
<BlockNoteSuggestionMenu />
</BlockNoteView>
</Box>
);
@@ -259,6 +167,7 @@ export const BlockNoteEditorVersion = ({
},
provider: undefined,
},
schema: blockNoteSchema,
},
[initialContent],
);
@@ -0,0 +1,35 @@
import { combineByGroup, filterSuggestionItems } from '@blocknote/core';
import '@blocknote/mantine/style.css';
import {
SuggestionMenuController,
getDefaultReactSlashMenuItems,
getPageBreakReactSlashMenuItems,
useBlockNoteEditor,
} from '@blocknote/react';
import React, { useMemo } from 'react';
import { DocsBlockNoteEditor } from '../types';
export const BlockNoteSuggestionMenu = () => {
const editor = useBlockNoteEditor() as DocsBlockNoteEditor;
const getSlashMenuItems = useMemo(() => {
return async (query: string) =>
Promise.resolve(
filterSuggestionItems(
combineByGroup(
getDefaultReactSlashMenuItems(editor),
getPageBreakReactSlashMenuItems(editor),
),
query,
),
);
}, [editor]);
return (
<SuggestionMenuController
triggerCharacter="/"
getItems={getSlashMenuItems}
/>
);
};
@@ -65,7 +65,7 @@ export const DocEditor = ({ doc, versionId }: DocEditorProps) => {
$css="overflow-x: clip; flex: 1;"
$position="relative"
>
<Box $css="flex:1;" $overflow="auto" $position="relative">
<Box $css="flex:1;" $position="relative" $width="100%">
{isVersion ? (
<DocVersionEditor docId={doc.id} versionId={versionId} />
) : (
@@ -1,9 +1,9 @@
import { BlockNoteEditor } from '@blocknote/core';
import { useEffect } from 'react';
import { useHeadingStore } from '../stores';
import { DocsBlockNoteEditor } from '../types';
export const useHeadings = (editor: BlockNoteEditor) => {
export const useHeadings = (editor: DocsBlockNoteEditor) => {
const { setHeadings, resetHeadings } = useHeadingStore();
useEffect(() => {
@@ -1,9 +1,10 @@
import { BlockNoteEditor } from '@blocknote/core';
import { create } from 'zustand';
import { DocsBlockNoteEditor } from '../types';
export interface UseEditorstore {
editor?: BlockNoteEditor;
setEditor: (editor: BlockNoteEditor | undefined) => void;
editor?: DocsBlockNoteEditor;
setEditor: (editor: DocsBlockNoteEditor | undefined) => void;
}
export const useEditorStore = create<UseEditorstore>((set) => ({
@@ -1,7 +1,6 @@
import { BlockNoteEditor } from '@blocknote/core';
import { create } from 'zustand';
import { HeadingBlock } from '../types';
import { DocsBlockNoteEditor, HeadingBlock } from '../types';
const recursiveTextContent = (content: HeadingBlock['content']): string => {
if (!content) {
@@ -21,7 +20,7 @@ const recursiveTextContent = (content: HeadingBlock['content']): string => {
export interface UseHeadingStore {
headings: HeadingBlock[];
setHeadings: (editor: BlockNoteEditor) => void;
setHeadings: (editor: DocsBlockNoteEditor) => void;
resetHeadings: () => void;
}
@@ -0,0 +1,121 @@
import { css } from 'styled-components';
export const cssEditor = (readonly: boolean) => css`
&,
& > .bn-container,
& .ProseMirror {
height: 100%;
.collaboration-cursor-custom__base {
position: relative;
}
.collaboration-cursor-custom__caret {
position: absolute;
height: 100%;
width: 2px;
bottom: 4%;
left: -1px;
}
.collaboration-cursor-custom__label {
color: #0d0d0d;
font-size: 12px;
font-weight: 600;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
position: absolute;
top: -17px;
padding: 0px 6px;
border-radius: 0px;
white-space: nowrap;
transition: clip-path 0.3s ease-in-out;
border-radius: 4px 4px 4px 0;
box-shadow: inset -2px 2px 6px #ffffff00;
clip-path: polygon(0 85%, 4% 85%, 4% 100%, 0% 100%);
}
.collaboration-cursor-custom__base[data-active]
.collaboration-cursor-custom__label {
pointer-events: none;
box-shadow: inset -2px 2px 6px #ffffff88;
clip-path: polygon(0 0, 100% 0%, 100% 100%, 0% 100%);
}
.bn-side-menu[data-block-type='heading'][data-level='1'] {
height: 50px;
}
.bn-side-menu[data-block-type='heading'][data-level='2'] {
height: 43px;
}
.bn-side-menu[data-block-type='heading'][data-level='3'] {
height: 35px;
}
h1 {
font-size: 1.875rem;
}
h2 {
font-size: 1.5rem;
}
h3 {
font-size: 1.25rem;
}
a {
color: var(--c--theme--colors--greyscale-500);
cursor: pointer;
}
.bn-block-group
.bn-block-group
.bn-block-outer:not([data-prev-depth-changed]):before {
border-left: none;
}
}
.bn-editor {
color: var(--c--theme--colors--greyscale-700);
}
.bn-block-outer:not(:first-child) {
&:has(h1) {
padding-top: 32px;
}
&:has(h2) {
padding-top: 24px;
}
&:has(h3) {
padding-top: 16px;
}
}
& .bn-inline-content code {
background-color: gainsboro;
padding: 2px;
border-radius: 4px;
}
@media screen and (width <= 560px) {
& .bn-editor {
${readonly && `padding-left: 10px;`}
}
.bn-side-menu[data-block-type='heading'][data-level='1'] {
height: 46px;
}
.bn-side-menu[data-block-type='heading'][data-level='2'] {
height: 40px;
}
.bn-side-menu[data-block-type='heading'][data-level='3'] {
height: 40px;
}
& .bn-editor h1 {
font-size: 1.6rem;
}
& .bn-editor h2 {
font-size: 1.35rem;
}
& .bn-editor h3 {
font-size: 1.2rem;
}
.bn-block-content[data-is-empty-and-focused][data-content-type='paragraph']
.bn-inline-content:has(> .ProseMirror-trailingBreak:only-child)::before {
font-size: 14px;
}
}
`;
@@ -1,3 +1,7 @@
import { BlockNoteEditor } from '@blocknote/core';
import { blockNoteSchema } from './components/BlockNoteEditor';
export interface DocAttachment {
file: string;
}
@@ -12,3 +16,9 @@ export type HeadingBlock = {
level: number;
};
};
export type DocsBlockNoteEditor = BlockNoteEditor<
typeof blockNoteSchema.blockSchema,
typeof blockNoteSchema.inlineContentSchema,
typeof blockNoteSchema.styleSchema
>;
@@ -1,5 +1,3 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
import {
Tooltip,
VariantType,
@@ -114,7 +112,6 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
defaultValue={isUntitled ? undefined : titleDisplay}
onKeyDownCapture={handleKeyDown}
suppressContentEditableWarning={true}
aria-label="doc title input"
onBlurCapture={(event) =>
handleTitleSubmit(event.target.textContent || '')
}
@@ -98,7 +98,61 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
const exporter = new PDFExporter(
editor.schema,
pdfDefaultSchemaMappings,
{
...pdfDefaultSchemaMappings,
blockMapping: {
...pdfDefaultSchemaMappings.blockMapping,
heading: (block, exporter) => {
const PIXELS_PER_POINT = 0.75;
const MERGE_RATIO = 7.5;
const FONT_SIZE = 16;
const fontSizeEM =
block.props.level === 1
? 2
: block.props.level === 2
? 1.5
: 1.17;
return (
<Text
style={{
fontSize: fontSizeEM * FONT_SIZE * PIXELS_PER_POINT,
fontWeight: 700,
marginTop: `${fontSizeEM * MERGE_RATIO}px`,
marginBottom: `${fontSizeEM * MERGE_RATIO}px`,
}}
>
{exporter.transformInlineContent(block.content)}
</Text>
);
},
paragraph: (block, exporter) => {
/**
* Breakline in the editor are not rendered in the PDF
* By adding a space if the block is empty we ensure that the block is rendered
*/
if (Array.isArray(block.content)) {
block.content.forEach((content) => {
if (content.type === 'text' && !content.text) {
content.text = ' ';
}
});
if (!block.content.length) {
block.content.push({
styles: {},
text: ' ',
type: 'text',
});
}
}
return (
<Text key={block.id}>
{exporter.transformInlineContent(block.content)}
</Text>
);
},
},
},
{
resolveFileUrl: async (url) =>
exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl),
@@ -48,10 +48,20 @@ export interface Doc {
abilities: {
accesses_manage: boolean;
accesses_view: boolean;
attachment_upload: true;
ai_transform: boolean;
ai_translate: boolean;
attachment_upload: boolean;
children_create: boolean;
children_list: boolean;
collaboration_auth: boolean;
destroy: boolean;
favorite: boolean;
invite_owner: boolean;
link_configuration: boolean;
media_auth: boolean;
move: boolean;
partial_update: boolean;
restore: boolean;
retrieve: boolean;
update: boolean;
versions_destroy: boolean;
@@ -51,7 +51,7 @@ export const DocSearchModal = ({ ...modalProps }: DocSearchModalProps) => {
return {
groupName: docs.length > 0 ? t('Select a document') : '',
elements: search ? docs : [],
emptyString: t('No document found'),
//emptyString: t('No document found'),
endActions: hasNextPage
? [{ content: <InView onChange={() => void fetchNextPage()} /> }]
: [],
@@ -96,6 +96,20 @@ export const DocSearchModal = ({ ...modalProps }: DocSearchModalProps) => {
renderElement={(doc) => <DocSearchItem doc={doc} />}
/>
)}
{/* Message accessible pour les résultats vides */}
{search && docsData.elements.length === 0 && (
<p
role="alert"
aria-live="polite"
style={{
textAlign: 'center',
marginTop: '1rem',
color: '#666',
}}
>
{t('No document found')}
</p>
)}
</Box>
</QuickSearch>
</Box>
@@ -1,8 +1,8 @@
import { BlockNoteEditor } from '@blocknote/core';
import { useState } from 'react';
import { BoxButton, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { DocsBlockNoteEditor } from '@/features/docs/doc-editor';
import { useResponsiveStore } from '@/stores';
const leftPaddingMap: { [key: number]: string } = {
@@ -17,7 +17,7 @@ export type HeadingsHighlight = {
}[];
interface HeadingProps {
editor: BlockNoteEditor;
editor: DocsBlockNoteEditor;
level: number;
text: string;
headingId: string;
@@ -49,7 +49,6 @@ export const DocsGridActions = ({
callback: () => {
openShareModal?.();
},
testId: `docs-grid-actions-share-${doc.id}`,
},
@@ -70,6 +69,7 @@ export const DocsGridActions = ({
iconName="more_horiz"
$theme="primary"
$variation="600"
aria-hidden="true"
/>
</DropdownMenu>
@@ -54,6 +54,9 @@ export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
$css={css`
flex: ${flexLeft};
align-items: center;
&:focus {
outline: 2px solidrgb(33, 34, 82);
}
`}
href={`/docs/${doc.id}`}
>
@@ -79,7 +82,11 @@ export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
>
<Tooltip
content={
<Text $textAlign="center" $variation="000">
<Text
id={`tooltip-access-${doc.id}`}
$textAlign="center"
$variation="000"
>
{isPublic
? t('Accessible to anyone')
: t('Accessible to authenticated users')}
@@ -87,12 +94,17 @@ export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
}
placement="top"
>
<div>
<div
role="button"
tabIndex={0}
aria-labelledby={`tooltip-access-${doc.id}`}
>
<Icon
$theme="greyscale"
$variation="600"
$size="14px"
iconName={isPublic ? 'public' : 'vpn_lock'}
aria-hidden="true"
/>
</div>
</Tooltip>
@@ -9,6 +9,7 @@ type Props = {
doc: Doc;
handleClick: () => void;
};
export const DocsGridItemSharedButton = ({ doc, handleClick }: Props) => {
const { t } = useTranslation();
const sharedCount = doc.nb_accesses;
@@ -18,11 +19,15 @@ export const DocsGridItemSharedButton = ({ doc, handleClick }: Props) => {
return <Box $minWidth="50px">&nbsp;</Box>;
}
const tooltipContent = t('Shared with {{count}} users', {
count: sharedCount,
});
return (
<Tooltip
content={
<Text $textAlign="center" $variation="000">
{t('Shared with {{count}} users', { count: sharedCount })}
<Text $variation="000" $textAlign="center">
{tooltipContent}
</Text>
}
placement="top"
@@ -36,8 +41,14 @@ export const DocsGridItemSharedButton = ({ doc, handleClick }: Props) => {
}}
color="tertiary"
size="nano"
icon={<Icon $variation="800" $theme="primary" iconName="group" />}
aria-label={tooltipContent} // Lecture directe pour les lecteurs d'écran
>
<Icon
$variation="800"
$theme="primary"
iconName="group"
aria-hidden="true" // Empêche la lecture de l'icône
/>
{sharedCount}
</Button>
</Tooltip>
@@ -47,9 +47,9 @@ export const SimpleDocItem = ({
`}
>
{isPinned ? (
<PinnedDocumentIcon aria-label={t('Pin document icon')} />
<PinnedDocumentIcon aria-label={t('Pinned document.')} />
) : (
<SimpleFileIcon aria-label={t('Simple document icon')} />
<SimpleFileIcon aria-label="" />
)}
</Box>
<Box $justify="center">
@@ -1,8 +1,7 @@
import Image from 'next/image';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { default as IconDocs } from '@/assets/icons/icon-docs.svg?url';
import IconDocs from '@/assets/icons/icon-docs.svg';
import { Box, StyledLink } from '@/components/';
import { useCunninghamTheme } from '@/cunningham';
import { ButtonLogin } from '@/features/auth';
@@ -51,7 +50,7 @@ export const Header = () => {
$height="fit-content"
$margin={{ top: 'auto' }}
>
<Image priority src={IconDocs} alt={t('Docs Logo')} width={25} />
<IconDocs aria-label={t('Docs Logo')} width={25} />
<Title />
</Box>
</StyledLink>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 407 KiB

@@ -0,0 +1,26 @@
<svg
width="24"
height="25"
viewBox="0 0 24 25"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_7060_4428)">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12 0.0153809C5.37 0.0153809 0 5.38538 0 12.0154C0 17.3254 3.435 21.8104 8.205 23.4004C8.805 23.5054 9.03 23.1454 9.03 22.8304C9.03 22.5454 9.015 21.6004 9.015 20.5954C6 21.1504 5.22 19.8604 4.98 19.1854C4.845 18.8404 4.26 17.7754 3.75 17.4904C3.33 17.2654 2.73 16.7104 3.735 16.6954C4.68 16.6804 5.355 17.5654 5.58 17.9254C6.66 19.7404 8.385 19.2304 9.075 18.9154C9.18 18.1354 9.495 17.6104 9.84 17.3104C7.17 17.0104 4.38 15.9754 4.38 11.3854C4.38 10.0804 4.845 9.00038 5.61 8.16038C5.49 7.86038 5.07 6.63038 5.73 4.98038C5.73 4.98038 6.735 4.66538 9.03 6.21038C9.99 5.94038 11.01 5.80538 12.03 5.80538C13.05 5.80538 14.07 5.94038 15.03 6.21038C17.325 4.65038 18.33 4.98038 18.33 4.98038C18.99 6.63038 18.57 7.86038 18.45 8.16038C19.215 9.00038 19.68 10.0654 19.68 11.3854C19.68 15.9904 16.875 17.0104 14.205 17.3104C14.64 17.6854 15.015 18.4054 15.015 19.5304C15.015 21.1354 15 22.4254 15 22.8304C15 23.1454 15.225 23.5204 15.825 23.4004C20.565 21.8104 24 17.3104 24 12.0154C24 5.38538 18.63 0.0153809 12 0.0153809Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_7060_4428">
<rect
width="24"
height="24"
fill="white"
transform="translate(0 0.0153809)"
/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1,3 +1,4 @@
import { Button } from '@openfun/cunningham-react';
import Image from 'next/image';
import { Trans, useTranslation } from 'react-i18next';
import { css } from 'styled-components';
@@ -10,6 +11,7 @@ import { Title } from '@/features/header';
import { useResponsiveStore } from '@/stores';
import SC5 from '../assets/SC5.png';
import GithubIcon from '../assets/github.svg';
import { HomeSection } from './HomeSection';
@@ -72,7 +74,7 @@ function HomeOpenSource() {
</Text>
<Text as="p" $display="inline">
<Trans t={t} i18nKey="home-content-open-source-part2">
You can easily self-host Docs (check our installation{' '}
You can easily self-hosted Docs (check our installation{' '}
<a
href="https://github.com/suitenumerique/docs/tree/main/docs"
target="_blank"
@@ -114,6 +116,27 @@ function HomeOpenSource() {
are interested in using or contributing to docs.
</Trans>
</Text>
<Box $direction="row" $gap="1rem" $margin={{ top: 'small' }}>
<Button
icon={
<Text $isMaterialIcon $color="white">
chat
</Text>
}
href="https://matrix.to/#/#docs-official:matrix.org"
target="_blank"
>
<Text $color="white">Matrix</Text>
</Button>
<Button
color="secondary"
icon={<GithubIcon />}
href="https://github.com/suitenumerique/docs"
target="_blank"
>
Github
</Button>
</Box>
</Box>
}
/>
@@ -1,7 +1,7 @@
import Image from 'next/image';
import { useTranslation } from 'react-i18next';
import { default as IconDocs } from '@/assets/icons/icon-docs.svg?url';
import IconDocs from '@/assets/icons/icon-docs.svg';
import { Box } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { ButtonTogglePanel, Title } from '@/features/header/';
@@ -61,7 +61,7 @@ export const HomeHeader = () => {
$position="relative"
$height="fit-content"
>
<Image priority src={IconDocs} alt={t('Docs Logo')} width={32} />
<IconDocs aria-label={t('Docs Logo')} width={32} />
<Title />
</Box>
</Box>
@@ -175,7 +175,7 @@ export const HomeSection = ({
height: 'fit-content',
margin: 'auto',
overflow: 'auto',
flexBasis: direction === 'column' ? '100%' : '50%',
flexBasis: direction === 'column' ? 'fit-content' : '50%',
}}
/>
)}
@@ -54,6 +54,7 @@ export const LanguagePicker = () => {
$theme="primary"
$weight="bold"
$variation="800"
aria-hidden="true"
>
translate
</Text>
@@ -52,6 +52,7 @@ export const LeftPanelTargetFilters = () => {
return (
<Box
role="tablist"
$justify="center"
$padding={{ horizontal: 'sm' }}
$gap={spacing['2xs']}
@@ -61,7 +62,7 @@ export const LeftPanelTargetFilters = () => {
return (
<BoxButton
aria-label={query.label}
role="tab"
key={query.label}
onClick={() => onSelectQuery(query.targetQuery)}
$direction="row"
@@ -85,6 +86,7 @@ export const LeftPanelTargetFilters = () => {
<Icon
$variation={isActive ? '1000' : '700'}
iconName={query.icon}
aria-hidden="true"
/>
<Text $variation={isActive ? '1000' : '700'} $size="sm">
{query.label}
@@ -50,8 +50,11 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
onClick={goToHome}
size="medium"
color="tertiary-text"
aria-label={t('Back to home page')}
icon={
<Icon $variation="800" $theme="primary" iconName="house" />
<span aria-hidden="true">
<Icon $variation="800" $theme="primary" iconName="house" />
</span>
}
/>
{authenticated && (
@@ -59,8 +62,15 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
onClick={searchModal.open}
size="medium"
color="tertiary-text"
aria-label={t('Search')}
icon={
<Icon $variation="800" $theme="primary" iconName="search" />
<span aria-hidden="true">
<Icon
$variation="800"
$theme="primary"
iconName="search"
/>
</span>
}
/>
)}
@@ -195,10 +195,20 @@ export class ApiPlugin implements WorkboxPlugin {
abilities: {
accesses_manage: true,
accesses_view: true,
ai_transform: true,
ai_translate: true,
attachment_upload: true,
children_create: true,
children_list: true,
collaboration_auth: true,
destroy: true,
favorite: true,
invite_owner: true,
link_configuration: true,
media_auth: true,
move: true,
partial_update: true,
restore: true,
retrieve: true,
update: true,
versions_destroy: true,
@@ -7,6 +7,8 @@
"AI seems busy! Please try again.": "KI scheint beschäftigt! Bitte versuchen Sie es erneut.",
"Accessibility": "Barrierefreiheit",
"Accessibility statement": "Erklärung zur Barrierefreiheit",
"Accessible to anyone": "Für jeden zugänglich",
"Accessible to authenticated users": "Für authentifizierte Benutzer zugänglich",
"Add": "Hinzufügen",
"Address:": "Anschrift:",
"All docs": "Alle Dokumente",
@@ -92,6 +94,7 @@
"Pending invitations": "Ausstehende Einladungen",
"Personal data and cookies": "Personenbezogene Daten und Cookies",
"Pin": "Anheften",
"Pinned document.": "Angeheftetes Dokument",
"Pinned documents": "Angepinnte Dokumente",
"Private": "Privat",
"Public": "Öffentlich",
@@ -118,6 +121,7 @@
"Share with {{count}} users_many": "Teilen mit {{count}} Benutzern",
"Share with {{count}} users_one": "Teilen mit {{count}} Benutzern",
"Share with {{count}} users_other": "Teilen mit {{count}} Benutzern",
"Shared with {{count}} users": "Mit {{count}} Benutzern geteilt",
"Shared with me": "Mit mir geteilt",
"Something bad happens, please retry.": "Etwas ist schiefgelaufen, bitte versuchen Sie es erneut.",
"Stéphanie Schaer: Interministerial Digital Director (DINUM).": "Stéphanie Schaer: Interministerielle Digitaldirektorin (DINUM).",
@@ -133,6 +137,7 @@
"This site places a small text file (a \"cookie\") on your computer when you visit it.": "Diese Website platziert beim Besuch auf Ihrem Computer eine kleine Textdatei (ein \"Cookie\").",
"This will protect your privacy, but will also prevent the owner from learning from your actions and creating a better experience for you and other users.": "Dies schützt Ihre Privatsphäre, verhindert jedoch auch, dass der Eigentümer aus Ihren Aktionen lernt und eine bessere Erfahrung für Sie und andere Benutzer schafft.",
"Too many requests. Please wait 60 seconds.": "Zu viele Anfragen. Bitte warten Sie 60 Sekunden.",
"Translate": "Übersetzen",
"Type a name or email": "Geben Sie einen Namen oder eine E-Mail-Adresse ein",
"Type the name of a document": "Geben Sie den Namen eines Dokuments ein",
"Unless otherwise stated, all content on this site is under": "Sofern nicht anders angegeben, steht der gesamte Inhalt dieser Website unter",
@@ -167,6 +172,7 @@
"translation": {
"\"{{email}}\" is already invited to the document.": "\"{{email}}\" est déjà invité à accéder au document.",
"\"{{email}}\" is already member of the document.": "\"{{email}}\" est déjà membre du document.",
"A new way to organize knowledge.": "Une nouvelle façon dorganiser les connaissances.",
"AI Actions": "Actions IA",
"AI seems busy! Please try again.": "L'IA semble occupée ! Veuillez réessayer.",
"Accessibility": "Accessibilité",
@@ -177,16 +183,22 @@
"Address:": "Adresse :",
"Administrator": "Administrateur",
"All docs": "Tous les documents",
"An uncompromising writing experience.": "Une expérience d'écriture sans compromis.",
"Anonymous": "Anonyme",
"Anyone with the link can edit the document": "N'importe qui avec le lien peut éditer le document",
"Anyone with the link can edit the document if they are logged in": "N'importe qui avec le lien peut éditer le document à condition qu'il soit connecté",
"Anyone with the link can see the document": "N'importe qui avec le lien peut voir le document",
"Anyone with the link can view the document if they are logged in": "N'importe qui avec le lien peut voir le document à condition qu'il soit connecté",
"Are you sure you want to delete the document \"{{title}}\"?": "Êtes-vous sûr de vouloir supprimer le document \"{{title}}\" ?",
"Available soon": "Disponible prochainement",
"Back to home page": "Retour à l'accueil",
"Banner image": "Image de la bannière",
"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",
"Collaborate": "Collaborer",
"Collaborate and write in real time, without layout constraints.": "Collaborez et rédigez en temps réel, sans contrainte de mise en page.",
"Collaborative writing, Simplified.": "L'écriture collaborative simplifiée.",
"Compliance status": "État de conformité",
"Confirm deletion": "Confirmer la suppression",
"Connected": "Connecté",
@@ -206,6 +218,10 @@
"Doc visibility card": "Carte de visibilité du doc",
"Docs": "Docs",
"Docs Logo": "Logo Docs",
"Docs is already available, log in to use it now.": "Docs est déjà disponible, connectez-vous pour lutiliser dès maintenant.",
"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 simplifie la collaboration en temps réel. Invitez des collaborateurs - agents publics ou partenaires externes - d'un clic pour voir leurs modifications en direct, tout en gardant un contrôle précis des accès pour la sécurité des données.",
"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 propose une expérience d'écriture intuitive. Son interface minimaliste privilégie le contenu sur la mise en page, tout en offrant l'essentiel : import de médias, mode hors-ligne et raccourcis clavier pour plus d'efficacité.",
"Docs transforms your documents into knowledge bases thanks to subpages, powerful search and the ability to pin your important documents.": "Docs transforme vos documents en bases de connaissances grâce aux sous-pages, une recherche performante et la possibilité d'épingler vos documents importants.",
"Docs: Your new companion to collaborate on documents efficiently, intuitively, and securely.": "Docs : Votre nouveau compagnon pour collaborer sur des documents efficacement, intuitivement et en toute sécurité.",
"Document owner": "Propriétaire du document",
"Document title updated successfully": "Titre du document mis à jour avec succès",
@@ -227,11 +243,14 @@
"Failed to copy link": "Échec de la copie du lien",
"Failed to copy to clipboard": "Échec de la copie dans le presse-papier",
"Failed to create the invitation for {{email}}.": "Impossible de créer l'invitation pour {{email}}.",
"Flexible export.": "Un export flexible.",
"Format": "Format",
"French Interministerial Directorate for Digital Affairs (DINUM), 20 avenue de Ségur 75007 Paris.": "Direction interministérielle des affaires numériques (DINUM), 20 avenue de Segur 75007 Paris.",
"Govs ❤️ Open Source.": "Gouvs ❤️ Open Source.",
"History": "Historique",
"If a member is editing, his works can be lost.": "Si un membre est en train d'éditer, ses travaux peuvent être perdus.",
"If you are unable to access a content or a service, you can contact the person responsible for https://lasuite.numerique.gouv.fr to be directed to an accessible alternative or to obtain the content in another form.": "Si vous ne pouvez pas accéder à un contenu ou à un service, vous pouvez contacter la personne responsable de https://lasuite. umerique.gouv.fr pour être dirigé vers une alternative accessible ou pour obtenir le contenu sous une autre forme.",
"Illustration": "Image",
"Illustration:": "Illustration :",
"Improvement and contact": "Amélioration et contact",
"Invite": "Inviter",
@@ -241,7 +260,7 @@
"It's true, you didn't have to click on a block that covers half the page to say you agree to the placement of cookies — even if you don't know what it means!": "C'est vrai, vous n'avez pas à cliquer sur un bloc qui couvre la moitié de la page pour dire que vous acceptez le placement de cookies — même si vous ne savez pas ce que cela signifie !",
"Language": "Langue",
"Last update: {{update}}": "Dernière mise à jour : {{update}}",
"Legal Notice": "Mentions Legales",
"Legal Notice": "Mentions Légales",
"Legal notice": "Mention légale",
"Link Copied !": "Lien copié !",
"Link parameters": "Paramètres du lien",
@@ -264,8 +283,10 @@
"OK": "OK",
"Offline ?!": "Hors-ligne ?!",
"Only invited people can access": "Seules les personnes invitées peuvent accéder",
"Open Source": "Open Source",
"Open the document options": "Ouvrir les options du document",
"Open the header menu": "Ouvrir le menu d'en-tête",
"Organize": "Organiser",
"Ouch !": "Aïe !",
"Owner": "Propriétaire",
"PDF": "PDF",
@@ -273,15 +294,18 @@
"Personal data and cookies": "Données personnelles et cookies",
"Pin": "Épingler",
"Pin document icon": "Icône épingler un document",
"Pinned document.": "Document épinglé",
"Pinned documents": "Documents épinglés",
"Private": "Privé",
"ProConnect Image": "Image ProConnect",
"Proconnect Login": "Login Proconnect",
"Public": "Public",
"Public document": "Document public",
"Publication Director": "Directeur de la publication",
"Publisher": "Éditeur",
"Quick search input": "Saisie de recherche rapide",
"Reader": "Lecteur",
"Reading": "Lecture seul",
"Reading": "Lecture seule",
"Remedies": "Voie de recours",
"Remove": "Supprimer",
"Rename": "Renommer",
@@ -299,12 +323,16 @@
"Share with {{count}} users_many": "Partager avec {{count}} utilisateurs",
"Share with {{count}} users_one": "Partager avec {{count}} utilisateur",
"Share with {{count}} users_other": "Partager avec {{count}} utilisateurs",
"Shared with {{count}} users": "Partagé avec {{count}} utilisateurs",
"Shared with me": "Partagés avec moi",
"Shared with {{count}} users_many": "Partager avec {{count}} utilisateurs",
"Shared with {{count}} users_one": "Partager avec {{count}} utilisateur",
"Shared with {{count}} users_other": "Partager avec {{count}} utilisateurs",
"Show more": "Voir plus",
"Simple and secure collaboration.": "Une collaboration simple et sécurisée.",
"Simple document icon": "Icône simple du document",
"Something bad happens, please retry.": "Une erreur inattendue s'est produite, veuillez réessayer.",
"Start Writing": "Commencer à écrire",
"Stéphanie Schaer: Interministerial Digital Director (DINUM).": "Stéphanie Schaer: Directrice numérique interministériel (DINUM).",
"Summarize": "Résumer",
"Summary": "Sommaire",
@@ -319,7 +347,9 @@
"This site does not display a cookie consent banner, why?": "Ce site n'affiche pas de bannière de consentement des cookies, pourquoi?",
"This site places a small text file (a \"cookie\") on your computer when you visit it.": "Ce site place un petit fichier texte (un « cookie ») sur votre ordinateur lorsque vous le visitez.",
"This will protect your privacy, but will also prevent the owner from learning from your actions and creating a better experience for you and other users.": "Cela protégera votre vie privée, mais empêchera également le propriétaire d'apprendre de vos actions et de créer une meilleure expérience pour vous et les autres utilisateurs.",
"To facilitate the circulation of documents, Docs allows you to export your content to the most common formats: PDF, Word or OpenDocument.": "Pour faciliter la circulation des documents, Docs permet d'exporter vos contenus vers les formats les plus courants : PDF, Word ou OpenDocument.",
"Too many requests. Please wait 60 seconds.": "Trop de demandes. Veuillez patienter 60 secondes.",
"Translate": "Traduire",
"Type a name or email": "Tapez un nom ou un email",
"Type the name of a document": "Tapez le nom d'un document",
"Unless otherwise stated, all content on this site is under": "Sauf mention contraire, tout le contenu de ce site est sous",
@@ -334,6 +364,7 @@
"Warning": "Attention",
"We simply comply with the law, which states that certain audience measurement tools, properly configured to respect privacy, are exempt from prior authorization.": "Nous nous conformons simplement à la loi, qui stipule que certains outils de mesure daudience, correctement configurés pour respecter la vie privée, sont exemptés de toute autorisation préalable.",
"We try to respond within 2 working days.": "Nous essayons de répondre dans les 2 jours ouvrables.",
"Write": "Écrire",
"You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.": "Vous êtes le seul propriétaire de ce groupe, faites d'un autre membre le propriétaire du groupe, avant de pouvoir modifier votre propre rôle ou vous supprimer du document.",
"You can oppose the tracking of your browsing on this website.": "Vous pouvez vous opposer au suivi de votre navigation sur ce site.",
"You can:": "Vous pouvez:",
@@ -345,6 +376,9 @@
"accessibility-dinum-services": "<strong>DINUM</strong> s'engage à rendre accessibles ses services numériques, conformément à l'article 47 de la loi n° 2005-102 du 11 février 2005.",
"accessibility-form-defenseurdesdroits": "Écrire un message au<1>Défenseur des droits</1>",
"accessibility-not-audit": "<strong>docs.numerique.gouv.fr</strong> n'est pas en conformité avec le RGAA 4.1. Le site n'a <strong>pas encore été audité.</strong>",
"home-content-open-source-part1": "Docs est construit sur <2>Django Rest Framework</2>, <5>Next.js</5>, et <8>MinIO</8>. Nous utilisons également <11>Yjs</11> et <15>BlockNote.js</15> dont nous sommes fiers sponsors.",
"home-content-open-source-part2": "Vous pouvez facilement auto-héberger Docs (consultez notre <2>documentation</2> d'installation avec des exemples prêts pour la production).<br/>Docs utilise une <8>licence</8> adaptée à l'innovation et aux entreprises.<br/>Les contributions sont les bienvenues (consultez notre feuille de route <13>ici</13>).",
"home-content-open-source-part3": "Docs est le résultat d'un effort conjoint mené par les gouvernements français 🇫🇷🥖 <1>(DINUM)</1> et allemand 🇩🇪🥨 <5>(ZenDiS)</5>. Nous sommes toujours à la recherche de nouveaux partenaires publics (nous embarquons actuellement les Pays-Bas 🇳🇱🧀). N'hésitez pas à nous contacter si vous souhaitez utiliser ou contribuer à Docs.",
"you have reported to the website manager a lack of accessibility that prevents you from accessing content or one of the services of the portal and you have not received a satisfactory response.": "vous avez signalé au responsable du site internet un défaut d'accessibilité qui vous empêche d'accéder à un contenu ou à un des services du portail et vous n'avez pas obtenu de réponse satisfaisante."
}
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "impress",
"version": "2.1.0",
"version": "2.2.0",
"private": true,
"workspaces": {
"packages": [
@@ -1,6 +1,6 @@
{
"name": "eslint-config-impress",
"version": "2.1.0",
"version": "2.2.0",
"license": "MIT",
"scripts": {
"lint": "eslint --ext .js ."
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "packages-i18n",
"version": "2.1.0",
"version": "2.2.0",
"private": true,
"scripts": {
"extract-translation": "yarn extract-translation:impress",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "server-y-provider",
"version": "2.1.0",
"version": "2.2.0",
"description": "Y.js provider for docs",
"repository": "https://github.com/numerique-gouv/impress",
"license": "MIT",
+3 -132
View File
@@ -3357,13 +3357,6 @@
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/fns@3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@react-pdf/fns/-/fns-3.1.0.tgz#cc1130b35e21b173d1d0fa1729db3285d209a9ea"
integrity sha512-BjT7C/IeYlrF4Pevlrlo+fILhSxsWSm6Ka/rQrQzYsyQuOsqI6bmBzsTW+T6ghqrD5HLRKr1n8vjAaE9g4rFhA==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/font@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@react-pdf/font/-/font-3.0.1.tgz#ea831f272e3b1418ffd2d9f70c835236e6564354"
@@ -3374,16 +3367,6 @@
fontkit "^2.0.2"
is-url "^1.2.4"
"@react-pdf/font@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@react-pdf/font/-/font-3.0.2.tgz#b29b5e2e83c5ce515148b8f150ae3795492ff8e1"
integrity sha512-5fGlAc8uC1ls+Atdc1J4EGcProH72lcE47TL2+EX54OgChl5m5H1+yrw/VufLd3Ua1e4YSvr53rYXVD2PUFzWA==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/types" "^2.7.1"
fontkit "^2.0.2"
is-url "^1.2.4"
"@react-pdf/image@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@react-pdf/image/-/image-3.0.1.tgz#5c08a7ddf5d07c53ea3377348e7bb07d17efe7c2"
@@ -3410,23 +3393,6 @@
queue "^6.0.1"
yoga-layout "^3.1.0"
"@react-pdf/layout@^4.2.2":
version "4.2.2"
resolved "https://registry.yarnpkg.com/@react-pdf/layout/-/layout-4.2.2.tgz#19dd4fae3264801f943a15404d90c9cdebce2645"
integrity sha512-PHX1yiRXF1nxtKtRgT7rKmA1D6QHaAaWfSQcCKumolsdD/KZnAX2jIRR6s8Sss03ak342K8gGs/udbZio17zhA==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/fns" "3.1.0"
"@react-pdf/image" "^3.0.1"
"@react-pdf/pdfkit" "^4.0.1"
"@react-pdf/primitives" "^4.1.0"
"@react-pdf/stylesheet" "^5.2.2"
"@react-pdf/textkit" "^5.0.2"
"@react-pdf/types" "^2.7.1"
emoji-regex "^10.3.0"
queue "^6.0.1"
yoga-layout "^3.2.1"
"@react-pdf/pdfkit@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@react-pdf/pdfkit/-/pdfkit-4.0.0.tgz#8dbbfc8e546ebd0b76e88bd525fd6bec43c19df4"
@@ -3440,20 +3406,6 @@
jay-peg "^1.1.0"
vite-compatible-readable-stream "^3.6.1"
"@react-pdf/pdfkit@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@react-pdf/pdfkit/-/pdfkit-4.0.1.tgz#ef7f20790a31ddc7a8e1a9104f5676e5456b2e26"
integrity sha512-p76g0DQOG5t3yDymQYL4EHgbhTKvADTaK5J8DHNqhQGxwOi0qrkKbtfe21tgdzBm6xrTauoBp+teKtUn+jr/zA==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/png-js" "^3.0.0"
browserify-zlib "^0.2.0"
crypto-js "^4.2.0"
fontkit "^2.0.2"
jay-peg "^1.1.1"
linebreak "^1.1.0"
vite-compatible-readable-stream "^3.6.1"
"@react-pdf/png-js@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@react-pdf/png-js/-/png-js-3.0.0.tgz#c0b7dc7c77e36f0830e9b7bccca7ddd64ada1c5e"
@@ -3466,11 +3418,6 @@
resolved "https://registry.yarnpkg.com/@react-pdf/primitives/-/primitives-4.0.0.tgz#0a710664923547c315386e82a643e58008612844"
integrity sha512-yp4E0rDL03NaUp/CnDBz3HQNfH2Mzdlgku57yhTMGNzetwB0NJusXcjYg5XsTGIXnR7Tv80JKI4O4ajj+oaLeQ==
"@react-pdf/primitives@^4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@react-pdf/primitives/-/primitives-4.1.0.tgz#85f389d40612b6bfa5e70d50eec0a7c09f0948f1"
integrity sha512-ijzkIBdazFFiZFLWLPOK3wIqfJt276ZqvXgDkz+HTgT4hsRo+wOc8ykBoYfKUlAGnFVb/SOZaN3D2mSi4AM2AA==
"@react-pdf/reconciler@^1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@react-pdf/reconciler/-/reconciler-1.1.3.tgz#6fbd0f601fcb4f7ff1ff7c4dcc4df6afbccd9129"
@@ -3495,41 +3442,7 @@
parse-svg-path "^0.1.2"
svg-arc-to-cubic-bezier "^3.2.0"
"@react-pdf/render@^4.1.1":
version "4.1.1"
resolved "https://registry.yarnpkg.com/@react-pdf/render/-/render-4.1.1.tgz#fd4152d8760146466ea0dd43a12e0e877744027f"
integrity sha512-F20HpVxgd666yKenH0sfMD2VbH8A7PT7iFZsMseY9/PDWB+u4eEDOYwo/j1qfxKwLybKMsGKdNv7ohc0APkHaw==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/fns" "3.1.0"
"@react-pdf/primitives" "^4.1.0"
"@react-pdf/textkit" "^5.0.2"
"@react-pdf/types" "^2.7.1"
abs-svg-path "^0.1.1"
color-string "^1.9.1"
normalize-svg-path "^1.1.0"
parse-svg-path "^0.1.2"
svg-arc-to-cubic-bezier "^3.2.0"
"@react-pdf/renderer@4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@react-pdf/renderer/-/renderer-4.2.1.tgz#9211d531579ff58965442abe452b3ffe84ddc46d"
integrity sha512-rkwJSvkXGhDCkjWgomwsQZdLhIll1p2MBDwPdaqcprH+eZ4JVXoeJCw4rhDyY4NgyP+LaJuVzDTnFYlrIhs47A==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/font" "^3.0.2"
"@react-pdf/layout" "^4.2.2"
"@react-pdf/pdfkit" "^4.0.1"
"@react-pdf/primitives" "^4.1.0"
"@react-pdf/reconciler" "^1.1.3"
"@react-pdf/render" "^4.1.1"
"@react-pdf/types" "^2.7.1"
events "^3.3.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
queue "^6.0.1"
"@react-pdf/renderer@^4.0.0":
"@react-pdf/renderer@4.1.6", "@react-pdf/renderer@^4.0.0":
version "4.1.6"
resolved "https://registry.yarnpkg.com/@react-pdf/renderer/-/renderer-4.1.6.tgz#257d4871192edb4d1716bc6399b5a7cf73ee3db3"
integrity sha512-hfQ0PsuVqfoYxkYgmkj+HFkylbB1QTpXY1rnlgnzJlrlSoNXjzPrCa/ty8jcHOwYA2lNoazIAoDatBIsc8K5pw==
@@ -3560,19 +3473,6 @@
media-engine "^1.0.3"
postcss-value-parser "^4.1.0"
"@react-pdf/stylesheet@^5.2.2":
version "5.2.2"
resolved "https://registry.yarnpkg.com/@react-pdf/stylesheet/-/stylesheet-5.2.2.tgz#6d5d2e0ea476bc508469eb4682d7f20b790b2b62"
integrity sha512-oHP+hZakETrecnZCSRPqNvFhSyBgoZSDOkonY9WJOxRkUb6P6A+mAVSOWBaNt2eM4FHMDpYDeR9stx+gAWn6gg==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/fns" "3.1.0"
"@react-pdf/types" "^2.7.1"
color-string "^1.9.1"
hsl-to-hex "^1.0.0"
media-engine "^1.0.3"
postcss-value-parser "^4.1.0"
"@react-pdf/textkit@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@react-pdf/textkit/-/textkit-5.0.1.tgz#284e55ff016f46c8bf364aa1d7bcef772c913662"
@@ -3584,27 +3484,11 @@
hyphen "^1.6.4"
unicode-properties "^1.4.1"
"@react-pdf/textkit@^5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@react-pdf/textkit/-/textkit-5.0.2.tgz#ff8ee086bc81ef4c706e51f3eeb1219228c8266c"
integrity sha512-vZoXznaZTQ/6ISpbCwAatZ6UNieUp12ByY4hlEXQ108VA3WDmcYm2usqk63LhZJiwE6ag+VyEus7R3CgDcxoMw==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/fns" "3.1.0"
bidi-js "^1.0.2"
hyphen "^1.6.4"
unicode-properties "^1.4.1"
"@react-pdf/types@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@react-pdf/types/-/types-2.7.0.tgz#56a0232ce420c313fe67cef193cbf57870a01477"
integrity sha512-7KrPPCpgRPKR+g+T127PE4bpw9Q84ZiY07EYRwXKVtTEVW9wJ5BZiF9smT9IvH19s+MQaDLmYRgjESsnqlyH0Q==
"@react-pdf/types@^2.7.1":
version "2.7.1"
resolved "https://registry.yarnpkg.com/@react-pdf/types/-/types-2.7.1.tgz#eb9f70be66b42c47f60c5afcc0af044ac48b98bf"
integrity sha512-MyjR1u+6SclQ/Tx6NP3/yoYZw7reXgC4OHFOrdMh/zeZ+ezfdGyovB+jdmVQuMe7Fsh64v7PUkO5tnsXHyCFWQ==
"@react-stately/autocomplete@3.0.0-alpha.0":
version "3.0.0-alpha.0"
resolved "https://registry.yarnpkg.com/@react-stately/autocomplete/-/autocomplete-3.0.0-alpha.0.tgz#3b80c82ba50d682dbbdd2f1f2c8b6ecc059f8afc"
@@ -6109,11 +5993,6 @@ bare-events@^2.2.0:
resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.4.tgz#16143d435e1ed9eafd1ab85f12b89b3357a41745"
integrity sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==
base64-js@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
integrity sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==
base64-js@^1.1.2, base64-js@^1.3.0, base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@@ -9387,7 +9266,7 @@ jake@^10.8.5:
filelist "^1.0.4"
minimatch "^3.1.2"
jay-peg@^1.1.0, jay-peg@^1.1.1:
jay-peg@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/jay-peg/-/jay-peg-1.1.1.tgz#fdf410b89fa7a295bf74424ffe4c9083dbe7c363"
integrity sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==
@@ -10030,14 +9909,6 @@ lilconfig@^3.1.2:
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4"
integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==
linebreak@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/linebreak/-/linebreak-1.1.0.tgz#831cf378d98bced381d8ab118f852bd50d81e46b"
integrity sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==
dependencies:
base64-js "0.0.8"
unicode-trie "^2.0.0"
lines-and-columns@^1.1.6:
version "1.2.4"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
@@ -14779,7 +14650,7 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
yoga-layout@^3.1.0, yoga-layout@^3.2.1:
yoga-layout@^3.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/yoga-layout/-/yoga-layout-3.2.1.tgz#d2d1ba06f0e81c2eb650c3e5ad8b0b4adde1e843"
integrity sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==
+1 -1
View File
@@ -93,4 +93,4 @@ releases:
environments:
dev:
values:
- version: 2.1.0
- version: 2.2.0
+1 -1
View File
@@ -1,5 +1,5 @@
apiVersion: v2
type: application
name: docs
version: 2.1.0
version: 2.2.0-beta.1
appVersion: latest
+3
View File
@@ -131,6 +131,7 @@
| `backend.persistence.volume-name.mountPath` | Path where the volume should be mounted to | |
| `backend.extraVolumeMounts` | Additional volumes to mount on the backend. | `[]` |
| `backend.extraVolumes` | Additional volumes to mount on the backend. | `[]` |
| `backend.pdb.enabled` | Enable pdb on backend | `true` |
### frontend
@@ -180,6 +181,7 @@
| `frontend.persistence.volume-name.mountPath` | Path where the volume should be mounted to | |
| `frontend.extraVolumeMounts` | Additional volumes to mount on the frontend. | `[]` |
| `frontend.extraVolumes` | Additional volumes to mount on the frontend. | `[]` |
| `frontend.pdb.enabled` | Enable pdb on frontend | `true` |
### posthog
@@ -261,3 +263,4 @@
| `yProvider.persistence.volume-name.mountPath` | Path where the volume should be mounted to | |
| `yProvider.extraVolumeMounts` | Additional volumes to mount on the yProvider. | `[]` |
| `yProvider.extraVolumes` | Additional volumes to mount on the yProvider. | `[]` |
| `yProvider.pdb.enabled` | Enable pdb on yProvider | `true` |
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
docker image ls | grep readme-generator-for-helm
if [ "$?" -ne "0" ]; then
@@ -138,3 +138,16 @@ spec:
emptyDir: {}
{{- end }}
{{- end }}
---
{{ if .Values.backend.pdb.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
spec:
maxUnavailable: 1
selector:
matchLabels:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 6 }}
{{ end }}
@@ -138,3 +138,16 @@ spec:
emptyDir: {}
{{- end }}
{{- end }}
---
{{ if .Values.frontend.pdb.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
spec:
maxUnavailable: 1
selector:
matchLabels:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 6 }}
{{ end }}
@@ -138,3 +138,16 @@ spec:
emptyDir: {}
{{- end }}
{{- end }}
---
{{ if .Values.yProvider.pdb.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
spec:
maxUnavailable: 1
selector:
matchLabels:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 6 }}
{{ end }}
+11
View File
@@ -308,6 +308,9 @@ backend:
## @param backend.extraVolumes Additional volumes to mount on the backend.
extraVolumes: []
## @param backend.pdb.enabled Enable pdb on backend
pdb:
enabled: true
## @section frontend
@@ -403,6 +406,10 @@ frontend:
## @param frontend.extraVolumes Additional volumes to mount on the frontend.
extraVolumes: []
## @param frontend.pdb.enabled Enable pdb on frontend
pdb:
enabled: true
## @section posthog
posthog:
@@ -571,3 +578,7 @@ yProvider:
## @param yProvider.extraVolumes Additional volumes to mount on the yProvider.
extraVolumes: []
## @param yProvider.pdb.enabled Enable pdb on yProvider
pdb:
enabled: true
+2 -2
View File
@@ -2,7 +2,7 @@
<mj-include path="./partial/header.mjml" />
<mj-body mj-class="bg--blue-100">
<mj-wrapper css-class="wrapper" padding="0 25px 0px 25px">
<mj-wrapper css-class="wrapper" padding="5px 25px 0px 25px">
<mj-section css-class="wrapper-logo">
<mj-column>
<mj-image
@@ -14,7 +14,7 @@
/>
</mj-column>
</mj-section>
<mj-section mj-class="bg--white-100" padding="30px 20px 60px 20px">
<mj-section mj-class="bg--white-100" padding="0px 20px 60px 20px">
<mj-column>
<mj-text align="center">
<h1>{{title|capfirst}}</h1>
+1 -1
View File
@@ -13,7 +13,7 @@
<mj-all
font-family="Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif"
font-size="16px"
line-height="1.3em"
line-height="normal"
color="#3A3A3A"
/>
</mj-attributes>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "2.1.0",
"version": "2.2.0",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {
+1977
View File
File diff suppressed because it is too large Load Diff