Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e47b02b6f2 | |||
| 75f71368f4 | |||
| 21f5feab3e | |||
| 8ec89a8348 | |||
| 3b80ac7b4e | |||
| 68df717854 | |||
| 2f52dddc84 | |||
| b1231cea7c | |||
| f9f32db854 | |||
| 0d967aba48 | |||
| 5ec58cef99 | |||
| 1170bdbfc1 | |||
| e807237dbe | |||
| fa6f3e8b7c | |||
| b1a18b2477 |
+34
-12
@@ -6,27 +6,49 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(frontend) integrate configurable Waffle #1795
|
||||
|
||||
### Fixed
|
||||
|
||||
- ✅(e2e) fix e2e test for other browsers #1799
|
||||
- 🐛(frontend) add fallback for unsupported Blocknote languages #1810
|
||||
- 🐛(frontend) fix emojipicker closing in tree #1808
|
||||
|
||||
### Changed
|
||||
|
||||
- ♿(frontend) improve accessibility:
|
||||
- ♿️(frontend) fix subdoc opening and emoji pick focus #1745
|
||||
- ♿️(frontend) Keyboard focus Fixes for docs Tree/Editor #1816
|
||||
|
||||
## [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
|
||||
|
||||
### Fixed
|
||||
|
||||
- ✅(backend) reduce flakiness on backend test #1769
|
||||
- 🐛(backend) fix TRASHBIN_CUTOFF_DAYS type error #1778
|
||||
|
||||
### Security
|
||||
|
||||
- 🔒️(backend) validate more strictly url used by cors-proxy endpoint #1768
|
||||
- ✨(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
|
||||
|
||||
- ✅(backend) reduce flakiness on backend test #1769
|
||||
- 🐛(frontend) fix clickable main content regression #1773
|
||||
- 🐛(backend) fix TRASHBIN_CUTOFF_DAYS type error #1778
|
||||
- 💄(frontend) fix icon position in callout block #1779
|
||||
|
||||
### Security
|
||||
|
||||
- 🔒️(backend) validate more strictly url used by cors-proxy endpoint #1768
|
||||
- 🔒️(frontend) fix props vulnerability in Interlinking #1792
|
||||
|
||||
## [4.3.0] - 2026-01-05
|
||||
|
||||
@@ -46,7 +68,6 @@ and this project adheres to
|
||||
|
||||
- 🐛(frontend) fix tables deletion #1739
|
||||
- 🐛(frontend) fix children not display when first resize #1753
|
||||
- 🐛(frontend) fix clickable main content regression #1773
|
||||
|
||||
## [4.2.0] - 2025-12-17
|
||||
|
||||
@@ -985,7 +1006,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
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@@ -1,4 +1,6 @@
|
||||
# Runtime Theming 🎨
|
||||
# Customization Guide 🛠 ️
|
||||
|
||||
## Runtime Theming 🎨
|
||||
|
||||
### How to Use
|
||||
|
||||
@@ -32,7 +34,7 @@ Then, set the `FRONTEND_CSS_URL` environment variable to the URL of your custom
|
||||
|
||||
----
|
||||
|
||||
# Runtime JavaScript Injection 🚀
|
||||
## Runtime JavaScript Injection 🚀
|
||||
|
||||
### How to Use
|
||||
|
||||
@@ -87,7 +89,7 @@ Then, set the `FRONTEND_JS_URL` environment variable to the URL of your custom J
|
||||
|
||||
----
|
||||
|
||||
# **Your Docs icon** 📝
|
||||
## **Your Docs icon** 📝
|
||||
|
||||
You can add your own Docs icon in the header from the theme customization file.
|
||||
|
||||
@@ -105,7 +107,7 @@ This configuration is optional. If not set, the default icon will be used.
|
||||
|
||||
----
|
||||
|
||||
# **Footer Configuration** 📝
|
||||
## **Footer Configuration** 📝
|
||||
|
||||
The footer is configurable from the theme customization file.
|
||||
|
||||
@@ -128,7 +130,7 @@ Below is a visual example of a configured footer ⬇️:
|
||||
|
||||
----
|
||||
|
||||
# **Custom Translations** 📝
|
||||
## **Custom Translations** 📝
|
||||
|
||||
The translations can be partially overridden from the theme customization file.
|
||||
|
||||
@@ -140,4 +142,36 @@ THEME_CUSTOMIZATION_FILE_PATH=<path>
|
||||
|
||||
### Example of JSON
|
||||
|
||||
The json must follow some rules: https://github.com/suitenumerique/docs/blob/main/src/helm/env.d/dev/configuration/theme/demo.json
|
||||
The json must follow some rules: https://github.com/suitenumerique/docs/blob/main/src/helm/env.d/dev/configuration/theme/demo.json
|
||||
|
||||
----
|
||||
|
||||
## **Waffle Configuration** 🧇
|
||||
|
||||
The Waffle (La Gaufre) is a widget that displays a grid of services.
|
||||
|
||||

|
||||
|
||||
### Settings 🔧
|
||||
|
||||
```shellscript
|
||||
THEME_CUSTOMIZATION_FILE_PATH=<path>
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The Waffle can be configured in the theme customization file with the `waffle` key.
|
||||
|
||||
### Available Properties
|
||||
|
||||
See: [LaGaufreV2Props](https://github.com/suitenumerique/ui-kit/blob/main/src/components/la-gaufre/LaGaufreV2.tsx#L49)
|
||||
|
||||
### Complete Example
|
||||
|
||||
From the theme customization file: https://github.com/suitenumerique/docs/blob/main/src/helm/env.d/dev/configuration/theme/demo.json
|
||||
|
||||
### Behavior
|
||||
|
||||
- If `data.services` is provided, the Waffle will display those services statically
|
||||
- If no data is provided, services can be fetched dynamically from an API endpoint thanks to the `apiUrl` property
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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} 的副本"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -73,7 +73,7 @@ test.describe('Doc Editor', () => {
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await page.locator('.bn-block-outer').last().click();
|
||||
await page.locator('.ProseMirror').focus();
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
createDoc,
|
||||
expectLoginPage,
|
||||
keyCloakSignIn,
|
||||
randomName,
|
||||
updateDocTitle,
|
||||
verifyDocName,
|
||||
} from './utils-common';
|
||||
@@ -20,50 +19,6 @@ test.describe('Doc Tree', () => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('create new sub pages', async ({ page, browserName }) => {
|
||||
const [titleParent] = await createDoc(
|
||||
page,
|
||||
'doc-tree-content',
|
||||
browserName,
|
||||
1,
|
||||
);
|
||||
await verifyDocName(page, titleParent);
|
||||
const addButton = page.getByTestId('new-doc-button');
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
|
||||
await expect(addButton).toBeVisible();
|
||||
|
||||
// Wait for and intercept the POST request to create a new page
|
||||
const responsePromise = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/documents/') &&
|
||||
response.url().includes('/children/') &&
|
||||
response.request().method() === 'POST',
|
||||
);
|
||||
|
||||
await clickOnAddRootSubPage(page);
|
||||
const response = await responsePromise;
|
||||
expect(response.ok()).toBeTruthy();
|
||||
const subPageJson = await response.json();
|
||||
|
||||
await expect(docTree).toBeVisible();
|
||||
const subPageItem = docTree
|
||||
.getByTestId(`doc-sub-page-item-${subPageJson.id}`)
|
||||
.first();
|
||||
|
||||
await expect(subPageItem).toBeVisible();
|
||||
await subPageItem.click();
|
||||
await verifyDocName(page, '');
|
||||
const input = page.getByRole('textbox', { name: 'Document title' });
|
||||
await input.click();
|
||||
const [randomDocName] = randomName('doc-tree-test', browserName, 1);
|
||||
await input.fill(randomDocName);
|
||||
await input.press('Enter');
|
||||
await expect(subPageItem.getByText(randomDocName)).toBeVisible();
|
||||
await page.reload();
|
||||
await expect(subPageItem.getByText(randomDocName)).toBeVisible();
|
||||
});
|
||||
|
||||
test('check the reorder of sub pages', async ({ page, browserName }) => {
|
||||
await createDoc(page, 'doc-tree-content', browserName, 1);
|
||||
const addButton = page.getByTestId('new-doc-button');
|
||||
|
||||
@@ -59,45 +59,90 @@ test.describe('Header', () => {
|
||||
).toBeVisible();
|
||||
|
||||
await expect(header.getByText('English')).toBeVisible();
|
||||
|
||||
await expect(
|
||||
header.getByRole('button', {
|
||||
name: 'Les services de La Suite numérique',
|
||||
}),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('checks La Gauffre interaction', async ({ page }) => {
|
||||
test('checks a custom waffle', async ({ page }) => {
|
||||
await overrideConfig(page, {
|
||||
FRONTEND_THEME: 'dsfr',
|
||||
theme_customization: {
|
||||
waffle: {
|
||||
data: {
|
||||
services: [
|
||||
{
|
||||
name: 'Docs E2E Custom 1',
|
||||
url: 'https://docs.numerique.gouv.fr/',
|
||||
maturity: 'stable',
|
||||
logo: 'https://lasuite.numerique.gouv.fr/assets/products/docs.svg',
|
||||
},
|
||||
{
|
||||
name: 'Docs E2E Custom 2',
|
||||
url: 'https://docs.numerique.gouv.fr/',
|
||||
maturity: 'stable',
|
||||
logo: 'https://lasuite.numerique.gouv.fr/assets/products/docs.svg',
|
||||
},
|
||||
],
|
||||
},
|
||||
showMoreLimit: 9,
|
||||
},
|
||||
},
|
||||
});
|
||||
await page.goto('/');
|
||||
|
||||
const header = page.locator('header').first();
|
||||
|
||||
await expect(
|
||||
header.getByRole('button', {
|
||||
name: 'Les services de La Suite numérique',
|
||||
}),
|
||||
header.getByRole('button', { name: 'Digital LaSuite services' }),
|
||||
).toBeVisible();
|
||||
|
||||
/**
|
||||
* La gaufre load a js file from a remote server,
|
||||
* The Waffle loads a js file from a remote server,
|
||||
* it takes some time to load the file and have the interaction available
|
||||
*/
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
await header
|
||||
.getByRole('button', { name: 'Digital LaSuite services' })
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs E2E Custom 1' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs E2E Custom 2' }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('checks the waffle dsfr', async ({ page }) => {
|
||||
await overrideConfig(page, {
|
||||
theme_customization: {
|
||||
waffle: {
|
||||
apiUrl: 'https://lasuite.numerique.gouv.fr/api/services',
|
||||
showMoreLimit: 9,
|
||||
},
|
||||
},
|
||||
});
|
||||
await page.goto('/');
|
||||
|
||||
const header = page.locator('header').first();
|
||||
|
||||
await expect(
|
||||
header.getByRole('button', { name: 'Digital LaSuite services' }),
|
||||
).toBeVisible();
|
||||
|
||||
/**
|
||||
* The Waffle loads a js file from a remote server,
|
||||
* it takes some time to load the file and have the interaction available
|
||||
*/
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
await header
|
||||
.getByRole('button', {
|
||||
name: 'Les services de La Suite numérique',
|
||||
name: 'Digital LaSuite services',
|
||||
})
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'France Transfert' }),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(page.getByRole('link', { name: 'Tchap' })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'Grist' })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'Visio' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,11 +169,6 @@ test.describe('Header mobile', () => {
|
||||
|
||||
await expect(header.getByLabel('Open the header menu')).toBeVisible();
|
||||
await expect(header.getByTestId('header-icon-docs')).toBeVisible();
|
||||
await expect(
|
||||
header.getByRole('button', {
|
||||
name: 'Les services de La Suite numérique',
|
||||
}),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -113,9 +113,6 @@ test.describe('Home page', () => {
|
||||
});
|
||||
await expect(languageButton).toBeVisible();
|
||||
|
||||
await expect(
|
||||
header.getByRole('button', { name: 'Les services de La Suite numé' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
header.getByRole('img', { name: 'Gouvernement Logo' }),
|
||||
).toBeVisible();
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { TestLanguage, createDoc, waitForLanguageSwitch } from './utils-common';
|
||||
import {
|
||||
TestLanguage,
|
||||
createDoc,
|
||||
overrideConfig,
|
||||
waitForLanguageSwitch,
|
||||
} from './utils-common';
|
||||
import { openSuggestionMenu } from './utils-editor';
|
||||
|
||||
test.describe('Language', () => {
|
||||
@@ -107,6 +112,15 @@ test.describe('Language', () => {
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await overrideConfig(page, {
|
||||
LANGUAGES: [
|
||||
['en-us', 'English'],
|
||||
['fr-fr', 'Français'],
|
||||
['sv-se', 'Svenska'],
|
||||
],
|
||||
LANGUAGE_CODE: 'en-us',
|
||||
});
|
||||
|
||||
await createDoc(page, 'doc-toolbar', browserName, 1);
|
||||
|
||||
const { editor, suggestionMenu } = await openSuggestionMenu({ page });
|
||||
@@ -126,5 +140,14 @@ test.describe('Language', () => {
|
||||
await expect(
|
||||
suggestionMenu.getByText('Titres', { exact: true }),
|
||||
).toBeVisible();
|
||||
|
||||
/**
|
||||
* Swedish is not yet supported in the BlockNote locales, so it should fallback to English
|
||||
*/
|
||||
await waitForLanguageSwitch(page, TestLanguage.Swedish);
|
||||
await openSuggestionMenu({ page });
|
||||
await expect(
|
||||
suggestionMenu.getByText('Headings', { exact: true }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
@@ -328,6 +330,10 @@ export const TestLanguage = {
|
||||
label: 'Deutsch',
|
||||
expectedLocale: ['de-de'],
|
||||
},
|
||||
Swedish: {
|
||||
label: 'Svenska',
|
||||
expectedLocale: ['sv-se'],
|
||||
},
|
||||
} as const;
|
||||
|
||||
type TestLanguageKey = keyof typeof TestLanguage;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,58 +1,53 @@
|
||||
import { defaultTokens } from '@gouvfr-lasuite/cunningham-react';
|
||||
import { cunninghamConfig as tokens } from '@gouvfr-lasuite/ui-kit';
|
||||
import merge from 'lodash/merge';
|
||||
import {
|
||||
dsfrGlobals,
|
||||
getUIKitThemesFromGlobals,
|
||||
whiteLabelGlobals,
|
||||
} from '@gouvfr-lasuite/ui-kit';
|
||||
|
||||
// Uikit does not provide the full list of tokens.
|
||||
// To be able to override correctly, we need to merge with the default tokens.
|
||||
let mergedColors = merge(
|
||||
defaultTokens.globals.colors,
|
||||
tokens.themes.default.globals.colors,
|
||||
);
|
||||
|
||||
mergedColors = {
|
||||
...mergedColors,
|
||||
'logo-1': '#2845C1',
|
||||
};
|
||||
|
||||
tokens.themes.default.globals = {
|
||||
...tokens.themes.default.globals,
|
||||
...{
|
||||
colors: mergedColors,
|
||||
font: {
|
||||
...tokens.themes.default.globals.font,
|
||||
families: {
|
||||
base: 'sans-serif',
|
||||
accent: 'sans-serif',
|
||||
const themeWhiteLabelLight = getUIKitThemesFromGlobals(whiteLabelGlobals, {
|
||||
prefix: 'default',
|
||||
variants: ['light'],
|
||||
overrides: {
|
||||
globals: {
|
||||
spacing: {
|
||||
'0': '0rem',
|
||||
none: '0rem',
|
||||
auto: 'auto',
|
||||
bx: '2.2rem',
|
||||
full: '100%',
|
||||
'3xs': '0.25rem',
|
||||
'2xs': '0.375rem',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
logo: {
|
||||
src: '',
|
||||
alt: '',
|
||||
widthHeader: '',
|
||||
widthFooter: '',
|
||||
},
|
||||
'home-proconnect': false,
|
||||
icon: {
|
||||
src: '/assets/icon-docs.svg',
|
||||
width: '32px',
|
||||
height: 'auto',
|
||||
},
|
||||
favicon: {
|
||||
'png-light': '/assets/favicon-light.png',
|
||||
'png-dark': '/assets/favicon-dark.png',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const themeDefault = {
|
||||
default: themeWhiteLabelLight['default-light'],
|
||||
};
|
||||
|
||||
tokens.themes.default.components = {
|
||||
...tokens.themes.default.components,
|
||||
...{
|
||||
logo: {
|
||||
src: '',
|
||||
alt: '',
|
||||
widthHeader: '',
|
||||
widthFooter: '',
|
||||
},
|
||||
'la-gaufre': false,
|
||||
'home-proconnect': false,
|
||||
icon: {
|
||||
src: '/assets/icon-docs.svg',
|
||||
width: '32px',
|
||||
height: 'auto',
|
||||
},
|
||||
favicon: {
|
||||
'png-light': '/assets/favicon-light.png',
|
||||
'png-dark': '/assets/favicon-dark.png',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const dsfrTheme = {
|
||||
dsfr: {
|
||||
const themesDSFRLight = getUIKitThemesFromGlobals(dsfrGlobals, {
|
||||
prefix: 'dsfr',
|
||||
variants: ['light'],
|
||||
overrides: {
|
||||
globals: {
|
||||
font: {
|
||||
families: {
|
||||
@@ -68,7 +63,6 @@ const dsfrTheme = {
|
||||
widthFooter: '220px',
|
||||
alt: 'Gouvernement Logo',
|
||||
},
|
||||
'la-gaufre': true,
|
||||
'home-proconnect': true,
|
||||
icon: {
|
||||
src: '/assets/icon-docs-dsfr.svg',
|
||||
@@ -82,317 +76,16 @@ const dsfrTheme = {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const genericTheme = {
|
||||
generic: {
|
||||
globals: {
|
||||
colors: {
|
||||
'brand-050': '#EEF1FA',
|
||||
'brand-100': '#DDE2F5',
|
||||
'brand-150': '#CED3F1',
|
||||
'brand-200': '#BEC5F0',
|
||||
'brand-250': '#AFB5F1',
|
||||
'brand-300': '#A0A5F6',
|
||||
'brand-350': '#8F94FD',
|
||||
'brand-400': '#8184FC',
|
||||
'brand-450': '#7576EE',
|
||||
'brand-500': '#6969DF',
|
||||
'brand-550': '#5E5CD0',
|
||||
'brand-600': '#534FC2',
|
||||
'brand-650': '#4844AD',
|
||||
'brand-700': '#3E3B98',
|
||||
'brand-750': '#36347D',
|
||||
'brand-800': '#2D2F5F',
|
||||
'brand-850': '#262848',
|
||||
'brand-900': '#1C1E32',
|
||||
'brand-950': '#11131F',
|
||||
'gray-000': '#FFFFFF',
|
||||
'gray-025': '#F8F8F9',
|
||||
'gray-050': '#F0F0F3',
|
||||
'gray-100': '#E2E2EA',
|
||||
'gray-150': '#D3D4E0',
|
||||
'gray-200': '#C5C6D5',
|
||||
'gray-250': '#B7B7CB',
|
||||
'gray-300': '#A9A9BF',
|
||||
'gray-350': '#9C9CB2',
|
||||
'gray-400': '#8F8FA4',
|
||||
'gray-450': '#828297',
|
||||
'gray-500': '#75758A',
|
||||
'gray-550': '#69697D',
|
||||
'gray-600': '#5D5D70',
|
||||
'gray-650': '#515164',
|
||||
'gray-700': '#454558',
|
||||
'gray-750': '#3A3A4C',
|
||||
'gray-800': '#2F303D',
|
||||
'gray-850': '#25252F',
|
||||
'gray-900': '#1B1B23',
|
||||
'gray-950': '#111114',
|
||||
'gray-1000': '#000000',
|
||||
'info-050': '#EAF2F9',
|
||||
'info-100': '#D5E4F3',
|
||||
'info-150': '#BFD7F0',
|
||||
'info-200': '#A7CAEE',
|
||||
'info-250': '#8DBDEF',
|
||||
'info-300': '#6EB0F2',
|
||||
'info-350': '#50A2F5',
|
||||
'info-400': '#3593F4',
|
||||
'info-450': '#1185ED',
|
||||
'info-500': '#0077DE',
|
||||
'info-550': '#0069CF',
|
||||
'info-600': '#005BC0',
|
||||
'info-650': '#0D4EAA',
|
||||
'info-700': '#124394',
|
||||
'info-750': '#163878',
|
||||
'info-800': '#192F5A',
|
||||
'info-850': '#192541',
|
||||
'info-900': '#141B2D',
|
||||
'info-950': '#0C111C',
|
||||
'success-050': '#E8F1EA',
|
||||
'success-100': '#CFE4D4',
|
||||
'success-150': '#BAD9C1',
|
||||
'success-200': '#A2CFAD',
|
||||
'success-250': '#86C597',
|
||||
'success-300': '#6CBA83',
|
||||
'success-350': '#4FB070',
|
||||
'success-400': '#40A363',
|
||||
'success-450': '#309556',
|
||||
'success-500': '#1E884A',
|
||||
'success-550': '#027B3E',
|
||||
'success-600': '#016D31',
|
||||
'success-650': '#006024',
|
||||
'success-700': '#005317',
|
||||
'success-750': '#0D4511',
|
||||
'success-800': '#11380E',
|
||||
'success-850': '#132A11',
|
||||
'success-900': '#101E0F',
|
||||
'success-950': '#091209',
|
||||
'warning-050': '#F8F0E9',
|
||||
'warning-100': '#F1E0D3',
|
||||
'warning-150': '#ECD0BC',
|
||||
'warning-200': '#E8C0A4',
|
||||
'warning-250': '#E8AE8A',
|
||||
'warning-300': '#EB9970',
|
||||
'warning-350': '#E98456',
|
||||
'warning-400': '#E57036',
|
||||
'warning-450': '#DA5E18',
|
||||
'warning-500': '#CB5000',
|
||||
'warning-550': '#BC4200',
|
||||
'warning-600': '#AD3300',
|
||||
'warning-650': '#9E2300',
|
||||
'warning-700': '#882011',
|
||||
'warning-750': '#731E16',
|
||||
'warning-800': '#58201A',
|
||||
'warning-850': '#401D18',
|
||||
'warning-900': '#2E1714',
|
||||
'warning-950': '#1D0F0D',
|
||||
'error-050': '#F9EFEC',
|
||||
'error-100': '#F4DFD9',
|
||||
'error-150': '#F0CEC6',
|
||||
'error-200': '#EEBCB2',
|
||||
'error-250': '#EEA99D',
|
||||
'error-300': '#EF9486',
|
||||
'error-350': '#F37C6E',
|
||||
'error-400': '#F65F53',
|
||||
'error-450': '#F0463D',
|
||||
'error-500': '#E82322',
|
||||
'error-550': '#D7010E',
|
||||
'error-600': '#C00100',
|
||||
'error-650': '#AA0000',
|
||||
'error-700': '#910C06',
|
||||
'error-750': '#731E16',
|
||||
'error-800': '#58201A',
|
||||
'error-850': '#401D18',
|
||||
'error-900': '#2E1714',
|
||||
'error-950': '#1D0F0D',
|
||||
'red-050': '#FAEFEE',
|
||||
'red-100': '#F4DEDD',
|
||||
'red-150': '#F1CDCB',
|
||||
'red-200': '#EFBBBA',
|
||||
'red-250': '#EEA8A8',
|
||||
'red-300': '#F09394',
|
||||
'red-350': '#F37B7E',
|
||||
'red-400': '#EF6569',
|
||||
'red-450': '#E94A55',
|
||||
'red-500': '#DA3B49',
|
||||
'red-550': '#CA2A3C',
|
||||
'red-600': '#BB1330',
|
||||
'red-650': '#A90021',
|
||||
'red-700': '#910A13',
|
||||
'red-750': '#731E16',
|
||||
'red-800': '#58201A',
|
||||
'red-850': '#411D18',
|
||||
'red-900': '#2E1714',
|
||||
'red-950': '#1D0F0D',
|
||||
'orange-050': '#F8F0E9',
|
||||
'orange-100': '#F1E0D3',
|
||||
'orange-150': '#ECD0BD',
|
||||
'orange-200': '#EABFA6',
|
||||
'orange-250': '#EBAC90',
|
||||
'orange-300': '#EC9772',
|
||||
'orange-350': '#E5845A',
|
||||
'orange-400': '#D6774D',
|
||||
'orange-450': '#C86A40',
|
||||
'orange-500': '#B95D33',
|
||||
'orange-550': '#AB5025',
|
||||
'orange-600': '#9D4315',
|
||||
'orange-650': '#8F3600',
|
||||
'orange-700': '#812900',
|
||||
'orange-750': '#6C2511',
|
||||
'orange-800': '#572017',
|
||||
'orange-850': '#401D18',
|
||||
'orange-900': '#2E1714',
|
||||
'orange-950': '#1D0F0D',
|
||||
'brown-050': '#F6F0E8',
|
||||
'brown-100': '#F1E0D3',
|
||||
'brown-150': '#EBD0BA',
|
||||
'brown-200': '#E2C0A6',
|
||||
'brown-250': '#D4B398',
|
||||
'brown-300': '#C6A58B',
|
||||
'brown-350': '#B8987E',
|
||||
'brown-400': '#AA8B71',
|
||||
'brown-450': '#9D7E65',
|
||||
'brown-500': '#8F7158',
|
||||
'brown-550': '#82654C',
|
||||
'brown-600': '#765841',
|
||||
'brown-650': '#694C35',
|
||||
'brown-700': '#5D412A',
|
||||
'brown-750': '#51361E',
|
||||
'brown-800': '#452A13',
|
||||
'brown-850': '#392008',
|
||||
'brown-900': '#29180A',
|
||||
'brown-950': '#1B0F08',
|
||||
'yellow-050': '#F3F0E7',
|
||||
'yellow-100': '#E9E2CF',
|
||||
'yellow-150': '#E1D4B7',
|
||||
'yellow-200': '#D9C599',
|
||||
'yellow-250': '#D2B677',
|
||||
'yellow-300': '#CAA756',
|
||||
'yellow-350': '#C2972E',
|
||||
'yellow-400': '#B98900',
|
||||
'yellow-450': '#AB7B00',
|
||||
'yellow-500': '#9D6E00',
|
||||
'yellow-550': '#916100',
|
||||
'yellow-600': '#855400',
|
||||
'yellow-650': '#784700',
|
||||
'yellow-700': '#6C3A00',
|
||||
'yellow-750': '#5F2E00',
|
||||
'yellow-800': '#512302',
|
||||
'yellow-850': '#3E1D10',
|
||||
'yellow-900': '#2D1711',
|
||||
'yellow-950': '#1D0F0D',
|
||||
'green-050': '#E6F1E9',
|
||||
'green-100': '#CFE4D5',
|
||||
'green-150': '#B8D8C1',
|
||||
'green-200': '#A0CFAE',
|
||||
'green-250': '#84C59A',
|
||||
'green-300': '#65BA86',
|
||||
'green-350': '#45B173',
|
||||
'green-400': '#23A562',
|
||||
'green-450': '#029755',
|
||||
'green-500': '#008948',
|
||||
'green-550': '#017B3B',
|
||||
'green-600': '#006E2E',
|
||||
'green-650': '#006022',
|
||||
'green-700': '#005314',
|
||||
'green-750': '#0D4510',
|
||||
'green-800': '#11380E',
|
||||
'green-850': '#132A11',
|
||||
'green-900': '#101E0F',
|
||||
'green-950': '#091209',
|
||||
'blue1-050': '#EBF1F9',
|
||||
'blue1-100': '#D6E4F4',
|
||||
'blue1-150': '#C1D7F0',
|
||||
'blue1-200': '#AACAEF',
|
||||
'blue1-250': '#8FBCEF',
|
||||
'blue1-300': '#7CAFEB',
|
||||
'blue1-350': '#68A1E4',
|
||||
'blue1-400': '#5B94D6',
|
||||
'blue1-450': '#4E86C7',
|
||||
'blue1-500': '#4279B9',
|
||||
'blue1-550': '#356CAC',
|
||||
'blue1-600': '#28609E',
|
||||
'blue1-650': '#1B5390',
|
||||
'blue1-700': '#0B4783',
|
||||
'blue1-750': '#0F3C6E',
|
||||
'blue1-800': '#133059',
|
||||
'blue1-850': '#152641',
|
||||
'blue1-900': '#121C2D',
|
||||
'blue1-950': '#0B111C',
|
||||
'blue2-050': '#E7F3F4',
|
||||
'blue2-100': '#CEE7E9',
|
||||
'blue2-150': '#B2DCE0',
|
||||
'blue2-200': '#91D1D7',
|
||||
'blue2-250': '#68C7D0',
|
||||
'blue2-300': '#43BBC5',
|
||||
'blue2-350': '#00AFBA',
|
||||
'blue2-400': '#01A0AA',
|
||||
'blue2-450': '#00929D',
|
||||
'blue2-500': '#00848F',
|
||||
'blue2-550': '#007682',
|
||||
'blue2-600': '#016874',
|
||||
'blue2-650': '#005B67',
|
||||
'blue2-700': '#004E5A',
|
||||
'blue2-750': '#00424E',
|
||||
'blue2-800': '#003642',
|
||||
'blue2-850': '#002A38',
|
||||
'blue2-900': '#061E28',
|
||||
'blue2-950': '#071219',
|
||||
'purple-050': '#F7F0F6',
|
||||
'purple-100': '#EEE0EE',
|
||||
'purple-150': '#E7D1E7',
|
||||
'purple-200': '#DBBFE4',
|
||||
'purple-250': '#D3AEE2',
|
||||
'purple-300': '#CB99E1',
|
||||
'purple-350': '#C188D9',
|
||||
'purple-400': '#B47BCB',
|
||||
'purple-450': '#A66EBD',
|
||||
'purple-500': '#9961AF',
|
||||
'purple-550': '#8B55A1',
|
||||
'purple-600': '#7E4894',
|
||||
'purple-650': '#723C87',
|
||||
'purple-700': '#633376',
|
||||
'purple-750': '#552A65',
|
||||
'purple-800': '#452551',
|
||||
'purple-850': '#35213D',
|
||||
'purple-900': '#261A2C',
|
||||
'purple-950': '#17111C',
|
||||
'pink-050': '#F8EFF4',
|
||||
'pink-100': '#F0DFEA',
|
||||
'pink-150': '#EACEDF',
|
||||
'pink-200': '#E9BBD1',
|
||||
'pink-250': '#E9A7C2',
|
||||
'pink-300': '#E095B4',
|
||||
'pink-350': '#D685A8',
|
||||
'pink-400': '#C7799B',
|
||||
'pink-450': '#B86C8D',
|
||||
'pink-500': '#AA5F80',
|
||||
'pink-550': '#9C5374',
|
||||
'pink-600': '#8E4767',
|
||||
'pink-650': '#813B5B',
|
||||
'pink-700': '#732E4F',
|
||||
'pink-750': '#632643',
|
||||
'pink-800': '#521F38',
|
||||
'pink-850': '#3E1C2B',
|
||||
'pink-900': '#2D171F',
|
||||
'pink-950': '#1C0E12',
|
||||
},
|
||||
font: {
|
||||
families: {
|
||||
base: 'Inter, Roboto Flex Variable, sans-serif',
|
||||
accent: 'Inter, Roboto Flex Variable, sans-serif',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
const themesDSFR = {
|
||||
dsfr: themesDSFRLight['dsfr-light'],
|
||||
};
|
||||
|
||||
const docsTokens = {
|
||||
...tokens,
|
||||
themes: {
|
||||
...tokens.themes,
|
||||
...dsfrTheme,
|
||||
...genericTheme,
|
||||
...themeDefault,
|
||||
...themesDSFR,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
+16
-1
@@ -5,11 +5,14 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box } from '@/components';
|
||||
|
||||
export const PICKER_HEIGHT = 500;
|
||||
|
||||
interface EmojiPickerProps {
|
||||
emojiData: EmojiMartData;
|
||||
onClickOutside: () => void;
|
||||
onEmojiSelect: ({ native }: { native: string }) => void;
|
||||
withOverlay?: boolean;
|
||||
onEscape?: () => void;
|
||||
}
|
||||
|
||||
export const EmojiPicker = ({
|
||||
@@ -17,14 +20,26 @@ export const EmojiPicker = ({
|
||||
onClickOutside,
|
||||
onEmojiSelect,
|
||||
withOverlay = false,
|
||||
onEscape,
|
||||
}: EmojiPickerProps) => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === 'Escape') {
|
||||
if (onEscape) {
|
||||
onEscape();
|
||||
} else {
|
||||
onClickOutside();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const pickerContent = (
|
||||
<Box $position="absolute" $zIndex={1000} $margin="2rem 0 0 0">
|
||||
<Box $position="absolute" $zIndex={1000} onKeyDownCapture={handleKeyDown}>
|
||||
<Picker
|
||||
data={emojiData}
|
||||
locale={i18n.resolvedLanguage}
|
||||
autoFocus
|
||||
onClickOutside={onClickOutside}
|
||||
onEmojiSelect={onEmojiSelect}
|
||||
previewPosition="none"
|
||||
@@ -3,6 +3,8 @@ export * from './BoxButton';
|
||||
export * from './Card';
|
||||
export * from './DropButton';
|
||||
export * from './dropdown-menu/DropdownMenu';
|
||||
export * from './Emoji/EmojiPicker';
|
||||
export { default as emojidata } from './Emoji/initEmojiCallout';
|
||||
export * from './quick-search';
|
||||
export * from './Icon';
|
||||
export * from './InfiniteScroll';
|
||||
|
||||
@@ -4,13 +4,14 @@ import { Resource } from 'i18next';
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
import { Theme } from '@/cunningham/';
|
||||
import { FooterType } from '@/features/footer';
|
||||
import { HeaderType } from '@/features/header';
|
||||
import { HeaderType, WaffleType } from '@/features/header';
|
||||
import { PostHogConf } from '@/services';
|
||||
|
||||
interface ThemeCustomization {
|
||||
footer?: FooterType;
|
||||
translations?: Resource;
|
||||
header?: HeaderType;
|
||||
waffle?: WaffleType;
|
||||
}
|
||||
|
||||
export interface ConfigResponse {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,7 @@ const getMergedTokens = (theme: Theme) => {
|
||||
return merge({}, tokens.themes['default'], tokens.themes[theme]);
|
||||
};
|
||||
|
||||
const DEFAULT_THEME: Theme = 'generic';
|
||||
const DEFAULT_THEME: Theme = 'default';
|
||||
const defaultTokens = getMergedTokens(DEFAULT_THEME);
|
||||
|
||||
const initialState: ThemeStore = {
|
||||
|
||||
+4
-3
@@ -93,7 +93,10 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
||||
|
||||
useSaveDoc(doc.id, provider.document, isConnectedToCollabServer);
|
||||
const { i18n } = useTranslation();
|
||||
const lang = i18n.resolvedLanguage;
|
||||
let lang = i18n.resolvedLanguage;
|
||||
if (!lang || !(lang in locales)) {
|
||||
lang = 'en';
|
||||
}
|
||||
|
||||
const { uploadFile, errorAttachment } = useUploadFile(doc.id);
|
||||
|
||||
@@ -259,7 +262,6 @@ export const BlockNoteReader = ({
|
||||
const { user } = useAuth();
|
||||
const { setEditor } = useEditorStore();
|
||||
const { threadStore } = useComments(docId, false, user);
|
||||
const { t } = useTranslation();
|
||||
const editor = useCreateBlockNote(
|
||||
{
|
||||
collaboration: {
|
||||
@@ -305,7 +307,6 @@ export const BlockNoteReader = ({
|
||||
editor={editor}
|
||||
editable={false}
|
||||
theme="light"
|
||||
aria-label={t('Document viewer')}
|
||||
formattingToolbar={false}
|
||||
slashMenu={false}
|
||||
comments={false}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Box, Loading } from '@/components';
|
||||
import { DocHeader } from '@/docs/doc-header/';
|
||||
import {
|
||||
Doc,
|
||||
useDocFocusManagement,
|
||||
useIsCollaborativeEditable,
|
||||
useProviderStore,
|
||||
} from '@/docs/doc-management';
|
||||
@@ -82,7 +83,9 @@ export const DocEditor = ({ doc }: DocEditorProps) => {
|
||||
const readOnly =
|
||||
!doc.abilities.partial_update || !isEditable || isLoading || isDeletedDoc;
|
||||
const { setIsSkeletonVisible } = useSkeletonStore();
|
||||
const isProviderReady = isReady && provider;
|
||||
const isProviderReady = Boolean(isReady && provider);
|
||||
|
||||
useDocFocusManagement(doc.id, isProviderReady);
|
||||
|
||||
useEffect(() => {
|
||||
if (isProviderReady) {
|
||||
|
||||
+7
-10
@@ -12,12 +12,9 @@ import { TFunction } from 'i18next';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { createGlobalStyle, css } from 'styled-components';
|
||||
|
||||
import { Box, BoxButton, Icon } from '@/components';
|
||||
import { Box, BoxButton, EmojiPicker, Icon, emojidata } from '@/components';
|
||||
|
||||
import { DocsBlockNoteEditor } from '../../types';
|
||||
import { EmojiPicker } from '../EmojiPicker';
|
||||
|
||||
import emojidata from './initEmojiCallout';
|
||||
|
||||
const CalloutBlockStyle = createGlobalStyle`
|
||||
.bn-block-content[data-content-type="callout"][data-background-color] {
|
||||
@@ -97,15 +94,15 @@ const CalloutComponent = ({
|
||||
`}
|
||||
>
|
||||
<CalloutBlockStyle />
|
||||
<Box $position="relative">
|
||||
<Box
|
||||
$position="relative"
|
||||
$css={css`
|
||||
align-self: start;
|
||||
`}
|
||||
>
|
||||
<BoxButton
|
||||
contentEditable={false}
|
||||
onClick={toggleEmojiPicker}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Escape' && openEmojiPicker) {
|
||||
setOpenEmojiPicker(false);
|
||||
}
|
||||
}}
|
||||
$css={css`
|
||||
font-size: 1.125rem;
|
||||
cursor: ${isEditable ? 'pointer' : 'default'};
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
export * from './AccessibleImageBlock';
|
||||
export * from './CalloutBlock';
|
||||
export { default as emojidata } from './initEmojiCallout';
|
||||
export * from './PdfBlock';
|
||||
export * from './UploadLoaderBlock';
|
||||
|
||||
+87
-34
@@ -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 <LinkSelected {...inlineContent.props} />;
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<LinkSelected
|
||||
docId={inlineContent.props.docId}
|
||||
title={inlineContent.props.title}
|
||||
isEditable={editor.isEditable}
|
||||
updateInlineContent={updateInlineContent}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
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<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
void router.push(url);
|
||||
void router.push(`/docs/${docId}/`);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
-1
@@ -247,7 +247,6 @@ export const SearchPage = ({
|
||||
{
|
||||
type: 'interlinkingLinkInline',
|
||||
props: {
|
||||
url: `/docs/${doc.id}`,
|
||||
docId: doc.id,
|
||||
title: doc.title || untitledDocument,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './BlockNoteEditor';
|
||||
export * from './DocEditor';
|
||||
export * from './EmojiPicker';
|
||||
export * from './custom-blocks/';
|
||||
|
||||
+10
-2
@@ -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}/`,
|
||||
});
|
||||
};
|
||||
|
||||
+9
-3
@@ -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}`,
|
||||
);
|
||||
};
|
||||
|
||||
+11
-3
@@ -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 (
|
||||
<Link
|
||||
src={window.location.origin + inline.props.url}
|
||||
src={window.location.origin + `/docs/${inline.props.docId}/`}
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
color: 'black',
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
<Image src={DocSelectedIcon.src} />{' '}
|
||||
<Text>{inline.props.title}</Text>{' '}
|
||||
{emoji || <Image src={DocSelectedIcon.src} />}{' '}
|
||||
<Text>{titleWithoutEmoji}</Text>{' '}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
import { MouseEvent, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { BoxButton, BoxButtonType, Text, TextType } from '@/components';
|
||||
import { EmojiPicker, emojidata } from '@/docs/doc-editor/';
|
||||
import {
|
||||
Box,
|
||||
BoxButton,
|
||||
BoxButtonType,
|
||||
EmojiPicker,
|
||||
PICKER_HEIGHT,
|
||||
Text,
|
||||
TextType,
|
||||
emojidata,
|
||||
} from '@/components';
|
||||
|
||||
import { useDocTitleUpdate } from '../hooks/useDocTitleUpdate';
|
||||
import { cssSelectors } from '../utils';
|
||||
|
||||
const getClosestTreeItem = (element: HTMLElement | null) =>
|
||||
element?.closest<HTMLElement>(cssSelectors.DOC_TREE_ROW) ??
|
||||
element?.closest<HTMLElement>(cssSelectors.DOC_TREE_NODE) ??
|
||||
element?.closest<HTMLElement>('[role="treeitem"]') ??
|
||||
null;
|
||||
|
||||
type DocIconProps = TextType & {
|
||||
buttonProps?: BoxButtonType;
|
||||
@@ -30,6 +47,7 @@ export const DocIcon = ({
|
||||
...textProps
|
||||
}: DocIconProps) => {
|
||||
const { updateDocEmoji } = useDocTitleUpdate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const iconRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -43,6 +61,14 @@ export const DocIcon = ({
|
||||
return defaultIcon;
|
||||
}
|
||||
|
||||
const emojiLabel = withEmojiPicker
|
||||
? emoji
|
||||
? t('Edit document emoji')
|
||||
: t('Add emoji')
|
||||
: emoji
|
||||
? t('Document emoji')
|
||||
: undefined;
|
||||
|
||||
const toggleEmojiPicker = (e: MouseEvent) => {
|
||||
if (withEmojiPicker) {
|
||||
e.stopPropagation();
|
||||
@@ -50,9 +76,24 @@ export const DocIcon = ({
|
||||
|
||||
if (!openEmojiPicker && iconRef.current) {
|
||||
const rect = iconRef.current.getBoundingClientRect();
|
||||
|
||||
const pickerHeight = PICKER_HEIGHT;
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const spaceAbove = rect.top;
|
||||
|
||||
// Position picker above if not enough space below and enough space above
|
||||
const shouldPositionAbove =
|
||||
spaceBelow < pickerHeight && spaceAbove >= pickerHeight;
|
||||
|
||||
// Offset to align the picker properly
|
||||
const ROW_OFFSET_TOP = 55;
|
||||
const ROW_OFFSET_BOTTOM = 10;
|
||||
|
||||
setPickerPosition({
|
||||
top: rect.bottom + window.scrollY + 8,
|
||||
left: rect.left + window.scrollX,
|
||||
top: shouldPositionAbove
|
||||
? rect.top - pickerHeight + ROW_OFFSET_TOP
|
||||
: rect.bottom + ROW_OFFSET_BOTTOM,
|
||||
left: rect.left,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,6 +117,30 @@ export const DocIcon = ({
|
||||
setOpenEmojiPicker(false);
|
||||
};
|
||||
|
||||
const handleEscape = () => {
|
||||
setOpenEmojiPicker(false);
|
||||
window.requestAnimationFrame(() => {
|
||||
const localTreeItem = getClosestTreeItem(iconRef.current);
|
||||
const docTree = document.querySelector<HTMLElement>(
|
||||
cssSelectors.DOC_TREE,
|
||||
);
|
||||
const docTreeItem =
|
||||
localTreeItem ||
|
||||
docTree?.querySelector<HTMLElement>(
|
||||
cssSelectors.DOC_TREE_FOCUSED_NODE,
|
||||
) ||
|
||||
docTree?.querySelector<HTMLElement>(
|
||||
cssSelectors.DOC_TREE_SELECTED_ROW,
|
||||
) ||
|
||||
docTree?.querySelector<HTMLElement>(
|
||||
cssSelectors.DOC_TREE_SELECTED_NODE,
|
||||
) ||
|
||||
document.querySelector<HTMLElement>(cssSelectors.DOC_TREE_ROOT);
|
||||
|
||||
docTreeItem?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BoxButton
|
||||
@@ -83,6 +148,8 @@ export const DocIcon = ({
|
||||
ref={iconRef}
|
||||
onClick={toggleEmojiPicker}
|
||||
color="tertiary-text"
|
||||
aria-label={emojiLabel}
|
||||
title={emojiLabel}
|
||||
{...buttonProps}
|
||||
>
|
||||
{!emoji ? (
|
||||
@@ -102,21 +169,22 @@ export const DocIcon = ({
|
||||
</BoxButton>
|
||||
{openEmojiPicker &&
|
||||
createPortal(
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: pickerPosition.top,
|
||||
left: pickerPosition.left,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
<Box
|
||||
$position="fixed"
|
||||
$css={css`
|
||||
top: ${pickerPosition.top}px;
|
||||
left: ${pickerPosition.left}px;
|
||||
z-index: 1000;
|
||||
`}
|
||||
>
|
||||
<EmojiPicker
|
||||
emojiData={emojidata}
|
||||
onEmojiSelect={handleEmojiSelect}
|
||||
onClickOutside={handleClickOutside}
|
||||
withOverlay={true}
|
||||
onEscape={handleEscape}
|
||||
/>
|
||||
</div>,
|
||||
</Box>,
|
||||
document.body,
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './useCollaboration';
|
||||
export * from './useCopyDocLink';
|
||||
export * from './useCreateChildDocTree';
|
||||
export * from './useDocFocusManagement';
|
||||
export * from './useDocTitleUpdate';
|
||||
export * from './useDocUtils';
|
||||
export * from './useIsCollaborativeEditable';
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { cssSelectors } from '@/docs/doc-management/utils';
|
||||
|
||||
const isWithin = (el: Element | null, selector: string) =>
|
||||
!!el?.closest(selector);
|
||||
|
||||
export const useDocFocusManagement = (docId?: string, isReady = true) => {
|
||||
// 1) Auto-focus title when opening a doc
|
||||
useEffect(() => {
|
||||
if (!docId || !isReady || typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const frameId = window.requestAnimationFrame(() => {
|
||||
const titleElement = document.querySelector<HTMLElement>(
|
||||
cssSelectors.DOC_TITLE,
|
||||
);
|
||||
if (!titleElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid stealing focus if user is already in the doc tree or editor.
|
||||
const activeEl = document.activeElement;
|
||||
const active = activeEl instanceof Element ? activeEl : null;
|
||||
const isInDocUI =
|
||||
isWithin(active, cssSelectors.DOC_EDITOR_FOCUS) ||
|
||||
isWithin(active, cssSelectors.DOC_TREE);
|
||||
|
||||
const isBodyFocused = activeEl === document.body;
|
||||
|
||||
if (isBodyFocused && !isInDocUI && activeEl !== titleElement) {
|
||||
titleElement.focus();
|
||||
}
|
||||
});
|
||||
|
||||
return () => window.cancelAnimationFrame(frameId);
|
||||
}, [docId, isReady]);
|
||||
|
||||
// 2) Escape from editor/title -> focus back the selected tree item (or root)
|
||||
useEffect(() => {
|
||||
if (!docId || !isReady || typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleFocusShortcut = (event: KeyboardEvent) => {
|
||||
if (event.key !== 'F6' || event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = event.target instanceof Element ? event.target : null;
|
||||
const activeEl = document.activeElement;
|
||||
const active = activeEl instanceof Element ? activeEl : null;
|
||||
|
||||
const isDocFocus =
|
||||
isWithin(target, cssSelectors.DOC_EDITOR_FOCUS) ||
|
||||
isWithin(active, cssSelectors.DOC_EDITOR_FOCUS) ||
|
||||
isWithin(target, cssSelectors.DOC_TITLE) ||
|
||||
isWithin(active, cssSelectors.DOC_TITLE);
|
||||
|
||||
if (!isDocFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
const docTree = document.querySelector<HTMLElement>(
|
||||
cssSelectors.DOC_TREE,
|
||||
);
|
||||
|
||||
const docTreeItem =
|
||||
docTree?.querySelector<HTMLElement>(
|
||||
cssSelectors.DOC_TREE_SELECTED_ROW,
|
||||
) ||
|
||||
docTree?.querySelector<HTMLElement>(
|
||||
cssSelectors.DOC_TREE_SELECTED_NODE,
|
||||
) ||
|
||||
document.querySelector<HTMLElement>(cssSelectors.DOC_TREE_ROOT);
|
||||
|
||||
if (!docTreeItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
docTreeItem.focus();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleFocusShortcut, true);
|
||||
return () =>
|
||||
document.removeEventListener('keydown', handleFocusShortcut, true);
|
||||
}, [docId, isReady]);
|
||||
};
|
||||
@@ -38,3 +38,15 @@ export const getEmojiAndTitle = (title: string) => {
|
||||
|
||||
return { emoji: null, titleWithoutEmoji: title };
|
||||
};
|
||||
|
||||
export const cssSelectors = {
|
||||
DOC_TITLE: '.--docs--doc-title-input[contenteditable="true"]',
|
||||
DOC_TREE_ROOT: '[data-testid="doc-tree-root-item"]',
|
||||
DOC_TREE: '[data-testid="doc-tree"]',
|
||||
DOC_EDITOR_FOCUS: '.--docs--main-editor, .--docs--doc-title-input',
|
||||
DOC_TREE_ROW: '.c__tree-view--row',
|
||||
DOC_TREE_NODE: '.c__tree-view--node',
|
||||
DOC_TREE_FOCUSED_NODE: '.c__tree-view--node.isFocused',
|
||||
DOC_TREE_SELECTED_ROW: '.c__tree-view--row[aria-selected="true"]',
|
||||
DOC_TREE_SELECTED_NODE: '.c__tree-view--node[aria-selected="true"]',
|
||||
} as const;
|
||||
|
||||
@@ -199,7 +199,6 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
||||
<ButtonCloseModal
|
||||
aria-label={t('Close the share modal')}
|
||||
onClick={onClose}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
|
||||
@@ -128,6 +128,27 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleRowKeyDown = useCallback((e: React.KeyboardEvent) => {
|
||||
if (e.key !== 'Enter') {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = e.target as HTMLElement | null;
|
||||
if (
|
||||
!target ||
|
||||
!(
|
||||
target.classList.contains('c__tree-view--row') ||
|
||||
target.classList.contains('c__tree-view--node')
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.currentTarget
|
||||
.querySelector<HTMLDivElement>('.c__tree-view--node')
|
||||
?.click();
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* This effect is used to reset the tree when a new document
|
||||
* that is not part of the current tree is loaded.
|
||||
@@ -231,7 +252,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
|
||||
{/* Keyboard instructions for screen readers */}
|
||||
<Box id="doc-tree-keyboard-instructions" className="sr-only">
|
||||
{t(
|
||||
'Use arrow keys to navigate between documents. Press Enter to open a document. Press F2 to focus the emoji button when available, then press F2 again to access document actions.',
|
||||
'Use arrow keys to navigate between documents. Press Enter to open a document. Press F2 to focus the emoji button when available, then press F2 again to access document actions. Press F6 to return focus from the document to the tree.',
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
@@ -357,6 +378,9 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
|
||||
}}
|
||||
rootNodeId={treeContext.root.id}
|
||||
renderNode={DocSubPageItem}
|
||||
rowProps={{
|
||||
onKeyDown: handleRowKeyDown,
|
||||
}}
|
||||
/>
|
||||
</Overlayer>
|
||||
)}
|
||||
|
||||
@@ -12,8 +12,8 @@ import { useResponsiveStore } from '@/stores';
|
||||
import { HEADER_HEIGHT } from '../conf';
|
||||
|
||||
import { ButtonTogglePanel } from './ButtonTogglePanel';
|
||||
import { LaGaufre } from './LaGaufre';
|
||||
import { Title } from './Title';
|
||||
import { Waffle } from './Waffle';
|
||||
|
||||
export const Header = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -85,7 +85,7 @@ export const Header = () => {
|
||||
</StyledLink>
|
||||
{!isDesktop ? (
|
||||
<Box $direction="row" $gap={spacingsTokens['sm']}>
|
||||
<LaGaufre />
|
||||
<Waffle />
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
@@ -96,7 +96,7 @@ export const Header = () => {
|
||||
>
|
||||
<ButtonLogin />
|
||||
<LanguagePicker />
|
||||
<LaGaufre />
|
||||
<Waffle />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { Gaufre } from '@gouvfr-lasuite/integration';
|
||||
import '@gouvfr-lasuite/integration/dist/css/gaufre.css';
|
||||
import Script from 'next/script';
|
||||
import React from 'react';
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
const GaufreStyle = createGlobalStyle`
|
||||
.lasuite-gaufre-btn{
|
||||
box-shadow: inset 0 0 0 0 !important;
|
||||
border-radius: var(--c--components--button--border-radius) !important;
|
||||
transition: all var(--c--globals--transitions--duration) var(--c--globals--transitions--ease-out) !important;
|
||||
&:hover, &:focus-visible {
|
||||
background: var(--c--contextuals--background--semantic--contextual--primary) !important;
|
||||
}
|
||||
color: var(--c--contextuals--content--semantic--brand--tertiary) !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export const LaGaufre = () => {
|
||||
const { componentTokens } = useCunninghamTheme();
|
||||
|
||||
if (!componentTokens['la-gaufre']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src="https://integration.lasuite.numerique.gouv.fr/api/v1/gaufre.js"
|
||||
strategy="lazyOnload"
|
||||
id="lasuite-gaufre-script"
|
||||
/>
|
||||
<GaufreStyle />
|
||||
<Gaufre variant="small" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import { LaGaufreV2, LaGaufreV2Props } from '@gouvfr-lasuite/ui-kit';
|
||||
import React from 'react';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { useConfig } from '@/core';
|
||||
|
||||
type WaffleAPIType = {
|
||||
apiUrl: LaGaufreV2Props['apiUrl'];
|
||||
data?: never;
|
||||
};
|
||||
|
||||
type WaffleDataType = {
|
||||
apiUrl?: never;
|
||||
data?: LaGaufreV2Props['data'];
|
||||
};
|
||||
|
||||
export type WaffleType = Omit<
|
||||
LaGaufreV2Props,
|
||||
'apiUrl' | 'data' | 'widgetPath'
|
||||
> &
|
||||
(WaffleAPIType | WaffleDataType) & {
|
||||
widgetPath?: string;
|
||||
};
|
||||
|
||||
const LaGaufreV2Fixed = LaGaufreV2 as React.ComponentType<WaffleType>;
|
||||
|
||||
export const Waffle = () => {
|
||||
const { data: conf } = useConfig();
|
||||
|
||||
const waffleConfig = conf?.theme_customization?.waffle;
|
||||
|
||||
if (!waffleConfig?.apiUrl && !waffleConfig?.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
$css={css`
|
||||
& > div {
|
||||
display: flex;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<LaGaufreV2Fixed {...waffleConfig} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './ButtonTogglePanel';
|
||||
export * from './Header';
|
||||
export * from './LaGaufre';
|
||||
export * from './Waffle';
|
||||
export * from './Title';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Box } from '@/components';
|
||||
import { useConfig } from '@/core';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { ButtonTogglePanel, Title } from '@/features/header/';
|
||||
import { LaGaufre } from '@/features/header/components/LaGaufre';
|
||||
import { Waffle } from '@/features/header/components/Waffle';
|
||||
import { LanguagePicker } from '@/features/language';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
@@ -81,7 +81,7 @@ export const HomeHeader = () => {
|
||||
{!isSmallMobile && (
|
||||
<Box $direction="row" $gap="1rem" $align="center">
|
||||
<LanguagePicker />
|
||||
<LaGaufre />
|
||||
<Waffle />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -72,7 +72,14 @@ export const ResizableLeftPanel = ({
|
||||
<Panel
|
||||
ref={ref}
|
||||
order={0}
|
||||
defaultSize={isDesktop ? panelSizePercent : 0}
|
||||
defaultSize={
|
||||
isDesktop
|
||||
? Math.max(
|
||||
minPanelSizePercent,
|
||||
Math.min(panelSizePercent, maxPanelSizePercent),
|
||||
)
|
||||
: 0
|
||||
}
|
||||
minSize={isDesktop ? minPanelSizePercent : 0}
|
||||
maxSize={isDesktop ? maxPanelSizePercent : 0}
|
||||
onResize={handleResize}
|
||||
|
||||
@@ -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": "Люди з доступом через батьківський документ",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "impress",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"private": true,
|
||||
"repository": "https://github.com/suitenumerique/docs",
|
||||
"author": "DINUM",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -139,5 +139,30 @@
|
||||
"src": "/assets/icon-docs.svg",
|
||||
"width": "32px"
|
||||
}
|
||||
},
|
||||
"waffle": {
|
||||
"data": {
|
||||
"services": [
|
||||
{
|
||||
"name": "Docs",
|
||||
"url": "https://docs.numerique.gouv.fr/",
|
||||
"maturity": "stable",
|
||||
"logo": "https://lasuite.numerique.gouv.fr/assets/products/docs.svg"
|
||||
},
|
||||
{
|
||||
"name": "Visio",
|
||||
"url": "https://visio.numerique.gouv.fr/",
|
||||
"maturity": "stable",
|
||||
"logo": "https://lasuite.numerique.gouv.fr/assets/products/visio.svg"
|
||||
},
|
||||
{
|
||||
"name": "Fichiers",
|
||||
"url": "https://fichiers.numerique.gouv.fr/",
|
||||
"maturity": "stable",
|
||||
"logo": "https://lasuite.numerique.gouv.fr/assets/products/fichiers.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"showMoreLimit": 9
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
type: application
|
||||
name: docs
|
||||
version: 4.3.0
|
||||
version: 4.4.0
|
||||
appVersion: latest
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user