diff --git a/CHANGELOG.md b/CHANGELOG.md
index 82e409e3..d0a25024 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,13 +6,29 @@ and this project adheres to
## [Unreleased]
+### Fixed
+
+- ✅(e2e) fix e2e test for other browsers #1799
+
+### Changed
+
+- 🚸(backend) sort user search results by proximity with the active user #1802
+
+## [4.4.0] - 2026-01-13
+
### Added
- ✨(backend) add documents/all endpoint with descendants #1553
- ✅(export) add PDF regression tests #1762
- 📝(docs) Add language configuration documentation #1757
- 🔒(helm) Set default security context #1750
-- ✨(backend) use langfuse to monitor AI actions
+- ✨(backend) use langfuse to monitor AI actions #1776
+
+### Changed
+
+- ♿(frontend) improve accessibility:
+ - ♿(frontend) make html export accessible to screen reader users #1743
+ - ♿(frontend) add missing label and fix Axes errors to improve a11y #1693
### Fixed
@@ -24,12 +40,7 @@ and this project adheres to
### Security
- 🔒️(backend) validate more strictly url used by cors-proxy endpoint #1768
-
-### Changed
-
-- ♿(frontend) improve accessibility:
- - ♿(frontend) make html export accessible to screen reader users #1743
- - ♿(frontend) add missing label and fix Axes errors to improve a11y #1693
+- 🔒️(frontend) fix props vulnerability in Interlinking #1792
## [4.3.0] - 2026-01-05
@@ -987,7 +998,8 @@ and this project adheres to
- ✨(frontend) Coming Soon page (#67)
- 🚀 Impress, project to manage your documents easily and collaboratively.
-[unreleased]: https://github.com/suitenumerique/docs/compare/v4.3.0...main
+[unreleased]: https://github.com/suitenumerique/docs/compare/v4.4.0...main
+[v4.4.0]: https://github.com/suitenumerique/docs/releases/v4.4.0
[v4.3.0]: https://github.com/suitenumerique/docs/releases/v4.3.0
[v4.2.0]: https://github.com/suitenumerique/docs/releases/v4.2.0
[v4.1.0]: https://github.com/suitenumerique/docs/releases/v4.1.0
diff --git a/renovate.json b/renovate.json
index 250dee8e..7f8e9cab 100644
--- a/renovate.json
+++ b/renovate.json
@@ -31,6 +31,13 @@
"matchPackageNames": ["django"],
"allowedVersions": "<6.0.0"
},
+ {
+
+ "groupName": "allowed celery versions",
+ "matchManagers": ["pep621"],
+ "matchPackageNames": ["celery"],
+ "allowedVersions": "<5.6.0"
+ },
{
"enabled": false,
"groupName": "ignored js dependencies",
diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py
index c4a137ee..a9cc5a7c 100644
--- a/src/backend/core/api/viewsets.py
+++ b/src/backend/core/api/viewsets.py
@@ -59,7 +59,12 @@ from core.services.search_indexers import (
get_visited_document_ids_of,
)
from core.tasks.mail import send_ask_for_access_mail
-from core.utils import extract_attachments, filter_descendants
+from core.utils import (
+ extract_attachments,
+ extract_email_domain_parts,
+ filter_descendants,
+ users_sharing_documents_with,
+)
from . import permissions, serializers, utils
from .filters import DocumentFilter, ListDocumentFilter, UserSearchFilter
@@ -218,18 +223,77 @@ class UserViewSet(
# Use trigram similarity for non-email-like queries
# For performance reasons we filter first by similarity, which relies on an
- # index, then only calculate precise similarity scores for sorting purposes
+ # index, then only calculate precise similarity scores for sorting purposes.
+ #
+ # Additionally we reorder results to prefer users "closer" to the current
+ # user: users they recently shared documents with, same full domain, same
+ # partial domain (e.g. both end with "gouv.fr"). To achieve that without
+ # complex SQL we build a proximity score in Python and returnthe top N results.
+ current_user = self.request.user
+ shared_map = users_sharing_documents_with(current_user)
- return (
+ user_full_domain, user_partial_domain = extract_email_domain_parts(
+ current_user.email or ""
+ )
+
+ candidates = list(
queryset.annotate(
sim_email=TrigramSimilarity("email", query),
sim_name=TrigramSimilarity("full_name", query),
)
.annotate(similarity=Greatest("sim_email", "sim_name"))
.filter(similarity__gt=0.2)
- .order_by("-similarity")[: settings.API_USERS_LIST_LIMIT]
+ .order_by("-similarity")
)
+ # Build ordering key for each candidate
+ def _sort_key(u):
+ # shared priority: most recent first
+ # Use shared_last_at timestamp numeric for secondary ordering when shared.
+ shared_last_at = shared_map.get(u.id)
+ if shared_last_at:
+ is_shared = 1
+ shared_score = int(shared_last_at.timestamp())
+ else:
+ is_shared = 0
+ shared_score = 0
+
+ # domain proximity
+ candidate_full_domain, candidate_partial_domain = (
+ extract_email_domain_parts(u.email or "")
+ )
+
+ same_full_domain = (
+ 1
+ if candidate_full_domain and candidate_full_domain == user_full_domain
+ else 0
+ )
+
+ same_partial_domain = (
+ 1
+ if candidate_partial_domain
+ and candidate_partial_domain == user_partial_domain
+ else 0
+ )
+
+ # similarity fallback
+ sim = getattr(u, "similarity", 0) or 0
+
+ return (
+ is_shared,
+ shared_score,
+ same_full_domain,
+ same_partial_domain,
+ sim,
+ )
+
+ # Sort candidates by the key descending and return top N as a queryset-like
+ # list. Keep return type consistent with previous behavior (QuerySet slice
+ # was returned) by returning a list of model instances.
+ candidates.sort(key=_sort_key, reverse=True)
+
+ return candidates[: settings.API_USERS_LIST_LIMIT]
+
@drf.decorators.action(
detail=False,
methods=["get"],
@@ -2231,8 +2295,7 @@ class InvitationViewset(
)
# Abilities are computed based on logged-in user's role and
# the user role on each document access
- .annotate(user_roles=db.Subquery(user_roles_query))
- .distinct()
+ .annotate(user_roles=db.Subquery(user_roles_query)).distinct()
)
return queryset
diff --git a/src/backend/core/tests/test_api_users.py b/src/backend/core/tests/test_api_users.py
index f2a30e93..86437703 100644
--- a/src/backend/core/tests/test_api_users.py
+++ b/src/backend/core/tests/test_api_users.py
@@ -2,6 +2,8 @@
Test users API endpoints in the impress core app.
"""
+from django.utils import timezone
+
import pytest
from rest_framework.test import APIClient
@@ -201,10 +203,85 @@ def test_api_users_list_query_accented_full_name():
assert users == []
+def test_api_users_list_sorted_by_closest_match():
+ """
+ Authenticated users should be able to list users and the results should be
+ sorted by closest match to the query.
+
+ Sorting criteria are :
+ - Shared documents with the user (most recent first)
+ - Same full email domain (example.gouv.fr)
+ - Same partial email domain (gouv.fr)
+
+ Case in point: the logged-in user has recently shared documents
+ with pierre.dupont@beta.gouv.fr and less recently with pierre.durand@impots.gouv.fr.
+
+ Other users named Pierre also exist:
+ - pierre.thomas@example.com
+ - pierre.petit@anct.gouv.fr
+ - pierre.robert@culture.gouv.fr
+
+ The search results should be ordered as follows:
+
+ # Shared with first
+ - pierre.dupond@beta.gouv.fr # Most recent first
+ - pierre.durand@impots.gouv.fr
+ # Same full domain second
+ - pierre.petit@anct.gouv.fr
+ # Same partial domain third
+ - pierre.robert@culture.gouv.fr
+ # Others last
+ - paul.thomas@example.com
+ """
+
+ user = factories.UserFactory(
+ email="martin.bernard@anct.gouv.fr", full_name="Martin Bernard"
+ )
+
+ client = APIClient()
+ client.force_login(user)
+
+ pierre_1 = factories.UserFactory(email="pierre.dupont@beta.gouv.fr")
+ pierre_2 = factories.UserFactory(email="pierre.durand@impots.gouv.fr")
+ pierre_3 = factories.UserFactory(email="pierre.thomas@example.com")
+ pierre_4 = factories.UserFactory(email="pierre.petit@anct.gouv.fr")
+ pierre_5 = factories.UserFactory(email="pierre.robert@culture.gouv.fr")
+
+ document_1 = factories.DocumentFactory(creator=user)
+ document_2 = factories.DocumentFactory(creator=user)
+ factories.UserDocumentAccessFactory(user=user, document=document_1)
+ factories.UserDocumentAccessFactory(user=user, document=document_2)
+
+ now = timezone.now()
+ last_week = now - timezone.timedelta(days=7)
+ last_month = now - timezone.timedelta(days=30)
+
+ # The factory cannot set the created_at directly, so we force it after creation
+ p1_d1 = factories.UserDocumentAccessFactory(user=pierre_1, document=document_1)
+ p1_d1.created_at = last_week
+ p1_d1.save()
+
+ p2_d2 = factories.UserDocumentAccessFactory(user=pierre_2, document=document_2)
+ p2_d2.created_at = last_month
+ p2_d2.save()
+
+ response = client.get("/api/v1.0/users/?q=Pierre")
+ assert response.status_code == 200
+ user_ids = [user["email"] for user in response.json()]
+
+ assert user_ids == [
+ str(pierre_1.email),
+ str(pierre_2.email),
+ str(pierre_4.email),
+ str(pierre_5.email),
+ str(pierre_3.email),
+ ]
+
+
def test_api_users_list_limit(settings):
"""
Authenticated users should be able to list users and the number of results
- should be limited to 10.
+ should be limited to API_USERS_LIST_LIMIT (by default 5).
"""
user = factories.UserFactory()
diff --git a/src/backend/core/tests/test_utils.py b/src/backend/core/tests/test_utils.py
index 42d588c5..820ac3d1 100644
--- a/src/backend/core/tests/test_utils.py
+++ b/src/backend/core/tests/test_utils.py
@@ -100,3 +100,18 @@ def test_utils_get_ancestor_to_descendants_map_multiple_paths():
"000100020005": {"000100020005"},
"00010003": {"00010003"},
}
+
+
+def test_utils_extract_email_domain_parts_when_email_is_valid():
+ """Test extraction of email domain parts."""
+ email = "firstname.lastname@numerique.gouv.fr"
+ full_domain, partial_domain = utils.extract_email_domain_parts(email)
+ assert full_domain == "numerique.gouv.fr"
+ assert partial_domain == "gouv.fr"
+
+
+def test_utils_extract_email_domain_parts_when_email_is_empty():
+ empty_email = ""
+ full_domain, partial_domain = utils.extract_email_domain_parts(empty_email)
+ assert full_domain == ""
+ assert partial_domain == ""
diff --git a/src/backend/core/tests/test_utils_users_sharing_documents_with.py b/src/backend/core/tests/test_utils_users_sharing_documents_with.py
new file mode 100644
index 00000000..38635aa2
--- /dev/null
+++ b/src/backend/core/tests/test_utils_users_sharing_documents_with.py
@@ -0,0 +1,60 @@
+from django.utils import timezone
+
+import pytest
+
+from core import factories, utils
+
+pytestmark = pytest.mark.django_db
+
+
+def test_utils_users_sharing_documents_with():
+ """Test users_sharing_documents_with function."""
+
+ user = factories.UserFactory(
+ email="martin.bernard@anct.gouv.fr", full_name="Martin Bernard"
+ )
+
+ pierre_1 = factories.UserFactory(
+ email="pierre.dupont@beta.gouv.fr", full_name="Pierre Dupont"
+ )
+ pierre_2 = factories.UserFactory(
+ email="pierre.durand@impots.gouv.fr", full_name="Pierre Durand"
+ )
+
+ now = timezone.now()
+ yesterday = now - timezone.timedelta(days=1)
+ last_week = now - timezone.timedelta(days=7)
+ last_month = now - timezone.timedelta(days=30)
+
+ document_1 = factories.DocumentFactory(creator=user)
+ document_2 = factories.DocumentFactory(creator=user)
+ document_3 = factories.DocumentFactory(creator=user)
+
+ factories.UserDocumentAccessFactory(user=user, document=document_1)
+ factories.UserDocumentAccessFactory(user=user, document=document_2)
+ factories.UserDocumentAccessFactory(user=user, document=document_3)
+
+ # The factory cannot set the created_at directly, so we force it after creation
+ doc_1_pierre_1 = factories.UserDocumentAccessFactory(
+ user=pierre_1, document=document_1, created_at=last_week
+ )
+ doc_1_pierre_1.created_at = last_week
+ doc_1_pierre_1.save()
+ doc_2_pierre_2 = factories.UserDocumentAccessFactory(
+ user=pierre_2, document=document_2
+ )
+ doc_2_pierre_2.created_at = last_month
+ doc_2_pierre_2.save()
+
+ doc_3_pierre_2 = factories.UserDocumentAccessFactory(
+ user=pierre_2, document=document_3
+ )
+ doc_3_pierre_2.created_at = yesterday
+ doc_3_pierre_2.save()
+
+ shared_map = utils.users_sharing_documents_with(user)
+
+ assert shared_map == {
+ pierre_1.id: last_week,
+ pierre_2.id: yesterday,
+ }
diff --git a/src/backend/core/utils.py b/src/backend/core/utils.py
index 357ede03..32f452e4 100644
--- a/src/backend/core/utils.py
+++ b/src/backend/core/utils.py
@@ -4,10 +4,14 @@ import base64
import re
from collections import defaultdict
+from django.core.exceptions import ValidationError
+from django.core.validators import validate_email
+from django.db import models as db
+
import pycrdt
from bs4 import BeautifulSoup
-from core import enums
+from core import enums, models
def get_ancestor_to_descendants_map(paths, steplen):
@@ -96,3 +100,39 @@ def extract_attachments(content):
xml_content = base64_yjs_to_xml(content)
return re.findall(enums.MEDIA_STORAGE_URL_EXTRACT, xml_content)
+
+
+def users_sharing_documents_with(user):
+ """
+ Returns a map of users sharing documents with the given user,
+ sorted by last shared date.
+ """
+
+ user_docs_qs = models.DocumentAccess.objects.filter(user=user).values_list(
+ "document_id", flat=True
+ )
+ shared_qs = (
+ models.DocumentAccess.objects.filter(document_id__in=user_docs_qs)
+ .exclude(user=user)
+ .values("user")
+ .annotate(last_shared=db.Max("created_at"))
+ )
+ return {item["user"]: item["last_shared"] for item in shared_qs}
+
+
+def extract_email_domain_parts(email):
+ """
+ Extracts the full domain and partial domain from an email address as a tuple.
+ The partial domain consists of the last two segments of the domain, eg. "gouv.fr".
+
+ If the email is invalid (eg, is an empty string), returns empty strings.
+ """
+ try:
+ validate_email(email)
+ except ValidationError:
+ return "", ""
+
+ domain = email.split("@", 1)[1].lower()
+ parts = domain.split(".")
+ partial_domain = ".".join(parts[-2:]) if len(parts) >= 2 else domain
+ return domain, partial_domain
diff --git a/src/backend/locale/br_FR/LC_MESSAGES/django.po b/src/backend/locale/br_FR/LC_MESSAGES/django.po
index 3a946232..1c321e74 100644
--- a/src/backend/locale/br_FR/LC_MESSAGES/django.po
+++ b/src/backend/locale/br_FR/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: Breton\n"
"Language: br_FR\n"
@@ -79,7 +79,7 @@ msgstr "Doare korf"
msgid "Format"
msgstr "Stumm"
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr "eilenn {title}"
diff --git a/src/backend/locale/de_DE/LC_MESSAGES/django.po b/src/backend/locale/de_DE/LC_MESSAGES/django.po
index 8edff316..7215f3d1 100644
--- a/src/backend/locale/de_DE/LC_MESSAGES/django.po
+++ b/src/backend/locale/de_DE/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -79,7 +79,7 @@ msgstr "Typ"
msgid "Format"
msgstr "Format"
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr "Kopie von {title}"
diff --git a/src/backend/locale/en_US/LC_MESSAGES/django.po b/src/backend/locale/en_US/LC_MESSAGES/django.po
index 8ff076d1..90f6c72c 100644
--- a/src/backend/locale/en_US/LC_MESSAGES/django.po
+++ b/src/backend/locale/en_US/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: English\n"
"Language: en_US\n"
@@ -79,7 +79,7 @@ msgstr ""
msgid "Format"
msgstr ""
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr ""
diff --git a/src/backend/locale/es_ES/LC_MESSAGES/django.po b/src/backend/locale/es_ES/LC_MESSAGES/django.po
index 828a1b41..c4f38cad 100644
--- a/src/backend/locale/es_ES/LC_MESSAGES/django.po
+++ b/src/backend/locale/es_ES/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
@@ -79,7 +79,7 @@ msgstr "Tipo de Cuerpo"
msgid "Format"
msgstr "Formato"
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr "copia de {title}"
diff --git a/src/backend/locale/fr_FR/LC_MESSAGES/django.po b/src/backend/locale/fr_FR/LC_MESSAGES/django.po
index af9e20d6..a0d47f41 100644
--- a/src/backend/locale/fr_FR/LC_MESSAGES/django.po
+++ b/src/backend/locale/fr_FR/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -79,7 +79,7 @@ msgstr "Type de corps"
msgid "Format"
msgstr "Format"
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr "copie de {title}"
diff --git a/src/backend/locale/it_IT/LC_MESSAGES/django.po b/src/backend/locale/it_IT/LC_MESSAGES/django.po
index 47dd1dba..10f4a41a 100644
--- a/src/backend/locale/it_IT/LC_MESSAGES/django.po
+++ b/src/backend/locale/it_IT/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Language: it_IT\n"
@@ -79,7 +79,7 @@ msgstr ""
msgid "Format"
msgstr "Formato"
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr "copia di {title}"
diff --git a/src/backend/locale/nl_NL/LC_MESSAGES/django.po b/src/backend/locale/nl_NL/LC_MESSAGES/django.po
index d1ebbae3..b52b0a61 100644
--- a/src/backend/locale/nl_NL/LC_MESSAGES/django.po
+++ b/src/backend/locale/nl_NL/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Language: nl_NL\n"
@@ -79,7 +79,7 @@ msgstr "Text type"
msgid "Format"
msgstr "Formaat"
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr "kopie van {title}"
diff --git a/src/backend/locale/pt_PT/LC_MESSAGES/django.po b/src/backend/locale/pt_PT/LC_MESSAGES/django.po
index fd65edc6..8a997815 100644
--- a/src/backend/locale/pt_PT/LC_MESSAGES/django.po
+++ b/src/backend/locale/pt_PT/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Language: pt_PT\n"
@@ -79,7 +79,7 @@ msgstr "Tipo de corpo"
msgid "Format"
msgstr "Formato"
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr "cópia de {title}"
diff --git a/src/backend/locale/ru_RU/LC_MESSAGES/django.po b/src/backend/locale/ru_RU/LC_MESSAGES/django.po
index eb5a1c21..8853268c 100644
--- a/src/backend/locale/ru_RU/LC_MESSAGES/django.po
+++ b/src/backend/locale/ru_RU/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"Language: ru_RU\n"
@@ -79,7 +79,7 @@ msgstr "Тип сообщения"
msgid "Format"
msgstr "Формат"
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr "копия {title}"
diff --git a/src/backend/locale/sl_SI/LC_MESSAGES/django.po b/src/backend/locale/sl_SI/LC_MESSAGES/django.po
index 5ee111c6..a7e7e2b6 100644
--- a/src/backend/locale/sl_SI/LC_MESSAGES/django.po
+++ b/src/backend/locale/sl_SI/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: Slovenian\n"
"Language: sl_SI\n"
@@ -79,7 +79,7 @@ msgstr "Vrsta telesa"
msgid "Format"
msgstr "Oblika"
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr ""
diff --git a/src/backend/locale/sv_SE/LC_MESSAGES/django.po b/src/backend/locale/sv_SE/LC_MESSAGES/django.po
index 51d8cc6f..b6c7a980 100644
--- a/src/backend/locale/sv_SE/LC_MESSAGES/django.po
+++ b/src/backend/locale/sv_SE/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: Swedish\n"
"Language: sv_SE\n"
@@ -79,7 +79,7 @@ msgstr ""
msgid "Format"
msgstr "Format"
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr ""
diff --git a/src/backend/locale/tr_TR/LC_MESSAGES/django.po b/src/backend/locale/tr_TR/LC_MESSAGES/django.po
index cdf4bd5d..1e179aee 100644
--- a/src/backend/locale/tr_TR/LC_MESSAGES/django.po
+++ b/src/backend/locale/tr_TR/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"Language: tr_TR\n"
@@ -79,7 +79,7 @@ msgstr ""
msgid "Format"
msgstr ""
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr ""
diff --git a/src/backend/locale/uk_UA/LC_MESSAGES/django.po b/src/backend/locale/uk_UA/LC_MESSAGES/django.po
index 20128a23..b2c08c8d 100644
--- a/src/backend/locale/uk_UA/LC_MESSAGES/django.po
+++ b/src/backend/locale/uk_UA/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: Ukrainian\n"
"Language: uk_UA\n"
@@ -79,7 +79,7 @@ msgstr "Тип вмісту"
msgid "Format"
msgstr "Формат"
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr "копія {title}"
diff --git a/src/backend/locale/zh_CN/LC_MESSAGES/django.po b/src/backend/locale/zh_CN/LC_MESSAGES/django.po
index 7e140f51..d5190b5d 100644
--- a/src/backend/locale/zh_CN/LC_MESSAGES/django.po
+++ b/src/backend/locale/zh_CN/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-12-16 21:44+0000\n"
-"PO-Revision-Date: 2026-01-05 08:21\n"
+"POT-Creation-Date: 2026-01-08 15:38+0000\n"
+"PO-Revision-Date: 2026-01-13 13:17\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
@@ -79,7 +79,7 @@ msgstr "正文类型"
msgid "Format"
msgstr "格式"
-#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024
+#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081
#, python-brace-format
msgid "copy of {title}"
msgstr "{title} 的副本"
diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml
index f58d055b..d532906d 100644
--- a/src/backend/pyproject.toml
+++ b/src/backend/pyproject.toml
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "impress"
-version = "4.3.0"
+version = "4.4.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -28,7 +28,7 @@ dependencies = [
"beautifulsoup4==4.14.3",
"boto3==1.42.17",
"Brotli==1.2.0",
- "celery[redis]==5.6.0",
+ "celery[redis]==5.5.3",
"django-configurations==2.5.1",
"django-cors-headers==4.9.0",
"django-countries==8.2.0",
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts
index 665a9f7a..b73814b4 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts
@@ -58,7 +58,7 @@ test.describe('Doc Comments', () => {
await page.getByRole('button', { name: '👍' }).click();
await expect(
- thread.getByRole('img', { name: 'E2E Chromium' }).first(),
+ thread.getByRole('img', { name: `E2E ${browserName}` }).first(),
).toBeVisible();
await expect(thread.getByText('This is a comment').first()).toBeVisible();
await expect(thread.getByText(`E2E ${browserName}`).first()).toBeVisible();
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-inherited-share.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-inherited-share.spec.ts
index efc19da6..7ad02d31 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-inherited-share.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-inherited-share.spec.ts
@@ -21,7 +21,7 @@ test.describe('Inherited share accesses', () => {
`doc-share-member-row-user.test@${browserName}.test`,
);
await expect(user).toBeVisible();
- await expect(user.getByText('E2E Chromium')).toBeVisible();
+ await expect(user.getByText(`E2E ${browserName}`)).toBeVisible();
await expect(user.getByText('Owner')).toBeVisible();
await page
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts
index 67adf44c..da4a722d 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts
@@ -224,7 +224,9 @@ export const updateDocTitle = async (page: Page, title: string) => {
await expect(input).toHaveText('');
await expect(input).toBeVisible();
await input.click();
- await input.fill(title);
+ await input.fill(title, {
+ force: true,
+ });
await input.click();
await input.blur();
await verifyDocName(page, title);
diff --git a/src/frontend/apps/e2e/package.json b/src/frontend/apps/e2e/package.json
index 86e3a26b..7328a51b 100644
--- a/src/frontend/apps/e2e/package.json
+++ b/src/frontend/apps/e2e/package.json
@@ -1,6 +1,6 @@
{
"name": "app-e2e",
- "version": "4.3.0",
+ "version": "4.4.0",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json
index cb701fc9..71646706 100644
--- a/src/frontend/apps/impress/package.json
+++ b/src/frontend/apps/impress/package.json
@@ -1,6 +1,6 @@
{
"name": "app-impress",
- "version": "4.3.0",
+ "version": "4.4.0",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
@@ -68,6 +68,7 @@
"react-select": "5.10.2",
"styled-components": "6.1.19",
"use-debounce": "10.0.6",
+ "uuid": "13.0.0",
"y-protocols": "1.0.7",
"yjs": "*",
"zustand": "5.0.9"
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx
index 5dc9a2be..be5145dd 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx
@@ -1,21 +1,23 @@
-/* eslint-disable react-hooks/rules-of-hooks */
+import {
+ PartialCustomInlineContentFromConfig,
+ StyleSchema,
+} from '@blocknote/core';
import { createReactInlineContentSpec } from '@blocknote/react';
+import * as Sentry from '@sentry/nextjs';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { css } from 'styled-components';
+import { validate as uuidValidate } from 'uuid';
import { BoxButton, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import SelectedPageIcon from '@/docs/doc-editor/assets/doc-selected.svg';
-import { getEmojiAndTitle, useDoc } from '@/docs/doc-management';
+import { getEmojiAndTitle, useDoc } from '@/docs/doc-management/';
export const InterlinkingLinkInlineContent = createReactInlineContentSpec(
{
type: 'interlinkingLinkInline',
propSchema: {
- url: {
- default: '',
- },
docId: {
default: '',
},
@@ -27,46 +29,97 @@ export const InterlinkingLinkInlineContent = createReactInlineContentSpec(
},
{
render: ({ editor, inlineContent, updateInlineContent }) => {
- const { data: doc } = useDoc({ id: inlineContent.props.docId });
- const isEditable = editor.isEditable;
+ if (!inlineContent.props.docId) {
+ return null;
+ }
/**
- * Update the content title if the referenced doc title changes
+ * Should not happen
*/
- useEffect(() => {
- if (
- isEditable &&
- doc?.title &&
- doc.title !== inlineContent.props.title
- ) {
- updateInlineContent({
- type: 'interlinkingLinkInline',
- props: {
- ...inlineContent.props,
- title: doc.title,
- },
- });
- }
+ if (!uuidValidate(inlineContent.props.docId)) {
+ Sentry.captureException(
+ new Error(`Invalid docId: ${inlineContent.props.docId}`),
+ {
+ extra: { info: 'InterlinkingLinkInlineContent' },
+ },
+ );
- /**
- * ⚠️ When doing collaborative editing, doc?.title might be out of sync
- * causing an infinite loop of updates.
- * To prevent this, we only run this effect when doc?.title changes,
- * not when inlineContent.props.title changes.
- */
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [doc?.title, isEditable]);
+ updateInlineContent({
+ type: 'interlinkingLinkInline',
+ props: {
+ docId: '',
+ title: '',
+ },
+ });
- return ;
+ return null;
+ }
+
+ return (
+
+ );
},
},
);
interface LinkSelectedProps {
- url: string;
+ docId: string;
title: string;
+ isEditable: boolean;
+ updateInlineContent: (
+ update: PartialCustomInlineContentFromConfig<
+ {
+ readonly type: 'interlinkingLinkInline';
+ readonly propSchema: {
+ readonly docId: {
+ readonly default: '';
+ };
+ readonly title: {
+ readonly default: '';
+ };
+ };
+ readonly content: 'none';
+ },
+ StyleSchema
+ >,
+ ) => void;
}
-const LinkSelected = ({ url, title }: LinkSelectedProps) => {
+export const LinkSelected = ({
+ docId,
+ title,
+ isEditable,
+ updateInlineContent,
+}: LinkSelectedProps) => {
+ const { data: doc } = useDoc({ id: docId });
+
+ /**
+ * Update the content title if the referenced doc title changes
+ */
+ useEffect(() => {
+ if (isEditable && doc?.title && doc.title !== title) {
+ updateInlineContent({
+ type: 'interlinkingLinkInline',
+ props: {
+ docId,
+ title: doc.title,
+ },
+ });
+ }
+
+ /**
+ * ⚠️ When doing collaborative editing, doc?.title might be out of sync
+ * causing an infinite loop of updates.
+ * To prevent this, we only run this effect when doc?.title changes,
+ * not when inlineContent.props.title changes.
+ */
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [doc?.title, docId, isEditable]);
+
const { colorsTokens } = useCunninghamTheme();
const { emoji, titleWithoutEmoji } = getEmojiAndTitle(title);
@@ -74,7 +127,7 @@ const LinkSelected = ({ url, title }: LinkSelectedProps) => {
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
- void router.push(url);
+ void router.push(`/docs/${docId}/`);
};
return (
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx
index 5ef163cf..47428e75 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx
@@ -247,7 +247,6 @@ export const SearchPage = ({
{
type: 'interlinkingLinkInline',
props: {
- url: `/docs/${doc.id}`,
docId: doc.id,
title: doc.title || untitledDocument,
},
diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkDocx.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkDocx.tsx
index afb8c0e7..7da97f08 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkDocx.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkDocx.tsx
@@ -1,16 +1,24 @@
import { ExternalHyperlink, TextRun } from 'docx';
+import { getEmojiAndTitle } from '@/docs/doc-management';
+
import { DocsExporterDocx } from '../types';
export const inlineContentMappingInterlinkingLinkDocx: DocsExporterDocx['mappings']['inlineContentMapping']['interlinkingLinkInline'] =
(inline) => {
+ if (!inline.props.docId) {
+ return new TextRun('');
+ }
+
+ const { emoji, titleWithoutEmoji } = getEmojiAndTitle(inline.props.title);
+
return new ExternalHyperlink({
children: [
new TextRun({
- text: `📄${inline.props.title}`,
+ text: `${emoji || '📄'}${titleWithoutEmoji}`,
bold: true,
}),
],
- link: window.location.origin + inline.props.url,
+ link: window.location.origin + `/docs/${inline.props.docId}/`,
});
};
diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkODT.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkODT.tsx
index 4ffadf08..ef9d3d9a 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkODT.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkODT.tsx
@@ -1,11 +1,17 @@
import React from 'react';
+import { getEmojiAndTitle } from '@/docs/doc-management';
+
import { DocsExporterODT } from '../types';
export const inlineContentMappingInterlinkingLinkODT: DocsExporterODT['mappings']['inlineContentMapping']['interlinkingLinkInline'] =
(inline) => {
- const url = window.location.origin + inline.props.url;
- const title = inline.props.title;
+ if (!inline.props.docId) {
+ return null;
+ }
+
+ const { emoji, titleWithoutEmoji } = getEmojiAndTitle(inline.props.title);
+ const url = window.location.origin + `/docs/${inline.props.docId}/`;
// Create ODT hyperlink using React.createElement to avoid TypeScript JSX namespace issues
// Uses the same structure as BlockNote's default link mapping
@@ -18,6 +24,6 @@ export const inlineContentMappingInterlinkingLinkODT: DocsExporterODT['mappings'
xlinkShow: 'replace',
xlinkHref: url,
},
- `📄${title}`,
+ `${emoji || '📄'}${titleWithoutEmoji}`,
);
};
diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkPDF.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkPDF.tsx
index c2d204b7..87165dfa 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkPDF.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkPDF.tsx
@@ -1,21 +1,29 @@
import { Image, Link, Text } from '@react-pdf/renderer';
+import { getEmojiAndTitle } from '@/docs/doc-management';
+
import DocSelectedIcon from '../assets/doc-selected.png';
import { DocsExporterPDF } from '../types';
export const inlineContentMappingInterlinkingLinkPDF: DocsExporterPDF['mappings']['inlineContentMapping']['interlinkingLinkInline'] =
(inline) => {
+ if (!inline.props.docId) {
+ return <>>;
+ }
+
+ const { emoji, titleWithoutEmoji } = getEmojiAndTitle(inline.props.title);
+
return (
{' '}
- {' '}
- {inline.props.title}{' '}
+ {emoji || }{' '}
+ {titleWithoutEmoji}{' '}
);
};
diff --git a/src/frontend/apps/impress/src/i18n/translations.json b/src/frontend/apps/impress/src/i18n/translations.json
index 8a239412..45372811 100644
--- a/src/frontend/apps/impress/src/i18n/translations.json
+++ b/src/frontend/apps/impress/src/i18n/translations.json
@@ -779,12 +779,12 @@
"Document deleted": "Document supprimé",
"Document duplicated successfully!": "Document dupliqué avec succès !",
"Document editor": "Éditeur de document",
+ "Document emoji": "Émoji de document",
"Document owner": "Propriétaire du document",
"Document role text": "Texte du rôle du document",
"Document sections": "Sections du document",
"Document title": "Titre du document",
"Document tree": "Arborescence du document",
- "Document viewer": "Visualiseur de document",
"Document visibility": "Visibilité du document",
"Documents grid": "Grille des documents",
"Docx": "Docx",
@@ -826,6 +826,7 @@
"Image 403": "Image 403",
"Image: {{title}}": "Image : {{title}}",
"Insufficient access rights to view the document.": "Droits d'accès insuffisants pour voir le document.",
+ "Invalid or missing PDF file.": "Fichier PDF non valide ou manquant.",
"Invite": "Inviter",
"Invite new members": "Inviter de nouveaux membres",
"Invite {{count}} members_many": "Inviter {{count}} membres",
@@ -884,6 +885,7 @@
"Others are editing. Your network prevent changes.": "D'autres sont en cours d'édition. Votre réseau empêche les changements.",
"Owner": "Propriétaire",
"PDF": "PDF",
+ "PDF document": "Document PDF",
"Page Not Found - Error 404": "Page introuvable - Erreur 404",
"Pending invitations": "Invitations en attente",
"People with access via the parent document": "Personnes ayant accès au document parent",
@@ -1222,7 +1224,6 @@
"Document sections": "Document secties",
"Document title": "Documenttitel",
"Document tree": "Boomstructuur document",
- "Document viewer": "Document-viewer",
"Document visibility": "Document toegankelijkheid",
"Documents grid": "Documenten overzicht",
"Docx": "Docx",
@@ -1264,6 +1265,7 @@
"Image 403": "Afbeelding 403",
"Image: {{title}}": "Afbeelding: {{title}}",
"Insufficient access rights to view the document.": "Onvoldoende toegangsrechten om het document te bekijken.",
+ "Invalid or missing PDF file.": "Ongeldig of ontbrekend PDF-bestand.",
"Invite": "Uitnodigen",
"Invite new members": "Nieuwe leden uitnodigen",
"Invite {{count}} members_many": "Nodig {{count}} leden uit",
@@ -1322,6 +1324,7 @@
"Others are editing. Your network prevent changes.": "Anderen zijn aan het bewerken. Uw netwerk voorkomt wijzigingen.",
"Owner": "Eigenaar",
"PDF": "PDF",
+ "PDF document": "Pdf-document",
"Page Not Found - Error 404": "Pagina niet gevonden - Fout 404",
"Pending invitations": "Openstaande uitnodigingen",
"People with access via the parent document": "Gebruikers met toegang via het bovenliggend document",
@@ -1516,12 +1519,12 @@
"Document deleted": "Документ удалён",
"Document duplicated successfully!": "Документ успешно дублирован!",
"Document editor": "Редактор документа",
+ "Document emoji": "Эмодзи документа",
"Document owner": "Владелец документа",
"Document role text": "Текст роли документа",
"Document sections": "Разделы документа",
"Document title": "Заголовок документа",
"Document tree": "Иерархия документа",
- "Document viewer": "Просмотрщик документа",
"Document visibility": "Видимость документа",
"Documents grid": "Сетка документов",
"Docx": "Docx",
@@ -1563,6 +1566,7 @@
"Image 403": "Изображение 403",
"Image: {{title}}": "Изображение: {{title}}",
"Insufficient access rights to view the document.": "Недостаточно прав доступа для просмотра документа.",
+ "Invalid or missing PDF file.": "Повреждённый или отсутствующий PDF-файл.",
"Invite": "Приглашение",
"Invite new members": "Пригласить новых участников",
"Invite {{count}} members_many": "Пригласить {{count}} участников",
@@ -1621,6 +1625,7 @@
"Others are editing. Your network prevent changes.": "Другие участники редактируют этот документ. Ваша сеть не позволяет вам присоединиться.",
"Owner": "Владелец",
"PDF": "PDF",
+ "PDF document": "Документ PDF",
"Page Not Found - Error 404": "Страница не найдена - ошибка 404",
"Pending invitations": "Ожидающие приглашения",
"People with access via the parent document": "Люди, имеющие доступ через родительский документ",
@@ -1918,12 +1923,12 @@
"Document deleted": "Документ видалено",
"Document duplicated successfully!": "Документ успішно продубльовано!",
"Document editor": "Редактор документа",
+ "Document emoji": "Емодзі документу",
"Document owner": "Власник документа",
"Document role text": "Текст ролі документа",
"Document sections": "Розділи документу",
"Document title": "Назва документа",
"Document tree": "Дерево документа",
- "Document viewer": "Переглядач документа",
"Document visibility": "Видимість документа",
"Documents grid": "Сітка документів",
"Docx": "Docx",
@@ -1965,6 +1970,7 @@
"Image 403": "Зображення 403",
"Image: {{title}}": "Зображення: {{title}}",
"Insufficient access rights to view the document.": "Недостатньо прав для перегляду документа.",
+ "Invalid or missing PDF file.": "Неприпустимий або відсутній PDF-файл.",
"Invite": "Запрошення",
"Invite new members": "Запросити нових учасників",
"Invite {{count}} members_many": "Запросити {{count}} учасників",
@@ -2023,6 +2029,7 @@
"Others are editing. Your network prevent changes.": "Інші учасники редагують документ. Ваша мережа не дозволяє вам вносити зміни.",
"Owner": "Власник",
"PDF": "PDF",
+ "PDF document": "Документ PDF",
"Page Not Found - Error 404": "Сторінку не знайдено - Помилка 404",
"Pending invitations": "Запрошення в очікуванні",
"People with access via the parent document": "Люди з доступом через батьківський документ",
diff --git a/src/frontend/package.json b/src/frontend/package.json
index 9673dfe6..d1260f69 100644
--- a/src/frontend/package.json
+++ b/src/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "impress",
- "version": "4.3.0",
+ "version": "4.4.0",
"private": true,
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
diff --git a/src/frontend/packages/eslint-plugin-docs/package.json b/src/frontend/packages/eslint-plugin-docs/package.json
index e46a5415..d2a418e5 100644
--- a/src/frontend/packages/eslint-plugin-docs/package.json
+++ b/src/frontend/packages/eslint-plugin-docs/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-docs",
- "version": "4.3.0",
+ "version": "4.4.0",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
diff --git a/src/frontend/packages/i18n/package.json b/src/frontend/packages/i18n/package.json
index 06270622..a9f7d4e1 100644
--- a/src/frontend/packages/i18n/package.json
+++ b/src/frontend/packages/i18n/package.json
@@ -1,6 +1,6 @@
{
"name": "packages-i18n",
- "version": "4.3.0",
+ "version": "4.4.0",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
diff --git a/src/frontend/servers/y-provider/package.json b/src/frontend/servers/y-provider/package.json
index bc4c9b63..44639fc9 100644
--- a/src/frontend/servers/y-provider/package.json
+++ b/src/frontend/servers/y-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "server-y-provider",
- "version": "4.3.0",
+ "version": "4.4.0",
"description": "Y.js provider for docs",
"repository": "https://github.com/suitenumerique/docs",
"license": "MIT",
diff --git a/src/helm/helmfile.yaml.gotmpl b/src/helm/helmfile.yaml.gotmpl
index 445601c3..a815ad6e 100644
--- a/src/helm/helmfile.yaml.gotmpl
+++ b/src/helm/helmfile.yaml.gotmpl
@@ -1,10 +1,10 @@
environments:
dev:
values:
- - version: 4.3.0
+ - version: 4.4.0
feature:
values:
- - version: 4.3.0
+ - version: 4.4.0
feature: ci
domain: example.com
imageTag: demo
diff --git a/src/helm/impress/Chart.yaml b/src/helm/impress/Chart.yaml
index 57487a2f..4697c5ee 100644
--- a/src/helm/impress/Chart.yaml
+++ b/src/helm/impress/Chart.yaml
@@ -1,5 +1,5 @@
apiVersion: v2
type: application
name: docs
-version: 4.3.0
+version: 4.4.0
appVersion: latest
diff --git a/src/mail/package.json b/src/mail/package.json
index 0b3b6597..0e6829e7 100644
--- a/src/mail/package.json
+++ b/src/mail/package.json
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
- "version": "4.3.0",
+ "version": "4.4.0",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {