Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d12b608db9 | |||
| 08a0eb59c8 | |||
| 0afc50fb93 | |||
| c48a4309c1 | |||
| a212417fb8 | |||
| 500d4ea5ac | |||
| 8a057b9c39 | |||
| 6a12ac560e |
+12
-1
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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": "*",
|
||||
|
||||
@@ -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": "L’exportation 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.",
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "packages-i18n",
|
||||
"version": "3.4.0",
|
||||
"version": "3.4.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"extract-translation": "yarn extract-translation:impress",
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
environments:
|
||||
dev:
|
||||
values:
|
||||
- version: 3.4.0
|
||||
- version: 3.4.1
|
||||
---
|
||||
repositories:
|
||||
- name: bitnami
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
type: application
|
||||
name: docs
|
||||
version: 3.4.0
|
||||
version: 3.4.1
|
||||
appVersion: latest
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user