Compare commits

..

8 Commits

Author SHA1 Message Date
Anthony LC d12b608db9 🔖(patch) release 3.4.1
Fixed:
- 🌐(frontend) keep simple tag during export
- 🐛(back) manage can-edit endpoint without created room in the ws
- 🐛(frontend) fix action buttons not clickable
- 🐛(frontend) fix crash share modal on grid options
2025-07-15 16:14:43 +02:00
Anthony LC 08a0eb59c8 🐛(frontend) fix crash share modal on grid options
The share modal in the DocsGridItem component
was crashing when opened due to a provider not
initialized.
2025-07-15 11:36:44 +02:00
renovate[bot] 0afc50fb93 ⬆️(dependencies) update js dependencies 2025-07-15 03:15:51 +00:00
renovate[bot] c48a4309c1 ⬆️(dependencies) update python dependencies 2025-07-11 06:14:43 +00:00
Anthony LC a212417fb8 🐛(frontend) fix action buttons not clickable (#1162)
If the title was too long, or the children deepness too deep, the action
buttons in the doc tree were not clickable.
This commit fixes the issue by ensuring that the action buttons are
always clickable, regardless of the title length or children depth.
2025-07-11 08:13:01 +02:00
Manuel Raynaud 500d4ea5ac 🐛(back) manage can-edit endpoint without created room in the ws (#1152)
In a scenario where the first user is editing a docs without websocket
and nobody has reached the websocket server first, the y-provider
service will return a 404 and we don't handle this case in the can-edit
endpoint leading to a server error.
2025-07-10 12:24:38 +00:00
Anthony LC 8a057b9c39 🌐(i18n) update translated strings
Update translated files with new translations
2025-07-10 12:48:52 +02:00
Anthony LC 6a12ac560e 🌐(frontend) keep simple tag during export
When we export translations, we want to keep the
simple tags like `<strong>` instead of converting
it to `<1>` and `</1>`.
2025-07-10 12:38:28 +02:00
21 changed files with 652 additions and 448 deletions
+12 -1
View File
@@ -8,6 +8,16 @@ and this project adheres to
## [Unreleased]
## [3.4.1] - 2025-07-15
### Fixed
- 🌐(frontend) keep simple tag during export #1154
- 🐛(back) manage can-edit endpoint without created room
in the ws #1152
- 🐛(frontend) fix action buttons not clickable #1162
- 🐛(frontend) fix crash share modal on grid options #1174
## [3.4.0] - 2025-07-09
### Added
@@ -636,7 +646,8 @@ and this project adheres to
- ✨(frontend) Coming Soon page (#67)
- 🚀 Impress, project to manage your documents easily and collaboratively.
[unreleased]: https://github.com/numerique-gouv/impress/compare/v3.4.0...main
[unreleased]: https://github.com/numerique-gouv/impress/compare/v3.4.1...main
[v3.4.1]: https://github.com/numerique-gouv/impress/releases/v3.4.1
[v3.4.0]: https://github.com/numerique-gouv/impress/releases/v3.4.0
[v3.3.0]: https://github.com/numerique-gouv/impress/releases/v3.3.0
[v3.2.1]: https://github.com/numerique-gouv/impress/releases/v3.2.1
@@ -62,10 +62,14 @@ class CollaborationService:
except requests.RequestException as e:
raise requests.HTTPError("Failed to get document connection info.") from e
if response.status_code != 200:
raise requests.HTTPError(
f"Failed to get document connection info. Status code: {response.status_code}, "
f"Response: {response.text}"
)
result = response.json()
return result.get("count", 0), result.get("exists", False)
if response.status_code == 200:
result = response.json()
return result.get("count", 0), result.get("exists", False)
if response.status_code == 404:
return 0, False
raise requests.HTTPError(
f"Failed to get document connection info. Status code: {response.status_code}, "
f"Response: {response.text}"
)
@@ -246,3 +246,73 @@ def test_api_documents_can_edit_websocket_server_unreachable_fallback_to_no_webs
assert cache.get(f"docs:no-websocket:{document.id}") == "other_session_key"
assert ws_resp.call_count == 1
@responses.activate
def test_api_documents_can_edit_websocket_server_room_not_found(
settings,
):
"""
When the websocket server returns a 404, the document can be updated like if the user was
not connected to the websocket.
"""
user = factories.UserFactory(with_owned_document=True)
client = APIClient()
client.force_login(user)
session_key = client.session.session_key
document = factories.DocumentFactory(users=[(user, "editor")])
settings.COLLABORATION_API_URL = "http://example.com/"
settings.COLLABORATION_SERVER_SECRET = "secret-token"
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
endpoint_url = (
f"{settings.COLLABORATION_API_URL}get-connections/"
f"?room={document.id}&sessionKey={session_key}"
)
ws_resp = responses.get(endpoint_url, status=404)
assert cache.get(f"docs:no-websocket:{document.id}") is None
response = client.get(
f"/api/v1.0/documents/{document.id!s}/can-edit/",
)
assert response.status_code == 200
assert response.json() == {"can_edit": True}
assert ws_resp.call_count == 1
@responses.activate
def test_api_documents_can_edit_websocket_server_room_not_found_other_already_editing(
settings,
):
"""
When the websocket server returns a 404 and another user is editing the document,
the response should be can-edit=False.
"""
user = factories.UserFactory(with_owned_document=True)
client = APIClient()
client.force_login(user)
session_key = client.session.session_key
document = factories.DocumentFactory(users=[(user, "editor")])
settings.COLLABORATION_API_URL = "http://example.com/"
settings.COLLABORATION_SERVER_SECRET = "secret-token"
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
endpoint_url = (
f"{settings.COLLABORATION_API_URL}get-connections/"
f"?room={document.id}&sessionKey={session_key}"
)
ws_resp = responses.get(endpoint_url, status=404)
cache.set(f"docs:no-websocket:{document.id}", "other_session_key")
response = client.get(
f"/api/v1.0/documents/{document.id!s}/can-edit/",
)
assert response.status_code == 200
assert response.json() == {"can_edit": False}
assert ws_resp.call_count == 1
@@ -539,6 +539,47 @@ def test_api_documents_update_websocket_server_unreachable_fallback_to_no_websoc
assert ws_resp.call_count == 1
@responses.activate
def test_api_documents_update_websocket_server_room_not_found_fallback_to_no_websocket_other_users(
settings,
):
"""
When the WebSocket server does not have the room created, the logic should fallback to
no-WebSocket. If another user is already editing, the update must be denied.
"""
user = factories.UserFactory(with_owned_document=True)
client = APIClient()
client.force_login(user)
session_key = client.session.session_key
document = factories.DocumentFactory(users=[(user, "editor")])
new_document_values = serializers.DocumentSerializer(
instance=factories.DocumentFactory()
).data
new_document_values["websocket"] = False
settings.COLLABORATION_API_URL = "http://example.com/"
settings.COLLABORATION_SERVER_SECRET = "secret-token"
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
endpoint_url = (
f"{settings.COLLABORATION_API_URL}get-connections/"
f"?room={document.id}&sessionKey={session_key}"
)
ws_resp = responses.get(endpoint_url, status=404)
cache.set(f"docs:no-websocket:{document.id}", "other_session_key")
response = client.put(
f"/api/v1.0/documents/{document.id!s}/",
new_document_values,
format="json",
)
assert response.status_code == 403
assert cache.get(f"docs:no-websocket:{document.id}") == "other_session_key"
assert ws_resp.call_count == 1
@responses.activate
def test_api_documents_update_force_websocket_param_to_true(settings):
"""
+5 -5
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "impress"
version = "3.4.0"
version = "3.4.1"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -26,7 +26,7 @@ readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"beautifulsoup4==4.13.4",
"boto3==1.39.3",
"boto3==1.39.4",
"Brotli==1.1.0",
"celery[redis]==5.5.3",
"django-configurations==2.5.1",
@@ -34,7 +34,7 @@ dependencies = [
"django-countries==7.6.1",
"django-csp==4.0",
"django-filter==25.1",
"django-lasuite[all]==0.0.10",
"django-lasuite[all]==0.0.11",
"django-parler==2.3",
"django-redis==6.0.0",
"django-storages[s3]==1.14.6",
@@ -52,9 +52,9 @@ dependencies = [
"markdown==3.8.2",
"mozilla-django-oidc==4.0.1",
"nested-multipart-parser==1.5.0",
"openai==1.93.0",
"openai==1.95.0",
"psycopg[binary]==3.2.9",
"pycrdt==0.12.23",
"pycrdt==0.12.25",
"PyJWT==2.10.1",
"python-magic==0.4.27",
"redis<6.0.0",
@@ -91,6 +91,22 @@ test.describe('Document grid item options', () => {
await page.goto('/');
});
test('it checks the share modal', async ({ page, browserName }) => {
const [docTitle] = await createDoc(page, `check share modal`, browserName);
await page.goto('/');
await expect(page.getByText(docTitle)).toBeVisible();
const row = await getGridRow(page, docTitle);
await row.getByText(`more_horiz`).click();
await page.getByRole('menuitem', { name: 'Share' }).click();
await expect(
page.getByRole('dialog').getByText('Share the document'),
).toBeVisible();
});
test('it pins a document', async ({ page, browserName }) => {
const [docTitle] = await createDoc(page, `Favorite doc`, browserName);
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "app-e2e",
"version": "3.4.0",
"version": "3.4.1",
"private": true,
"scripts": {
"lint": "eslint . --ext .ts",
@@ -12,7 +12,7 @@
"test:ui::chromium": "yarn test:ui --project=chromium"
},
"devDependencies": {
"@playwright/test": "1.53.2",
"@playwright/test": "1.54.1",
"@types/node": "*",
"@types/pdf-parse": "1.1.5",
"eslint-config-impress": "*",
+11 -11
View File
@@ -1,6 +1,6 @@
{
"name": "app-impress",
"version": "3.4.0",
"version": "3.4.1",
"private": true,
"scripts": {
"dev": "next dev",
@@ -32,27 +32,27 @@
"@hocuspocus/provider": "2.15.2",
"@openfun/cunningham-react": "3.1.0",
"@react-pdf/renderer": "4.3.0",
"@sentry/nextjs": "9.35.0",
"@tanstack/react-query": "5.81.5",
"@sentry/nextjs": "9.38.0",
"@tanstack/react-query": "5.83.0",
"canvg": "4.0.3",
"clsx": "2.1.1",
"cmdk": "1.1.1",
"crisp-sdk-web": "1.0.25",
"docx": "9.5.0",
"emoji-mart": "5.6.0",
"i18next": "25.3.1",
"i18next": "25.3.2",
"i18next-browser-languagedetector": "8.2.0",
"idb": "8.0.3",
"lodash": "4.17.21",
"luxon": "3.6.1",
"next": "15.3.5",
"posthog-js": "1.256.2",
"luxon": "3.7.1",
"next": "15.4.1",
"posthog-js": "1.257.0",
"react": "*",
"react-aria-components": "1.10.1",
"react-dom": "*",
"react-i18next": "15.6.0",
"react-intersection-observer": "9.16.0",
"react-select": "5.10.1",
"react-select": "5.10.2",
"styled-components": "6.1.19",
"use-debounce": "10.0.5",
"y-protocols": "1.0.6",
@@ -61,7 +61,7 @@
},
"devDependencies": {
"@svgr/webpack": "8.1.0",
"@tanstack/react-query-devtools": "5.81.5",
"@tanstack/react-query-devtools": "5.83.0",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.3.0",
@@ -73,7 +73,7 @@
"@types/react": "*",
"@types/react-dom": "*",
"cross-env": "7.0.3",
"dotenv": "17.1.0",
"dotenv": "17.2.0",
"eslint-config-impress": "*",
"fetch-mock": "9.11.0",
"jest": "30.0.4",
@@ -84,7 +84,7 @@
"stylelint-config-standard": "38.0.0",
"stylelint-prettier": "5.0.3",
"typescript": "*",
"webpack": "5.99.9",
"webpack": "5.100.1",
"workbox-webpack-plugin": "7.1.0"
}
}
@@ -18,7 +18,7 @@ import {
import { useLeftPanelStore } from '@/features/left-panel';
import { useResponsiveStore } from '@/stores';
import Logo from './../assets/sub-page-logo.svg';
import SubPageIcon from './../assets/sub-page-logo.svg';
import { DocTreeItemActions } from './DocTreeItemActions';
const ItemTextCss = css`
@@ -99,6 +99,7 @@ export const DocSubPageItem = (props: Props) => {
return (
<Box
className="--docs-sub-page-item"
$position="relative"
$css={css`
background-color: ${actionsOpen
? 'var(--c--theme--colors--greyscale-100)'
@@ -106,6 +107,17 @@ export const DocSubPageItem = (props: Props) => {
.light-doc-item-actions {
display: ${actionsOpen || !isDesktop ? 'flex' : 'none'};
position: absolute;
right: 0;
background: ${isDesktop
? 'var(--c--theme--colors--greyscale-100)'
: 'var(--c--theme--colors--greyscale-000)'};
}
.c__tree-view--node.isSelected {
.light-doc-item-actions {
background: var(--c--theme--colors--greyscale-100);
}
}
&:hover {
@@ -114,6 +126,7 @@ export const DocSubPageItem = (props: Props) => {
.light-doc-item-actions {
display: flex;
background: var(--c--theme--colors--greyscale-100);
}
}
`}
@@ -136,7 +149,7 @@ export const DocSubPageItem = (props: Props) => {
$minHeight="24px"
>
<Box $width="16px" $height="16px">
<Logo />
<SubPageIcon />
</Box>
<Box
@@ -136,6 +136,10 @@ export const DocTree = ({ initialTargetId }: DocTreeProps) => {
.c__tree-view--container {
z-index: 1;
margin-top: -10px;
.c__tree-view {
overflow: hidden !important;
}
}
`}
>
@@ -1,3 +1,4 @@
import { TreeProvider } from '@gouvfr-lasuite/ui-kit';
import { Tooltip, useModal } from '@openfun/cunningham-react';
import { DateTime } from 'luxon';
import { useTranslation } from 'react-i18next';
@@ -143,7 +144,9 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
</Box>
</Box>
{shareModal.isOpen && (
<DocShareModal doc={doc} onClose={shareModal.close} />
<TreeProvider initialNodeId={doc.id}>
<DocShareModal doc={doc} onClose={shareModal.close} />
</TreeProvider>
)}
</>
);
@@ -591,7 +591,7 @@
"The document has been deleted.": "Le document a bien été supprimé.",
"The document visibility has been updated.": "La visibilité du document a été mise à jour.",
"The export failed": "Lexportation a échoué",
"This document and <1>any sub-documents</1> will be permanently deleted. This action is irreversible.": "Ce document et <1>tous les sous-documents</1> seront définitivement supprimés. Cette action est irréversible.",
"This document and <strong>any sub-documents</strong> will be permanently deleted. This action is irreversible.": "Ce document et <strong>tous les sous-documents</strong> seront définitivement supprimés. Cette action est irréversible.",
"This file is flagged as unsafe.": "Ce fichier est marqué comme non sûr.",
"This means you can't edit until others leave.": "Cela signifie que vous ne pouvez pas éditer tant que d'autres éditeurs sont présents sur le document.",
"This user has access inherited from a parent page.": "Cet utilisateur a un accès hérité d'une page parente.",
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "impress",
"version": "3.4.0",
"version": "3.4.1",
"private": true,
"workspaces": {
"packages": [
@@ -28,11 +28,11 @@
"server:test": "yarn COLLABORATION_SERVER run test"
},
"resolutions": {
"@types/node": "22.16.0",
"@types/node": "22.16.3",
"@types/react": "19.1.8",
"@types/react-dom": "19.1.6",
"@typescript-eslint/eslint-plugin": "8.36.0",
"@typescript-eslint/parser": "8.36.0",
"@typescript-eslint/eslint-plugin": "8.37.0",
"@typescript-eslint/parser": "8.37.0",
"eslint": "8.57.0",
"react": "19.1.0",
"react-dom": "19.1.0",
@@ -1,24 +1,24 @@
{
"name": "eslint-config-impress",
"version": "3.4.0",
"version": "3.4.1",
"license": "MIT",
"scripts": {
"lint": "eslint --ext .js ."
},
"dependencies": {
"@next/eslint-plugin-next": "15.3.5",
"@next/eslint-plugin-next": "15.4.1",
"@tanstack/eslint-plugin-query": "5.81.2",
"@typescript-eslint/eslint-plugin": "*",
"@typescript-eslint/parser": "*",
"eslint": "*",
"eslint-config-next": "15.3.5",
"eslint-config-next": "15.4.1",
"eslint-config-prettier": "10.1.5",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-jest": "29.0.1",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-playwright": "2.2.0",
"eslint-plugin-prettier": "5.5.1",
"eslint-plugin-testing-library": "7.5.3",
"eslint-plugin-testing-library": "7.6.0",
"prettier": "3.6.2"
}
}
@@ -7,6 +7,16 @@ const config = {
keySeparator: false,
nsSeparator: false,
namespaceSeparator: false,
lexers: {
tsx: [
{
lexer: 'JsxLexer',
functions: ['t'],
transSupportBasicHtmlNodes: true, // Disable automatic conversion
transKeepBasicHtmlNodesFor: ['strong', 'b', 'i', 'code', 'br'],
},
],
},
};
export default config;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "packages-i18n",
"version": "3.4.0",
"version": "3.4.1",
"private": true,
"scripts": {
"extract-translation": "yarn extract-translation:impress",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "server-y-provider",
"version": "3.4.0",
"version": "3.4.1",
"description": "Y.js provider for docs",
"repository": "https://github.com/numerique-gouv/impress",
"license": "MIT",
@@ -18,8 +18,8 @@
"dependencies": {
"@blocknote/server-util": "0.33.0",
"@hocuspocus/server": "2.15.2",
"@sentry/node": "9.35.0",
"@sentry/profiling-node": "9.35.0",
"@sentry/node": "9.38.0",
"@sentry/profiling-node": "9.38.0",
"axios": "1.10.0",
"cors": "2.8.5",
"express": "5.1.0",
+435 -403
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
environments:
dev:
values:
- version: 3.4.0
- version: 3.4.1
---
repositories:
- name: bitnami
+1 -1
View File
@@ -1,5 +1,5 @@
apiVersion: v2
type: application
name: docs
version: 3.4.0
version: 3.4.1
appVersion: latest
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "3.4.0",
"version": "3.4.1",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {