Compare commits

...

4 Commits

Author SHA1 Message Date
Sylvain Boissel d786e9703e 🐛(backend) do not filter deleted docs for the owner in the tree view
Fixes the bug described in #1254 (Delete document request conflict)

In the tree request, deleted documents for
which the user is the owner are not filtered anymore.
2026-03-24 17:02:43 +01:00
ZouicheOmar 0d09f761dc 💄(frontend) improve comments highlights
Updated comments styles to respect design proposal,
adding distinguishable highlighting, click and hover
style interactions.
2026-03-24 09:38:31 +01:00
Manuel Raynaud ce5f9a1417 🔖(patch) release 4.8.3
Changed

- 💫(frontend) fix the help button to the bottom in tree #2073
- ️(frontend) improve version history list accessibility #2033
- ️(frontend) fix more options menu feedback for screen readers #2071
- (frontend) focus skip link on headings and skip grid dropzone #1983
- ️(frontend) fix search modal accessibility issues #2054
- ️(frontend) add sr-only format to export download button #2088
- ️(frontend) announce formatting shortcuts for screen readers #2070
- (frontend) add markdown copy icon for Copy as Markdown option #2096
- ♻️(backend) skip saving in database a document when payload is empty #2062

Fixed

- ️(frontend) fix aria-labels for table of contents #2065
- 🐛(backend) allow using search endpoint without refresh token enabled #2097
2026-03-23 17:32:50 +01:00
Anthony LC 83a24c3796 ️(frontend) add debounce WebSocket reconnect
We add a debounce mechanism to the WebSocket
reconnect logic in the `useProviderStore` to
prevent rapid reconnection attempts that can
lead to performance issues and potential server
overload.
2026-03-23 17:01:02 +01:00
15 changed files with 137 additions and 23 deletions
+13 -1
View File
@@ -8,6 +8,16 @@ and this project adheres to
### Changed
- 💄(frontend) improve comments highlights #1961
### Fixed
- 🐛(backend) do not filter deleted documents for the owner in the tree #2121
## [v4.8.3] - 2026-03-23
### Changed
- ♿️(frontend) improve version history list accessibility #2033
- ♿(frontend) focus skip link on headings and skip grid dropzone #1983
- ♿️(frontend) add sr-only format to export download button #2088
@@ -15,6 +25,7 @@ and this project adheres to
- ✨(frontend) add markdown copy icon for Copy as Markdown option #2096
- ♻️(backend) skip saving in database a document when payload is empty #2062
- ♻️(frontend) refacto Version modal to fit with the design system #2091
- ⚡️(frontend) add debounce WebSocket reconnect #2104
### Fixed
@@ -1170,7 +1181,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.8.2...main
[unreleased]: https://github.com/suitenumerique/docs/compare/v4.8.3...main
[v4.8.3]: https://github.com/suitenumerique/docs/releases/v4.8.3
[v4.8.2]: https://github.com/suitenumerique/docs/releases/v4.8.2
[v4.8.1]: https://github.com/suitenumerique/docs/releases/v4.8.1
[v4.8.0]: https://github.com/suitenumerique/docs/releases/v4.8.0
+14 -1
View File
@@ -1209,7 +1209,20 @@ class DocumentViewSet(
path__startswith=ancestor.path, depth=ancestor.depth + 1
)
children = self.queryset.filter(children_clause, deleted_at__isnull=True)
# Include deleted documents owned by the user, if any.
if request.user.is_authenticated:
owned_document_ids = models.DocumentAccess.objects.filter(
user=user,
role=models.RoleChoices.OWNER,
document__deleted_at__isnull=False,
).values("document_id")
children = self.queryset.filter(
children_clause,
db.Q(deleted_at__isnull=True) | db.Q(id__in=owned_document_ids),
)
else:
children = self.queryset.filter(children_clause, deleted_at__isnull=True)
queryset = (
ancestors.select_related("creator").filter(
@@ -1258,3 +1258,58 @@ def test_api_documents_tree_list_deleted_document_owner(django_assert_num_querie
assert content["children"][0][
"deleted_at"
] == child.ancestors_deleted_at.isoformat().replace("+00:00", "Z")
def test_api_documents_tree_deleted_child_visible_to_owner():
"""
A deleted child document should appear in the tree for users who are its direct owner,
even when they are not the owner of the parent.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
parent = factories.DocumentFactory(link_reach="public")
owned_child = factories.DocumentFactory(parent=parent, users=[(user, "owner")])
other_child = factories.DocumentFactory(parent=parent)
owned_child.soft_delete()
owned_child.refresh_from_db()
response = client.get(f"/api/v1.0/documents/{parent.id!s}/tree/")
assert response.status_code == 200
content = response.json()
child_ids = [c["id"] for c in content["children"]]
assert str(owned_child.id) in child_ids
assert str(other_child.id) in child_ids
owned_child_data = next(
c for c in content["children"] if c["id"] == str(owned_child.id)
)
assert owned_child_data["deleted_at"] == owned_child.deleted_at.isoformat().replace(
"+00:00", "Z"
)
def test_api_documents_tree_deleted_child_hidden_from_non_owner():
"""
A deleted child document should not appear in the tree for users who are not its owner.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
parent = factories.DocumentFactory(link_reach="public")
deleted_child = factories.DocumentFactory(parent=parent)
visible_child = factories.DocumentFactory(parent=parent)
deleted_child.soft_delete()
response = client.get(f"/api/v1.0/documents/{parent.id!s}/tree/")
assert response.status_code == 200
content = response.json()
child_ids = [c["id"] for c in content["children"]]
assert str(deleted_child.id) not in child_ids
assert str(visible_child.id) in child_ids
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "impress"
version = "4.8.2"
version = "4.8.3"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -131,12 +131,13 @@ test.describe('Doc Comments', () => {
await thread.getByRole('paragraph').first().fill('This is a comment');
await thread.locator('[data-test="save"]').click();
await expect(thread.getByText('This is a comment').first()).toBeHidden();
await expect(editor.getByText('Hello')).toHaveClass('bn-thread-mark');
// Check background color changed
await expect(editor.getByText('Hello')).toHaveCSS(
'background-color',
'rgba(237, 180, 0, 0.4)',
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
);
await editor.first().click();
await editor.getByText('Hello').click();
@@ -185,6 +186,7 @@ test.describe('Doc Comments', () => {
await thread.getByText('This is an edited comment').first().hover();
await thread.locator('[data-test="resolve"]').click();
await expect(thread).toBeHidden();
await expect(editor.getByText('Hello')).toHaveCSS(
'background-color',
'rgba(0, 0, 0, 0)',
@@ -196,11 +198,13 @@ test.describe('Doc Comments', () => {
await thread.getByRole('paragraph').first().fill('This is a new comment');
await thread.locator('[data-test="save"]').click();
await expect(editor.getByText('Hello')).toHaveClass('bn-thread-mark');
await expect(editor.getByText('Hello')).toHaveCSS(
'background-color',
'rgba(237, 180, 0, 0.4)',
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
);
await editor.first().click();
await editor.getByText('Hello').click();
@@ -208,6 +212,7 @@ test.describe('Doc Comments', () => {
await thread.locator('[data-test="moreactions"]').first().click();
await getMenuItem(thread, 'Delete comment').click();
await expect(editor.getByText('Hello')).not.toHaveClass('bn-thread-mark');
await expect(editor.getByText('Hello')).toHaveCSS(
'background-color',
'rgba(0, 0, 0, 0)',
@@ -263,7 +268,7 @@ test.describe('Doc Comments', () => {
await expect(otherEditor.getByText('Hello')).toHaveCSS(
'background-color',
'rgba(237, 180, 0, 0.4)',
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
);
// We change the role of the second user to reader
@@ -298,7 +303,7 @@ test.describe('Doc Comments', () => {
await expect(otherEditor.getByText('Hello')).toHaveCSS(
'background-color',
'rgba(237, 180, 0, 0.4)',
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
);
await otherEditor.getByText('Hello').click();
await expect(
@@ -344,7 +349,7 @@ test.describe('Doc Comments', () => {
await expect(editor1.getByText('Document One')).toHaveCSS(
'background-color',
'rgba(237, 180, 0, 0.4)',
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
);
await editor1.getByText('Document One').click();
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "app-e2e",
"version": "4.8.2",
"version": "4.8.3",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "app-impress",
"version": "4.8.2",
"version": "4.8.3",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
@@ -8,12 +8,37 @@ export const cssComments = (
& .--docs--main-editor .ProseMirror {
// Comments marks in the editor
.bn-editor {
.bn-thread-mark:not([data-orphan='true']),
.bn-thread-mark-selected:not([data-orphan='true']) {
background: ${canSeeComment ? '#EDB40066' : 'transparent'};
color: var(--c--globals--colors--gray-700);
// Resets blocknote comments styles
.bn-thread-mark,
.bn-thread-mark-selected {
background-color: transparent;
}
${canSeeComment &&
css`
.bn-thread-mark:not([data-orphan='true']) {
background-color: color-mix(
in srgb,
var(--c--contextuals--background--palette--yellow--tertiary) 40%,
transparent
);
border-bottom: 2px solid
var(--c--contextuals--background--palette--yellow--secondary);
mix-blend-mode: multiply;
transition:
background-color var(--c--globals--transitions--duration),
border-bottom-color var(--c--globals--transitions--duration);
&:has(.bn-thread-mark-selected) {
background-color: var(
--c--contextuals--background--palette--yellow--tertiary
);
}
}
`}
[data-show-selection] {
color: HighlightText;
}
@@ -30,6 +30,8 @@ const defaultValues = {
type ExtendedCloseEvent = CloseEvent & { wasClean: boolean };
let reconnectTimeout: ReturnType<typeof setTimeout> | undefined;
export const useProviderStore = create<UseCollaborationStore>((set, get) => ({
...defaultValues,
createProvider: (wsUrl, storeId, initialDoc) => {
@@ -60,7 +62,8 @@ export const useProviderStore = create<UseCollaborationStore>((set, get) => ({
return;
}
void provider.connect();
clearTimeout(reconnectTimeout);
reconnectTimeout = setTimeout(() => void provider.connect(), 1000);
}
},
onAuthenticationFailed() {
@@ -119,6 +122,7 @@ export const useProviderStore = create<UseCollaborationStore>((set, get) => ({
return provider;
},
destroyProvider: () => {
clearTimeout(reconnectTimeout);
const provider = get().provider;
if (provider) {
provider.destroy();
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "impress",
"version": "4.8.2",
"version": "4.8.3",
"private": true,
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-docs",
"version": "4.8.2",
"version": "4.8.3",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "packages-i18n",
"version": "4.8.2",
"version": "4.8.3",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
+2 -2
View File
@@ -1,10 +1,10 @@
environments:
dev:
values:
- version: 4.8.2
- version: 4.8.3
feature:
values:
- version: 4.8.2
- version: 4.8.3
feature: ci
domain: example.com
imageTag: demo
+1 -1
View File
@@ -1,5 +1,5 @@
apiVersion: v2
type: application
name: docs
version: 4.8.2
version: 4.8.3
appVersion: latest
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "4.8.2",
"version": "4.8.3",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {