Compare commits

..

33 Commits

Author SHA1 Message Date
Anthony LC 167375231b 🔖(minor) release 4.7.0
Added:
- (helm) allow all keys in configMap as env var

Changed:
- 📝(docs) improve README and add documentation hub
- ️(frontend) restore focus to triggers after closing menus and modals
- 🚸(frontend) change position elements toolbar
- ️(frontend) add focus on open to modals

Fixed:
- 🐛(frontend) analytic feature flags problem
- 🐛(frontend) fix home collapsing panel
- 🐛(frontend) fix disabled color on icon Dropdown
- 🐛(frontend) fix zIndex table of content
- 🐛(frontend) fix bug when language not supported by BN
- 🐛 (backend) prevent privileged users from requesting access
2026-03-10 10:25:11 +01:00
Anthony LC c17fb3e6cc 🩹(frontend) fix small ui issue
- Better position of close icon modal
- Fix some z-index issues with the floating bar
- Fix translation not made
2026-03-10 10:25:11 +01:00
AntoLC 1be89180fe 🌐(i18n) update translated strings
Update translated files with new translations
2026-03-09 18:27:30 +01:00
Manuel Raynaud 6a3b33ec32 ⬇️(backend) downgrade pydantic-ai-slim to version 1.58.0
We upgrade pydantic-ai-slim to version 1.63.0, we have an error when
Docs is run in preprod environment.
We have to investigate why and we decided to downgrade to a working
version
2026-03-09 18:27:30 +01:00
Anthony LC 29f2c2ebdf 💄(frontend) fix flickering tree title
The title was flickering because the icons were
loading and pushing the div to the left. We do not
need the icons part if we are not hovering the tree
item, so we can hide it until we hover the item.
2026-03-09 14:43:38 +01:00
Anthony LC 9d320092df ⬆️(frontend) bump blocknote to 0.47.1
Bump BlockNote to 0.47.1 to fix the issues with
the ai selections.
2026-03-09 14:43:38 +01:00
Anthony LC 77535b0292 🚸(frontend) change position elements toolbar
The AI and comment toolbar buttons are now
positioned at the end of the toolbar.
They were taking too much space on the left
which was not ideal for user experience.
2026-03-09 14:43:38 +01:00
Anthony LC 770c22b1a6 🐛(frontend) fix bug when language not supported by BN
When a language was not supported by BlockNote,
the editor would crash. This commit adds a default
locale fallback to prevent the crash and ensure
the editor remains functional even when the
user's language is not supported.
2026-03-09 14:43:37 +01:00
Anthony LC 3c980512be 🐛(frontend) improve position ai menu elements
The right part of the ai menu was not correctly
positioned, this commit fixes this issue.
2026-03-09 14:43:37 +01:00
Anthony LC 76cb6d66a4 🐛(frontend) fix panel collapse handle
When handling to the left, the panel could totally
collapse, and do beeing unsync with the state of
the left panel (open or closed).
We now keep the collapsing deactivate until
the state of the left panel is closing.
2026-03-09 14:03:57 +01:00
Anthony LC 6cef5ff2a0 🐛(frontend) fix buttons closing panel
When clicking on the home button, the left panel was
collapsing, it is a behavior that we want only when
mobile. We improved the logic to only collapse the
panel when we are on mobile devices.
2026-03-09 14:03:57 +01:00
Manuel Raynaud d816234839 ♻️(backend) change treebeard NumConv useage
In version 5 of django-treebeard they change the usage of NumConv for
something easier. They also introduce a breaking change, we have to
change the usage of this class in the migration 15.
2026-03-09 12:10:53 +01:00
renovate[bot] 5dd66f0cdc ⬆️(dependencies) update python dependencies 2026-03-09 12:10:52 +01:00
dtinth-ampere-devbox 0a4052d023 ♻️(frontend) replace hardcoded colors with contextual tokens
Replaced hardcoded global color tokens
(gray-000, gray-100, gray-600, white, gainsboro)
with Cunningham contextual tokens to enable proper
dark mode support.

Components updated:
- LeftPanel: background colors
- DropButton: popover background
- DropdownMenu: menu item background
- DocEditor: editor container background
- DocEditor styles: BlockNote CSS variables,
  inline code, links
- TableContent: panel and heading backgrounds

This allows the UI to automatically adapt between
light and dark themes.

Signed-off-by: dtinth-ampere-devbox <dtinth@spacet.me>
Signed-off-by: dtinth on MBP M1 <dtinth@spacet.me>
2026-03-09 11:21:40 +01:00
Anthony LC 189594c839 🐛(frontend) fix zIndex table of content
When we open a modal, the table of content is
above the modal, which is not the expected behavior.
This commit fixes the zIndex of the table of
content to be below the modal.
2026-03-09 10:31:47 +01:00
Mohamed El Amine BOUKERFA ca286b6de7 🐛(backend) prevent privileged users from requesting access
Privileged users of a document (owners and admins) should not be
allowed to create an access request on a document they already control.
Without aguardrail, they could inadvertently inflate the access
request queue with redundant entries.

More critically, if an owner submits an access request on their own
document, another admin could accept it and grant them a lower role
(e.g. reader), which would silently strip them of their ownership.

Signed-off-by: Mohamed El Amine BOUKERFA <boukerfa.ma@gmail.com>
2026-03-09 10:07:23 +01:00
Anthony LC 6062d0e9c4 🐛(frontend) fix disabled color on icon Dropdown
When the item was disabled, the icon color was not
updated to the disabled color. This commit fixes
this issue by applying the disabled color
to the icon when the item is disabled.
2026-03-06 15:06:00 +01:00
Anthony LC a51b34a04e 🐛(frontend) analytic feature flags problem
When multiple analytics are registered, if one
analytic has the flag at true, the feature was activated,
even if another analytic had it at false.
We change the logic to require all analytics to
have the flag at true for the feature to be activated.
2026-03-06 14:40:14 +01:00
Johannes Kastl f294a8e5a3 (helm) allow all keys in configMap as env var in backend jobs
Add `envFrom` support to backend jobs and cronjobs
(`backend_job.yml`, `backend_job_createsuperuser.yaml`,
`backend_job_migrate.yaml`, `backend_cronjob_list.yaml`),
mirroring the `envFrom` support already added to the deployments.

This allows injecting all keys from a ConfigMap or Secret as
environment variables via `backend.envFrom` in the Helm values.

Signed-off-by: Johannes Kastl <git@johannes-kastl.de>
2026-03-06 12:31:54 +01:00
Johannes Kastl b4591cda10 (helm) allow keys in configMap or secret as env var
The current envVars construct only allows single
keys in a configMap or secret to be used as
environment variables.
This commits keeps this functionality for
backwards compatibility, but adds another
envFrom variable that allows using all keys in a
secret or configMap as environment variables.

Signed-off-by: Johannes Kastl <git@johannes-kastl.de>
2026-03-06 12:29:01 +01:00
Johannes Kastl 301bf43cb7 🐛(helm) only use env: if envVars is defined
Previously, the `env:` key was always rendered in
Kubernetes manifests even when no environment
variables were defined, resulting in an empty
`env:` block. This wraps the entire `env:`
section in a conditional so it is only included
when `envVars` is actually set.

Signed-off-by: Johannes Kastl <git@johannes-kastl.de>
2026-03-06 12:24:12 +01:00
Johannes Kastl f155e9217e (helm) allow mounting secrets via extraVolumes
The chart already allows mounting configMaps. This commits adds
the functionality to allow mounting secrets.

Signed-off-by: Johannes Kastl <git@johannes-kastl.de>
2026-03-06 11:32:52 +01:00
Cyril 09fb9671e4 ️(frontend) restore focus on docIcon after picker
Use focus store to return focus after emoji picker close.
2026-03-06 10:45:01 +01:00
Cyril 4c0c1f423e ️(frontend) restore focus in docToolBox after modals
Use focus store to return focus to toolbar buttons.
2026-03-06 10:44:55 +01:00
Cyril 83fe903587 ️(frontend) restore focus in docsGrid after modals
Use focus store to restore focus to triggers in docsGrid.
2026-03-06 10:44:49 +01:00
Cyril 200b975c6d ️(frontend) restore focus in docTree after actions
Improve F2 shortcut handling in DocTree and DocSubPageItem.
2026-03-06 10:44:42 +01:00
Cyril 9536227c52 ️(frontend) add focus restore store
Create Zustand store to track last focused element
and restore focus after overlay close. DropdownMenu
auto-stores trigger ref when an option is clicked.
2026-03-06 10:44:27 +01:00
networkException fb4c502c75 🐛(backend) properly encode attachment upload content-disposition header
This patch changes the Content-Disposition header set on S3 PutObject
requests when uploading attachments to be encoded using a Django helper,
properly encoding UTF-8 in filenames.

This fixes compatibility for uploading attachments with non ASCII file names
to Garage, since the HTTP library used by Garage validates that HTTP Headers
are ASCII only.

Signed-off-by: networkException <git@nwex.de>
2026-03-06 10:13:24 +01:00
Stephan Meijer 77aee5652a ⬆️(ci) upgrade GitHub Actions workflow steps to latest versions
Update all GitHub Actions to their latest major versions for improved
performance, security patches, and Node.js runtime compatibility.

Signed-off-by: Stephan Meijer <me@stephanmeijer.com>
2026-03-06 09:51:18 +01:00
Manuel Raynaud 7cceffff13 🔥(backend) remove rust from the back-builder Dockerfile stage
Rust was installed to build the pycrdt wheel. There is no need anymore
to install rust, a wheel is available for this package.
2026-03-05 21:38:05 +01:00
Manuel Raynaud a028df54ce ⬆️(backend) upgrade pycrdt to version 0.12.47
This version has a wheel for musl linux platform, it should speed up the
build of the docker images
2026-03-05 21:38:05 +01:00
Cyril 25cf11c90f ️(frontend) add focus on open to modals
Use autoFocus on primary button when opening modals.

Made-with: Cursor
2026-03-05 17:39:02 +01:00
pav d1a3519646 📝(docs) improve readme and add documentation hub (#1870)
## Purpose

This pull request improves the project’s documentation entry points and
overall readability to make Docs more approachable for new users and
contributors.

While reviewing the repository, I noticed that the project highlights
documentation and Markdown support, but the front-page README contained
several Markdown syntax issues and inconsistencies. This made the
landing experience feel less polished than the quality of the project
itself. The goal of this change is to provide a cleaner, more
consistent, and more professional first impression.

Please let me know and I can apply any changes, or edit other .md files
as needed!

## Proposal

- Rewrite the root README to be tighter, easier to scan, and more
user-facing
- Add a documentation landing page at `/docs/README.md` with a
structured table of contents
- Introduce `docs/instances.md` to list public Docs instances  

## External contributions

Thank you for your contribution! 🎉  

Please ensure the following items are checked before submitting your
pull request:

- [x] I have read and followed the [contributing
guidelines](https://github.com/suitenumerique/docs/blob/main/CONTRIBUTING.md)
- [x] I have read and agreed to the [Code of
Conduct](https://github.com/suitenumerique/docs/blob/main/CODE_OF_CONDUCT.md)
- [x] I have signed off my commits with `git commit --signoff` (DCO
compliance)
- [x] I have signed my commits with my SSH or GPG key (`git commit -S`)
- [x] My commit messages follow the required format: `<gitmoji>(type)
title description`
- [x] I have added a changelog entry under `## [Unreleased]` section  
- [x] I have not added tests because this PR only contains documentation
changes

---------

Signed-off-by: actuallypav <61046893+actuallypav@users.noreply.github.com>
2026-03-05 14:55:35 +00:00
102 changed files with 1226 additions and 606 deletions
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Create empty source files
run: |
touch src/backend/locale/django.pot
@@ -48,7 +48,7 @@ jobs:
CROWDIN_BASE_PATH: "../src/"
# frontend i18n
- name: Restore the frontend cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
+4 -4
View File
@@ -20,10 +20,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
# Backend i18n
- name: Install Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.13.3"
cache: "pip"
@@ -33,7 +33,7 @@ jobs:
run: pip install --user .
working-directory: src/backend
- name: Restore the mail templates
uses: actions/cache@v4
uses: actions/cache@v5
id: mail-templates
with:
path: "src/backend/core/templates/mail"
@@ -49,7 +49,7 @@ jobs:
DJANGO_CONFIGURATION=Build python manage.py makemessages -a --keep-pot
# frontend i18n
- name: Restore the frontend cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
+8 -8
View File
@@ -20,16 +20,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Restore the frontend cache
uses: actions/cache@v4
uses: actions/cache@v5
id: front-node_modules
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
- name: Setup Node.js
if: steps.front-node_modules.outputs.cache-hit != 'true'
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node_version }}
- name: Install dependencies
@@ -37,7 +37,7 @@ jobs:
run: cd src/frontend/ && yarn install --frozen-lockfile
- name: Cache install frontend
if: steps.front-node_modules.outputs.cache-hit != 'true'
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
@@ -50,10 +50,10 @@ jobs:
working-directory: src/mail
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Restore the mail templates
uses: actions/cache@v4
uses: actions/cache@v5
id: mail-templates
with:
path: "src/backend/core/templates/mail"
@@ -61,7 +61,7 @@ jobs:
- name: Setup Node.js
if: steps.mail-templates.outputs.cache-hit != 'true'
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node_version }}
@@ -79,7 +79,7 @@ jobs:
- name: Cache mail templates
if: steps.mail-templates.outputs.cache-hit != 'true'
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: "src/backend/core/templates/mail"
key: mail-templates-${{ hashFiles('src/mail/mjml') }}
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
+3 -3
View File
@@ -22,7 +22,7 @@ jobs:
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
@@ -68,7 +68,7 @@ jobs:
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
@@ -117,7 +117,7 @@ jobs:
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Helmfile lint
shell: bash
+19 -19
View File
@@ -23,15 +23,15 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22.x"
- name: Restore the frontend cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
@@ -47,14 +47,14 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22.x"
- name: Restore the frontend cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
@@ -69,15 +69,15 @@ jobs:
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22.x"
- name: Restore the frontend cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
@@ -111,15 +111,15 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22.x"
- name: Restore the frontend cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
@@ -157,11 +157,11 @@ jobs:
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Detect relevant changes
id: changes
uses: dorny/paths-filter@v2
uses: dorny/paths-filter@v3
with:
filters: |
lock:
@@ -170,7 +170,7 @@ jobs:
- 'src/frontend/apps/impress/**'
- name: Restore the frontend cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
@@ -178,7 +178,7 @@ jobs:
- name: Setup Node.js
if: steps.changes.outputs.lock == 'true' || steps.changes.outputs.app == 'true'
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22.x"
@@ -205,14 +205,14 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22.x"
- name: Restore the frontend cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
+9 -9
View File
@@ -19,7 +19,7 @@ jobs:
if: github.event_name == 'pull_request' # Makes sense only for pull requests
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: show
@@ -46,7 +46,7 @@ jobs:
github.event_name == 'pull_request'
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 50
- name: Check that the CHANGELOG has been modified in the current branch
@@ -56,7 +56,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Check CHANGELOG max line length
run: |
max_line_length=$(cat CHANGELOG.md | grep -Ev "^\[.*\]: https://github.com" | wc -L)
@@ -70,7 +70,7 @@ jobs:
if: github.event_name == 'pull_request'
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install codespell
run: pip install --user codespell
- name: Check for typos
@@ -92,9 +92,9 @@ jobs:
working-directory: src/backend
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.13.3"
cache: "pip"
@@ -146,7 +146,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Create writable /data
run: |
@@ -154,7 +154,7 @@ jobs:
sudo mkdir -p /data/static
- name: Restore the mail templates
uses: actions/cache@v4
uses: actions/cache@v5
id: mail-templates
with:
path: "src/backend/core/templates/mail"
@@ -190,7 +190,7 @@ jobs:
mc version enable impress/impress-media-storage"
- name: Install Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.13.3"
cache: "pip"
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
+25 -1
View File
@@ -6,6 +6,29 @@ and this project adheres to
## [Unreleased]
## [v4.7.0] - 2026-03-09
### Added
- ✨(helm) allow all keys in configMap as env var #1872
### Changed
- 📝(docs) improve README and add documentation hub #1870
- ♿️(frontend) restore focus to triggers after closing menus and modals #1863
- 🚸(frontend) change position elements toolbar #1957
- ♿️(frontend) add focus on open to modals #1948
### Fixed
- 🐛(frontend) analytic feature flags problem #1953
- 🐛(frontend) fix home collapsing panel #1954
- 🐛(frontend) fix disabled color on icon Dropdown #1950
- 🐛(frontend) fix zIndex table of content #1949
- 🐛(frontend) fix bug when language not supported by BN #1957
- 🐛 (backend) prevent privileged users from requesting access #1898
## [v4.6.0] - 2026-03-03
### Added
@@ -1060,7 +1083,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.6.0...main
[unreleased]: https://github.com/suitenumerique/docs/compare/v4.7.0...main
[v4.7.0]: https://github.com/suitenumerique/docs/releases/v4.7.0
[v4.6.0]: https://github.com/suitenumerique/docs/releases/v4.6.0
[v4.5.0]: https://github.com/suitenumerique/docs/releases/v4.5.0
[v4.4.0]: https://github.com/suitenumerique/docs/releases/v4.4.0
-7
View File
@@ -14,13 +14,6 @@ FROM base AS back-builder
WORKDIR /builder
# Install Rust and Cargo using Alpine's package manager
RUN apk add --no-cache \
build-base \
libffi-dev \
rust \
cargo
# Copy required python dependencies
COPY ./src/backend /builder
+148 -136
View File
@@ -3,226 +3,238 @@
<img alt="Docs" src="/docs/assets/banner-docs.png" width="100%" />
</a>
</p>
<p align="center">
<a href="https://github.com/suitenumerique/docs/stargazers/">
<img src="https://img.shields.io/github/stars/suitenumerique/docs" alt="">
</a>
<a href='https://github.com/suitenumerique/docs/blob/main/CONTRIBUTING.md'><img alt='PRs Welcome' src='https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=shields'/></a>
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/suitenumerique/docs"/>
<img alt="GitHub closed issues" src="https://img.shields.io/github/issues-closed/suitenumerique/docs"/>
<a href="https://github.com/suitenumerique/docs/blob/main/CONTRIBUTING.md">
<img alt="PRs Welcome" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg"/>
</a>
<a href="https://github.com/suitenumerique/docs/blob/main/LICENSE">
<img alt="MIT License" src="https://img.shields.io/github/license/suitenumerique/docs"/>
</a>
</p>
<p align="center">
<a href="https://matrix.to/#/#docs-official:matrix.org">
Chat on Matrix
</a> - <a href="/docs/">
Documentation
</a> - <a href="#getting-started-">
Getting started
</a> - <a href="mailto:docs@numerique.gouv.fr">
Reach out
</a>
</p>
# La Suite Docs : Collaborative Text Editing
Docs, where your notes can become knowledge through live collaboration.
<p align="center">
<a href="https://matrix.to/#/#docs-official:matrix.org">Chat on Matrix</a> •
<a href="/docs/">Documentation</a> •
<a href="#try-docs">Try Docs</a> •
<a href="mailto:docs@numerique.gouv.fr">Contact us</a>
</p>
<img src="/docs/assets/docs_live_collaboration_light.gif" width="100%" align="center"/>
# La Suite Docs: Collaborative Text Editing
## Why use Docs ❓
Docs is a collaborative text editor designed to address common challenges in knowledge building and sharing.
**Docs, where your notes can become knowledge through live collaboration.**
### Write
* 😌 Get simple, accessible online editing for your team.
* 💅 Create clean documents with beautiful formatting options.
* 🖌️ Focus on your content using either the in-line editor, or [the Markdown syntax](https://www.markdownguide.org/basic-syntax/).
* 🧱 Quickly design your page thanks to the many block types, accessible from the `/` slash commands, as well as keyboard shortcuts.
* 🔌 Write offline! Your edits will be synced once you're back online.
* ✨ Save time thanks to our AI actions, such as rephrasing, summarizing, fixing typos, translating, etc. You can even turn your selected text into a prompt!
Docs is an open-source collaborative editor that helps teams write, organize, and share knowledge together - in real time.
### Work together
* 🤝 Enjoy live editing! See your team collaborate in real time.
* 🔒 Keep your information secure thanks to granular access control. Only share with the right people.
* 📑 Export your content in multiple formats (`.odt`, `.docx`, `.pdf`) with customizable templates.
* 📚 Turn your team's collaborative work into organized knowledge with Subpages.
![Live collaboration demo](/docs/assets/docs_live_collaboration_light.gif)
### Self-host
#### 🚀 Docs is easy to install on your own servers
We use Kubernetes for our [production instance](https://docs.numerique.gouv.fr/) but also support Docker Compose. The community contributed a couple other methods (Nix, YunoHost etc.) check out the [docs](/docs/installation/README.md) to get detailed instructions and examples.
## What is Docs?
#### 🌍 Known instances
We hope to see many more, here is an incomplete list of public Docs instances. Feel free to make a PR to add ones that are not listed below🙏
Docs is an open-source alternative to tools like Notion or Google Docs, focused on:
| Url | Org | Public |
| --- | --- | ------- |
| [docs.numerique.gouv.fr](https://docs.numerique.gouv.fr/) | DINUM | French public agents working for the central administration and the extended public sphere. ProConnect is required to login in or sign up|
| [docs.suite.anct.gouv.fr](https://docs.suite.anct.gouv.fr/) | ANCT | French public agents working for the territorial administration and the extended public sphere. ProConnect is required to login in or sign up|
| [notes.demo.opendesk.eu](https://notes.demo.opendesk.eu) | ZenDiS | Demo instance of OpenDesk. Request access to get credentials |
| [notes.liiib.re](https://notes.liiib.re/) | lasuite.coop | Free and open demo to all. Content and accounts are reset after one month |
| [docs.federated.nexus](https://docs.federated.nexus/) | federated.nexus | Public instance, but you have to [sign up for a Federated Nexus account](https://federated.nexus/register/). |
| [docs.demo.mosacloud.eu](https://docs.demo.mosacloud.eu/) | mosa.cloud | Demo instance of mosa.cloud, a dutch company providing services around La Suite apps. |
- Real-time collaboration
- Clean, structured documents
- Knowledge organization
- Data ownership & self-hosting
#### ⚠️ Advanced features
For some advanced features (ex: Export as PDF) Docs relies on XL packages from BlockNote. These are licenced under GPL and are not MIT compatible. You can perfectly use Docs without these packages by setting the environment variable `PUBLISH_AS_MIT` to true. That way you'll build an image of the application without the features that are not MIT compatible. Read the [environment variables documentation](/docs/env.md) for more information.
***Built for public organizations, companies, and open communities.***
## Getting started 🔧
## Why use Docs?
### Test it
### Writing
You can test Docs on your browser by visiting this [demo document](https://docs.la-suite.eu/docs/9137bbb5-3e8a-4ff7-8a36-fcc4e8bd57f4/)
- Rich-text & Markdown editing
- Slash commands & block system
- Beautiful formatting
- Offline editing
- Optional AI writing helpers (rewirite, summarize, translate, fix typos)
### Run Docs locally
### Collaboration
> ⚠️ The methods described below for running Docs locally is **for testing purposes only**. It is based on building Docs using [Minio](https://min.io/) as an S3-compatible storage solution. Of course you can choose any S3-compatible storage solution.
- Live cursors & presence
- Comments & sharing
- Granular access control
**Prerequisite**
### Knowledge management
Make sure you have a recent version of Docker and [Docker Compose](https://docs.docker.com/compose/install) installed on your laptop, then type:
- Subpages & hierarchy
- Searchable content
```shellscript
$ docker -v
### Export/Import & interoperability
Docker version 20.10.2, build 2291f61
- Import to `.docx` and `.md`
- Export to `.docx`, `.odt`, `.pdf`
$ docker compose version
## Try Docs
Docker Compose version v2.32.4
Experience Docs instantly - no installation required.
- 🔗 [Open a live demo document][demo]
- 🌍 [Browse public instances][instances]
[demo]: https://docs.la-suite.eu/docs/9137bbb5-3e8a-4ff7-8a36-fcc4e8bd57f4/
[instances]: /docs/instances.md
## Self-hosting
Docs supports Kubernetes, Docker Compose, and community-provided methods such as Nix and YunoHost.
Get started with self-hosting: [Installation guide](/docs/installation/README.md)
> [!WARNING]
> Some advanced features (for example: `Export as PDF`) rely on XL packages from Blocknote.
> These packages are licensed under GPL and are **not MIT-compatible**
>
> You can run Docs **without these packages** by building with:
>
> ```bash
> PUBLISH_AS_MIT=true
> ```
>
> This builds an image of Docs without non-MIT features.
>
> More details can be found in [environment variables](/docs/env.md)
## Local Development (for contributors)
Run Docs locally for development and testing.
> [!WARNING]
> This setup is intended **for development and testing only**.
> It uses Minio as an S3-compatible storage backend, but any S3-compatible service can be used.
### Prerequisites
- Docker
- Docker Compose
- GNU Make
Verify installation:
```bash
docker -v
docker compose version
```
> ⚠️ You may need to run the following commands with `sudo`, but this can be avoided by adding your user to the local `docker` group.
> If you encounounter permission errors, you may need to use `sudo`, or add your user to the `docker` group.
**Project bootstrap**
### Bootstrap the project
The easiest way to start working on the project is to use [GNU Make](https://www.gnu.org/software/make/):
The easiest way to start is using GNU Make:
```shellscript
$ make bootstrap FLUSH_ARGS='--no-input'
```bash
make bootstrap FLUSH_ARGS='--no-input'
```
This command builds the `app-dev` and `frontend-dev` containers, installs dependencies, performs database migrations and compiles translations. It's a good idea to use this command each time you are pulling code from the project repository to avoid dependency-related or migration-related issues.
This builds the `app-dev` and `fronted-dev` containers, installs dependencies, runs database migrations, and compiles translations.
Your Docker services should now be up and running 🎉
It is recommend to run this command after pulling new code.
You can access the project by going to <http://localhost:3000>.
You will be prompted to log in. The default credentials are:
Start services:
```bash
make run
```
Open <https://localhost:3000>
Default credentials (development only):
```md
username: impress
password: impress
```
📝 Note that if you need to run them afterwards, you can use the eponymous Make rule:
### Frontend development mode
```shellscript
$ make run
For frontend work, running outside Docker is often more convenient:
```bash
make frontend-development-install
make run-frontend-development
```
⚠️ For the frontend developer, it is often better to run the frontend in development mode locally.
### Backend only
To do so, install the frontend dependencies with the following command:
Starting all services except the frontend container:
```shellscript
$ make frontend-development-install
```bash
make run-backend
```
And run the frontend locally in development mode with the following command:
### Tests & Linting
```shellscript
$ make run-frontend-development
```bash
make frontend-test
make frontend-lint
```
To start all the services, except the frontend container, you can use the following command:
### Demo content
```shellscript
$ make run-backend
Create a basic demo site:
```bash
make demo
```
To execute frontend tests & linting only
```shellscript
$ make frontend-test
$ make frontend-lint
### More Make targets
To check all available Make rules:
```bash
make help
```
**Adding content**
### Django admin
You can create a basic demo site by running this command:
Create a superuser:
```shellscript
$ make demo
```bash
make superuser
```
Finally, you can check all available Make rules using this command:
Admin UI: <http://localhost:8071/admin>
```shellscript
$ make help
```
## Contributing
**Django admin**
This project is community-driven and PRs are welcome.
You can access the Django admin site at:
- [Contribution guide](CONTRIBUTING.md)
- [Translations](https://crowdin.com/project/lasuite-docs)
- [Chat with us!](https://matrix.to/#/#docs-official:matrix.org)
<http://localhost:8071/admin>.
## Roadmap
You first need to create a superuser account:
Curious where Docs is headed?
```shellscript
$ make superuser
```
## Feedback 🙋‍♂️🙋‍♀️
We'd love to hear your thoughts, and hear about your experiments, so come and say hi on [Matrix](https://matrix.to/#/#docs-official:matrix.org).
## Roadmap 💡
Want to know where the project is headed? [🗺️ Checkout our roadmap](https://github.com/orgs/numerique-gouv/projects/13/views/11)
Explore upcoming features, priorities and long-term direction on our [public roadmap](https://docs.numerique.gouv.fr/docs/d1d3788e-c619-41ff-abe8-2d079da2f084/).
## License 📝
This work is released under the MIT License (see [LICENSE](https://github.com/suitenumerique/docs/blob/main/LICENSE)).
While Docs is a public-driven initiative, our license choice is an invitation for private sector actors to use, sell and contribute to the project.
## Contributing 🙌
This project is intended to be community-driven, so please, do not hesitate to [get in touch](https://matrix.to/#/#docs-official:matrix.org) if you have any question related to our implementation or design decisions.
You can help us with translations on [Crowdin](https://crowdin.com/project/lasuite-docs).
If you intend to make pull requests, see [CONTRIBUTING](https://github.com/suitenumerique/docs/blob/main/CONTRIBUTING.md) for guidelines.
## Directory structure:
```markdown
docs
├── bin - executable scripts or binaries that are used for various tasks, such as setup scripts, utility scripts, or custom commands.
├── crowdin - for crowdin translations, a tool or service that helps manage translations for the project.
├── docker - Dockerfiles and related configuration files used to build Docker images for the project. These images can be used for development, testing, or production environments.
├── docs - documentation for the project, including user guides, API documentation, and other helpful resources.
├── env.d/development - environment-specific configuration files for the development environment. These files might include environment variables, configuration settings, or other setup files needed for development.
├── gitlint - configuration files for `gitlint`, a tool that enforces commit message guidelines to ensure consistency and quality in commit messages.
├── playground - experimental or temporary code, where developers can test new features or ideas without affecting the main codebase.
└── src - main source code directory, containing the core application code, libraries, and modules of the project.
```
While Docs is a public-driven initiative, our license choice is an invitation for private sector actors to use, sell and contribute to the project.
## Credits ❤️
### Stack
Docs is built on top of [Django Rest Framework](https://www.django-rest-framework.org/), [Next.js](https://nextjs.org/), [BlockNote.js](https://www.blocknotejs.org/), [HocusPocus](https://tiptap.dev/docs/hocuspocus/introduction) and [Yjs](https://yjs.dev/). We thank the contributors of all these projects for their awesome work!
Docs is built on top of [Django Rest Framework](https://www.django-rest-framework.org/), [Next.js](https://nextjs.org/), [ProseMirror](https://prosemirror.net/), [BlockNote.js](https://www.blocknotejs.org/), [HocusPocus](https://tiptap.dev/docs/hocuspocus/introduction), and [Yjs](https://yjs.dev/). We thank the contributors of all these projects for their awesome work!
We are proud sponsors of [BlockNotejs](https://www.blocknotejs.org/) and [Yjs](https://yjs.dev/).
We are proud sponsors of [BlockNotejs](https://www.blocknotejs.org/) and [Yjs](https://yjs.dev/).
---
### Gov ❤️ open source
Docs is the result of a joint effort led by the French 🇫🇷🥖 ([DINUM](https://www.numerique.gouv.fr/dinum/)) and German 🇩🇪🥨 governments ([ZenDiS](https://zendis.de/)).
We are always looking for new public partners (we are currently onboarding the Netherlands 🇳🇱🧀), feel free to [reach out](mailto:docs@numerique.gouv.fr) if you are interested in using or contributing to Docs.
Docs is the result of a joint initiative led by the French 🇫🇷 ([DINUM](https://www.numerique.gouv.fr/dinum/)) Government and German 🇩🇪 government ([ZenDiS](https://zendis.de/)).
We are always looking for new public partners (we are currently onboarding the Netherlands 🇳🇱), feel free to [contact us](mailto:docs@numerique.gouv.fr) if you are interested in using or contributing to Docs.
<p align="center">
<img src="/docs/assets/europe_opensource.png" width="50%"/>
<img src="/docs/assets/europe_opensource.png" width="50%"/ alt="Europe Opensource">
</p>
+39
View File
@@ -0,0 +1,39 @@
# Docs Documentation
Welcome to the official documentation for Docs.
This documentation is organized by topic and audience.
Use the section below to quickly find what you are looking for.
---
## Table of Contents
- Getting started
- [System requirements](system-requirements.md)
- [Installation overview](installation/README.md)
- [Docker Compose deployment](installation/compose.md)
- [Docker Compose examples](examples/compose/)
- [Kubernetes deployment](installation/kubernetes.md)
- [Helm values examples](examples/helm/)
- Configuration
- [Environment variables](env.md)
- [Customization](customization.md)
- [Language configuration](languages-configuration.md)
- [Search configuration](search.md)
- Architecture & design
- [Architecture overview](architecture.md)
- [Architectural Decision Records (ADR)](adr/)
- Usage & operations
- [Public instances](instances.md)
- [Releases & upgrades](release.md)
- [Troubleshooting](troubleshoot.md)
- Project & product
- [Roadmap](roadmap.md)
- Assets
- [Branding & visuals](assets/)
+77
View File
@@ -0,0 +1,77 @@
# 🌍 Public Docs Instances
This page lists known public instances of **Docs**.
These instances are operated by different organizations and may have different access policies.
If you run a public instance and would like it listed here, feel free to open a pull request.
---
## 🏛️ Public Organizations
### docs.numerique.gouv.fr
**Organization:** DINUM
**Audience:** French public agents working for central administration and extended public sphere
**Access:** ProConnect account required
<https://docs.numerique.gouv.fr/>
### docs.suite.anct.gouv.fr
**Organization:** ANCT
**Audience:** French public agents working for territorial administration and extended public sphere
**Access:** ProConnect account required
<https://docs.suite.anct.gouv.fr/>
### notes.demo.opendesk.eu
**Organization:** ZenDiS
**Type:** OpenDesk demo instance
**Access:** Request credentials
<https://notes.demo.opendesk.eu/>
---
## 🏢 Private Sector
### docs.demo.mosacloud.eu
**Organization:** mosa.cloud
**Type:** Demo instance
<https://docs.demo.mosacloud.eu/>
### notes.liiib.re
**Organization:** lasuite.coop
**Access:** Public demo
**Notes:** Content and accounts reset monthly
<https://notes.liiib.re/>
### notes.lasuite.coop
**Organization:** lasuite.coop
**Access:** Public
<https://notes.lasuite.coop/>
---
## 🤝 NGOs
### docs.federated.nexus
**Organization:** federated.nexus
**Access:** Public with account registration
<https://docs.federated.nexus/>
---
## Add your instance
To add your instance:
1. Fork the repository
2. Edit `docs/instances.md`
3. Add your instance following the existing format
4. Open a pull request
Thank you for helping grow the Docs ecosystem ❤️
+6
View File
@@ -37,6 +37,12 @@
"matchPackageNames": ["celery"],
"allowedVersions": "<5.6.0"
},
{
"groupName": "allowed pydantic-ai-slim versions",
"matchManagers": ["pep621"],
"matchPackageNames": ["pydantic-ai-slim"],
"allowedVersions": "<1.59.0"
},
{
"enabled": false,
"groupName": "ignored js dependencies",
+17 -2
View File
@@ -27,6 +27,7 @@ from django.urls import reverse
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.http import content_disposition_header
from django.utils.text import capfirst, slugify
from django.utils.translation import gettext_lazy as _
@@ -1661,11 +1662,19 @@ class DocumentViewSet(
or serializer.validated_data["is_unsafe"]
):
extra_args.update(
{"ContentDisposition": f'attachment; filename="{file_name:s}"'}
{
"ContentDisposition": content_disposition_header(
as_attachment=True, filename=file_name
)
}
)
else:
extra_args.update(
{"ContentDisposition": f'inline; filename="{file_name:s}"'}
{
"ContentDisposition": content_disposition_header(
as_attachment=False, filename=file_name
)
}
)
file = serializer.validated_data["file"]
@@ -2514,6 +2523,12 @@ class DocumentAskForAccessViewSet(
"""Create a document ask for access resource."""
document = self.get_document_or_404()
if document.get_role(request.user) in models.PRIVILEGED_ROLES:
return drf.response.Response(
{"detail": "You already have privileged access to this document."},
status=drf.status.HTTP_400_BAD_REQUEST,
)
serializer = serializers.DocumentAskForAccessCreateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
@@ -22,7 +22,7 @@ def set_path_on_existing_documents(apps, schema_editor):
# Iterate over all existing documents and make them root nodes
documents = Document.objects.order_by("created_at").values_list("id", flat=True)
numconv = NumConv(len(ALPHABET), ALPHABET)
numconv = NumConv(ALPHABET)
updates = []
for i, pk in enumerate(documents):
@@ -9,6 +9,7 @@ import pytest
from rest_framework.test import APIClient
from core.api.serializers import UserSerializer
from core.choices import PRIVILEGED_ROLES
from core.factories import (
DocumentAskForAccessFactory,
DocumentFactory,
@@ -199,6 +200,27 @@ def test_api_documents_ask_for_access_create_authenticated_already_has_ask_for_a
assert response.json() == {"detail": "You already ask to access to this document."}
@pytest.mark.parametrize("role", PRIVILEGED_ROLES)
def test_api_documents_ask_for_access_create_authenticated_already_has_privileged_access(
role,
):
"""
Authenticated users with privileged access (owner or admin) should not be able to
create a document ask for access.
"""
user = UserFactory()
document = DocumentFactory(users=[(user, role)])
client = APIClient()
client.force_login(user)
response = client.post(f"/api/v1.0/documents/{document.id}/ask-for-access/")
assert response.status_code == 400
assert response.json() == {
"detail": "You already have privileged access to this document."
}
## List
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: Breton\n"
"Language: br_FR\n"
@@ -75,7 +75,7 @@ msgstr "Ar vaezienn-mañ a zo rekis."
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr ""
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, 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: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -75,7 +75,7 @@ msgstr ""
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr ""
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, 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: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: English\n"
"Language: en_US\n"
@@ -75,7 +75,7 @@ msgstr ""
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr ""
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, 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: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
@@ -75,7 +75,7 @@ msgstr ""
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr ""
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, 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: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -75,7 +75,7 @@ msgstr "Ce champ est obligatoire."
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr "La portée du lien '%(link_reach)s' n'est pas autorisée en fonction de la configuration du document parent."
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, 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: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Language: it_IT\n"
@@ -75,7 +75,7 @@ msgstr ""
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr ""
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, 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: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Language: nl_NL\n"
@@ -75,7 +75,7 @@ msgstr "Dit veld is verplicht."
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr "Link bereik '%(link_reach)s' is niet toegestaan op basis van bovenliggende documentconfiguratie."
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, python-brace-format
msgid "copy of {title}"
msgstr "kopie van {title}"
@@ -516,7 +516,7 @@ msgstr "Deze email is al geassocieerd met een geregistreerde gebruiker."
#: build/lib/impress/settings.py:702 impress/settings.py:702
msgid "Docs AI"
msgstr ""
msgstr "Docs AI"
#: core/templates/mail/html/template.html:153
#: core/templates/mail/text/template.txt:3
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Language: pt_PT\n"
@@ -75,7 +75,7 @@ msgstr ""
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr ""
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, 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: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"Language: ru_RU\n"
@@ -75,7 +75,7 @@ msgstr "Это поле обязательное."
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr "Доступ по ссылке '%(link_reach)s' запрещён в соответствии с настройками родительского документа."
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, 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: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: Slovenian\n"
"Language: sl_SI\n"
@@ -75,7 +75,7 @@ msgstr ""
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr ""
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, 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: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: Swedish\n"
"Language: sv_SE\n"
@@ -75,7 +75,7 @@ msgstr ""
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr ""
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, 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: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"Language: tr_TR\n"
@@ -75,7 +75,7 @@ msgstr ""
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr ""
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, 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: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: Ukrainian\n"
"Language: uk_UA\n"
@@ -75,7 +75,7 @@ msgstr "Це поле є обов’язковим."
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr "Доступ до посилання '%(link_reach)s' заборонено на основі конфігурації батьківського документа."
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, 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: 2026-02-26 09:13+0000\n"
"PO-Revision-Date: 2026-02-27 08:24\n"
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
"PO-Revision-Date: 2026-03-09 14:02\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
@@ -75,7 +75,7 @@ msgstr "此欄位為必填。"
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
msgstr "根據父文件設定,不允許連結範圍「%(link_reach)s」。"
#: build/lib/core/api/viewsets.py:1278 core/api/viewsets.py:1278
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
#, python-brace-format
msgid "copy of {title}"
msgstr "{title} 的副本"
+24 -24
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "impress"
version = "4.6.0"
version = "4.7.0"
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.14.3",
"boto3==1.42.17",
"boto3==1.42.59",
"Brotli==1.2.0",
"celery[redis]==5.5.3",
"django-configurations==2.5.1",
@@ -34,37 +34,37 @@ dependencies = [
"django-countries==8.2.0",
"django-csp==4.0",
"django-filter==25.2",
"django-lasuite[all]==0.0.22",
"django-lasuite[all]==0.0.24",
"django-parler==2.3",
"django-redis==6.0.0",
"django-storages[s3]==1.14.6",
"django-timezone-field>=5.1",
"django<6.0.0",
"django-treebeard==4.8.0",
"django-treebeard==5.0.5",
"djangorestframework==3.16.1",
"drf_spectacular==0.29.0",
"dockerflow==2024.4.2",
"dockerflow==2026.1.26",
"easy_thumbnails==2.10.1",
"factory_boy==3.3.3",
"gunicorn==23.0.0",
"jsonschema==4.25.1",
"langfuse==3.11.2",
"gunicorn==25.1.0",
"jsonschema==4.26.0",
"langfuse==3.14.5",
"lxml==6.0.2",
"markdown==3.10",
"markdown==3.10.2",
"mozilla-django-oidc==5.0.2",
"nested-multipart-parser==1.6.0",
"openai==2.21.0",
"psycopg[binary]==3.3.2",
"pycrdt==0.12.44",
"openai==2.24.0",
"psycopg[binary]==3.3.3",
"pycrdt==0.12.47",
"pydantic==2.12.5",
"pydantic-ai-slim[openai,logfire,web]==1.58.0",
"PyJWT==2.10.1",
"PyJWT==2.11.0",
"python-magic==0.4.27",
"redis<6.0.0",
"requests==2.32.5",
"sentry-sdk==2.48.0",
"uvicorn==0.40.0",
"whitenoise==6.11.0",
"sentry-sdk==2.53.0",
"uvicorn==0.41.0",
"whitenoise==6.12.0",
]
[project.urls]
@@ -77,21 +77,21 @@ dependencies = [
dev = [
"django-extensions==4.1",
"django-test-migrations==1.5.0",
"drf-spectacular-sidecar==2025.12.1",
"drf-spectacular-sidecar==2026.3.1",
"freezegun==1.5.5",
"ipdb==0.13.13",
"ipython==9.8.0",
"pyfakefs==6.0.0",
"pylint-django==2.6.1",
"ipython==9.10.0",
"pyfakefs==6.1.3",
"pylint-django==2.7.0",
"pylint<4.0.0",
"pytest-cov==7.0.0",
"pytest-django==4.11.1",
"pytest-django==4.12.0",
"pytest==9.0.2",
"pytest-icdiff==0.9",
"pytest-xdist==3.8.0",
"responses==0.25.8",
"ruff==0.14.10",
"types-requests==2.32.4.20250913",
"responses==0.26.0",
"ruff==0.15.4",
"types-requests==2.32.4.20260107",
]
[tool.setuptools]
@@ -41,7 +41,7 @@ test.describe('Doc Comments', () => {
// We add a comment with the first user
const editor = await writeInEditor({ page, text: 'Hello World' });
await editor.getByText('Hello').selectText();
await page.getByRole('button', { name: 'Comment', exact: true }).click();
await page.getByRole('button', { name: 'Add comment' }).click();
const thread = page.locator('.bn-thread');
await thread.getByRole('paragraph').first().fill('This is a comment');
@@ -124,7 +124,7 @@ test.describe('Doc Comments', () => {
// Checks add react reaction
const editor = await writeInEditor({ page, text: 'Hello' });
await editor.getByText('Hello').selectText();
await page.getByRole('button', { name: 'Comment', exact: true }).click();
await page.getByRole('button', { name: 'Add comment' }).click();
const thread = page.locator('.bn-thread');
await thread.getByRole('paragraph').first().fill('This is a comment');
@@ -191,7 +191,7 @@ test.describe('Doc Comments', () => {
/* Delete the last comment remove the thread */
await editor.getByText('Hello').selectText();
await page.getByRole('button', { name: 'Comment', exact: true }).click();
await page.getByRole('button', { name: 'Add comment' }).click();
await thread.getByRole('paragraph').first().fill('This is a new comment');
await thread.locator('[data-test="save"]').click();
@@ -249,9 +249,7 @@ test.describe('Doc Comments', () => {
editor.getByText('Hello, I can edit the document'),
).toBeVisible();
await otherEditor.getByText('Hello').selectText();
await otherPage
.getByRole('button', { name: 'Comment', exact: true })
.click();
await otherPage.getByRole('button', { name: 'Add comment' }).click();
const otherThread = otherPage.locator('.bn-thread');
await otherThread
.getByRole('paragraph')
@@ -282,7 +280,7 @@ test.describe('Doc Comments', () => {
await expect(otherThread).toBeHidden();
await otherEditor.getByText('Hello').selectText();
await expect(
otherPage.getByRole('button', { name: 'Comment', exact: true }),
otherPage.getByRole('button', { name: 'Add comment' }),
).toBeHidden();
await otherPage.reload();
@@ -336,7 +334,7 @@ test.describe('Doc Comments', () => {
// We add a comment in the first document
const editor1 = await writeInEditor({ page, text: 'Document One' });
await editor1.getByText('Document One').selectText();
await page.getByRole('button', { name: 'Comment', exact: true }).click();
await page.getByRole('button', { name: 'Add comment' }).click();
const thread1 = page.locator('.bn-thread');
await thread1.getByRole('paragraph').first().fill('Comment in Doc One');
@@ -390,7 +388,7 @@ test.describe('Doc Comments mobile', () => {
// Checks add react reaction
const editor = await writeInEditor({ page, text: 'Hello' });
await editor.getByText('Hello').selectText();
await page.getByRole('button', { name: 'Comment', exact: true }).click();
await page.getByRole('button', { name: 'Add comment' }).click();
const thread = page.locator('.bn-thread');
await thread.getByRole('paragraph').first().fill('This is a comment');
@@ -380,7 +380,9 @@ test.describe('Doc grid move', () => {
await verifyDocName(page, titleDoc2);
const docTree = page.getByTestId('doc-tree');
await expect(docTree.getByText(titleDoc1)).toBeVisible();
await expect(docTree.getByText(titleDoc1)).toBeVisible({
timeout: 15000,
});
await cleanup();
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "app-e2e",
"version": "4.6.0",
"version": "4.7.0",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "app-impress",
"version": "4.6.0",
"version": "4.7.0",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
@@ -20,15 +20,15 @@
"dependencies": {
"@ag-media/react-pdf-table": "2.0.3",
"@ai-sdk/openai": "3.0.19",
"@blocknote/code-block": "0.47.0",
"@blocknote/core": "0.47.0",
"@blocknote/mantine": "0.47.0",
"@blocknote/react": "0.47.0",
"@blocknote/xl-ai": "0.47.0",
"@blocknote/xl-docx-exporter": "0.47.0",
"@blocknote/xl-multi-column": "0.47.0",
"@blocknote/xl-odt-exporter": "0.47.0",
"@blocknote/xl-pdf-exporter": "0.47.0",
"@blocknote/code-block": "0.47.1",
"@blocknote/core": "0.47.1",
"@blocknote/mantine": "0.47.1",
"@blocknote/react": "0.47.1",
"@blocknote/xl-ai": "0.47.1",
"@blocknote/xl-docx-exporter": "0.47.1",
"@blocknote/xl-multi-column": "0.47.1",
"@blocknote/xl-odt-exporter": "0.47.1",
"@blocknote/xl-pdf-exporter": "0.47.1",
"@dnd-kit/core": "6.3.1",
"@dnd-kit/modifiers": "9.0.0",
"@emoji-mart/data": "1.2.1",
@@ -1,3 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 18C8.45 18 7.97917 17.8042 7.5875 17.4125C7.19583 17.0208 7 16.55 7 16V4C7 3.45 7.19583 2.97917 7.5875 2.5875C7.97917 2.19583 8.45 2 9 2H18C18.55 2 19.0208 2.19583 19.4125 2.5875C19.8042 2.97917 20 3.45 20 4V16C20 16.55 19.8042 17.0208 19.4125 17.4125C19.0208 17.8042 18.55 18 18 18H9ZM9 16H18V4H9V16ZM5 22C4.45 22 3.97917 21.8042 3.5875 21.4125C3.19583 21.0208 3 20.55 3 20V6H5V20H16V22H5Z" fill="#222631"/>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M9 18C8.45 18 7.97917 17.8042 7.5875 17.4125C7.19583 17.0208 7 16.55 7 16V4C7 3.45 7.19583 2.97917 7.5875 2.5875C7.97917 2.19583 8.45 2 9 2H18C18.55 2 19.0208 2.19583 19.4125 2.5875C19.8042 2.97917 20 3.45 20 4V16C20 16.55 19.8042 17.0208 19.4125 17.4125C19.0208 17.8042 18.55 18 18 18H9ZM9 16H18V4H9V16ZM5 22C4.45 22 3.97917 21.8042 3.5875 21.4125C3.19583 21.0208 3 20.55 3 20V6H5V20H16V22H5Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 524 B

After

Width:  |  Height:  |  Size: 519 B

@@ -1,3 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 21C6.45 21 5.97917 20.8042 5.5875 20.4125C5.19583 20.0208 5 19.55 5 19V6H4V4H9V3H15V4H20V6H19V19C19 19.55 18.8042 20.0208 18.4125 20.4125C18.0208 20.8042 17.55 21 17 21H7ZM17 6H7V19H17V6ZM9 17H11V8H9V17ZM13 17H15V8H13V17Z" fill="#222631"/>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M7 21C6.45 21 5.97917 20.8042 5.5875 20.4125C5.19583 20.0208 5 19.55 5 19V6H4V4H9V3H15V4H20V6H19V19C19 19.55 18.8042 20.0208 18.4125 20.4125C18.0208 20.8042 17.55 21 17 21H7ZM17 6H7V19H17V6ZM9 17H11V8H9V17ZM13 17H15V8H13V17Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 355 B

After

Width:  |  Height:  |  Size: 350 B

@@ -1,3 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 21C9.7 21 7.69583 20.2375 5.9875 18.7125C4.27917 17.1875 3.3 15.2833 3.05 13H5.1C5.33333 14.7333 6.10417 16.1667 7.4125 17.3C8.72083 18.4333 10.25 19 12 19C13.95 19 15.6042 18.3208 16.9625 16.9625C18.3208 15.6042 19 13.95 19 12C19 10.05 18.3208 8.39583 16.9625 7.0375C15.6042 5.67917 13.95 5 12 5C10.85 5 9.775 5.26667 8.775 5.8C7.775 6.33333 6.93333 7.06667 6.25 8H9V10H3V4H5V6.35C5.85 5.28333 6.8875 4.45833 8.1125 3.875C9.3375 3.29167 10.6333 3 12 3C13.25 3 14.4208 3.2375 15.5125 3.7125C16.6042 4.1875 17.5542 4.82917 18.3625 5.6375C19.1708 6.44583 19.8125 7.39583 20.2875 8.4875C20.7625 9.57917 21 10.75 21 12C21 13.25 20.7625 14.4208 20.2875 15.5125C19.8125 16.6042 19.1708 17.5542 18.3625 18.3625C17.5542 19.1708 16.6042 19.8125 15.5125 20.2875C14.4208 20.7625 13.25 21 12 21ZM14.8 16.2L11 12.4V7H13V11.6L16.2 14.8L14.8 16.2Z" fill="#222631"/>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 21C9.7 21 7.69583 20.2375 5.9875 18.7125C4.27917 17.1875 3.3 15.2833 3.05 13H5.1C5.33333 14.7333 6.10417 16.1667 7.4125 17.3C8.72083 18.4333 10.25 19 12 19C13.95 19 15.6042 18.3208 16.9625 16.9625C18.3208 15.6042 19 13.95 19 12C19 10.05 18.3208 8.39583 16.9625 7.0375C15.6042 5.67917 13.95 5 12 5C10.85 5 9.775 5.26667 8.775 5.8C7.775 6.33333 6.93333 7.06667 6.25 8H9V10H3V4H5V6.35C5.85 5.28333 6.8875 4.45833 8.1125 3.875C9.3375 3.29167 10.6333 3 12 3C13.25 3 14.4208 3.2375 15.5125 3.7125C16.6042 4.1875 17.5542 4.82917 18.3625 5.6375C19.1708 6.44583 19.8125 7.39583 20.2875 8.4875C20.7625 9.57917 21 10.75 21 12C21 13.25 20.7625 14.4208 20.2875 15.5125C19.8125 16.6042 19.1708 17.5542 18.3625 18.3625C17.5542 19.1708 16.6042 19.8125 15.5125 20.2875C14.4208 20.7625 13.25 21 12 21ZM14.8 16.2L11 12.4V7H13V11.6L16.2 14.8L14.8 16.2Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 967 B

After

Width:  |  Height:  |  Size: 962 B

@@ -9,11 +9,12 @@ import { Button, Popover } from 'react-aria-components';
import styled, { css } from 'styled-components';
import { useCunninghamTheme } from '@/cunningham';
import { useFocusStore } from '@/stores';
import { BoxProps } from './Box';
const StyledPopover = styled(Popover)`
background-color: white;
background-color: var(--c--contextuals--background--surface--primary);
border-radius: var(--c--globals--spacings--st);
box-shadow: 0 0 6px 0 rgba(0, 0, 145, 0.1);
border: 1px solid var(--c--contextuals--border--surface--primary);
@@ -70,8 +71,9 @@ export const DropButton = ({
const { themeTokens } = useCunninghamTheme();
const font = themeTokens['font']?.['families']['base'];
const [isLocalOpen, setIsLocalOpen] = useState(isOpen);
const addLastFocus = useFocusStore((state) => state.addLastFocus);
const triggerRef = useRef(null);
const triggerRef = useRef<HTMLButtonElement | null>(null);
useEffect(() => {
setIsLocalOpen(isOpen);
@@ -86,7 +88,10 @@ export const DropButton = ({
<>
<StyledButton
ref={triggerRef}
onPress={() => onOpenChangeHandler(true)}
onPress={() => {
addLastFocus(triggerRef.current);
onOpenChangeHandler(true);
}}
aria-label={label}
data-testid={testId}
$css={css`
@@ -193,7 +193,7 @@ export const DropdownMenu = ({
key={option.label}
$align="center"
$justify="space-between"
$background={colorsTokens['gray-000']}
$background="var(--c--contextuals--background--surface--primary)"
$color={colorsTokens['brand-600']}
$padding={{ vertical: 'xs', horizontal: 'base' }}
$width="100%"
@@ -250,15 +250,21 @@ export const DropdownMenu = ({
{option.icon && typeof option.icon === 'string' && (
<Icon
$size="20px"
$theme="gray"
$theme="neutral"
$variation={isDisabled ? 'tertiary' : 'primary'}
iconName={option.icon}
aria-hidden="true"
/>
)}
{option.icon &&
typeof option.icon !== 'string' &&
option.icon}
{option.icon && typeof option.icon !== 'string' && (
<Box
$theme="neutral"
$variation={isDisabled ? 'tertiary' : 'primary'}
>
{option.icon}
</Box>
)}
<Text $variation={isDisabled ? 'tertiary' : 'primary'}>
{option.label}
</Text>
@@ -52,6 +52,7 @@ export const AlertModal = ({
return (
<Modal
closeOnClickOutside
isOpen={isOpen}
size={ModalSize.MEDIUM}
onClose={onClose}
@@ -76,6 +76,18 @@
background-color: transparent;
}
.c__modal__close .c__button {
right: 0;
top: 0;
position: relative;
}
.c__modal__close {
position: absolute;
top: 4px;
right: 4px;
}
/**
* Tooltip
*/
@@ -40,14 +40,22 @@ const AIMenuStyle = createGlobalStyle`
height: 18px;
width: 18px;
}
.--docs--ai-menu input[name="ai-prompt"]{
padding-inline-start: 3rem;
}
.--docs--ai-menu .mantine-TextInput-section[data-position="left"] {
margin-inline: 0.75rem;
}
.--docs--ai-menu .mantine-TextInput-section[data-position="right"] {
inset-inline-end: 2rem;
.--docs--ai-menu {
input[name="ai-prompt"]{
padding-inline-start: 3rem;
}
.mantine-TextInput-wrapper {
display: flex
}
.mantine-TextInput-section[data-position="left"] {
margin-inline: 0.75rem;
}
.mantine-TextInput-section[data-position="right"] {
inset-inline-end: var(--c--globals--spacings--sm);
position: relative;
flex-shrink: 0;
width: auto;
}
}
`;
@@ -8,7 +8,7 @@ import {
} from '@blocknote/core';
import { CommentsExtension } from '@blocknote/core/comments';
import '@blocknote/core/fonts/inter.css';
import * as locales from '@blocknote/core/locales';
import * as localesBN from '@blocknote/core/locales';
import { BlockNoteView } from '@blocknote/mantine';
import '@blocknote/mantine/style.css';
import { useCreateBlockNote } from '@blocknote/react';
@@ -26,7 +26,7 @@ import { Doc, useProviderStore } from '@/docs/doc-management';
import { avatarUrlFromName, useAuth } from '@/features/auth';
import { useAnalytics } from '@/libs/Analytics';
import { AI_FEATURE_FLAG } from '../conf';
import { AI_FEATURE_FLAG, DEFAULT_LOCALE } from '../conf';
import {
useHeadings,
useSaveDoc,
@@ -52,14 +52,14 @@ import {
const AIMenu = BlockNoteAI?.AIMenu;
const AIMenuController = BlockNoteAI?.AIMenuController;
const useAI = BlockNoteAI?.useAI;
const localesAI = BlockNoteAI?.localesAI;
const localesBNAI = BlockNoteAI?.localesAI;
import {
InterlinkingLinkInlineContent,
InterlinkingSearchInlineContent,
} from './custom-inline-content';
import XLMultiColumn from './xl-multi-column';
const multiColumnLocales = XLMultiColumn?.locales;
const localesBNMultiColumn = XLMultiColumn?.locales;
const withMultiColumn = XLMultiColumn?.withMultiColumn;
const baseBlockNoteSchema = withPageBreak(
@@ -100,10 +100,20 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
useSaveDoc(doc.id, provider.document, isConnectedToCollabServer);
const { i18n, t } = useTranslation();
let lang = i18n.resolvedLanguage;
if (!lang || !(lang in locales)) {
lang = 'en';
}
const langLocalesBN =
!i18n.resolvedLanguage || !(i18n.resolvedLanguage in localesBN)
? DEFAULT_LOCALE
: i18n.resolvedLanguage;
const langLocalesBNMultiColumn =
!i18n.resolvedLanguage ||
!localesBNMultiColumn ||
!(i18n.resolvedLanguage in localesBNMultiColumn)
? DEFAULT_LOCALE
: i18n.resolvedLanguage;
const langLocalesBNAI =
!i18n.resolvedLanguage || !(i18n.resolvedLanguage in localesBNAI)
? DEFAULT_LOCALE
: i18n.resolvedLanguage;
const { uploadFile, errorAttachment } = useUploadFile(doc.id);
const conf = useConfig().data;
@@ -180,11 +190,13 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
showCursorLabels: showCursorLabels as 'always' | 'activity',
},
dictionary: {
...locales[lang as keyof typeof locales],
...(multiColumnLocales && {
...localesBN[langLocalesBN as keyof typeof localesBN],
...(localesBNMultiColumn && {
multi_column:
multiColumnLocales[lang as keyof typeof multiColumnLocales],
ai: localesAI?.[lang as keyof typeof localesAI],
localesBNMultiColumn[
langLocalesBNMultiColumn as keyof typeof localesBNMultiColumn
],
ai: localesBNAI?.[langLocalesBNAI as keyof typeof localesBNAI],
}),
},
pasteHandler: ({ event, defaultPasteHandler }) => {
@@ -228,7 +240,9 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
[
aiExtension,
cursorName,
lang,
langLocalesBN,
langLocalesBNMultiColumn,
langLocalesBNAI,
provider,
uploadFile,
threadStore,
@@ -72,11 +72,11 @@ export const BlockNoteToolbar = ({ aiAllowed }: { aiAllowed: boolean }) => {
const formattingToolbar = useCallback(() => {
return (
<FormattingToolbar>
{aiAllowed && AIToolbarButton && <AIToolbarButton />}
{toolbarItems}
<CommentToolbarButton />
{toolbarItems}
{aiAllowed && AIToolbarButton && <AIToolbarButton />}
{/* Extra button to do some AI powered actions - only if AIToolbarButton is not available because of MIT license */}
{conf?.AI_FEATURE_ENABLED && conf?.AI_FEATURE_LEGACY_ENABLED && (
@@ -24,6 +24,7 @@ export const ModalConfirmDownloadUnsafe = ({
<>
<Button
aria-label={t('Cancel the download')}
autoFocus
variant="secondary"
onClick={() => onClose()}
>
@@ -59,7 +59,7 @@ export const DocEditorContainer = ({
<Box $css="flex:1;" $position="relative" $width="100%">
<Box
$padding={{ top: 'md', bottom: '2rem' }}
$background="white"
$background="var(--c--contextuals--background--surface--primary)"
className={clsx('--docs--editor-container', {
'--docs--doc-readonly': readOnly,
'--docs--doc-deleted': isDeletedDoc,
@@ -29,7 +29,7 @@ export const CommentToolbarButton = () => {
const Components = useComponentsContext();
const { currentDoc } = useDocStore();
const { t } = useTranslation();
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const { colorsTokens } = useCunninghamTheme();
const comments = useExtension('comments') as unknown as ReturnType<
ReturnType<typeof CommentsExtension>
>;
@@ -78,22 +78,16 @@ export const CommentToolbarButton = () => {
}}
aria-haspopup="dialog"
data-test="comment-toolbar-button"
aria-label={t('Add comment')}
mainTooltip={t('Add comment')}
>
<Box
$direction="row"
$align="center"
$gap={spacingsTokens['xs']}
$padding={{ right: '2xs' }}
>
<Icon
iconName="comment"
className="--docs--icon-bg"
$theme="gray"
$padding="0.15rem"
$size="md"
/>
{t('Comment')}
</Box>
<Icon
iconName="comment"
className="--docs--icon-bg"
$theme="gray"
$padding="0.15rem"
$size="md"
/>
</Components.Generic.Toolbar.Button>
<Box
$background={colorsTokens['gray-100']}
@@ -1,2 +1,3 @@
export const ANALYZE_URL = 'media-check';
export const AI_FEATURE_FLAG = 'ai_blocknote';
export const DEFAULT_LOCALE = 'en';
@@ -203,6 +203,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
aria-label={t('Cancel the download')}
variant="secondary"
fullWidth
autoFocus
onClick={() => onClose()}
>
{t('Cancel')}
@@ -75,7 +75,12 @@ export const AlertNetworkModal = ({ onClose }: AlertNetworkModalProps) => {
onClose={() => onClose()}
rightActions={
<>
<Button aria-label={t('OK')} onClick={onClose} color="error">
<Button
aria-label={t('OK')}
onClick={onClose}
color="error"
autoFocus
>
{t('I understand')}
</Button>
</>
@@ -6,6 +6,7 @@ import { css } from 'styled-components';
import { Box, Icon } from '@/components';
import { Doc } from '@/docs/doc-management';
import { useFocusStore } from '@/stores';
interface BoutonShareProps {
displayNbAccess: boolean;
@@ -23,6 +24,7 @@ export const BoutonShare = ({
open,
}: BoutonShareProps) => {
const { t } = useTranslation();
const addLastFocus = useFocusStore((state) => state.addLastFocus);
const treeContext = useTreeContext<Doc>();
/**
@@ -63,7 +65,10 @@ export const BoutonShare = ({
disabled={isDisabled}
/>
}
onClick={open}
onClick={(e) => {
addLastFocus(e.currentTarget as HTMLElement);
open();
}}
size="medium"
disabled={isDisabled}
>
@@ -77,7 +82,10 @@ export const BoutonShare = ({
<Button
color="brand"
variant="tertiary"
onClick={open}
onClick={(e) => {
addLastFocus(e.currentTarget as HTMLElement);
open();
}}
size="medium"
disabled={isDisabled}
>
@@ -44,7 +44,7 @@ import {
KEY_LIST_DOC_VERSIONS,
ModalSelectVersion,
} from '@/docs/doc-versioning';
import { useResponsiveStore } from '@/stores';
import { useFocusStore, useResponsiveStore } from '@/stores';
import { useCopyCurrentEditorToClipboard } from '../hooks/useCopyCurrentEditorToClipboard';
@@ -70,6 +70,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
const selectHistoryModal = useModal();
const modalShare = useModal();
const { addLastFocus, restoreFocus } = useFocusStore();
const { isSmallMobile, isMobile } = useResponsiveStore();
const copyDocLink = useCopyDocLink(doc.id);
const { mutate: duplicateDoc } = useDuplicateDoc({
@@ -224,7 +225,8 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
icon={
<Icon iconName="download" $color="inherit" aria-hidden={true} />
}
onClick={() => {
onClick={(e) => {
addLastFocus(e.currentTarget as HTMLElement);
setIsModalExportOpen(true);
}}
size={isSmallMobile ? 'small' : 'medium'}
@@ -249,17 +251,29 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
{modalShare.isOpen && (
<DocShareModal
onClose={() => modalShare.close()}
onClose={() => {
modalShare.close();
restoreFocus();
}}
doc={doc}
isRootDoc={treeContext?.root?.id === doc.id}
/>
)}
{isModalExportOpen && ModalExport && (
<ModalExport onClose={() => setIsModalExportOpen(false)} doc={doc} />
<ModalExport
onClose={() => {
setIsModalExportOpen(false);
restoreFocus();
}}
doc={doc}
/>
)}
{isModalRemoveOpen && (
<ModalRemoveDoc
onClose={() => setIsModalRemoveOpen(false)}
onClose={() => {
setIsModalRemoveOpen(false);
restoreFocus();
}}
doc={doc}
onSuccess={() => {
const isTopParent = doc.id === treeContext?.root?.id;
@@ -281,7 +295,10 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
)}
{selectHistoryModal.isOpen && (
<ModalSelectVersion
onClose={() => selectHistoryModal.close()}
onClose={() => {
selectHistoryModal.close();
restoreFocus();
}}
doc={doc}
/>
)}
@@ -31,7 +31,7 @@ export const FloatingBar = () => {
margin-left: calc(-${base});
margin-right: calc(-${base});
margin-top: calc(-${base});
z-index: 1000;
z-index: 21; // Under editor select box but above other elements (e.g., doc title, suggestion menu)
display: flex;
align-items: flex-start;
justify-content: flex-start;
@@ -13,6 +13,7 @@ import {
TextType,
emojidata,
} from '@/components';
import { useFocusStore } from '@/stores';
import { useDocTitleUpdate } from '../hooks/useDocTitleUpdate';
@@ -41,6 +42,7 @@ export const DocIcon = ({
}: DocIconProps) => {
const { updateDocEmoji } = useDocTitleUpdate();
const { t } = useTranslation();
const { addLastFocus, restoreFocus } = useFocusStore();
const iconRef = useRef<HTMLDivElement>(null);
@@ -90,12 +92,16 @@ export const DocIcon = ({
});
}
if (!openEmojiPicker) {
addLastFocus(iconRef.current);
}
setOpenEmojiPicker(!openEmojiPicker);
}
};
const handleEmojiSelect = ({ native }: { native: string }) => {
setOpenEmojiPicker(false);
restoreFocus();
// Update document emoji if docId is provided
if (docId && title !== undefined) {
@@ -108,6 +114,7 @@ export const DocIcon = ({
const handleClickOutside = () => {
setOpenEmojiPicker(false);
restoreFocus();
};
return (
@@ -38,6 +38,7 @@ export const ModalRemoveDoc = ({
const { push } = useRouter();
const { hasChildren } = useDocUtils(doc);
const cancelButtonRef = useRef<ButtonElement>(null);
const {
mutate: removeDoc,
isError,
@@ -60,17 +61,14 @@ export const ModalRemoveDoc = ({
},
},
});
// react-aria Popover restores focus to its trigger asynchronously
// when closing, which races with autoFocus when the modal is opened
// from a dropdown. This ensures focus wins after that restoration.
useEffect(() => {
const TIMEOUT_MODAL_MOUNTING = 100;
const timeoutId = setTimeout(() => {
const buttonElement = cancelButtonRef.current;
if (buttonElement) {
buttonElement.focus();
}
}, TIMEOUT_MODAL_MOUNTING);
return () => clearTimeout(timeoutId);
const id = requestAnimationFrame(() => {
cancelButtonRef.current?.focus();
});
return () => cancelAnimationFrame(id);
}, []);
const keyboardAction = useKeyboardAction();
@@ -100,6 +98,7 @@ export const ModalRemoveDoc = ({
aria-label={t('Cancel the deletion')}
variant="secondary"
fullWidth
autoFocus
onClick={handleClose}
onKeyDown={handleCloseKeyDown}
>
@@ -58,7 +58,11 @@ export const Heading = ({
});
}}
$radius="var(--c--globals--spacings--st)"
$background={isActive ? `${colorsTokens['gray-100']}` : 'none'}
$background={
isActive
? 'var(--c--contextuals--background--semantic--neutral--secondary)'
: 'none'
}
$css={css`
text-align: left;
&:focus-visible {
@@ -61,7 +61,7 @@ export const TableContent = () => {
$width={!isOpen ? '40px' : '200px'}
$height={!isOpen ? '40px' : 'auto'}
$maxHeight="calc(50vh - 60px)"
$zIndex={2000}
$zIndex={1000}
$align="center"
$padding={isOpen ? 'xs' : '0'}
$justify="center"
@@ -72,7 +72,7 @@ export const TableContent = () => {
border: 1px solid ${colorsTokens['brand-100']};
overflow: hidden;
border-radius: ${spacingsTokens['3xs']};
background: ${colorsTokens['gray-000']};
background: var(--c--contextuals--background--surface--primary);
${isOpen &&
css`
display: flex;
@@ -71,7 +71,7 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
allChildren as TreeViewDataType<Doc>[],
);
treeContext?.treeData.setSelectedNode(createdDoc);
togglePanel();
togglePanel({ type: 'mobile' });
})
.catch(console.error);
} else {
@@ -85,7 +85,7 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
node.open();
router.push(`/docs/${createdDoc.id}`);
treeContext?.treeData.setSelectedNode(newDoc);
togglePanel();
togglePanel({ type: 'mobile' });
}
};
@@ -99,11 +99,16 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
const buttonOptionRef = useRef<HTMLDivElement | null>(null);
const handleKeyDown = (e: React.KeyboardEvent) => {
// F2: focus first action button
const shouldOpenActions = !menuOpen && node.isFocused;
const target = e.target as HTMLElement | null;
const isInActions = !!target?.closest('.light-doc-item-actions');
const isOnEmojiButton = !!target?.closest('.--docs--doc-icon');
const shouldOpenActions =
!menuOpen && !isInActions && (node.isFocused || isOnEmojiButton);
if (e.key === 'F2' && shouldOpenActions) {
buttonOptionRef.current?.focus();
e.stopPropagation();
e.preventDefault();
return;
}
};
@@ -130,7 +135,7 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
aria-disabled={isDisabled}
onKeyDown={handleKeyDown}
$css={css`
background-color: var(--c--globals--colors--gray-000);
background-color: var(--c--contextuals--background--surface--primary);
.light-doc-item-actions {
display: ${menuOpen || !isDesktop ? 'flex' : 'none'};
right: var(--c--globals--spacings--0);
@@ -97,16 +97,24 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
// Handle keyboard navigation for root item
const handleRootKeyDown = useCallback(
(e: React.KeyboardEvent) => {
// F2: focus first action button
if (e.key === 'F2' && !rootActionsOpen) {
e.preventDefault();
rootButtonOptionRef.current?.focus();
const target = e.target as HTMLElement | null;
const isInActions = !!target?.closest('.doc-tree-root-item-actions');
const isOnEmojiButton = !!target?.closest('.--docs--doc-icon');
const isOnRootItem = target === e.currentTarget;
if (e.key === 'F2' && !rootActionsOpen && !isInActions) {
if (
isOnEmojiButton ||
isOnRootItem ||
target?.classList.contains('c__tree-view--node')
) {
e.preventDefault();
rootButtonOptionRef.current?.focus();
}
return;
}
// Ignore if focus is in actions
const target = e.target as HTMLElement | null;
if (target?.closest('.doc-tree-root-item-actions')) {
if (isInActions) {
return;
}
@@ -279,6 +287,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
padding: ${spacingsTokens['2xs']};
border-radius: var(--c--globals--spacings--st);
width: 100%;
min-width: 200px;
background-color: ${rootIsSelected || rootActionsOpen
? 'var(--c--contextuals--background--semantic--contextual--primary)'
: 'transparent'};
@@ -296,8 +305,8 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
}
.doc-tree-root-item-actions {
display: flex;
opacity: ${rootActionsOpen ? '1' : '0'};
display: ${rootActionsOpen ? 'flex' : 'none'};
&:has(.isOpen) {
opacity: 1;
@@ -76,6 +76,7 @@ export const ModalConfirmationVersion = ({
aria-label={`${t('Cancel')} - ${t('Warning')}`}
variant="secondary"
fullWidth
autoFocus
onClick={() => onClose()}
>
{t('Cancel')}
@@ -132,6 +132,7 @@ export const ModalSelectVersion = ({
</Text>
<ButtonCloseModal
aria-label={t('Close the version history modal')}
autoFocus
onClick={onClose}
size="nano"
/>
@@ -167,9 +167,13 @@ export const DocMoveModal = ({
$weight="normal"
$textAlign="left"
>
<Trans t={t}>
Choose the new location for <strong>{docTitle}</strong>.
</Trans>
<Trans
i18nKey="Choose the new location for <strong>{{title}}</strong>."
values={{
title: docTitle,
}}
components={{ strong: <strong /> }}
/>
</Text>
</Box>
<HorizontalSeparator />
@@ -20,6 +20,8 @@ import {
useTrans,
} from '@/docs/doc-management';
import { DocShareModal } from '@/docs/doc-share';
import { MAIN_LAYOUT_ID } from '@/layouts/conf';
import { useFocusStore } from '@/stores';
import { DocMoveModal } from './DocMoveModal';
@@ -29,13 +31,21 @@ interface DocsGridActionsProps {
export const DocsGridActions = ({ doc }: DocsGridActionsProps) => {
const { t } = useTranslation();
const restoreFocus = useFocusStore((state) => state.restoreFocus);
const deleteModal = useModal();
const shareModal = useModal();
const importModal = useModal();
const { untitledDocument } = useTrans();
const { mutate: duplicateDoc } = useDuplicateDoc();
const { mutate: duplicateDoc } = useDuplicateDoc({
onSuccess: () => {
const mainContent = document.getElementById(MAIN_LAYOUT_ID);
if (mainContent) {
requestAnimationFrame(() => mainContent.focus());
}
},
});
const removeFavoriteDoc = useDeleteFavoriteDoc({
listInvalidQueries: [KEY_LIST_DOC, KEY_LIST_FAVORITE_DOC],
@@ -135,15 +145,30 @@ export const DocsGridActions = ({ doc }: DocsGridActionsProps) => {
</DropdownMenu>
{deleteModal.isOpen && (
<ModalRemoveDoc onClose={deleteModal.onClose} doc={doc} />
<ModalRemoveDoc
onClose={() => {
deleteModal.onClose();
restoreFocus();
}}
doc={doc}
/>
)}
{shareModal.isOpen && (
<DocShareModal doc={doc} onClose={shareModal.close} />
<DocShareModal
doc={doc}
onClose={() => {
shareModal.close();
restoreFocus();
}}
/>
)}
{importModal.isOpen && (
<DocMoveModal
doc={doc}
onClose={importModal.close}
onClose={() => {
importModal.close();
restoreFocus();
}}
isOpen={importModal.isOpen}
/>
)}
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { Box, Icon, Text } from '@/components';
import { Doc } from '@/docs/doc-management';
import { DocShareModal } from '@/docs/doc-share';
import { useFocusStore } from '@/stores';
type Props = {
doc: Doc;
@@ -14,6 +15,7 @@ export const DocsGridItemSharedButton = ({ doc, disabled }: Props) => {
const sharedCount = doc.nb_accesses_direct;
const isShared = sharedCount - 1 > 0;
const shareModal = useModal();
const { addLastFocus, restoreFocus } = useFocusStore();
if (!isShared) {
return <Box $minWidth="50px">&nbsp;</Box>;
@@ -40,6 +42,7 @@ export const DocsGridItemSharedButton = ({ doc, disabled }: Props) => {
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
addLastFocus(event.currentTarget as HTMLElement);
shareModal.open();
}}
color="brand"
@@ -60,7 +63,13 @@ export const DocsGridItemSharedButton = ({ doc, disabled }: Props) => {
</Button>
</Tooltip>
{shareModal.isOpen && (
<DocShareModal doc={doc} onClose={shareModal.close} />
<DocShareModal
doc={doc}
onClose={() => {
shareModal.close();
restoreFocus();
}}
/>
)}
</>
);
@@ -7,11 +7,11 @@ import { useConfig } from '@/core/config';
import { useCunninghamTheme } from '@/cunningham';
import { ButtonLogin } from '@/features/auth';
import { LanguagePicker } from '@/features/language';
import { LeftPanelToggleMobile } from '@/features/left-panel';
import { useResponsiveStore } from '@/stores';
import { HEADER_HEIGHT } from '../conf';
import { ButtonTogglePanel } from './ButtonTogglePanel';
import { Title } from './Title';
import { Waffle } from './Waffle';
@@ -46,7 +46,7 @@ export const Header = () => {
var(--c--contextuals--border--surface--primary);
`}
>
{!isDesktop && <ButtonTogglePanel />}
{!isDesktop && <LeftPanelToggleMobile />}
<StyledLink
href="/"
data-testid="header-logo-link"
@@ -1,4 +1,3 @@
export * from './ButtonTogglePanel';
export * from './Header';
export * from './Waffle';
export * from './Title';
@@ -3,9 +3,9 @@ import Image from 'next/image';
import { Box } from '@/components';
import { useConfig } from '@/core';
import { useCunninghamTheme } from '@/cunningham';
import { ButtonTogglePanel, Title } from '@/features/header/';
import { Waffle } from '@/features/header/components/Waffle';
import { Title, Waffle } from '@/features/header';
import { LanguagePicker } from '@/features/language';
import { LeftPanelToggleMobile } from '@/features/left-panel';
import { useResponsiveStore } from '@/stores';
export const HEADER_HEIGHT = 91;
@@ -42,7 +42,7 @@ export const HomeHeader = () => {
>
{isSmallMobile && (
<Box $position="absolute" $css="left: 1rem;">
<ButtonTogglePanel />
<LeftPanelToggleMobile />
</Box>
)}
{!isSmallMobile && logo?.src && (
@@ -7,14 +7,12 @@ import { Box, Icon, StyledLink, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { DocDefaultFilter } from '@/docs/doc-management';
import { useLeftPanelStore } from '@/features/left-panel';
import { useResponsiveStore } from '@/stores';
export const LeftPanelTargetFilters = () => {
const { t } = useTranslation();
const pathname = usePathname();
const searchParams = useSearchParams();
const { isDesktop } = useResponsiveStore();
const { closePanel } = useLeftPanelStore();
const { colorsTokens, spacingsTokens } = useCunninghamTheme();
@@ -52,9 +50,7 @@ export const LeftPanelTargetFilters = () => {
};
const handleFilterClick = () => {
if (!isDesktop) {
closePanel();
}
closePanel({ type: 'mobile' });
};
return (
@@ -1,5 +1,3 @@
import { usePathname } from 'next/navigation';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { createGlobalStyle, css } from 'styled-components';
@@ -26,15 +24,9 @@ export const LeftPanel = () => {
const { t } = useTranslation();
const { spacingsTokens } = useCunninghamTheme();
const { togglePanel, isPanelOpen, isPanelOpenMobile } = useLeftPanelStore();
const { isPanelOpen, isPanelOpenMobile } = useLeftPanelStore();
const isPanelOpenState = isDesktop ? isPanelOpen : isPanelOpenMobile;
const pathname = usePathname();
useEffect(() => {
togglePanel(isDesktop);
}, [pathname, isDesktop, togglePanel]);
return (
<>
{isDesktop && (
@@ -44,7 +36,9 @@ export const LeftPanel = () => {
height: calc(100vh - ${HEADER_HEIGHT}px);
width: 100%;
overflow: hidden;
background-color: var(--c--globals--colors--gray-000);
background-color: var(
--c--contextuals--background--surface--primary
);
`}
className="--docs--left-panel-desktop"
as="nav"
@@ -73,7 +67,9 @@ export const LeftPanel = () => {
border-right: 1px solid var(--c--globals--colors--gray-200);
position: fixed;
transform: translateX(${isPanelOpenState ? '0' : '-100dvw'});
background-color: var(--c--globals--colors--gray-000);
background-color: var(
--c--contextuals--background--surface--primary
);
overflow-y: auto;
overflow-x: hidden;
`}
@@ -71,7 +71,7 @@ export const LeftPanelCollapseButton = () => {
>
<Button
size="small"
onClick={() => togglePanel()}
onClick={() => togglePanel({ type: 'desktop' })}
aria-label={ariaLabel}
aria-expanded={isPanelOpen}
color="neutral"
@@ -38,7 +38,7 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
const goToHome = () => {
void router.push('/');
togglePanel();
togglePanel({ type: 'mobile' });
};
return (
@@ -4,9 +4,8 @@ import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Icon } from '@/components';
import { useCreateDoc } from '@/features/docs/doc-management';
import { useCreateDoc } from '@/docs/doc-management';
import { useSkeletonStore } from '@/features/skeletons';
import { useResponsiveStore } from '@/stores';
import { useLeftPanelStore } from '../stores';
@@ -14,7 +13,6 @@ export const LeftPanelHeaderButton = () => {
const router = useRouter();
const { t } = useTranslation();
const { closePanel } = useLeftPanelStore();
const { isDesktop } = useResponsiveStore();
const { setIsSkeletonVisible } = useSkeletonStore();
const [isNavigating, setIsNavigating] = useState(false);
@@ -27,9 +25,7 @@ export const LeftPanelHeaderButton = () => {
.then(() => {
// The skeleton will be disabled by the [id] page once the data is loaded
setIsNavigating(false);
if (!isDesktop) {
closePanel();
}
closePanel({ type: 'mobile' });
})
.catch(() => {
// In case of navigation error, disable the skeleton
@@ -4,14 +4,14 @@ import { useTranslation } from 'react-i18next';
import { Icon } from '@/components/';
import { useLeftPanelStore } from '@/features/left-panel';
export const ButtonTogglePanel = () => {
export const LeftPanelToggleMobile = () => {
const { t } = useTranslation();
const { isPanelOpenMobile, togglePanel } = useLeftPanelStore();
return (
<Button
size="medium"
onClick={() => togglePanel()}
onClick={() => togglePanel({ type: 'mobile' })}
aria-label={t(
isPanelOpenMobile ? 'Close the header menu' : 'Open the header menu',
)}
@@ -23,7 +23,7 @@ export const ButtonTogglePanel = () => {
iconName={isPanelOpenMobile ? 'close' : 'menu'}
/>
}
className="--docs--button-toggle-panel"
className="--docs--button-toggle-panel-mobile"
data-testid="header-menu-toggle"
/>
);
@@ -6,17 +6,15 @@ import {
PanelResizeHandle,
} from 'react-resizable-panels';
import { useLeftPanelStore } from '@/features/left-panel/stores';
import { useResponsiveStore } from '@/stores';
import { useLeftPanelStore } from '../stores';
// Convert a target pixel width to a percentage of the current viewport width.
const pxToPercent = (px: number) => {
return (px / window.innerWidth) * 100;
};
const PANEL_TOGGLE_TRANSITION =
'flex-grow 180ms var(--c--globals--transitions--ease-out), flex-basis 180ms var(--c--globals--transitions--ease-out)';
type ResizableLeftPanelProps = {
leftPanel: React.ReactNode;
children: React.ReactNode;
@@ -34,70 +32,58 @@ export const ResizableLeftPanel = ({
const { isPanelOpen } = useLeftPanelStore();
const ref = useRef<ImperativePanelHandle>(null);
const savedWidthPxRef = useRef<number>(minPanelSizePx);
const previousPanelOpenRef = useRef<boolean>(isPanelOpen);
const [isToggleAnimating, setIsToggleAnimating] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const minPanelSizePercent = pxToPercent(minPanelSizePx);
const maxPanelSizePercent = Math.min(pxToPercent(maxPanelSizePx), 40);
const [panelSizePercent, setPanelSizePercent] = useState(() => {
const initialSize = pxToPercent(minPanelSizePx);
return Math.max(
minPanelSizePercent,
Math.min(initialSize, maxPanelSizePercent),
);
});
/**
* When the panel is toggled open/closed, we want
* to either expand/collapse
*/
useEffect(() => {
const syncPanelState = () => {
if (!ref.current || !isDesktop) {
return;
}
if (!isPanelOpen) {
ref.current.collapse();
return;
}
const restoredSizePercent = Math.max(
minPanelSizePercent,
Math.min(pxToPercent(savedWidthPxRef.current), maxPanelSizePercent),
);
ref.current.expand();
ref.current.resize(restoredSizePercent);
};
const hasPanelToggleChanged = previousPanelOpenRef.current !== isPanelOpen;
previousPanelOpenRef.current = isPanelOpen;
if (hasPanelToggleChanged) {
setIsToggleAnimating(true);
const animationFrameId = requestAnimationFrame(() => {
syncPanelState();
});
const timeoutId = setTimeout(() => {
setIsToggleAnimating(false);
}, 180);
return () => {
window.cancelAnimationFrame(animationFrameId);
window.clearTimeout(timeoutId);
};
if (!ref.current || !isDesktop) {
return;
}
if (isPanelOpen) {
ref.current.expand();
} else {
ref.current.collapse();
}
}, [isPanelOpen, isDesktop]);
syncPanelState();
if (!isDesktop || !isPanelOpen) {
// Keep pixel width constant on window resize
useEffect(() => {
if (!isDesktop) {
return;
}
const handleResize = () => {
syncPanelState();
const newPercent = pxToPercent(savedWidthPxRef.current);
setPanelSizePercent(newPercent);
if (ref.current) {
ref.current.resize?.(newPercent - (ref.current.getSize() || 0));
}
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, [isDesktop, isPanelOpen, minPanelSizePercent, maxPanelSizePercent]);
}, [isDesktop]);
const handleResize = (sizePercent: number) => {
if (isDesktop && sizePercent > 0) {
const widthPx = (sizePercent / 100) * window.innerWidth;
savedWidthPxRef.current = widthPx;
}
const widthPx = (sizePercent / 100) * window.innerWidth;
savedWidthPxRef.current = widthPx;
setPanelSizePercent(sizePercent);
};
return (
@@ -105,21 +91,19 @@ export const ResizableLeftPanel = ({
<Panel
ref={ref}
className="--docs--resizable-left-panel"
order={0}
collapsible
collapsible={!isPanelOpen}
collapsedSize={0}
style={{
overflow: 'hidden',
transition: isToggleAnimating ? PANEL_TOGGLE_TRANSITION : 'none',
transition: isDragging
? 'none'
: 'flex var(--c--globals--transitions--duration) var(--c--globals--transitions--ease-out)',
}}
order={0}
defaultSize={
isDesktop
? Math.max(
minPanelSizePercent,
Math.min(
pxToPercent(savedWidthPxRef.current),
maxPanelSizePercent,
),
Math.min(panelSizePercent, maxPanelSizePercent),
)
: 0
}
@@ -129,25 +113,20 @@ export const ResizableLeftPanel = ({
>
{leftPanel}
</Panel>
<PanelResizeHandle
style={{
borderRightWidth: '1px',
borderRightStyle: 'solid',
borderRightColor: 'var(--c--contextuals--border--surface--primary)',
width: '1px',
cursor: 'col-resize',
}}
disabled={!isDesktop || !isPanelOpen}
/>
<Panel
order={1}
style={{
transition: isToggleAnimating ? PANEL_TOGGLE_TRANSITION : 'none',
}}
>
{children}
</Panel>
{isPanelOpen && (
<PanelResizeHandle
style={{
borderRightWidth: '1px',
borderRightStyle: 'solid',
borderRightColor: 'var(--c--contextuals--border--surface--primary)',
width: '1px',
cursor: 'col-resize',
}}
onDragging={setIsDragging}
disabled={!isDesktop}
/>
)}
<Panel order={1}>{children}</Panel>
</PanelGroup>
);
};
@@ -1,3 +1,4 @@
export * from './LeftPanel';
export * from './LeftPanelCollapseButton';
export * from './LeftPanelToggleMobile';
export * from './ResizableLeftPanel';
@@ -1,31 +1,55 @@
import { create } from 'zustand';
type TogglePanelType = { type: 'desktop' | 'mobile' };
type TogglePanelArgs = {
value?: boolean;
} & Partial<TogglePanelType>;
interface LeftPanelState {
isPanelOpen: boolean;
isPanelOpenMobile: boolean;
togglePanel: (value?: boolean) => void;
closePanel: () => void;
togglePanel: (args?: TogglePanelArgs) => void;
closePanel: (args?: TogglePanelType) => void;
}
export const useLeftPanelStore = create<LeftPanelState>((set, get) => ({
isPanelOpen: true,
isPanelOpenMobile: false,
togglePanel: (value?: boolean) => {
if (value === true) {
set({ isPanelOpen: true });
return;
}
if (value === false) {
set({ isPanelOpen: false, isPanelOpenMobile: false });
togglePanel: ({ value, type }: TogglePanelArgs = {}) => {
if (typeof value === 'boolean') {
if (type === 'mobile') {
set({ isPanelOpenMobile: value });
return;
}
if (type === 'desktop') {
set({ isPanelOpen: value });
return;
}
set({ isPanelOpen: value, isPanelOpenMobile: value });
return;
}
const { isPanelOpen, isPanelOpenMobile } = get();
set({
isPanelOpen: !isPanelOpen,
isPanelOpenMobile: !isPanelOpenMobile,
});
if (type === 'mobile') {
set({ isPanelOpenMobile: !isPanelOpenMobile });
return;
}
if (type === 'desktop') {
set({ isPanelOpen: !isPanelOpen });
return;
}
set({ isPanelOpen: !isPanelOpen, isPanelOpenMobile: !isPanelOpenMobile });
},
closePanel: () => {
closePanel: ({ type }: Partial<TogglePanelType> = {}) => {
if (type === 'mobile') {
set({ isPanelOpenMobile: false });
return;
}
if (type === 'desktop') {
set({ isPanelOpen: false });
return;
}
set({ isPanelOpen: false, isPanelOpenMobile: false });
},
}));
@@ -741,11 +741,14 @@
"Cancel": "Annuler",
"Cancel the deletion": "Annuler la suppression",
"Cancel the download": "Annuler le téléchargement",
"Cancel the move": "Annuler le déplacement",
"Change role for {{email}}": "Changer le rôle pour {{email}}",
"Change role for {{name}}": "Changer le rôle pour {{name}}",
"Choose the new location for <strong>{{title}}</strong>.": "Choisissez le nouvel emplacement pour <strong>{{title}}</strong>.",
"Close the access request modal": "Fermer la fenêtre modale de demande d'accès",
"Close the delete modal": "Fermer la fenêtre modale de suppression",
"Close the download modal": "Fermer la fenêtre modale de téléchargement",
"Close the move modal": "Fermer",
"Close the search modal": "Fermer la fenêtre modale de recherche",
"Close the share modal": "Fermer la fenêtre modale de partage",
"Close the version history modal": "Fermer la fenêtre modale de l'historique des versions",
@@ -858,15 +861,22 @@
"List request access card": "Carte de liste des demandes d'accès",
"List search user result card": "Liste des résultats de la recherche utilisateur",
"Load more": "Afficher plus",
"Loading documents...": "Chargement des documents...",
"Log in to access the document.": "Connectez-vous pour accéder au document.",
"Login": "Connexion",
"Logout": "Se déconnecter",
"Main content": "Contenu principal",
"Modal confirmation for moving a document": "Confirmation pour déplacer un document",
"Modal confirmation to download the attachment": "Modale de confirmation pour télécharger la pièce jointe",
"More docs": "Plus de documents",
"More options": "Plus d'options",
"Move": "Déplacer",
"Move Modal": "Déplacer",
"Move document": "Déplacer le document",
"Move here": "Déplacer ici",
"Move into a doc": "Déplacer dans un doc",
"Move modal": "Déplacer",
"Move the document to the selected location": "Déplacer le document vers l'emplacement sélectionné",
"Move to my docs": "Déplacer vers mes docs",
"My docs": "Mes documents",
"Name": "Nom",
@@ -916,12 +926,14 @@
"Rename": "Renommer",
"Rephrase": "Reformuler",
"Request access": "Demander l'accès",
"Request access modal": "Demande d'accès",
"Reset": "Réinitialiser",
"Restore": "Restaurer",
"Root document {{title}}": "Document racine pour le {{title}}",
"Search": "Rechercher",
"Search by title": "Recherchez par titre",
"Search docs": "Rechercher des docs",
"Search for a doc": "Rechercher un document",
"Search modal": "Modale de partage",
"Search results": "Résultats de la recherche",
"Search user result": "Résultat de la recherche utilisateur",
@@ -989,6 +1001,8 @@
"You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.": "Vous êtes le seul propriétaire de ce groupe, faites d'un autre membre le propriétaire du groupe, avant de pouvoir modifier votre propre rôle ou vous supprimer du document.",
"You can view this document but need additional access to see its members or modify settings.": "Vous pouvez voir ce document mais vous avez besoin d'un accès supplémentaire pour voir ses membres ou modifier les paramètres.",
"You cannot restrict access to a subpage relative to its parent page.": "Vous ne pouvez pas restreindre l'accès à une sous-page par rapport à sa page parente.",
"You don't have permission to move this document to <strong>{{targetDocumentTitle}}</strong>. You need edit access to the destination. Request access, then try again.": "Vous n'êtes pas autorisé à déplacer ce document vers <strong>{{targetDocumentTitle}}</strong>. Vous devez disposer d'un accès en modification au dossier de destination. Veuillez demander l'accès, puis réessayer.",
"You have already requested access to this document.": "Vous avez déjà demandé l'accès à ce document.",
"You must be at least the administrator of the target document": "Vous devez être au moins l'administrateur du document cible",
"You must be the owner to move the document": "Vous devez être le propriétaire pour déplacer le document",
"You're currently viewing a sub-document. To gain access, please request permission from the main document.": "Vous visualisez actuellement un sous-document. Pour obtenir un accès, veuillez demander la permission du document principal.",
@@ -1156,7 +1170,12 @@
"401 Unauthorized": "401 Ongeautoriseerd",
"A new way to organize knowledge.": "Een nieuwe manier om kennis te organiseren.",
"AI Actions": "AI Actie",
"AI is thinking": "AI denkt na",
"AI is writing": "AI schrijft",
"AI request failed": "AI verzoek mislukt",
"AI response ready for review": "AI reactie klaar voor beoordeling",
"AI seems busy! Please try again.": "AI is bezig! Probeer het later opnieuw.",
"Accept anyway": "Toch accepteren",
"Access Denied - Error 403": "Toegang geweigerd - Fout 403",
"Access Requests": "Toegangsverzoeken",
"Access request sent successfully.": "Toegangsverzoek is succesvol verzonden.",
@@ -1173,6 +1192,7 @@
"All docs": "Alle documenten",
"An error occurred during email validation.": "Er is een fout opgetreden tijdens de e-mailvalidatie.",
"An error occurred while restoring the document: {{error}}": "Er is een fout opgetreden tijdens het herstellen van het document: {{error}}",
"An error occurred...": "Er is een fout opgetreden...",
"An uncompromising writing experience.": "Een compromisloze schrijfervaring.",
"An unexpected error occurred.": "Er deed zich een onverwachte fout voor.",
"Analyzing file...": "Bestand analyseren...",
@@ -1183,6 +1203,8 @@
"Anyone with the link can view the document if they are logged in": "Iedereen met deze link kan het document zien, mits ze ingelogd zijn",
"Approve": "Goedkeuren",
"As this is a sub-document, please request access to the parent document to enable these features.": "Aangezien dit een subdocument is, vraag om toegang tot het bovenliggende document om deze functies in te schakelen.",
"Ask AI": "Vraag AI",
"Ask anything...": "Stel een vraag...",
"Available soon": "Binnenkort beschikbaar",
"Back to homepage": "Terug naar startpagina",
"Banner image": "Banner afbeelding",
@@ -1193,11 +1215,14 @@
"Cancel": "Annuleren",
"Cancel the deletion": "Verwijderen annuleren",
"Cancel the download": "Annuleer de download",
"Cancel the move": "Annuleer verplaatsen",
"Change role for {{email}}": "Rol wijzigen voor {{email}}",
"Change role for {{name}}": "Rol wijzigen voor {{name}}",
"Choose the new location for <strong>{{title}}</strong>.": "Kies de nieuwe locatie voor <strong>{{title}}</strong>.",
"Close the access request modal": "Sluit het toegangsverzoek venster",
"Close the delete modal": "Sluit het verwijder venster",
"Close the download modal": "Sluit het downloadvenster",
"Close the move modal": "Sluit het deelvenster",
"Close the search modal": "Sluit het zoekvenster",
"Close the share modal": "Sluit het deelvenster",
"Close the version history modal": "Sluit versie geschiedenis venster",
@@ -1310,15 +1335,22 @@
"List request access card": "Kaart voor de lijst van toegangsverzoeken",
"List search user result card": "Gebruikersresultaten weergeven op zoekkaart",
"Load more": "Laad meer",
"Loading documents...": "Document wordt geladen...",
"Log in to access the document.": "Log in om toegang tot het document te krijgen.",
"Login": "Inloggen",
"Logout": "Uitloggen",
"Main content": "Hoofdinhoud",
"Modal confirmation for moving a document": "Modal bevestiging voor het verplaatsen van een document",
"Modal confirmation to download the attachment": "Bevestiging om bijlage te downloaden",
"More docs": "Meer documenten",
"More options": "Meer opties",
"Move": "Verplaats",
"Move Modal": "Modal verplaatsen",
"Move document": "Document verplaatsen",
"Move here": "Hierheen verplaatsen",
"Move into a doc": "Verplaats naar een doc",
"Move modal": "Modal verplaatsen",
"Move the document to the selected location": "Verplaats het document naar de geselecteerde locatie",
"Move to my docs": "Verplaatsen naar mijn documenten",
"My docs": "Mijn documenten",
"Name": "Naam",
@@ -1368,12 +1400,14 @@
"Rename": "Hernoem",
"Rephrase": "Herschrijf",
"Request access": "Toegang aanvragen",
"Request access modal": "Verzoek toegang modal",
"Reset": "Herstellen",
"Restore": "Herstel",
"Root document {{title}}": "Hoofddocument {{title}}",
"Search": "Zoeken",
"Search by title": "Zoek op titel",
"Search docs": "Zoek documenten",
"Search for a doc": "Een doc zoeken",
"Search modal": "Zoekvenster",
"Search results": "Zoekresultaten",
"Search user result": "Zoekresultaten",
@@ -1398,6 +1432,7 @@
"Simple document icon": "Eenvoudig documentpictogram",
"Something bad happens, please retry.": "Een fout heeft plaatsgevonden, probeer het opnieuw.",
"Start Writing": "Begin met schrijven",
"Stop": "Stop",
"Summarize": "Vat samen",
"Summary": "Samenvatting",
"The antivirus has detected an anomaly in your file.": "Antivirus heeft een afwijking in uw bestand ontdekt.",
@@ -1411,6 +1446,7 @@
"The document visibility restored.": "De zichtbaarheid van het document is hersteld.",
"The export failed": "Het exporteren is mislukt",
"The link sharing rules differ from the parent document": "De regels voor het delen via een link verschillen van het bovenliggende document",
"Thinking...": "Denken...",
"This document and <strong>any sub-documents</strong> will be placed in the trashbin. You can restore it within {{days}} days.": "Dit document en <strong>alle sub-documenten</strong> zullen in de prullenbak worden geplaatst. Je kunt het binnen {{days}} dagen herstellen.",
"This document will be placed in the trashbin. You can restore it within {{days}} days.": "Dit document zal worden geplaatst in de prullenbak. U kunt het herstellen binnen {{days}} dagen.",
"This file is flagged as unsafe.": "Het bestand is gemarkeerd als onveilig.",
@@ -1435,9 +1471,12 @@
"Warning": "Waarschuwing",
"Why you can't edit the document?": "Waarom kunt u het document niet bewerken?",
"Write": "Schrijf",
"Writing...": "Schrijven...",
"You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.": "U bent de enige eigenaar van deze groep, maak een ander lid de groepseigenaar voordat u uw eigen rol kunt wijzigen of kan worden verwijderd van het document.",
"You can view this document but need additional access to see its members or modify settings.": "U kunt dit document bekijken, maar u heeft extra toegang nodig om de leden te kunnen bekijken of de instellingen aan te passen.",
"You cannot restrict access to a subpage relative to its parent page.": "U kunt de toegang niet beperken tot een subpagina met betrekking tot de bovenliggende pagina.",
"You don't have permission to move this document to <strong>{{targetDocumentTitle}}</strong>. You need edit access to the destination. Request access, then try again.": "U bent niet gemachtigd om dit document te verplaatsen naar <strong>{{targetDocumentTitle}}</strong>. U moet de toegang tot de bestemming bewerken. Vraag toegang aan en probeer het opnieuw.",
"You have already requested access to this document.": "U hebt al toegang gevraagd tot dit document.",
"You must be at least the administrator of the target document": "U moet tenminste beheerder zijn van het doeldocument",
"You must be the owner to move the document": "U moet de eigenaar zijn om het document te verplaatsen",
"You're currently viewing a sub-document. To gain access, please request permission from the main document.": "U bekijkt momenteel een subdocument. Om toegang te krijgen, vraagt u toestemming voor het hoofddocument.",
@@ -1513,11 +1552,14 @@
"Cancel": "Отмена",
"Cancel the deletion": "Отменить удаление",
"Cancel the download": "Отменить загрузку",
"Cancel the move": "Отмена перемещения",
"Change role for {{email}}": "Изменить роль для {{email}}",
"Change role for {{name}}": "Изменить роль для {{name}}",
"Choose the new location for <strong>{{title}}</strong>.": "Выберите новое расположение для <strong>{{title}}</strong>.",
"Close the access request modal": "Закрыть окно запроса доступа",
"Close the delete modal": "Закрыть окно удаления",
"Close the download modal": "Закрыть окно загрузки",
"Close the move modal": "Закрыть",
"Close the search modal": "Закрыть окно поиска",
"Close the share modal": "Закрыть окно общего доступа",
"Close the version history modal": "Закрыть историю версии",
@@ -1630,15 +1672,22 @@
"List request access card": "Карта списка запроса доступа",
"List search user result card": "Карта списка результатов поиска пользователей",
"Load more": "Загрузить ещё",
"Loading documents...": "Загрузка документов...",
"Log in to access the document.": "Войдите, чтобы получить доступ к документу.",
"Login": "Войти",
"Logout": "Выйти",
"Main content": "Основное содержимое",
"Modal confirmation for moving a document": "Подтверждение перемещения документа",
"Modal confirmation to download the attachment": "Подтверждение загрузки вложения",
"More docs": "Больше документов",
"More options": "Больше параметров",
"Move": "Переместить",
"Move Modal": "Перемещение",
"Move document": "Переместить документ",
"Move here": "Переместить сюда",
"Move into a doc": "Перенести в документ",
"Move modal": "Перемещение",
"Move the document to the selected location": "Переместить документ в выбранное место",
"Move to my docs": "Переместить в мои документы",
"My docs": "Мои документы",
"Name": "Название",
@@ -1688,12 +1737,14 @@
"Rename": "Переименовать",
"Rephrase": "Переформулировать",
"Request access": "Запрос доступа",
"Request access modal": "Запрос доступа",
"Reset": "Сброс",
"Restore": "Восстановить",
"Root document {{title}}": "Корневой документ {{title}}",
"Search": "Поиск",
"Search by title": "Поиск по названию",
"Search docs": "Поиск документов",
"Search for a doc": "Поиск документов",
"Search modal": "Поиск",
"Search results": "Результаты поиска",
"Search user result": "Результат поиска пользователя",
@@ -1761,6 +1812,8 @@
"You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.": "Вы единственный владелец этой группы. Прежде чем вы измените свою собственную роль или потеряете доступ к документу, назначьте другого владельца группы.",
"You can view this document but need additional access to see its members or modify settings.": "Вы можете просмотреть этот документ, но для получения списка участников или изменения настроек требуются дополнительные разрешения.",
"You cannot restrict access to a subpage relative to its parent page.": "Нельзя ограничить доступ ко вложенной странице относительно родительской страницы.",
"You don't have permission to move this document to <strong>{{targetDocumentTitle}}</strong>. You need edit access to the destination. Request access, then try again.": "У вас нет разрешения на перемещение этого документа в <strong>{{targetDocumentTitle}}</strong>. Вам необходимы права на редактирование в месте назначения. Запросите доступ, а затем попробуйте снова.",
"You have already requested access to this document.": "Вы уже запросили доступ к этому документу.",
"You must be at least the administrator of the target document": "Вы должны быть как минимум администратором целевого документа",
"You must be the owner to move the document": "Для перемещения документа вы должны быть его владельцем",
"You're currently viewing a sub-document. To gain access, please request permission from the main document.": "Сейчас вы просматриваете вложенный документ. Чтобы получить доступ, запросите разрешение для основного документа.",
@@ -1937,11 +1990,14 @@
"Cancel": "Скасувати",
"Cancel the deletion": "Скасувати видалення",
"Cancel the download": "Скасувати завантаження",
"Cancel the move": "Скасувати переміщення",
"Change role for {{email}}": "Змінити роль для {{email}}",
"Change role for {{name}}": "Змінити роль для {{name}}",
"Choose the new location for <strong>{{title}}</strong>.": "Виберіть нове розташування для <strong>{{title}}</strong>.",
"Close the access request modal": "Закрити вікно запиту доступу",
"Close the delete modal": "Закрити вікно видалення",
"Close the download modal": "Закрити вікно завантаження",
"Close the move modal": "Закрити",
"Close the search modal": "Закрити вікно пошуку",
"Close the share modal": "Закрити вікно загального доступу",
"Close the version history modal": "Закрити історію версій",
@@ -2054,15 +2110,22 @@
"List request access card": "Картка списку запитів доступу",
"List search user result card": "Картка списку результатів пошуку користувача",
"Load more": "Завантажити ще",
"Loading documents...": "Завантаження документів...",
"Log in to access the document.": "Увійдіть, щоб отримати доступ до документа.",
"Login": "Увійти",
"Logout": "Вийти",
"Main content": "Основний вміст",
"Modal confirmation for moving a document": "Підтвердження переміщення документа",
"Modal confirmation to download the attachment": "Підтвердження завантаження вкладень",
"More docs": "Більше документів",
"More options": "Більше параметрів",
"Move": "Переміщення",
"Move Modal": "Переміщення",
"Move document": "Переміщення документа",
"Move here": "Перемістити сюди",
"Move into a doc": "Перемістити документ",
"Move modal": "Переміщення",
"Move the document to the selected location": "Перемістити документ до вибраного розташування",
"Move to my docs": "Перемістити до моїх документів",
"My docs": "Мої документи",
"Name": "Назва",
@@ -2112,12 +2175,14 @@
"Rename": "Перейменувати",
"Rephrase": "Перефразувати",
"Request access": "Запит доступу",
"Request access modal": "Запит доступу",
"Reset": "Скинути",
"Restore": "Відновити",
"Root document {{title}}": "Корінь документа {{title}}",
"Search": "Пошук",
"Search by title": "Пошук за назвою",
"Search docs": "Пошук документів",
"Search for a doc": "Пошук документу",
"Search modal": "Підтвердження пошуку",
"Search results": "Результати пошуку",
"Search user result": "Результат пошуку користувача",
@@ -2185,6 +2250,8 @@
"You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.": "Ви єдиний власник цієї групи, призначте іншого учасника її власником. Інакше ви не зможете змінити власну роль або вийти з команди редакторів документу.",
"You can view this document but need additional access to see its members or modify settings.": "Ви можете переглянути цей документ, але для перегляду його учасників або зміни налаштувань потрібні додаткові дозволи.",
"You cannot restrict access to a subpage relative to its parent page.": "Не можна обмежити доступ до вкладеної сторінки відносно її батьківської сторінки.",
"You don't have permission to move this document to <strong>{{targetDocumentTitle}}</strong>. You need edit access to the destination. Request access, then try again.": "У вас немає дозволу на переміщення цього документа до <strong>{{targetDocumentTitle}}</strong>. Вам потрібен доступ для редагування у цьому місці призначення. Запросіть доступ і повторіть спробу.",
"You have already requested access to this document.": "Ви вже просили доступ до цього документа.",
"You must be at least the administrator of the target document": "Ви повинні бути принаймні адміністратором цільового документа",
"You must be the owner to move the document": "Для переміщення документа ви повинні бути його власником",
"You're currently viewing a sub-document. To gain access, please request permission from the main document.": "Зараз ви переглядаєте вкладений документ. Щоб отримати доступ, будь ласка, запитайте дозвіл від головного документа.",
@@ -56,16 +56,18 @@ export class Analytics {
/**
* Check if a feature flag is activated
*
* Feature flags are activated if at least one analytic is activated
* because we don't want to hide feature if the user does not
* use analytics (AB testing, etc)
* A feature flag is considered active only if ALL analytics agree it is.
* This ensures that if one analytic explicitly disables a flag,
* it takes precedence over analytics that do not manage flags.
* If no analytics are registered, default to true so features are not hidden
* when analytics are not configured.
*/
public static isFeatureFlagActivated(flagName: string): boolean {
if (!Analytics.analytics.length) {
return true;
}
return Analytics.analytics.some((analytic) =>
return Analytics.analytics.every((analytic) =>
analytic.isFeatureFlagActivated(flagName),
);
}
@@ -0,0 +1,95 @@
import { render, screen } from '@testing-library/react';
import React, { Fragment } from 'react';
import { AbstractAnalytic, Analytics, useAnalytics } from '@/libs';
import { AppWrapper } from '@/tests/utils';
class TestAnalytic1 extends AbstractAnalytic {
public constructor() {
super();
}
public Provider() {
return <Fragment />;
}
public trackEvent() {}
public isFeatureFlagActivated(flagName: string): boolean {
if (flagName === 'test-flag') {
return false;
}
if (flagName === 'test-flag2') {
return true;
}
return true;
}
}
class TestAnalytic2 extends AbstractAnalytic {
public constructor() {
super();
}
public Provider() {
return <Fragment />;
}
public trackEvent() {}
public isFeatureFlagActivated(): boolean {
return true;
}
}
const TestComponent = ({ flag }: { flag: string }) => {
const { isFeatureFlagActivated } = useAnalytics();
return (
<div>
{isFeatureFlagActivated(flag) ? (
<span>Feature is enabled</span>
) : (
<span>Feature is not enabled</span>
)}
</div>
);
};
describe('Analytics feature flag', () => {
beforeEach(() => {
Analytics.clearAnalytics();
});
test('renders feature when feature flag is not existing', async () => {
new TestAnalytic1();
new TestAnalytic2();
render(<TestComponent flag="unexisting-flag" />, {
wrapper: AppWrapper,
});
expect(await screen.findByText('Feature is enabled')).toBeInTheDocument();
});
test('renders feature when feature flag is not enabled', async () => {
new TestAnalytic1();
new TestAnalytic2();
render(<TestComponent flag="test-flag" />, {
wrapper: AppWrapper,
});
expect(screen.getByText('Feature is not enabled')).toBeInTheDocument();
});
test('renders feature when feature flag is enabled', async () => {
new TestAnalytic1();
new TestAnalytic2();
render(<TestComponent flag="test-flag2" />, {
wrapper: AppWrapper,
});
expect(screen.getByText('Feature is enabled')).toBeInTheDocument();
});
});
@@ -1,2 +1,3 @@
export * from './useBroadcastStore';
export * from './useFocusStore';
export * from './useResponsiveStore';
@@ -0,0 +1,23 @@
import { create } from 'zustand';
interface UseFocusStore {
lastFocusedElement: HTMLElement | null;
addLastFocus: (target: HTMLElement | null) => void;
restoreFocus: () => void;
}
export const useFocusStore = create<UseFocusStore>((set, get) => ({
lastFocusedElement: null,
addLastFocus: (target) => set({ lastFocusedElement: target }),
restoreFocus: () => {
const { lastFocusedElement } = get();
if (!lastFocusedElement) {
return;
}
requestAnimationFrame(() => {
lastFocusedElement.focus();
});
set({ lastFocusedElement: null });
},
}));
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "impress",
"version": "4.6.0",
"version": "4.7.0",
"private": true,
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-docs",
"version": "4.6.0",
"version": "4.7.0",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "packages-i18n",
"version": "4.6.0",
"version": "4.7.0",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "server-y-provider",
"version": "4.6.0",
"version": "4.7.0",
"description": "Y.js provider for docs",
"repository": "https://github.com/suitenumerique/docs",
"license": "MIT",
@@ -16,7 +16,7 @@
"node": ">=22"
},
"dependencies": {
"@blocknote/server-util": "0.47.0",
"@blocknote/server-util": "0.47.1",
"@hocuspocus/server": "3.4.4",
"@sentry/node": "10.38.0",
"@sentry/profiling-node": "10.38.0",
@@ -30,7 +30,7 @@
"yjs": "*"
},
"devDependencies": {
"@blocknote/core": "0.47.0",
"@blocknote/core": "0.47.1",
"@hocuspocus/provider": "3.4.4",
"@types/cors": "2.8.19",
"@types/express": "5.0.6",
+97 -70
View File
@@ -1262,12 +1262,12 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@blocknote/code-block@0.47.0":
version "0.47.0"
resolved "https://registry.yarnpkg.com/@blocknote/code-block/-/code-block-0.47.0.tgz#68954506e8ca75cb18746fd1eee34e29145718be"
integrity sha512-4FD3NoKWgfXPERKdOQYMYRvbzaFKZYYus+K248taUD47kuQSi8EiHsXHdJqMKJpKY+fGDlO8fC7wqdOYcIfgBQ==
"@blocknote/code-block@0.47.1":
version "0.47.1"
resolved "https://registry.yarnpkg.com/@blocknote/code-block/-/code-block-0.47.1.tgz#6497f6d40b6756c7202e00d8d99974924c8b4967"
integrity sha512-gZmLkJupX0uKukTvEiNjcW9FRJW5iNoAaEYF1SGMbBw2cTi+eC+Nyqjgy+8MF7k5A87/TgoA8RQPqNqrRWyHlg==
dependencies:
"@blocknote/core" "0.47.0"
"@blocknote/core" "0.47.1"
"@shikijs/core" "^3"
"@shikijs/engine-javascript" "^3"
"@shikijs/langs" "^3"
@@ -1275,10 +1275,10 @@
"@shikijs/themes" "^3"
"@shikijs/types" "^3"
"@blocknote/core@0.47.0":
version "0.47.0"
resolved "https://registry.yarnpkg.com/@blocknote/core/-/core-0.47.0.tgz#9f25c8ddddafe5677b97fe1e6a238f3303d8bd2d"
integrity sha512-i8QwAA8z/T97ZKPQy/8GTUicy3xBZy7xcR0fH/qA2xQq0xIaE7AZT92nS1sDvohxUsTJVNQBZ/zkpGKGj4Eq8g==
"@blocknote/core@0.47.1":
version "0.47.1"
resolved "https://registry.yarnpkg.com/@blocknote/core/-/core-0.47.1.tgz#27db936db487f97a981827d6f72bbf3cccfa00b7"
integrity sha512-jRDi7yJPSHQ2Ks1inGQ41dATvaaWF1Y+XP7WlO0hcl45MNsGbUYCdyB1/CZS8nzYfT/Gkof6aR472gstj6ztVw==
dependencies:
"@emoji-mart/data" "^1.2.1"
"@handlewithcare/prosemirror-inputrules" "^0.1.4"
@@ -1321,24 +1321,24 @@
y-protocols "^1.0.6"
yjs "^13.6.27"
"@blocknote/mantine@0.47.0":
version "0.47.0"
resolved "https://registry.yarnpkg.com/@blocknote/mantine/-/mantine-0.47.0.tgz#02855819255a45aca1b8362c817ab385a43484c7"
integrity sha512-qRntaUUNzueAa3bm0xRqpDq5HH4+JKI6TGZtsYQB/ENIiiIDGL2ow/GSqAViTo1PmQeCtDyQOPoag2p3bSR7kQ==
"@blocknote/mantine@0.47.1":
version "0.47.1"
resolved "https://registry.yarnpkg.com/@blocknote/mantine/-/mantine-0.47.1.tgz#2cd638812fab8d6d9a0c7cb125fad03a438afc85"
integrity sha512-lcmHo/o35lVrCoY6qhTLn3Rs+tiZTktG2fYffe8LK4/qqs1hId4Xp2pbARLOGnDEREKvCL1PUZ9JKifgzA0DhQ==
dependencies:
"@blocknote/core" "0.47.0"
"@blocknote/react" "0.47.0"
"@blocknote/core" "0.47.1"
"@blocknote/react" "0.47.1"
react-icons "^5.5.0"
"@blocknote/react@0.47.0":
version "0.47.0"
resolved "https://registry.yarnpkg.com/@blocknote/react/-/react-0.47.0.tgz#c57b60b5fb7afc0cff73f37f53ebab22ffb995bc"
integrity sha512-RgLCrccVCVySlnxSKoJfu1nHcJeahpgZQeQrLnVBqLMXicy4nB5vy5QYh5qQGFl7vaRGN3RVduDAZfOp96TF1g==
"@blocknote/react@0.47.1":
version "0.47.1"
resolved "https://registry.yarnpkg.com/@blocknote/react/-/react-0.47.1.tgz#5c55389f5d6b4a9ded658ebf1a4728b7364a623c"
integrity sha512-g+bfcjPuCSFmx+AMHJCBGFXPzaIzzXmidPXJGEMKZjvdjPNDr78+SnuYd3YU05+BleHQ+S2sBl6S8sBaYxFFNw==
dependencies:
"@blocknote/core" "0.47.0"
"@blocknote/core" "0.47.1"
"@emoji-mart/data" "^1.2.1"
"@floating-ui/react" "^0.27.16"
"@floating-ui/utils" "0.2.10"
"@floating-ui/react" "^0.27.18"
"@floating-ui/utils" "^0.2.10"
"@tanstack/react-store" "0.7.7"
"@tiptap/core" "^3.13.0"
"@tiptap/pm" "^3.13.0"
@@ -1350,13 +1350,13 @@
react-icons "^5.5.0"
use-sync-external-store "1.6.0"
"@blocknote/server-util@0.47.0":
version "0.47.0"
resolved "https://registry.yarnpkg.com/@blocknote/server-util/-/server-util-0.47.0.tgz#6fd555c68eb3ba19969ac2836459470700e2a7dd"
integrity sha512-vPE9U6FZ3FIfREs0bpmv3CLZyHnC5N43aSjaomu5OiqvBoDIRnXgoMvKHksBT8T0ZMuBl7JKW5ibPfoGYbnNZg==
"@blocknote/server-util@0.47.1":
version "0.47.1"
resolved "https://registry.yarnpkg.com/@blocknote/server-util/-/server-util-0.47.1.tgz#385d6e72ce5ea2078f78945bfcc327cd5cc01f42"
integrity sha512-WRNek+6mcqB7T+VJsElFTB1Fb4uVmxxRgJMwFF9LcxZDKP+GyyjASG+N0qaAi8qaSUh47FgZaWE2lkMYivUisg==
dependencies:
"@blocknote/core" "0.47.0"
"@blocknote/react" "0.47.0"
"@blocknote/core" "0.47.1"
"@blocknote/react" "0.47.1"
"@tiptap/core" "^3.13.0"
"@tiptap/pm" "^3.13.0"
jsdom "^25.0.1"
@@ -1364,17 +1364,17 @@
y-protocols "^1.0.6"
yjs "^13.6.27"
"@blocknote/xl-ai@0.47.0":
version "0.47.0"
resolved "https://registry.yarnpkg.com/@blocknote/xl-ai/-/xl-ai-0.47.0.tgz#fff198e65b05758ef1776bb24be4acc93917143a"
integrity sha512-czx1DyO5bMy2nBCQxesbMQyEZhFpqA6pRUyhnhUENHKievlUZgymRAH3DyO3Yl3STdZkwAiWQDkhVIQiecRNwA==
"@blocknote/xl-ai@0.47.1":
version "0.47.1"
resolved "https://registry.yarnpkg.com/@blocknote/xl-ai/-/xl-ai-0.47.1.tgz#6e0057b502f4890600aa5252fa5622d139f905b3"
integrity sha512-eW5oB7QI9t7D5Ijl7+C+fHYjJIfR1cS1j12IjvBV4mkZvbDEiLT67cDOdCmqRDWsybN4rFSjs6o5J3GrP2AXxQ==
dependencies:
"@ai-sdk/provider-utils" "^4.0.2"
"@ai-sdk/react" "^3.0.5"
"@blocknote/core" "0.47.0"
"@blocknote/mantine" "0.47.0"
"@blocknote/react" "0.47.0"
"@floating-ui/react" "^0.26.28"
"@blocknote/core" "0.47.1"
"@blocknote/mantine" "0.47.1"
"@blocknote/react" "0.47.1"
"@floating-ui/react" "^0.27.18"
"@handlewithcare/prosemirror-suggest-changes" "^0.1.8"
"@tiptap/core" "^3.13.0"
ai "^6.0.5"
@@ -1394,24 +1394,24 @@
unified "^11.0.5"
y-prosemirror "^1.3.7"
"@blocknote/xl-docx-exporter@0.47.0":
version "0.47.0"
resolved "https://registry.yarnpkg.com/@blocknote/xl-docx-exporter/-/xl-docx-exporter-0.47.0.tgz#b78ee9082538410efc5867cf276ec325a632ef23"
integrity sha512-qYldOglAi37ggiNaOA1avTG59AewvB0fGfHpVsQlrIYl4iVkm7CaqIco2OQg0OSMDd//M3i8SsOzgX5eRriXsg==
"@blocknote/xl-docx-exporter@0.47.1":
version "0.47.1"
resolved "https://registry.yarnpkg.com/@blocknote/xl-docx-exporter/-/xl-docx-exporter-0.47.1.tgz#7b1b39d9793b1078849f77d508f4c9969b416cff"
integrity sha512-MD5kTlH/EQXfLkpQD7h3D2ht4b+FSPI20w8ogd9p768BlDO84rWaw4ne1RUTWKZXcZqVR9KLXe2XgkoPTt7dTg==
dependencies:
"@blocknote/core" "0.47.0"
"@blocknote/xl-multi-column" "0.47.0"
"@blocknote/core" "0.47.1"
"@blocknote/xl-multi-column" "0.47.1"
buffer "^6.0.3"
docx "^9.5.1"
image-meta "^0.2.2"
"@blocknote/xl-multi-column@0.47.0":
version "0.47.0"
resolved "https://registry.yarnpkg.com/@blocknote/xl-multi-column/-/xl-multi-column-0.47.0.tgz#cd71dbacd6d7b2ff9d10440f5c7730779c280c03"
integrity sha512-UkVx3y5M/y9S2p3jijylh+aXJIrh6babBkQvGMd95El/qwjZs5pJKUg/wApOtyrhaP0genWdWLNka6ag0cD9aA==
"@blocknote/xl-multi-column@0.47.1":
version "0.47.1"
resolved "https://registry.yarnpkg.com/@blocknote/xl-multi-column/-/xl-multi-column-0.47.1.tgz#6d9de986c1ffa0db31ae9e19876a27e09afd1774"
integrity sha512-Ksxrt+002/c+DJqCLJuk/xeTEJ8iZSTxzBeDsO5kuUp2h5B2uMvMJ+Fj43SCtcaJYEML7fdXrQmxh6BTYs4dEg==
dependencies:
"@blocknote/core" "0.47.0"
"@blocknote/react" "0.47.0"
"@blocknote/core" "0.47.1"
"@blocknote/react" "0.47.1"
"@tiptap/core" "^3.13.0"
prosemirror-model "^1.25.4"
prosemirror-state "^1.4.4"
@@ -1420,25 +1420,25 @@
prosemirror-view "^1.41.4"
react-icons "^5.5.0"
"@blocknote/xl-odt-exporter@0.47.0":
version "0.47.0"
resolved "https://registry.yarnpkg.com/@blocknote/xl-odt-exporter/-/xl-odt-exporter-0.47.0.tgz#d8a510d2b7ca7eebc14d8638d69ca456761748fe"
integrity sha512-rfZkG3ajgc1v2Wtb0iqnqlEQJ7oQwauaq7WR+f9BJRwocYS4jzX0sa7iMDuuKfvehbgG09HXM7FkcNZKbj+yoQ==
"@blocknote/xl-odt-exporter@0.47.1":
version "0.47.1"
resolved "https://registry.yarnpkg.com/@blocknote/xl-odt-exporter/-/xl-odt-exporter-0.47.1.tgz#13cb5463b4dd7c608e5c9a41177e7b6995b81580"
integrity sha512-wyz+UGL7QpapmPKIU8WAPRKu9pUkZJrOaEm6vaBRFt2WaAzs2x7lWVU4TFy0gLfcVQEhgz/cqzofDxXZFKCZfw==
dependencies:
"@blocknote/core" "0.47.0"
"@blocknote/xl-multi-column" "0.47.0"
"@blocknote/core" "0.47.1"
"@blocknote/xl-multi-column" "0.47.1"
"@zip.js/zip.js" "^2.8.8"
buffer "^6.0.3"
image-meta "^0.2.2"
"@blocknote/xl-pdf-exporter@0.47.0":
version "0.47.0"
resolved "https://registry.yarnpkg.com/@blocknote/xl-pdf-exporter/-/xl-pdf-exporter-0.47.0.tgz#793c4b678f3ef276633bf56d9e385f12adb18fbc"
integrity sha512-HXtZSx5Xe6ZEhd78G+fYyvTOOl3XdLuFkzf+0StgbOx8PcU8Ts579Ysh3rVnftVI4EJhjytYyUeHuFS/iJFdmw==
"@blocknote/xl-pdf-exporter@0.47.1":
version "0.47.1"
resolved "https://registry.yarnpkg.com/@blocknote/xl-pdf-exporter/-/xl-pdf-exporter-0.47.1.tgz#1bd556a1b3844c5341bbd1da755bf50bbb631013"
integrity sha512-pltqLWhHsoOXqwsh75Ivk2q3/OJZwERfvbROmmJtYgKyAGcb5I9IvAkScoF1V5BpIhVl178ACA1ghEYYbyXdIA==
dependencies:
"@blocknote/core" "0.47.0"
"@blocknote/react" "0.47.0"
"@blocknote/xl-multi-column" "0.47.0"
"@blocknote/core" "0.47.1"
"@blocknote/react" "0.47.1"
"@blocknote/xl-multi-column" "0.47.1"
"@react-pdf/renderer" "^4.3.0"
buffer "^6.0.3"
docx "^9.5.1"
@@ -1929,6 +1929,13 @@
dependencies:
"@floating-ui/utils" "^0.2.10"
"@floating-ui/core@^1.7.5":
version "1.7.5"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.5.tgz#d4af157a03330af5a60e69da7a4692507ada0622"
integrity sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==
dependencies:
"@floating-ui/utils" "^0.2.11"
"@floating-ui/dom@^1.0.0", "@floating-ui/dom@^1.0.1":
version "1.7.4"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.4.tgz#ee667549998745c9c3e3e84683b909c31d6c9a77"
@@ -1945,21 +1952,27 @@
"@floating-ui/core" "^1.7.4"
"@floating-ui/utils" "^0.2.10"
"@floating-ui/react-dom@^2.1.2", "@floating-ui/react-dom@^2.1.7":
"@floating-ui/dom@^1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.6.tgz#f915bba5abbb177e1f227cacee1b4d0634b187bf"
integrity sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==
dependencies:
"@floating-ui/core" "^1.7.5"
"@floating-ui/utils" "^0.2.11"
"@floating-ui/react-dom@^2.1.7":
version "2.1.7"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.7.tgz#529475cc16ee4976ba3387968117e773d9aa703e"
integrity sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==
dependencies:
"@floating-ui/dom" "^1.7.5"
"@floating-ui/react@^0.26.28":
version "0.26.28"
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.28.tgz#93f44ebaeb02409312e9df9507e83aab4a8c0dc7"
integrity sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==
"@floating-ui/react-dom@^2.1.8":
version "2.1.8"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.8.tgz#5fb5a20d10aafb9505f38c24f38d00c8e1598893"
integrity sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==
dependencies:
"@floating-ui/react-dom" "^2.1.2"
"@floating-ui/utils" "^0.2.8"
tabbable "^6.0.0"
"@floating-ui/dom" "^1.7.6"
"@floating-ui/react@^0.27.16":
version "0.27.18"
@@ -1970,11 +1983,25 @@
"@floating-ui/utils" "^0.2.10"
tabbable "^6.0.0"
"@floating-ui/utils@0.2.10", "@floating-ui/utils@^0.2.10", "@floating-ui/utils@^0.2.8":
"@floating-ui/react@^0.27.18":
version "0.27.19"
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.27.19.tgz#d8d5d895b7cb97dac370bfbf55f3e630878fdf1f"
integrity sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==
dependencies:
"@floating-ui/react-dom" "^2.1.8"
"@floating-ui/utils" "^0.2.11"
tabbable "^6.0.0"
"@floating-ui/utils@^0.2.10":
version "0.2.10"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.10.tgz#a2a1e3812d14525f725d011a73eceb41fef5bc1c"
integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==
"@floating-ui/utils@^0.2.11":
version "0.2.11"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.11.tgz#a269e055e40e2f45873bae9d1a2fdccbd314ea3f"
integrity sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==
"@fontsource-variable/inter@5.2.8":
version "5.2.8"
resolved "https://registry.yarnpkg.com/@fontsource-variable/inter/-/inter-5.2.8.tgz#29b11476f5149f6a443b4df6516e26002d87941a"
+2 -2
View File
@@ -1,10 +1,10 @@
environments:
dev:
values:
- version: 4.6.0
- version: 4.7.0
feature:
values:
- version: 4.6.0
- version: 4.7.0
feature: ci
domain: example.com
imageTag: demo
+1 -1
View File
@@ -1,5 +1,5 @@
apiVersion: v2
type: application
name: docs
version: 4.6.0
version: 4.7.0
appVersion: latest
@@ -38,10 +38,14 @@ items:
imagePullPolicy: {{ ($.Values.backend.image | default dict).pullPolicy | default $.Values.image.pullPolicy }}
args:
{{- toYaml .command | nindent 22 }}
{{- if $envVars}}
env:
{{- if $envVars}}
{{- $envVars | indent 22 }}
{{- end }}
{{- end }}
{{- if .Values.backend.envFrom }}
envFrom:
{{- toYaml .Values.backend.envFrom | nindent 22 }}
{{- end }}
{{- with $.Values.backend.securityContext }}
securityContext:
{{- toYaml . | nindent 22 }}
@@ -105,4 +109,4 @@ items:
{{- end }}
{{- end }}
{{- end }}
{{- end }}
@@ -49,10 +49,14 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
env:
{{- if $envVars}}
{{- $envVars | indent 12 }}
{{- end }}
{{- end }}
{{- if .Values.backend.envFrom }}
envFrom:
{{- toYaml .Values.backend.envFrom | nindent 12 }}
{{- end }}
{{- with .Values.backend.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
@@ -135,6 +139,9 @@ spec:
{{- if .existingClaim }}
persistentVolumeClaim:
claimName: {{ .existingClaim }}
{{- else if .secret }}
secret:
{{ toYaml .secret | nindent 12 }}
{{- else if .hostPath }}
hostPath:
{{ toYaml .hostPath | nindent 12 }}
+6 -2
View File
@@ -44,10 +44,14 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
env:
{{- if $envVars}}
{{- $envVars | indent 12 }}
{{- end }}
{{- end }}
{{- if .Values.backend.envFrom }}
envFrom:
{{- toYaml .Values.backend.envFrom | nindent 12 }}
{{- end }}
{{- with .Values.backend.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
@@ -48,10 +48,14 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
env:
{{- if $envVars}}
{{- $envVars | indent 12 }}
{{- end }}
{{- end }}
{{- if .Values.backend.envFrom }}
envFrom:
{{- toYaml .Values.backend.envFrom | nindent 12 }}
{{- end }}
{{- with .Values.backend.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
@@ -48,10 +48,14 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
env:
{{- if $envVars}}
{{- $envVars | indent 12 }}
{{- end }}
{{- end }}
{{- if .Values.backend.envFrom }}
envFrom:
{{- toYaml .Values.backend.envFrom | nindent 12 }}
{{- end }}
{{- with .Values.backend.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
@@ -49,10 +49,14 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
env:
{{- if $envVars}}
{{- $envVars | indent 12 }}
{{- end }}
{{- end }}
{{- if .Values.backend.envFrom }}
envFrom:
{{- toYaml .Values.backend.envFrom | nindent 12 }}
{{- end }}
{{- with .Values.backend.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
@@ -135,6 +139,9 @@ spec:
{{- if .existingClaim }}
persistentVolumeClaim:
claimName: {{ .existingClaim }}
{{- else if .secret }}
secret:
{{ toYaml .secret | nindent 12 }}
{{- else if .hostPath }}
hostPath:
{{ toYaml .hostPath | nindent 12 }}
@@ -39,6 +39,10 @@ spec:
env:
{{- $envVars | indent 12 }}
{{- end }}
{{- if .Values.backend.envFrom }}
envFrom:
{{- toYaml .Values.backend.envFrom | nindent 12 }}
{{- end }}
{{- with .Values.docSpec.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
@@ -49,10 +49,14 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
env:
{{- if $envVars}}
{{- $envVars | indent 12 }}
{{- end }}
{{- end }}
{{- if .Values.frontend.envFrom }}
envFrom:
{{- toYaml .Values.frontend.envFrom | nindent 12 }}
{{- end }}
{{- with .Values.frontend.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
@@ -125,6 +129,9 @@ spec:
{{- if .existingClaim }}
persistentVolumeClaim:
claimName: {{ .existingClaim }}
{{- else if .secret }}
secret:
{{ toYaml .secret | nindent 12 }}
{{- else if .hostPath }}
hostPath:
{{ toYaml .hostPath | nindent 12 }}
@@ -49,10 +49,14 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
env:
{{- if $envVars}}
{{- $envVars | indent 12 }}
{{- end }}
{{- end }}
{{- if .Values.yProvider.envFrom }}
envFrom:
{{- toYaml .Values.yProvider.envFrom | nindent 12 }}
{{- end }}
{{- with .Values.yProvider.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
@@ -125,6 +129,9 @@ spec:
{{- if .existingClaim }}
persistentVolumeClaim:
claimName: {{ .existingClaim }}
{{- else if .secret }}
secret:
{{ toYaml .secret | nindent 12 }}
{{- else if .hostPath }}
hostPath:
{{ toYaml .hostPath | nindent 12 }}

Some files were not shown because too many files have changed in this diff Show More