mirror of
https://github.com/suitenumerique/docs.git
synced 2026-05-07 23:52:04 +02:00
Compare commits
26 Commits
v2.0.0
...
feature/ed
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef77889d25 | ||
|
|
b93b43abe8 | ||
|
|
dd8bb18f69 | ||
|
|
545e8b2a3c | ||
|
|
81837aff2b | ||
|
|
40c1107959 | ||
|
|
0d7d42254b | ||
|
|
67dc7feb98 | ||
|
|
5b4b100e90 | ||
|
|
b8be010389 | ||
|
|
97cfa2c1ad | ||
|
|
c018c6fcf5 | ||
|
|
70048328d1 | ||
|
|
55ddfe9181 | ||
|
|
ee41d156c7 | ||
|
|
5be2bc7360 | ||
|
|
e46ba4f506 | ||
|
|
7c8b969fa9 | ||
|
|
95515fd460 | ||
|
|
ce6cfc22ef | ||
|
|
4b3b441fc3 | ||
|
|
9194bf5a90 | ||
|
|
dc63a5839e | ||
|
|
d406846986 | ||
|
|
e85b07021e | ||
|
|
282200ac3d |
76
.github/workflows/crowdin_download.yml
vendored
Normal file
76
.github/workflows/crowdin_download.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
name: Download translations from Crowdin
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'release/**'
|
||||
|
||||
jobs:
|
||||
install-front:
|
||||
uses: ./.github/workflows/front-dependencies-installation.yml
|
||||
with:
|
||||
node_version: '20.x'
|
||||
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Create empty source files
|
||||
run: |
|
||||
touch src/backend/locale/django.pot
|
||||
mkdir -p src/frontend/packages/i18n/locales/impress/
|
||||
touch src/frontend/packages/i18n/locales/impress/translations-crowdin.json
|
||||
# crowdin workflow
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
config: crowdin/config.yml
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
create_pull_request: false
|
||||
push_translations: false
|
||||
push_sources: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# A numeric ID, found at https://crowdin.com/project/<projectName>/tools/api
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
|
||||
# Visit https://crowdin.com/settings#api-key to create this token
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
CROWDIN_BASE_PATH: "../src/"
|
||||
# frontend i18n
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
fail-on-cache-miss: true
|
||||
- name: generate translations files
|
||||
working-directory: src/frontend
|
||||
run: yarn i18n:deploy
|
||||
# Create a new PR
|
||||
- name: Create a new Pull Request with new translated strings
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
commit-message: |
|
||||
🌐(i18n) update translated strings
|
||||
|
||||
Update translated files with new translations
|
||||
title: 🌐(i18n) update translated strings
|
||||
body: |
|
||||
## Purpose
|
||||
|
||||
update translated strings
|
||||
|
||||
## Proposal
|
||||
|
||||
- [x] update translated strings
|
||||
branch: i18n/update-translations
|
||||
labels: i18n
|
||||
67
.github/workflows/crowdin_upload.yml
vendored
Normal file
67
.github/workflows/crowdin_upload.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: Update crowdin sources
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
install-front:
|
||||
uses: ./.github/workflows/front-dependencies-installation.yml
|
||||
with:
|
||||
node_version: '20.x'
|
||||
|
||||
synchronize-with-crowdin:
|
||||
needs: install-front
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
# Backend i18n
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.12.6"
|
||||
- name: Upgrade pip and setuptools
|
||||
run: pip install --upgrade pip setuptools
|
||||
- name: Install development dependencies
|
||||
run: pip install --user .
|
||||
working-directory: src/backend
|
||||
- name: Install gettext
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gettext pandoc
|
||||
- name: generate pot files
|
||||
working-directory: src/backend
|
||||
run: |
|
||||
DJANGO_CONFIGURATION=Build python manage.py makemessages -a --keep-pot
|
||||
# frontend i18n
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
fail-on-cache-miss: true
|
||||
- name: generate source translation file
|
||||
working-directory: src/frontend
|
||||
run: yarn i18n:extract
|
||||
# crowdin workflow
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
config: crowdin/config.yml
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
download_translations: false
|
||||
create_pull_request: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# A numeric ID, found at https://crowdin.com/project/<projectName>/tools/api
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
|
||||
# Visit https://crowdin.com/settings#api-key to create this token
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
CROWDIN_BASE_PATH: "../src/"
|
||||
36
.github/workflows/front-dependencies-installation.yml
vendored
Normal file
36
.github/workflows/front-dependencies-installation.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Install frontend installation reusable workflow
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
node_version:
|
||||
required: false
|
||||
default: '20.x'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
front-dependencies-installation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
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
|
||||
with:
|
||||
node-version: ${{ inputs.node_version }}
|
||||
- name: Install dependencies
|
||||
if: steps.front-node_modules.outputs.cache-hit != 'true'
|
||||
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
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
58
.github/workflows/impress-frontend.yml
vendored
58
.github/workflows/impress-frontend.yml
vendored
@@ -9,39 +9,15 @@ on:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
|
||||
install-front:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20.x"
|
||||
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
id: front-node_modules
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.front-node_modules.outputs.cache-hit != 'true'
|
||||
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
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
uses: ./.github/workflows/front-dependencies-installation.yml
|
||||
with:
|
||||
node_version: '20.x'
|
||||
|
||||
test-front:
|
||||
runs-on: ubuntu-latest
|
||||
needs: install-front
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -53,10 +29,10 @@ jobs:
|
||||
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
id: front-node_modules
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Test App
|
||||
run: cd src/frontend/ && yarn test
|
||||
@@ -68,29 +44,39 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20.x"
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
id: front-node_modules
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Check linting
|
||||
run: cd src/frontend/ && yarn lint
|
||||
|
||||
test-e2e-chromium:
|
||||
runs-on: ubuntu-latest
|
||||
needs: install-front
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20.x"
|
||||
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
id: front-node_modules
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Set e2e env variables
|
||||
run: cat env.d/development/common.e2e.dist >> env.d/development/common.dist
|
||||
@@ -141,12 +127,8 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
id: front-node_modules
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
- name: Install frontend dependencies
|
||||
uses: ./.github/workflows/front-dependencies-installation.yml
|
||||
|
||||
- name: Set e2e env variables
|
||||
run: cat env.d/development/common.e2e.dist >> env.d/development/common.dist
|
||||
|
||||
5
.github/workflows/impress.yml
vendored
5
.github/workflows/impress.yml
vendored
@@ -206,10 +206,11 @@ jobs:
|
||||
- name: Install development dependencies
|
||||
run: pip install --user .[dev]
|
||||
|
||||
- name: Install gettext (required to compile messages)
|
||||
- name: Install gettext (required to compile messages) and MIME support
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gettext pandoc
|
||||
sudo apt-get install -y gettext pandoc shared-mime-info
|
||||
sudo wget https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types -O /etc/mime.types
|
||||
|
||||
- name: Generate a MO file from strings extracted from the project
|
||||
run: python manage.py compilemessages
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -30,6 +30,7 @@ MANIFEST
|
||||
.next/
|
||||
|
||||
# Translations # Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Environments
|
||||
|
||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -9,6 +9,30 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## Added
|
||||
|
||||
- github actions to managed Crowdin workflow
|
||||
- 📈Integrate Posthog #540
|
||||
- 🏷️(backend) add content-type to uploaded files #552
|
||||
- ✨(frontend) export pdf docx front side #537
|
||||
|
||||
## Changed
|
||||
|
||||
- 💄(frontend) add abilities on doc row #581
|
||||
- 💄(frontend) improve DocsGridItem responsive padding #582
|
||||
|
||||
|
||||
## [2.0.1] - 2025-01-17
|
||||
|
||||
## Added
|
||||
|
||||
✨(frontend) add multi columns support for editor #533
|
||||
|
||||
## Fixed
|
||||
|
||||
-🐛(frontend) share modal is shown when you don't have the abilities #557
|
||||
-🐛(frontend) title copy break app #564
|
||||
|
||||
## [2.0.0] - 2025-01-13
|
||||
|
||||
## Added
|
||||
@@ -19,6 +43,8 @@ and this project adheres to
|
||||
- 💄(frontend) add filtering to left panel #475
|
||||
- ✨(frontend) new share modal ui #489
|
||||
- ✨(frontend) add favorite feature #515
|
||||
- 📝(documentation) Documentation about self-hosted installation #530
|
||||
- ✨(helm) helm versioning #530
|
||||
|
||||
## Changed
|
||||
|
||||
@@ -30,6 +56,7 @@ and this project adheres to
|
||||
- 💄(frontend) update DocHeader ui #448
|
||||
- 💄(frontend) update doc versioning ui #463
|
||||
- 💄(frontend) update doc summary ui #473
|
||||
- 📝(doc) update readme.md to match V2 changes #558
|
||||
|
||||
## Fixed
|
||||
|
||||
@@ -353,7 +380,8 @@ and this project adheres to
|
||||
- 🚀 Impress, project to manage your documents easily and collaboratively.
|
||||
|
||||
|
||||
[unreleased]: https://github.com/numerique-gouv/impress/compare/v2.0.0...main
|
||||
[unreleased]: https://github.com/numerique-gouv/impress/compare/v2.0.1...main
|
||||
[v2.0.1]: https://github.com/numerique-gouv/impress/releases/v2.0.1
|
||||
[v2.0.0]: https://github.com/numerique-gouv/impress/releases/v2.0.0
|
||||
[v1.10.0]: https://github.com/numerique-gouv/impress/releases/v1.10.0
|
||||
[v1.9.0]: https://github.com/numerique-gouv/impress/releases/v1.9.0
|
||||
|
||||
10
Dockerfile
10
Dockerfile
@@ -51,7 +51,7 @@ COPY ./src/backend /app/
|
||||
WORKDIR /app
|
||||
|
||||
# collectstatic
|
||||
RUN DJANGO_CONFIGURATION=Build DJANGO_JWT_PRIVATE_SIGNING_KEY=Dummy \
|
||||
RUN DJANGO_CONFIGURATION=Build \
|
||||
python manage.py collectstatic --noinput
|
||||
|
||||
# Replace duplicated file by a symlink to decrease the overall size of the
|
||||
@@ -72,10 +72,11 @@ RUN apk add \
|
||||
gettext \
|
||||
gdk-pixbuf \
|
||||
libffi-dev \
|
||||
pandoc \
|
||||
pango \
|
||||
shared-mime-info
|
||||
|
||||
RUN wget https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types -O /etc/mime.types
|
||||
|
||||
# Copy entrypoint
|
||||
COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint
|
||||
|
||||
@@ -92,6 +93,11 @@ COPY ./src/backend /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Generate compiled translation messages
|
||||
RUN DJANGO_CONFIGURATION=Build \
|
||||
python manage.py compilemessages
|
||||
|
||||
|
||||
# We wrap commands run in this container by the following entrypoint that
|
||||
# creates a user on-the-fly with the container user ID (see USER) and root group
|
||||
# ID.
|
||||
|
||||
160
README.md
160
README.md
@@ -1,113 +1,173 @@
|
||||
# Impress
|
||||
<p align="center">
|
||||
<a href="https://github.com/suitenumerique/docs">
|
||||
<img alt="Docs" src="/docs/assets/logo-docs.png" width="300" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Impress is a web application for real-time collaborative text editing with user and role based access rights.
|
||||
Features include :
|
||||
- User authentication through OIDC
|
||||
- BlocNote.js text editing experience (markdown support, dynamic conversion, block structure, slash commands for block creation)
|
||||
- Document export to pdf and docx from predefined templates
|
||||
- Granular document permissions
|
||||
- Public link sharing
|
||||
- Offline mode
|
||||
<p align="center">
|
||||
Welcome to Docs! The open source document editor where your notes can become knowledge through live collaboration
|
||||
</p>
|
||||
|
||||
Impress is built on top of [Django Rest Framework](https://www.django-rest-framework.org/), [Next.js](https://nextjs.org/) and [BlocNote.js](https://www.blocknotejs.org/)
|
||||
<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>
|
||||
</p>
|
||||
|
||||
## Getting started
|
||||
<img src="/docs/assets/docs_live_collaboration_light.gif" width="100%" align="center"/>
|
||||
|
||||
### Prerequisite
|
||||
## Why use Docs ❓
|
||||
Docs is a collaborative text editor designed to address common challenges in knowledge building and sharing.
|
||||
|
||||
Make sure you have a recent version of Docker and [Docker
|
||||
Compose](https://docs.docker.com/compose/install) installed on your laptop:
|
||||
### Write
|
||||
* 😌 Simple collaborative editing without the formatting complexity of markdown
|
||||
* 🔌 Offline? No problem, keep writing, your edits will get synced when back online
|
||||
* 💅 Create clean documents with limited but beautiful formatting options and focus on content
|
||||
* 🧱 Built for productivity (markdown support, many block types, slash commands, markdown support, keyboard shortcuts) (page in french sorry 😅).
|
||||
* ✨ Save time thanks to our AI actions (generate, sum up, correct, translate)
|
||||
|
||||
```bash
|
||||
### Collaborate
|
||||
* 🤝 Collaborate in realtime with your team mates
|
||||
* 🔒 Granular access control to keep your information secure and shared with the right people
|
||||
* 📑 Professional document exports in multiple formats (.odt, .doc, .pdf) with customizable templates
|
||||
* 📚 Built-in wiki functionality to transform your team's collaborative work into organized knowledge `ETA 02/2025`
|
||||
|
||||
### Self-host
|
||||
* 🚀 Easy to install, scalable and secure alternative to Notion, Outline or Confluence
|
||||
|
||||
## Getting started 🔧
|
||||
### Test it
|
||||
Test Docs on your browser by logging in on this [environment](https://impress-preprod.beta.numerique.gouv.fr/docs/0aa856e9-da41-4d59-b73d-a61cb2c1245f/)
|
||||
```
|
||||
email: test.docs@yopmail.com
|
||||
password: I'd<3ToTestDocs
|
||||
```
|
||||
### Run it locally
|
||||
**Prerequisite**
|
||||
Make sure you have a recent version of Docker and [Docker Compose](https://docs.docker.com/compose/install) installed on your laptop:
|
||||
|
||||
```shellscript
|
||||
$ docker -v
|
||||
Docker version 20.10.2, build 2291f61
|
||||
|
||||
Docker version 20.10.2, build 2291f61
|
||||
|
||||
$ docker compose -v
|
||||
docker compose version 1.27.4, build 40524192
|
||||
|
||||
docker compose version 1.27.4, build 40524192
|
||||
```
|
||||
|
||||
> ⚠️ You may need to run the following commands with `sudo` but this can be
|
||||
> avoided by assigning your user to the `docker` group.
|
||||
|
||||
### Project bootstrap
|
||||
> ⚠️ You may need to run the following commands with sudo but this can be avoided by assigning your user to the `docker` group.
|
||||
|
||||
**Project bootstrap**
|
||||
The easiest way to start working on the project is to use GNU Make:
|
||||
|
||||
```bash
|
||||
```shellscript
|
||||
$ make bootstrap FLUSH_ARGS='--no-input'
|
||||
```
|
||||
|
||||
This command builds the `app` container, installs dependencies, performs
|
||||
database migrations and compile translations. It's a good idea to use this
|
||||
command each time you are pulling code from the project repository to avoid
|
||||
dependency-releated or migration-releated issues.
|
||||
This command builds the `app` container, installs dependencies, performs database migrations and compile translations. It's a good idea to use this
|
||||
|
||||
command each time you are pulling code from the project repository to avoid dependency-releated or migration-releated issues.
|
||||
|
||||
Your Docker services should now be up and running 🎉
|
||||
|
||||
You can access to the project by going to http://localhost:3000.
|
||||
You can access to the project by going to <http://localhost:3000>.
|
||||
|
||||
You will be prompted to log in, the default credentials are:
|
||||
```bash
|
||||
|
||||
```shellscript
|
||||
username: impress
|
||||
|
||||
password: impress
|
||||
```
|
||||
|
||||
📝 Note that if you need to run them afterwards, you can use the eponym Make rule:
|
||||
|
||||
```bash
|
||||
```shellscript
|
||||
$ make run-with-frontend
|
||||
```
|
||||
|
||||
---
|
||||
⚠️ For the frontend developper, it is often better to run the frontend in development mode locally.
|
||||
|
||||
⚠️ For the frontend developper, it is often better to run the frontend in development mode locally.
|
||||
To do so, install the frontend dependencies with the following command:
|
||||
|
||||
```bash
|
||||
```shellscript
|
||||
$ make frontend-install
|
||||
```
|
||||
|
||||
And run the frontend locally in development mode with the following command:
|
||||
|
||||
```bash
|
||||
```shellscript
|
||||
$ make run-frontend-development
|
||||
```
|
||||
|
||||
To start all the services, except the frontend container, you can use the following command:
|
||||
|
||||
```bash
|
||||
```shellscript
|
||||
$ make run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Adding content
|
||||
|
||||
**Adding content**
|
||||
You can create a basic demo site by running:
|
||||
|
||||
$ make demo
|
||||
```shellscript
|
||||
$ make demo
|
||||
```
|
||||
|
||||
Finally, you can check all available Make rules using:
|
||||
|
||||
```bash
|
||||
```shellscript
|
||||
$ make help
|
||||
```
|
||||
|
||||
### Django admin
|
||||
|
||||
**Django admin**
|
||||
You can access the Django admin site at
|
||||
[http://localhost:8071/admin](http://localhost:8071/admin).
|
||||
|
||||
<http://localhost:8071/admin>.
|
||||
|
||||
You first need to create a superuser account:
|
||||
|
||||
```bash
|
||||
```shellscript
|
||||
$ make superuser
|
||||
```
|
||||
|
||||
## Contributing
|
||||
## 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).
|
||||
|
||||
This project is intended to be community-driven, so please, do not hesitate to
|
||||
get in touch if you have any question related to our implementation or design
|
||||
decisions.
|
||||
## Roadmap
|
||||
Want to know where the project is headed? [🗺️ Checkout our roadmap](https://github.com/orgs/numerique-gouv/projects/13/views/11)
|
||||
|
||||
## License
|
||||
## Licence 📝
|
||||
This work is released under the MIT License (see [LICENSE](https://github.com/suitenumerique/docs/blob/main/LICENSE)).
|
||||
|
||||
This work is released under the MIT License (see [LICENSE](./LICENSE)).
|
||||
While Docs is public driven initiative our licence 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 if you have any question related to our implementation or design decisions.
|
||||
|
||||
If you intend to make pull requests see CONTRIBUTING 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.
|
||||
```
|
||||
|
||||
## Credits ❤️
|
||||
### Stack
|
||||
Impress is built on top of [Django Rest Framework](https://www.django-rest-framework.org/), [Next.js](https://nextjs.org/), [MinIO](https://min.io/) and [BlocNote.js](https://www.blocknotejs.org/)
|
||||
|
||||
### States ❤️ open source
|
||||
Docs is the result of a joint effort lead by the French 🇫🇷🥖 ([DINUM](https://www.numerique.gouv.fr/dinum/)) and German 🇩🇪🥨 government ([ZenDiS](https://zendis.de/)). We are always looking for new public partners feel free to reach out if you are interested in using or contributing to docs.
|
||||
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Your crowdin's credentials
|
||||
#
|
||||
api_token_env: CROWDIN_API_TOKEN
|
||||
api_token_env: CROWDIN_PERSONAL_TOKEN
|
||||
project_id_env: CROWDIN_PROJECT_ID
|
||||
base_path_env: CROWDIN_BASE_PATH
|
||||
|
||||
@@ -15,11 +15,11 @@ preserve_hierarchy: true
|
||||
# Files configuration
|
||||
#
|
||||
files: [
|
||||
{
|
||||
source : "/backend/locale/django.pot",
|
||||
dest: "/backend-impress.pot",
|
||||
translation : "/backend/locale/%locale_with_underscore%/LC_MESSAGES/django.po"
|
||||
},
|
||||
{
|
||||
source : "/backend/locale/django.pot",
|
||||
dest: "/backend-impress.pot",
|
||||
translation : "/backend/locale/%locale_with_underscore%/LC_MESSAGES/django.po"
|
||||
},
|
||||
{
|
||||
source: "/frontend/packages/i18n/locales/impress/translations-crowdin.json",
|
||||
dest: "/frontend-impress.json",
|
||||
|
||||
@@ -151,7 +151,7 @@ services:
|
||||
image: node:18
|
||||
user: "${DOCKER_USER:-1000}"
|
||||
environment:
|
||||
HOME: /tmp
|
||||
HOME: /tmp
|
||||
volumes:
|
||||
- ".:/app"
|
||||
|
||||
|
||||
BIN
docs/assets/docs_live_collaboration_light.gif
Normal file
BIN
docs/assets/docs_live_collaboration_light.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 MiB |
BIN
docs/assets/logo-docs.png
Normal file
BIN
docs/assets/logo-docs.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
@@ -40,6 +40,7 @@ backend:
|
||||
LOGIN_REDIRECT_URL: https://impress.127.0.0.1.nip.io
|
||||
LOGIN_REDIRECT_URL_FAILURE: https://impress.127.0.0.1.nip.io
|
||||
LOGOUT_REDIRECT_URL: https://impress.127.0.0.1.nip.io
|
||||
POSTHOG_KEY: "{'id': 'posthog_key', 'host': 'https://product.impress.127.0.0.1.nip.io'}"
|
||||
DB_HOST: postgresql
|
||||
DB_NAME: impress
|
||||
DB_USER: dinum
|
||||
@@ -121,6 +122,12 @@ yProvider:
|
||||
COLLABORATION_SERVER_SECRET: my-secret
|
||||
Y_PROVIDER_API_KEY: my-secret
|
||||
|
||||
posthog:
|
||||
ingress:
|
||||
enabled: false
|
||||
ingressAssets:
|
||||
enabled: false
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
host: impress.127.0.0.1.nip.io
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
CROWDIN_API_TOKEN=Your-Api-Token
|
||||
CROWDIN_PERSONAL_TOKEN=Your-Personal-Token
|
||||
CROWDIN_PROJECT_ID=Your-Project-Id
|
||||
CROWDIN_BASE_PATH=/app/src
|
||||
|
||||
@@ -388,6 +388,7 @@ class FileUploadSerializer(serializers.Serializer):
|
||||
raise serializers.ValidationError("Could not determine file extension.")
|
||||
|
||||
self.context["expected_extension"] = extension
|
||||
self.context["content_type"] = magic_mime_type
|
||||
|
||||
return file
|
||||
|
||||
@@ -395,6 +396,7 @@ class FileUploadSerializer(serializers.Serializer):
|
||||
"""Override validate to add the computed extension to validated_data."""
|
||||
attrs["expected_extension"] = self.context["expected_extension"]
|
||||
attrs["is_unsafe"] = self.context["is_unsafe"]
|
||||
attrs["content_type"] = self.context["content_type"]
|
||||
return attrs
|
||||
|
||||
|
||||
|
||||
@@ -605,7 +605,10 @@ class DocumentViewSet(
|
||||
key = f"{document.key_base}/{ATTACHMENTS_FOLDER:s}/{file_id!s}.{extension:s}"
|
||||
|
||||
# Prepare metadata for storage
|
||||
extra_args = {"Metadata": {"owner": str(request.user.id)}}
|
||||
extra_args = {
|
||||
"Metadata": {"owner": str(request.user.id)},
|
||||
"ContentType": serializer.validated_data["content_type"],
|
||||
}
|
||||
if serializer.validated_data["is_unsafe"]:
|
||||
extra_args["Metadata"]["is_unsafe"] = "true"
|
||||
|
||||
@@ -936,40 +939,6 @@ class TemplateViewSet(
|
||||
role=models.RoleChoices.OWNER,
|
||||
)
|
||||
|
||||
@drf.decorators.action(
|
||||
detail=True,
|
||||
methods=["post"],
|
||||
url_path="generate-document",
|
||||
permission_classes=[permissions.AccessPermission],
|
||||
)
|
||||
# pylint: disable=unused-argument
|
||||
def generate_document(self, request, pk=None):
|
||||
"""
|
||||
Generate and return a document for this template around the
|
||||
body passed as argument.
|
||||
|
||||
2 types of body are accepted:
|
||||
- HTML: body_type = "html"
|
||||
- Markdown: body_type = "markdown"
|
||||
|
||||
2 types of documents can be generated:
|
||||
- PDF: format = "pdf"
|
||||
- Docx: format = "docx"
|
||||
"""
|
||||
serializer = serializers.DocumentGenerationSerializer(data=request.data)
|
||||
|
||||
if not serializer.is_valid():
|
||||
return drf.response.Response(
|
||||
serializer.errors, status=drf.status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
body = serializer.validated_data["body"]
|
||||
body_type = serializer.validated_data["body_type"]
|
||||
export_format = serializer.validated_data["format"]
|
||||
|
||||
template = self.get_object()
|
||||
return template.generate_document(body, body_type, export_format)
|
||||
|
||||
|
||||
class TemplateAccessViewSet(
|
||||
ResourceAccessViewsetMixin,
|
||||
@@ -1124,6 +1093,7 @@ class ConfigView(drf.views.APIView):
|
||||
"ENVIRONMENT",
|
||||
"FRONTEND_THEME",
|
||||
"MEDIA_BASE_URL",
|
||||
"POSTHOG_KEY",
|
||||
"LANGUAGES",
|
||||
"LANGUAGE_CODE",
|
||||
"SENTRY_DSN",
|
||||
|
||||
0
src/backend/core/management/__init__.py
Normal file
0
src/backend/core/management/__init__.py
Normal file
0
src/backend/core/management/commands/__init__.py
Normal file
0
src/backend/core/management/commands/__init__.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""Management command updating the metadata for all the files in the MinIO bucket."""
|
||||
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
import magic
|
||||
|
||||
from core.models import Document
|
||||
|
||||
# pylint: disable=too-many-locals, broad-exception-caught
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Update the metadata for all the files in the MinIO bucket."""
|
||||
|
||||
help = __doc__
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""Execute management command."""
|
||||
s3_client = default_storage.connection.meta.client
|
||||
bucket_name = default_storage.bucket_name
|
||||
|
||||
mime_detector = magic.Magic(mime=True)
|
||||
|
||||
documents = Document.objects.all()
|
||||
self.stdout.write(
|
||||
f"[INFO] Found {documents.count()} documents. Starting ContentType fix..."
|
||||
)
|
||||
|
||||
for doc in documents:
|
||||
doc_id_str = str(doc.id)
|
||||
prefix = f"{doc_id_str}/attachments/"
|
||||
self.stdout.write(
|
||||
f"[INFO] Processing attachments under prefix '{prefix}' ..."
|
||||
)
|
||||
|
||||
continuation_token = None
|
||||
total_updated = 0
|
||||
|
||||
while True:
|
||||
list_kwargs = {"Bucket": bucket_name, "Prefix": prefix}
|
||||
if continuation_token:
|
||||
list_kwargs["ContinuationToken"] = continuation_token
|
||||
|
||||
response = s3_client.list_objects_v2(**list_kwargs)
|
||||
|
||||
# If no objects found under this prefix, break out of the loop
|
||||
if "Contents" not in response:
|
||||
break
|
||||
|
||||
for obj in response["Contents"]:
|
||||
key = obj["Key"]
|
||||
|
||||
# Skip if it's a folder
|
||||
if key.endswith("/"):
|
||||
continue
|
||||
|
||||
try:
|
||||
# Get existing metadata
|
||||
head_resp = s3_client.head_object(Bucket=bucket_name, Key=key)
|
||||
|
||||
# Read first ~1KB for MIME detection
|
||||
partial_obj = s3_client.get_object(
|
||||
Bucket=bucket_name, Key=key, Range="bytes=0-1023"
|
||||
)
|
||||
partial_data = partial_obj["Body"].read()
|
||||
|
||||
# Detect MIME type
|
||||
magic_mime_type = mime_detector.from_buffer(partial_data)
|
||||
|
||||
# Update ContentType
|
||||
s3_client.copy_object(
|
||||
Bucket=bucket_name,
|
||||
CopySource={"Bucket": bucket_name, "Key": key},
|
||||
Key=key,
|
||||
ContentType=magic_mime_type,
|
||||
Metadata=head_resp.get("Metadata", {}),
|
||||
MetadataDirective="REPLACE",
|
||||
)
|
||||
total_updated += 1
|
||||
|
||||
except Exception as exc: # noqa
|
||||
self.stderr.write(
|
||||
f"[ERROR] Could not update ContentType for {key}: {exc}"
|
||||
)
|
||||
|
||||
if response.get("IsTruncated"):
|
||||
continuation_token = response.get("NextContinuationToken")
|
||||
else:
|
||||
break
|
||||
|
||||
if total_updated > 0:
|
||||
self.stdout.write(
|
||||
f"[INFO] -> Updated {total_updated} objects for Document {doc_id_str}."
|
||||
)
|
||||
@@ -5,11 +5,8 @@ Declare and configure the models for the impress core application
|
||||
|
||||
import hashlib
|
||||
import smtplib
|
||||
import tempfile
|
||||
import textwrap
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
from io import BytesIO
|
||||
from logging import getLogger
|
||||
|
||||
from django.conf import settings
|
||||
@@ -21,19 +18,12 @@ from django.core.files.base import ContentFile
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.mail import send_mail
|
||||
from django.db import models
|
||||
from django.http import FileResponse
|
||||
from django.template.base import Template as DjangoTemplate
|
||||
from django.template.context import Context
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import html, timezone
|
||||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property, lazy
|
||||
from django.utils.translation import get_language, override
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import frontmatter
|
||||
import markdown
|
||||
import pypandoc
|
||||
import weasyprint
|
||||
from botocore.exceptions import ClientError
|
||||
from timezone_field import TimeZoneField
|
||||
|
||||
@@ -794,107 +784,6 @@ class Template(BaseModel):
|
||||
"retrieve": can_get,
|
||||
}
|
||||
|
||||
def generate_pdf(self, body_html, metadata):
|
||||
"""
|
||||
Generate and return a pdf document wrapped around the current template
|
||||
"""
|
||||
document_html = weasyprint.HTML(
|
||||
string=DjangoTemplate(self.code).render(
|
||||
Context({"body": html.format_html(body_html), **metadata})
|
||||
)
|
||||
)
|
||||
css = weasyprint.CSS(
|
||||
string=self.css,
|
||||
font_config=weasyprint.text.fonts.FontConfiguration(),
|
||||
)
|
||||
|
||||
pdf_content = document_html.write_pdf(stylesheets=[css], zoom=1)
|
||||
response = FileResponse(BytesIO(pdf_content), content_type="application/pdf")
|
||||
response["Content-Disposition"] = f"attachment; filename={self.title}.pdf"
|
||||
|
||||
return response
|
||||
|
||||
def generate_word(self, body_html, metadata):
|
||||
"""
|
||||
Generate and return a docx document wrapped around the current template
|
||||
"""
|
||||
template_string = DjangoTemplate(self.code).render(
|
||||
Context({"body": html.format_html(body_html), **metadata})
|
||||
)
|
||||
|
||||
html_string = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
{self.css}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{template_string}
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
reference_docx = "core/static/reference.docx"
|
||||
output = BytesIO()
|
||||
|
||||
# Convert the HTML to a temporary docx file
|
||||
with tempfile.NamedTemporaryFile(suffix=".docx", prefix="docx_") as tmp_file:
|
||||
output_path = tmp_file.name
|
||||
|
||||
pypandoc.convert_text(
|
||||
html_string,
|
||||
"docx",
|
||||
format="html",
|
||||
outputfile=output_path,
|
||||
extra_args=["--reference-doc", reference_docx],
|
||||
)
|
||||
|
||||
# Create a BytesIO object to store the output of the temporary docx file
|
||||
with open(output_path, "rb") as f:
|
||||
output = BytesIO(f.read())
|
||||
|
||||
# Ensure the pointer is at the beginning
|
||||
output.seek(0)
|
||||
|
||||
response = FileResponse(
|
||||
output,
|
||||
content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
)
|
||||
response["Content-Disposition"] = f"attachment; filename={self.title}.docx"
|
||||
|
||||
return response
|
||||
|
||||
def generate_document(self, body, body_type, export_format):
|
||||
"""
|
||||
Generate and return a document for this template around the
|
||||
body passed as argument.
|
||||
|
||||
2 types of body are accepted:
|
||||
- HTML: body_type = "html"
|
||||
- Markdown: body_type = "markdown"
|
||||
|
||||
2 types of documents can be generated:
|
||||
- PDF: export_format = "pdf"
|
||||
- Docx: export_format = "docx"
|
||||
"""
|
||||
document = frontmatter.loads(body)
|
||||
metadata = document.metadata
|
||||
strip_body = document.content.strip()
|
||||
|
||||
if body_type == "html":
|
||||
body_html = strip_body
|
||||
else:
|
||||
body_html = (
|
||||
markdown.markdown(textwrap.dedent(strip_body)) if strip_body else ""
|
||||
)
|
||||
|
||||
if export_format == "pdf":
|
||||
return self.generate_pdf(body_html, metadata)
|
||||
|
||||
return self.generate_word(body_html, metadata)
|
||||
|
||||
|
||||
class TemplateAccess(BaseAccess):
|
||||
"""Relation model to give access to a template for a user or a team with a role."""
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Unit test for `update_files_content_type_metadata` command.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.management import call_command
|
||||
|
||||
import pytest
|
||||
|
||||
from core import factories
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_files_content_type_metadata():
|
||||
"""
|
||||
Test that the command `update_files_content_type_metadata`
|
||||
fixes the ContentType of attachment in the storage.
|
||||
"""
|
||||
s3_client = default_storage.connection.meta.client
|
||||
bucket_name = default_storage.bucket_name
|
||||
|
||||
# Create files with a wrong ContentType
|
||||
keys = []
|
||||
for _ in range(10):
|
||||
doc_id = uuid.uuid4()
|
||||
factories.DocumentFactory(id=doc_id)
|
||||
key = f"{doc_id}/attachments/testfile.png"
|
||||
keys.append(key)
|
||||
fake_png = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR..."
|
||||
s3_client.put_object(
|
||||
Bucket=bucket_name,
|
||||
Key=key,
|
||||
Body=fake_png,
|
||||
ContentType="text/plain",
|
||||
Metadata={"owner": "None"},
|
||||
)
|
||||
|
||||
# Call the command that fixes the ContentType
|
||||
call_command("update_files_content_type_metadata")
|
||||
|
||||
for key in keys:
|
||||
head_resp = s3_client.head_object(Bucket=bucket_name, Key=key)
|
||||
assert (
|
||||
head_resp["ContentType"] == "image/png"
|
||||
), f"ContentType not fixed, got {head_resp['ContentType']!r}"
|
||||
|
||||
# Check that original metadata was preserved
|
||||
assert head_resp["Metadata"].get("owner") == "None"
|
||||
@@ -64,12 +64,22 @@ def test_api_documents_attachment_upload_anonymous_success():
|
||||
assert response.status_code == 201
|
||||
|
||||
pattern = re.compile(rf"^/media/{document.id!s}/attachments/(.*)\.png")
|
||||
match = pattern.search(response.json()["file"])
|
||||
file_path = response.json()["file"]
|
||||
match = pattern.search(file_path)
|
||||
file_id = match.group(1)
|
||||
|
||||
# Validate that file_id is a valid UUID
|
||||
uuid.UUID(file_id)
|
||||
|
||||
# Now, check the metadata of the uploaded file
|
||||
key = file_path.replace("/media", "")
|
||||
file_head = default_storage.connection.meta.client.head_object(
|
||||
Bucket=default_storage.bucket_name, Key=key
|
||||
)
|
||||
|
||||
assert file_head["Metadata"] == {"owner": "None"}
|
||||
assert file_head["ContentType"] == "image/png"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reach, role",
|
||||
@@ -206,6 +216,7 @@ def test_api_documents_attachment_upload_success(via, role, mock_user_teams):
|
||||
Bucket=default_storage.bucket_name, Key=key
|
||||
)
|
||||
assert file_head["Metadata"] == {"owner": str(user.id)}
|
||||
assert file_head["ContentType"] == "image/png"
|
||||
|
||||
|
||||
def test_api_documents_attachment_upload_invalid(client):
|
||||
@@ -247,16 +258,18 @@ def test_api_documents_attachment_upload_size_limit_exceeded(settings):
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name,content,extension",
|
||||
"name,content,extension,content_type",
|
||||
[
|
||||
("test.exe", b"text", "exe"),
|
||||
("test", b"text", "txt"),
|
||||
("test.aaaaaa", b"test", "txt"),
|
||||
("test.txt", PIXEL, "txt"),
|
||||
("test.py", b"#!/usr/bin/python", "py"),
|
||||
("test.exe", b"text", "exe", "text/plain"),
|
||||
("test", b"text", "txt", "text/plain"),
|
||||
("test.aaaaaa", b"test", "txt", "text/plain"),
|
||||
("test.txt", PIXEL, "txt", "image/png"),
|
||||
("test.py", b"#!/usr/bin/python", "py", "text/plain"),
|
||||
],
|
||||
)
|
||||
def test_api_documents_attachment_upload_fix_extension(name, content, extension):
|
||||
def test_api_documents_attachment_upload_fix_extension(
|
||||
name, content, extension, content_type
|
||||
):
|
||||
"""
|
||||
A file with no extension or a wrong extension is accepted and the extension
|
||||
is corrected in storage.
|
||||
@@ -287,6 +300,7 @@ def test_api_documents_attachment_upload_fix_extension(name, content, extension)
|
||||
Bucket=default_storage.bucket_name, Key=key
|
||||
)
|
||||
assert file_head["Metadata"] == {"owner": str(user.id), "is_unsafe": "true"}
|
||||
assert file_head["ContentType"] == content_type
|
||||
|
||||
|
||||
def test_api_documents_attachment_upload_empty_file():
|
||||
@@ -335,3 +349,4 @@ def test_api_documents_attachment_upload_unsafe():
|
||||
Bucket=default_storage.bucket_name, Key=key
|
||||
)
|
||||
assert file_head["Metadata"] == {"owner": str(user.id), "is_unsafe": "true"}
|
||||
assert file_head["ContentType"] == "application/octet-stream"
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
"""
|
||||
Test users API endpoints in the impress core app.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core import factories
|
||||
from core.tests.conftest import TEAM, USER, VIA
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_api_templates_generate_document_anonymous_public():
|
||||
"""Anonymous users can generate pdf document with public templates."""
|
||||
template = factories.TemplateFactory(is_public=True)
|
||||
data = {
|
||||
"body": "# Test markdown body",
|
||||
}
|
||||
|
||||
response = APIClient().post(
|
||||
f"/api/v1.0/templates/{template.id!s}/generate-document/",
|
||||
data,
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "application/pdf"
|
||||
|
||||
|
||||
def test_api_templates_generate_document_anonymous_not_public():
|
||||
"""
|
||||
Anonymous users should not be allowed to generate pdf document with templates
|
||||
that are not marked as public.
|
||||
"""
|
||||
template = factories.TemplateFactory(is_public=False)
|
||||
data = {
|
||||
"body": "# Test markdown body",
|
||||
}
|
||||
|
||||
response = APIClient().post(
|
||||
f"/api/v1.0/templates/{template.id!s}/generate-document/",
|
||||
data,
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
assert response.json() == {
|
||||
"detail": "Authentication credentials were not provided."
|
||||
}
|
||||
|
||||
|
||||
def test_api_templates_generate_document_authenticated_public():
|
||||
"""Authenticated users can generate pdf document with public templates."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(is_public=True)
|
||||
data = {"body": "# Test markdown body"}
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/templates/{template.id!s}/generate-document/",
|
||||
data,
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "application/pdf"
|
||||
|
||||
|
||||
def test_api_templates_generate_document_authenticated_not_public():
|
||||
"""
|
||||
Authenticated users should not be allowed to generate pdf document with templates
|
||||
that are not marked as public.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(is_public=False)
|
||||
data = {"body": "# Test markdown body"}
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/templates/{template.id!s}/generate-document/",
|
||||
data,
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {
|
||||
"detail": "You do not have permission to perform this action."
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_templates_generate_document_related(via, mock_user_teams):
|
||||
"""Users related to a template can generate pdf document."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
access = None
|
||||
if via == USER:
|
||||
access = factories.UserTemplateAccessFactory(user=user)
|
||||
elif via == TEAM:
|
||||
mock_user_teams.return_value = ["lasuite", "unknown"]
|
||||
access = factories.TeamTemplateAccessFactory(team="lasuite")
|
||||
|
||||
data = {"body": "# Test markdown body"}
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/templates/{access.template_id!s}/generate-document/",
|
||||
data,
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "application/pdf"
|
||||
|
||||
|
||||
def test_api_templates_generate_document_type_html():
|
||||
"""Generate pdf document with the body type html."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(is_public=True)
|
||||
data = {"body": "<p>Test body</p>", "body_type": "html"}
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/templates/{template.id!s}/generate-document/",
|
||||
data,
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "application/pdf"
|
||||
|
||||
|
||||
def test_api_templates_generate_document_type_markdown():
|
||||
"""Generate pdf document with the body type markdown."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(is_public=True)
|
||||
data = {"body": "# Test markdown body", "body_type": "markdown"}
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/templates/{template.id!s}/generate-document/",
|
||||
data,
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "application/pdf"
|
||||
|
||||
|
||||
def test_api_templates_generate_document_type_unknown():
|
||||
"""Generate pdf document with the body type unknown."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(is_public=True)
|
||||
data = {"body": "# Test markdown body", "body_type": "unknown"}
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/templates/{template.id!s}/generate-document/",
|
||||
data,
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {
|
||||
"body_type": [
|
||||
'"unknown" is not a valid choice.',
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def test_api_templates_generate_document_export_docx():
|
||||
"""Generate pdf document with the body type html."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(is_public=True)
|
||||
data = {"body": "<p>Test body</p>", "body_type": "html", "format": "docx"}
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/templates/{template.id!s}/generate-document/",
|
||||
data,
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert (
|
||||
response.headers["content-type"]
|
||||
== "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
)
|
||||
@@ -20,6 +20,7 @@ pytestmark = pytest.mark.django_db
|
||||
CRISP_WEBSITE_ID="123",
|
||||
FRONTEND_THEME="test-theme",
|
||||
MEDIA_BASE_URL="http://testserver/",
|
||||
POSTHOG_KEY={"id": "132456", "host": "https://eu.i.posthog-test.com"},
|
||||
SENTRY_DSN="https://sentry.test/123",
|
||||
)
|
||||
@pytest.mark.parametrize("is_authenticated", [False, True])
|
||||
@@ -41,5 +42,6 @@ def test_api_config(is_authenticated):
|
||||
"LANGUAGES": [["en-us", "English"], ["fr-fr", "French"], ["de-de", "German"]],
|
||||
"LANGUAGE_CODE": "en-us",
|
||||
"MEDIA_BASE_URL": "http://testserver/",
|
||||
"POSTHOG_KEY": {"id": "132456", "host": "https://eu.i.posthog-test.com"},
|
||||
"SENTRY_DSN": "https://sentry.test/123",
|
||||
}
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
Unit tests for the Template model
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
@@ -189,31 +185,3 @@ def test_models_templates_get_abilities_preset_role(django_assert_num_queries):
|
||||
"partial_update": False,
|
||||
"generate_document": True,
|
||||
}
|
||||
|
||||
|
||||
def test_models_templates__generate_word():
|
||||
"""Generate word document and assert no tmp files are left in /tmp folder."""
|
||||
template = factories.TemplateFactory()
|
||||
response = template.generate_word("<p>Test body</p>", {})
|
||||
|
||||
assert response.status_code == 200
|
||||
assert len([f for f in os.listdir("/tmp") if f.startswith("docx_")]) == 0
|
||||
|
||||
|
||||
@mock.patch(
|
||||
"pypandoc.convert_text",
|
||||
side_effect=RuntimeError("Conversion failed"),
|
||||
)
|
||||
def test_models_templates__generate_word__raise_error(_mock_pypandoc):
|
||||
"""
|
||||
Generate word document and assert no tmp files are left in /tmp folder
|
||||
even when the conversion fails.
|
||||
"""
|
||||
template = factories.TemplateFactory()
|
||||
|
||||
try:
|
||||
template.generate_word("<p>Test body</p>", {})
|
||||
except RuntimeError as e:
|
||||
assert str(e) == "Conversion failed"
|
||||
time.sleep(0.5)
|
||||
assert len([f for f in os.listdir("/tmp") if f.startswith("docx_")]) == 0
|
||||
|
||||
@@ -1,10 +1,2 @@
|
||||
<page size="A4">
|
||||
<div class="header">
|
||||
<img width="200"
|
||||
src="https://impress-staging.beta.numerique.gouv.fr/assets/logo-gouv.png"
|
||||
/>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="body">{{ body }}</div>
|
||||
</div>
|
||||
</page>
|
||||
<img width="200" src="https://impress-staging.beta.numerique.gouv.fr/assets/logo-gouv.png" />
|
||||
<br/>
|
||||
@@ -1,20 +0,0 @@
|
||||
body {
|
||||
background: white;
|
||||
font-family: arial;
|
||||
}
|
||||
.header img {
|
||||
width: 5cm;
|
||||
margin-left: -0.4cm;
|
||||
}
|
||||
.body{
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
[custom-style="center"] {
|
||||
text-align: center;
|
||||
}
|
||||
[custom-style="right"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -390,6 +390,11 @@ class Base(Configuration):
|
||||
None, environ_name="FRONTEND_THEME", environ_prefix=None
|
||||
)
|
||||
|
||||
# Posthog
|
||||
POSTHOG_KEY = values.DictValue(
|
||||
None, environ_name="POSTHOG_KEY", environ_prefix=None
|
||||
)
|
||||
|
||||
# Crisp
|
||||
CRISP_WEBSITE_ID = values.Value(
|
||||
None, environ_name="CRISP_WEBSITE_ID", environ_prefix=None
|
||||
|
||||
Binary file not shown.
@@ -1,9 +1,9 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-people\n"
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-12-17 15:50+0000\n"
|
||||
"PO-Revision-Date: 2025-01-14 15:14\n"
|
||||
"POT-Creation-Date: 2025-01-15 21:00+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 09:27\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Language: de_DE\n"
|
||||
@@ -11,384 +11,342 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: lasuite-people\n"
|
||||
"X-Crowdin-Project-ID: 637934\n"
|
||||
"X-Crowdin-Project: lasuite-docs\n"
|
||||
"X-Crowdin-Project-ID: 754523\n"
|
||||
"X-Crowdin-Language: de\n"
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 8\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: core/admin.py:33
|
||||
#: build/lib/core/admin.py:33 core/admin.py:33
|
||||
msgid "Personal info"
|
||||
msgstr "Persönliche Daten"
|
||||
|
||||
#: core/admin.py:46
|
||||
#: build/lib/core/admin.py:46 core/admin.py:46
|
||||
msgid "Permissions"
|
||||
msgstr "Berechtigungen"
|
||||
|
||||
#: core/admin.py:58
|
||||
#: build/lib/core/admin.py:58 core/admin.py:58
|
||||
msgid "Important dates"
|
||||
msgstr "Wichtige Daten"
|
||||
|
||||
#: core/api/filters.py:16
|
||||
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
|
||||
msgid "Creator is me"
|
||||
msgstr "Ersteller bin ich"
|
||||
|
||||
#: core/api/filters.py:19
|
||||
#: build/lib/core/api/filters.py:19 core/api/filters.py:19
|
||||
msgid "Favorite"
|
||||
msgstr "Favorit"
|
||||
|
||||
#: core/api/filters.py:22
|
||||
#: build/lib/core/api/filters.py:22 core/api/filters.py:22
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: core/api/serializers.py:307
|
||||
#: build/lib/core/api/serializers.py:317 core/api/serializers.py:317
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Ein neues Dokument wurde in Ihrem Namen erstellt!"
|
||||
|
||||
#: core/api/serializers.py:311
|
||||
#: build/lib/core/api/serializers.py:321 core/api/serializers.py:321
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Sie sind Besitzer eines neuen Dokuments:"
|
||||
|
||||
#: core/api/serializers.py:414
|
||||
#: build/lib/core/api/serializers.py:422 core/api/serializers.py:422
|
||||
msgid "Body"
|
||||
msgstr "Inhalt"
|
||||
|
||||
#: core/api/serializers.py:417
|
||||
#: build/lib/core/api/serializers.py:425 core/api/serializers.py:425
|
||||
msgid "Body type"
|
||||
msgstr "Typ"
|
||||
|
||||
#: core/api/serializers.py:423
|
||||
#: build/lib/core/api/serializers.py:431 core/api/serializers.py:431
|
||||
msgid "Format"
|
||||
msgstr "Format"
|
||||
msgstr ""
|
||||
|
||||
#: core/authentication/backends.py:57
|
||||
#: build/lib/core/authentication/backends.py:61
|
||||
#: core/authentication/backends.py:61
|
||||
msgid "Invalid response format or token verification failed"
|
||||
msgstr "Ungültiges Antwortformat oder Token-Verifizierung fehlgeschlagen"
|
||||
|
||||
#: core/authentication/backends.py:81
|
||||
msgid "User info contained no recognizable user identification"
|
||||
msgstr "Benutzerinfo enthielt keine erkennbare Benutzeridentifikation"
|
||||
|
||||
#: core/authentication/backends.py:88
|
||||
#: build/lib/core/authentication/backends.py:108
|
||||
#: core/authentication/backends.py:108
|
||||
msgid "User account is disabled"
|
||||
msgstr "Benutzerkonto ist deaktiviert"
|
||||
|
||||
#: core/models.py:62 core/models.py:69
|
||||
#: build/lib/core/models.py:63 build/lib/core/models.py:70 core/models.py:63
|
||||
#: core/models.py:70
|
||||
msgid "Reader"
|
||||
msgstr "Lesen"
|
||||
|
||||
#: core/models.py:63 core/models.py:70
|
||||
#: build/lib/core/models.py:64 build/lib/core/models.py:71 core/models.py:64
|
||||
#: core/models.py:71
|
||||
msgid "Editor"
|
||||
msgstr "Bearbeiten"
|
||||
|
||||
#: core/models.py:71
|
||||
#: build/lib/core/models.py:72 core/models.py:72
|
||||
msgid "Administrator"
|
||||
msgstr "Administrator"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:72
|
||||
#: build/lib/core/models.py:73 core/models.py:73
|
||||
msgid "Owner"
|
||||
msgstr "Besitzer"
|
||||
|
||||
#: core/models.py:83
|
||||
#: build/lib/core/models.py:84 core/models.py:84
|
||||
msgid "Restricted"
|
||||
msgstr "Beschränkt"
|
||||
|
||||
#: core/models.py:87
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "Authenticated"
|
||||
msgstr "Authentifiziert"
|
||||
|
||||
#: core/models.py:89
|
||||
#: build/lib/core/models.py:90 core/models.py:90
|
||||
msgid "Public"
|
||||
msgstr "Öffentlich"
|
||||
|
||||
#: core/models.py:101
|
||||
#: build/lib/core/models.py:112 core/models.py:112
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:102
|
||||
#: build/lib/core/models.py:113 core/models.py:113
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:108
|
||||
#: build/lib/core/models.py:119 core/models.py:119
|
||||
msgid "created on"
|
||||
msgstr "Erstellt"
|
||||
|
||||
#: core/models.py:109
|
||||
#: build/lib/core/models.py:120 core/models.py:120
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "Datum und Uhrzeit, an dem ein Datensatz erstellt wurde"
|
||||
|
||||
#: core/models.py:114
|
||||
#: build/lib/core/models.py:125 core/models.py:125
|
||||
msgid "updated on"
|
||||
msgstr "Aktualisiert"
|
||||
|
||||
#: core/models.py:115
|
||||
#: build/lib/core/models.py:126 core/models.py:126
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "Datum und Uhrzeit, an dem zuletzt aktualisiert wurde"
|
||||
|
||||
#: core/models.py:135
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr "Geben Sie eine gültige Unterseite ein. Dieser Wert darf nur Buchstaben, Zahlen und die @/./+/-/_/: Zeichen enthalten."
|
||||
|
||||
#: core/models.py:141
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "sub"
|
||||
msgstr "unter"
|
||||
|
||||
#: core/models.py:143
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr "Erforderlich. 255 Zeichen oder weniger. Buchstaben, Zahlen und die Zeichen @/./+/-/_/:"
|
||||
|
||||
#: core/models.py:152
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
msgid "full name"
|
||||
msgstr "Name"
|
||||
|
||||
#: core/models.py:153
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
msgid "short name"
|
||||
msgstr "Kurzbezeichnung"
|
||||
|
||||
#: core/models.py:155
|
||||
#: build/lib/core/models.py:195 core/models.py:195
|
||||
msgid "identity email address"
|
||||
msgstr "Identitäts-E-Mail-Adresse"
|
||||
|
||||
#: core/models.py:160
|
||||
#: build/lib/core/models.py:200 core/models.py:200
|
||||
msgid "admin email address"
|
||||
msgstr "Admin E-Mail-Adresse"
|
||||
|
||||
#: core/models.py:167
|
||||
#: build/lib/core/models.py:207 core/models.py:207
|
||||
msgid "language"
|
||||
msgstr "Sprache"
|
||||
|
||||
#: core/models.py:168
|
||||
#: build/lib/core/models.py:208 core/models.py:208
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "Die Sprache, in der der Benutzer die Benutzeroberfläche sehen möchte."
|
||||
|
||||
#: core/models.py:174
|
||||
#: build/lib/core/models.py:214 core/models.py:214
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "Die Zeitzone, in der der Nutzer Zeiten sehen möchte."
|
||||
|
||||
#: core/models.py:177
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "device"
|
||||
msgstr "Gerät"
|
||||
|
||||
#: core/models.py:179
|
||||
#: build/lib/core/models.py:219 core/models.py:219
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Ob der Benutzer ein Gerät oder ein echter Benutzer ist."
|
||||
|
||||
#: core/models.py:182
|
||||
#: build/lib/core/models.py:222 core/models.py:222
|
||||
msgid "staff status"
|
||||
msgstr "Status des Teammitgliedes"
|
||||
|
||||
#: core/models.py:184
|
||||
#: build/lib/core/models.py:224 core/models.py:224
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Gibt an, ob der Benutzer sich in diese Admin-Seite einloggen kann."
|
||||
|
||||
#: core/models.py:187
|
||||
#: build/lib/core/models.py:227 core/models.py:227
|
||||
msgid "active"
|
||||
msgstr "aktiviert"
|
||||
|
||||
#: core/models.py:190
|
||||
#: build/lib/core/models.py:230 core/models.py:230
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "Ob dieser Benutzer als aktiviert behandelt werden soll. Deaktivieren Sie diese Option, anstatt Konten zu löschen."
|
||||
|
||||
#: core/models.py:202
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "user"
|
||||
msgstr "Benutzer"
|
||||
|
||||
#: core/models.py:203
|
||||
#: build/lib/core/models.py:243 core/models.py:243
|
||||
msgid "users"
|
||||
msgstr "Benutzer"
|
||||
|
||||
#: core/models.py:342 core/models.py:718
|
||||
#: build/lib/core/models.py:382 build/lib/core/models.py:758 core/models.py:382
|
||||
#: core/models.py:758
|
||||
msgid "title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: core/models.py:364
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Document"
|
||||
msgstr "Dokument"
|
||||
|
||||
#: core/models.py:365
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "Documents"
|
||||
msgstr "Dokumente"
|
||||
|
||||
#: core/models.py:368
|
||||
#: build/lib/core/models.py:408 core/models.py:408
|
||||
msgid "Untitled Document"
|
||||
msgstr "Unbenanntes Dokument"
|
||||
|
||||
#: core/models.py:593
|
||||
#: build/lib/core/models.py:633 core/models.py:633
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} hat ein Dokument mit Ihnen geteilt!"
|
||||
|
||||
#: core/models.py:597
|
||||
#: build/lib/core/models.py:637 core/models.py:637
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "{name} hat Sie mit der Rolle \"{role}\" zu folgendem Dokument eingeladen:"
|
||||
|
||||
#: core/models.py:600
|
||||
#: build/lib/core/models.py:640 core/models.py:640
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} hat ein Dokument mit Ihnen geteilt: {title}"
|
||||
|
||||
#: core/models.py:623
|
||||
#: build/lib/core/models.py:663 core/models.py:663
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Dokument/Benutzer Linkverfolgung"
|
||||
|
||||
#: core/models.py:624
|
||||
#: build/lib/core/models.py:664 core/models.py:664
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Dokument/Benutzer Linkverfolgung"
|
||||
|
||||
#: core/models.py:630
|
||||
#: build/lib/core/models.py:670 core/models.py:670
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:653
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Document favorite"
|
||||
msgstr "Dokumentenfavorit"
|
||||
|
||||
#: core/models.py:654
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "Document favorites"
|
||||
msgstr "Dokumentfavoriten"
|
||||
|
||||
#: core/models.py:660
|
||||
#: build/lib/core/models.py:700 core/models.py:700
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Dieses Dokument ist bereits durch den gleichen Benutzer favorisiert worden."
|
||||
|
||||
#: core/models.py:682
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
msgid "Document/user relation"
|
||||
msgstr "Dokument/Benutzerbeziehung"
|
||||
|
||||
#: core/models.py:683
|
||||
#: build/lib/core/models.py:723 core/models.py:723
|
||||
msgid "Document/user relations"
|
||||
msgstr "Dokument/Benutzerbeziehungen"
|
||||
|
||||
#: core/models.py:689
|
||||
#: build/lib/core/models.py:729 core/models.py:729
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Dieser Benutzer befindet sich bereits in diesem Dokument."
|
||||
|
||||
#: core/models.py:695
|
||||
#: build/lib/core/models.py:735 core/models.py:735
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Dieses Team befindet sich bereits in diesem Dokument."
|
||||
|
||||
#: core/models.py:701 core/models.py:890
|
||||
#: build/lib/core/models.py:741 build/lib/core/models.py:930 core/models.py:741
|
||||
#: core/models.py:930
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "Benutzer oder Team müssen gesetzt werden, nicht beides."
|
||||
|
||||
#: core/models.py:719
|
||||
#: build/lib/core/models.py:759 core/models.py:759
|
||||
msgid "description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: core/models.py:720
|
||||
#: build/lib/core/models.py:760 core/models.py:760
|
||||
msgid "code"
|
||||
msgstr "Code"
|
||||
|
||||
#: core/models.py:721
|
||||
#: build/lib/core/models.py:761 core/models.py:761
|
||||
msgid "css"
|
||||
msgstr "CSS"
|
||||
|
||||
#: core/models.py:723
|
||||
#: build/lib/core/models.py:763 core/models.py:763
|
||||
msgid "public"
|
||||
msgstr "öffentlich"
|
||||
|
||||
#: core/models.py:725
|
||||
#: build/lib/core/models.py:765 core/models.py:765
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr "Ob diese Vorlage für jedermann öffentlich ist."
|
||||
|
||||
#: core/models.py:731
|
||||
#: build/lib/core/models.py:771 core/models.py:771
|
||||
msgid "Template"
|
||||
msgstr "Vorlage"
|
||||
|
||||
#: core/models.py:732
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Templates"
|
||||
msgstr "Vorlagen"
|
||||
|
||||
#: core/models.py:871
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Template/user relation"
|
||||
msgstr "Vorlage/Benutzer-Beziehung"
|
||||
|
||||
#: core/models.py:872
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Template/user relations"
|
||||
msgstr "Vorlage/Benutzerbeziehungen"
|
||||
|
||||
#: core/models.py:878
|
||||
#: build/lib/core/models.py:918 core/models.py:918
|
||||
msgid "This user is already in this template."
|
||||
msgstr "Dieser Benutzer ist bereits in dieser Vorlage."
|
||||
|
||||
#: core/models.py:884
|
||||
#: build/lib/core/models.py:924 core/models.py:924
|
||||
msgid "This team is already in this template."
|
||||
msgstr "Dieses Team ist bereits in diesem Template."
|
||||
|
||||
#: core/models.py:907
|
||||
#: build/lib/core/models.py:947 core/models.py:947
|
||||
msgid "email address"
|
||||
msgstr "E-Mail-Adresse"
|
||||
|
||||
#: core/models.py:926
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document invitation"
|
||||
msgstr "Einladung zum Dokument"
|
||||
|
||||
#: core/models.py:927
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document invitations"
|
||||
msgstr "Dokumenteinladungen"
|
||||
|
||||
#: core/models.py:944
|
||||
#: build/lib/core/models.py:987 core/models.py:987
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Diese E-Mail ist bereits einem registrierten Benutzer zugeordnet."
|
||||
|
||||
#: core/templates/mail/html/hello.html:159 core/templates/mail/text/hello.txt:3
|
||||
msgid "Company logo"
|
||||
msgstr "Unternehmens-Logo"
|
||||
|
||||
#: core/templates/mail/html/hello.html:188 core/templates/mail/text/hello.txt:5
|
||||
#, python-format
|
||||
msgid "Hello %(name)s"
|
||||
msgstr "Guten Tag %(name)s!"
|
||||
|
||||
#: core/templates/mail/html/hello.html:188 core/templates/mail/text/hello.txt:5
|
||||
msgid "Hello"
|
||||
msgstr "Hallo"
|
||||
|
||||
#: core/templates/mail/html/hello.html:189 core/templates/mail/text/hello.txt:6
|
||||
msgid "Thank you very much for your visit!"
|
||||
msgstr "Vielen Dank für Ihren Besuch!"
|
||||
|
||||
#: core/templates/mail/html/hello.html:221
|
||||
#, python-format
|
||||
msgid "This mail has been sent to %(email)s by <a href=\"%(href)s\">%(name)s</a>"
|
||||
msgstr "Diese E-Mail wurde an %(email)s von <a href=\"%(href)s\">%(name)s</a> gesendet"
|
||||
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr "Logo-E-Mail"
|
||||
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr "Öffnen"
|
||||
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr " Docs, Ihr neues unentbehrliches Werkzeug für die Organisation, den Austausch und die Zusammenarbeit in Ihren Dokumenten als Team. "
|
||||
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
msgstr " Erstellt von %(brandname)s "
|
||||
|
||||
#: core/templates/mail/text/hello.txt:8
|
||||
#, python-format
|
||||
msgid "This mail has been sent to %(email)s by %(name)s [%(href)s]"
|
||||
msgstr "Diese E-Mail wurde an %(email)s von %(name)s [%(href)s ] gesendet"
|
||||
|
||||
#: impress/settings.py:236
|
||||
#: build/lib/impress/settings.py:236 impress/settings.py:236
|
||||
msgid "English"
|
||||
msgstr "Englisch"
|
||||
|
||||
#: impress/settings.py:237
|
||||
#: build/lib/impress/settings.py:237 impress/settings.py:237
|
||||
msgid "French"
|
||||
msgstr "Französisch"
|
||||
|
||||
#: impress/settings.py:238
|
||||
#: build/lib/impress/settings.py:238 impress/settings.py:238
|
||||
msgid "German"
|
||||
msgstr "Deutsch"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,9 +1,9 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-people\n"
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-12-17 15:50+0000\n"
|
||||
"PO-Revision-Date: 2024-12-17 15:53\n"
|
||||
"POT-Creation-Date: 2025-01-15 21:00+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 09:27\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: English\n"
|
||||
"Language: en_US\n"
|
||||
@@ -11,384 +11,342 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: lasuite-people\n"
|
||||
"X-Crowdin-Project-ID: 637934\n"
|
||||
"X-Crowdin-Project: lasuite-docs\n"
|
||||
"X-Crowdin-Project-ID: 754523\n"
|
||||
"X-Crowdin-Language: en\n"
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 8\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: core/admin.py:33
|
||||
#: build/lib/core/admin.py:33 core/admin.py:33
|
||||
msgid "Personal info"
|
||||
msgstr ""
|
||||
|
||||
#: core/admin.py:46
|
||||
#: build/lib/core/admin.py:46 core/admin.py:46
|
||||
msgid "Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: core/admin.py:58
|
||||
#: build/lib/core/admin.py:58 core/admin.py:58
|
||||
msgid "Important dates"
|
||||
msgstr ""
|
||||
|
||||
#: core/api/filters.py:16
|
||||
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
|
||||
msgid "Creator is me"
|
||||
msgstr ""
|
||||
|
||||
#: core/api/filters.py:19
|
||||
#: build/lib/core/api/filters.py:19 core/api/filters.py:19
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
#: core/api/filters.py:22
|
||||
#: build/lib/core/api/filters.py:22 core/api/filters.py:22
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: core/api/serializers.py:307
|
||||
#: build/lib/core/api/serializers.py:317 core/api/serializers.py:317
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr ""
|
||||
|
||||
#: core/api/serializers.py:311
|
||||
#: build/lib/core/api/serializers.py:321 core/api/serializers.py:321
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr ""
|
||||
|
||||
#: core/api/serializers.py:414
|
||||
#: build/lib/core/api/serializers.py:422 core/api/serializers.py:422
|
||||
msgid "Body"
|
||||
msgstr ""
|
||||
|
||||
#: core/api/serializers.py:417
|
||||
#: build/lib/core/api/serializers.py:425 core/api/serializers.py:425
|
||||
msgid "Body type"
|
||||
msgstr ""
|
||||
|
||||
#: core/api/serializers.py:423
|
||||
#: build/lib/core/api/serializers.py:431 core/api/serializers.py:431
|
||||
msgid "Format"
|
||||
msgstr ""
|
||||
|
||||
#: core/authentication/backends.py:57
|
||||
#: build/lib/core/authentication/backends.py:61
|
||||
#: core/authentication/backends.py:61
|
||||
msgid "Invalid response format or token verification failed"
|
||||
msgstr ""
|
||||
|
||||
#: core/authentication/backends.py:81
|
||||
msgid "User info contained no recognizable user identification"
|
||||
msgstr ""
|
||||
|
||||
#: core/authentication/backends.py:88
|
||||
#: build/lib/core/authentication/backends.py:108
|
||||
#: core/authentication/backends.py:108
|
||||
msgid "User account is disabled"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:62 core/models.py:69
|
||||
#: build/lib/core/models.py:63 build/lib/core/models.py:70 core/models.py:63
|
||||
#: core/models.py:70
|
||||
msgid "Reader"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:63 core/models.py:70
|
||||
#: build/lib/core/models.py:64 build/lib/core/models.py:71 core/models.py:64
|
||||
#: core/models.py:71
|
||||
msgid "Editor"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:71
|
||||
#: build/lib/core/models.py:72 core/models.py:72
|
||||
msgid "Administrator"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:72
|
||||
#: build/lib/core/models.py:73 core/models.py:73
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:83
|
||||
#: build/lib/core/models.py:84 core/models.py:84
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:87
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:89
|
||||
#: build/lib/core/models.py:90 core/models.py:90
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:101
|
||||
#: build/lib/core/models.py:112 core/models.py:112
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:102
|
||||
#: build/lib/core/models.py:113 core/models.py:113
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:108
|
||||
#: build/lib/core/models.py:119 core/models.py:119
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:109
|
||||
#: build/lib/core/models.py:120 core/models.py:120
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:114
|
||||
#: build/lib/core/models.py:125 core/models.py:125
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:115
|
||||
#: build/lib/core/models.py:126 core/models.py:126
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:135
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:141
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:143
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:152
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:153
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
msgid "short name"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:155
|
||||
#: build/lib/core/models.py:195 core/models.py:195
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:160
|
||||
#: build/lib/core/models.py:200 core/models.py:200
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:167
|
||||
#: build/lib/core/models.py:207 core/models.py:207
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:168
|
||||
#: build/lib/core/models.py:208 core/models.py:208
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:174
|
||||
#: build/lib/core/models.py:214 core/models.py:214
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:177
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "device"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:179
|
||||
#: build/lib/core/models.py:219 core/models.py:219
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:182
|
||||
#: build/lib/core/models.py:222 core/models.py:222
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:184
|
||||
#: build/lib/core/models.py:224 core/models.py:224
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:187
|
||||
#: build/lib/core/models.py:227 core/models.py:227
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:190
|
||||
#: build/lib/core/models.py:230 core/models.py:230
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:202
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:203
|
||||
#: build/lib/core/models.py:243 core/models.py:243
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:342 core/models.py:718
|
||||
#: build/lib/core/models.py:382 build/lib/core/models.py:758 core/models.py:382
|
||||
#: core/models.py:758
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:364
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:365
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:368
|
||||
#: build/lib/core/models.py:408 core/models.py:408
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:593
|
||||
#: build/lib/core/models.py:633 core/models.py:633
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:597
|
||||
#: build/lib/core/models.py:637 core/models.py:637
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:600
|
||||
#: build/lib/core/models.py:640 core/models.py:640
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:623
|
||||
#: build/lib/core/models.py:663 core/models.py:663
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:624
|
||||
#: build/lib/core/models.py:664 core/models.py:664
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:630
|
||||
#: build/lib/core/models.py:670 core/models.py:670
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:653
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:654
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:660
|
||||
#: build/lib/core/models.py:700 core/models.py:700
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:682
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:683
|
||||
#: build/lib/core/models.py:723 core/models.py:723
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:689
|
||||
#: build/lib/core/models.py:729 core/models.py:729
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:695
|
||||
#: build/lib/core/models.py:735 core/models.py:735
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:701 core/models.py:890
|
||||
#: build/lib/core/models.py:741 build/lib/core/models.py:930 core/models.py:741
|
||||
#: core/models.py:930
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:719
|
||||
#: build/lib/core/models.py:759 core/models.py:759
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:720
|
||||
#: build/lib/core/models.py:760 core/models.py:760
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:721
|
||||
#: build/lib/core/models.py:761 core/models.py:761
|
||||
msgid "css"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:723
|
||||
#: build/lib/core/models.py:763 core/models.py:763
|
||||
msgid "public"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:725
|
||||
#: build/lib/core/models.py:765 core/models.py:765
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:731
|
||||
#: build/lib/core/models.py:771 core/models.py:771
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:732
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:871
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:872
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:878
|
||||
#: build/lib/core/models.py:918 core/models.py:918
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:884
|
||||
#: build/lib/core/models.py:924 core/models.py:924
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:907
|
||||
#: build/lib/core/models.py:947 core/models.py:947
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:926
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:927
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:944
|
||||
#: build/lib/core/models.py:987 core/models.py:987
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/hello.html:159 core/templates/mail/text/hello.txt:3
|
||||
msgid "Company logo"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/hello.html:188 core/templates/mail/text/hello.txt:5
|
||||
#, python-format
|
||||
msgid "Hello %(name)s"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/hello.html:188 core/templates/mail/text/hello.txt:5
|
||||
msgid "Hello"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/hello.html:189 core/templates/mail/text/hello.txt:6
|
||||
msgid "Thank you very much for your visit!"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/hello.html:221
|
||||
#, python-format
|
||||
msgid "This mail has been sent to %(email)s by <a href=\"%(href)s\">%(name)s</a>"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/text/hello.txt:8
|
||||
#, python-format
|
||||
msgid "This mail has been sent to %(email)s by %(name)s [%(href)s]"
|
||||
msgstr ""
|
||||
|
||||
#: impress/settings.py:236
|
||||
#: build/lib/impress/settings.py:236 impress/settings.py:236
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: impress/settings.py:237
|
||||
#: build/lib/impress/settings.py:237 impress/settings.py:237
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: impress/settings.py:238
|
||||
#: build/lib/impress/settings.py:238 impress/settings.py:238
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,9 +1,9 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-people\n"
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-12-17 15:50+0000\n"
|
||||
"PO-Revision-Date: 2024-12-17 15:53\n"
|
||||
"POT-Creation-Date: 2025-01-15 21:00+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 09:27\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr_FR\n"
|
||||
@@ -11,384 +11,342 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"X-Crowdin-Project: lasuite-people\n"
|
||||
"X-Crowdin-Project-ID: 637934\n"
|
||||
"X-Crowdin-Project: lasuite-docs\n"
|
||||
"X-Crowdin-Project-ID: 754523\n"
|
||||
"X-Crowdin-Language: fr\n"
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 8\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: core/admin.py:33
|
||||
#: build/lib/core/admin.py:33 core/admin.py:33
|
||||
msgid "Personal info"
|
||||
msgstr "Infos Personnelles"
|
||||
|
||||
#: core/admin.py:46
|
||||
#: build/lib/core/admin.py:46 core/admin.py:46
|
||||
msgid "Permissions"
|
||||
msgstr "Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: core/admin.py:58
|
||||
#: build/lib/core/admin.py:58 core/admin.py:58
|
||||
msgid "Important dates"
|
||||
msgstr "Dates importantes"
|
||||
|
||||
#: core/api/filters.py:16
|
||||
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
|
||||
msgid "Creator is me"
|
||||
msgstr ""
|
||||
|
||||
#: core/api/filters.py:19
|
||||
#: build/lib/core/api/filters.py:19 core/api/filters.py:19
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
#: core/api/filters.py:22
|
||||
#: build/lib/core/api/filters.py:22 core/api/filters.py:22
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: core/api/serializers.py:307
|
||||
#: build/lib/core/api/serializers.py:317 core/api/serializers.py:317
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Un nouveau document a été créé pour vous !"
|
||||
|
||||
#: core/api/serializers.py:311
|
||||
#: build/lib/core/api/serializers.py:321 core/api/serializers.py:321
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Vous avez été déclaré propriétaire d'un nouveau document :"
|
||||
|
||||
#: core/api/serializers.py:414
|
||||
#: build/lib/core/api/serializers.py:422 core/api/serializers.py:422
|
||||
msgid "Body"
|
||||
msgstr ""
|
||||
|
||||
#: core/api/serializers.py:417
|
||||
#: build/lib/core/api/serializers.py:425 core/api/serializers.py:425
|
||||
msgid "Body type"
|
||||
msgstr ""
|
||||
|
||||
#: core/api/serializers.py:423
|
||||
#: build/lib/core/api/serializers.py:431 core/api/serializers.py:431
|
||||
msgid "Format"
|
||||
msgstr ""
|
||||
|
||||
#: core/authentication/backends.py:57
|
||||
#: build/lib/core/authentication/backends.py:61
|
||||
#: core/authentication/backends.py:61
|
||||
msgid "Invalid response format or token verification failed"
|
||||
msgstr ""
|
||||
|
||||
#: core/authentication/backends.py:81
|
||||
msgid "User info contained no recognizable user identification"
|
||||
msgstr ""
|
||||
|
||||
#: core/authentication/backends.py:88
|
||||
#: build/lib/core/authentication/backends.py:108
|
||||
#: core/authentication/backends.py:108
|
||||
msgid "User account is disabled"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:62 core/models.py:69
|
||||
#: build/lib/core/models.py:63 build/lib/core/models.py:70 core/models.py:63
|
||||
#: core/models.py:70
|
||||
msgid "Reader"
|
||||
msgstr "Lecteur"
|
||||
|
||||
#: core/models.py:63 core/models.py:70
|
||||
#: build/lib/core/models.py:64 build/lib/core/models.py:71 core/models.py:64
|
||||
#: core/models.py:71
|
||||
msgid "Editor"
|
||||
msgstr "Éditeur"
|
||||
|
||||
#: core/models.py:71
|
||||
#: build/lib/core/models.py:72 core/models.py:72
|
||||
msgid "Administrator"
|
||||
msgstr "Administrateur"
|
||||
|
||||
#: core/models.py:72
|
||||
#: build/lib/core/models.py:73 core/models.py:73
|
||||
msgid "Owner"
|
||||
msgstr "Propriétaire"
|
||||
|
||||
#: core/models.py:83
|
||||
#: build/lib/core/models.py:84 core/models.py:84
|
||||
msgid "Restricted"
|
||||
msgstr "Restreint"
|
||||
|
||||
#: core/models.py:87
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "Authenticated"
|
||||
msgstr "Authentifié"
|
||||
|
||||
#: core/models.py:89
|
||||
#: build/lib/core/models.py:90 core/models.py:90
|
||||
msgid "Public"
|
||||
msgstr "Public"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:101
|
||||
#: build/lib/core/models.py:112 core/models.py:112
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:102
|
||||
#: build/lib/core/models.py:113 core/models.py:113
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:108
|
||||
#: build/lib/core/models.py:119 core/models.py:119
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:109
|
||||
#: build/lib/core/models.py:120 core/models.py:120
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:114
|
||||
#: build/lib/core/models.py:125 core/models.py:125
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:115
|
||||
#: build/lib/core/models.py:126 core/models.py:126
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:135
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:141
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:143
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:152
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:153
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
msgid "short name"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:155
|
||||
#: build/lib/core/models.py:195 core/models.py:195
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:160
|
||||
#: build/lib/core/models.py:200 core/models.py:200
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:167
|
||||
#: build/lib/core/models.py:207 core/models.py:207
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:168
|
||||
#: build/lib/core/models.py:208 core/models.py:208
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:174
|
||||
#: build/lib/core/models.py:214 core/models.py:214
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:177
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "device"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:179
|
||||
#: build/lib/core/models.py:219 core/models.py:219
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:182
|
||||
#: build/lib/core/models.py:222 core/models.py:222
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:184
|
||||
#: build/lib/core/models.py:224 core/models.py:224
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:187
|
||||
#: build/lib/core/models.py:227 core/models.py:227
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:190
|
||||
#: build/lib/core/models.py:230 core/models.py:230
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:202
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:203
|
||||
#: build/lib/core/models.py:243 core/models.py:243
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:342 core/models.py:718
|
||||
#: build/lib/core/models.py:382 build/lib/core/models.py:758 core/models.py:382
|
||||
#: core/models.py:758
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:364
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:365
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:368
|
||||
#: build/lib/core/models.py:408 core/models.py:408
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:593
|
||||
#: build/lib/core/models.py:633 core/models.py:633
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} a partagé un document avec vous!"
|
||||
|
||||
#: core/models.py:597
|
||||
#: build/lib/core/models.py:637 core/models.py:637
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "{name} vous a invité avec le rôle \"{role}\" sur le document suivant:"
|
||||
|
||||
#: core/models.py:600
|
||||
#: build/lib/core/models.py:640 core/models.py:640
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} a partagé un document avec vous: {title}"
|
||||
|
||||
#: core/models.py:623
|
||||
#: build/lib/core/models.py:663 core/models.py:663
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:624
|
||||
#: build/lib/core/models.py:664 core/models.py:664
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:630
|
||||
#: build/lib/core/models.py:670 core/models.py:670
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:653
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:654
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:660
|
||||
#: build/lib/core/models.py:700 core/models.py:700
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:682
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:683
|
||||
#: build/lib/core/models.py:723 core/models.py:723
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:689
|
||||
#: build/lib/core/models.py:729 core/models.py:729
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:695
|
||||
#: build/lib/core/models.py:735 core/models.py:735
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:701 core/models.py:890
|
||||
#: build/lib/core/models.py:741 build/lib/core/models.py:930 core/models.py:741
|
||||
#: core/models.py:930
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:719
|
||||
#: build/lib/core/models.py:759 core/models.py:759
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:720
|
||||
#: build/lib/core/models.py:760 core/models.py:760
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:721
|
||||
#: build/lib/core/models.py:761 core/models.py:761
|
||||
msgid "css"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:723
|
||||
#: build/lib/core/models.py:763 core/models.py:763
|
||||
msgid "public"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:725
|
||||
#: build/lib/core/models.py:765 core/models.py:765
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:731
|
||||
#: build/lib/core/models.py:771 core/models.py:771
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:732
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:871
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:872
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:878
|
||||
#: build/lib/core/models.py:918 core/models.py:918
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:884
|
||||
#: build/lib/core/models.py:924 core/models.py:924
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:907
|
||||
#: build/lib/core/models.py:947 core/models.py:947
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:926
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:927
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:944
|
||||
#: build/lib/core/models.py:987 core/models.py:987
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/hello.html:159 core/templates/mail/text/hello.txt:3
|
||||
msgid "Company logo"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/hello.html:188 core/templates/mail/text/hello.txt:5
|
||||
#, python-format
|
||||
msgid "Hello %(name)s"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/hello.html:188 core/templates/mail/text/hello.txt:5
|
||||
msgid "Hello"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/hello.html:189 core/templates/mail/text/hello.txt:6
|
||||
msgid "Thank you very much for your visit!"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/hello.html:221
|
||||
#, python-format
|
||||
msgid "This mail has been sent to %(email)s by <a href=\"%(href)s\">%(name)s</a>"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr "Ouvrir"
|
||||
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr " Docs, votre nouvel outil incontournable pour organiser, partager et collaborer sur vos documents en équipe. "
|
||||
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
msgstr " Proposé par %(brandname)s "
|
||||
|
||||
#: core/templates/mail/text/hello.txt:8
|
||||
#, python-format
|
||||
msgid "This mail has been sent to %(email)s by %(name)s [%(href)s]"
|
||||
msgstr ""
|
||||
|
||||
#: impress/settings.py:236
|
||||
#: build/lib/impress/settings.py:236 impress/settings.py:236
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: impress/settings.py:237
|
||||
#: build/lib/impress/settings.py:237 impress/settings.py:237
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: impress/settings.py:238
|
||||
#: build/lib/impress/settings.py:238 impress/settings.py:238
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
|
||||
352
src/backend/locale/nl_NL/LC_MESSAGES/django.po
Normal file
352
src/backend/locale/nl_NL/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,352 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-15 21:00+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 09:27\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"Language: nl_NL\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: lasuite-docs\n"
|
||||
"X-Crowdin-Project-ID: 754523\n"
|
||||
"X-Crowdin-Language: nl\n"
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:33 core/admin.py:33
|
||||
msgid "Personal info"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:46 core/admin.py:46
|
||||
msgid "Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:58 core/admin.py:58
|
||||
msgid "Important dates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
|
||||
msgid "Creator is me"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:19 core/api/filters.py:19
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:22 core/api/filters.py:22
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:317 core/api/serializers.py:317
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:321 core/api/serializers.py:321
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:422 core/api/serializers.py:422
|
||||
msgid "Body"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:425 core/api/serializers.py:425
|
||||
msgid "Body type"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:431 core/api/serializers.py:431
|
||||
msgid "Format"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/authentication/backends.py:61
|
||||
#: core/authentication/backends.py:61
|
||||
msgid "Invalid response format or token verification failed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/authentication/backends.py:108
|
||||
#: core/authentication/backends.py:108
|
||||
msgid "User account is disabled"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:63 build/lib/core/models.py:70 core/models.py:63
|
||||
#: core/models.py:70
|
||||
msgid "Reader"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:64 build/lib/core/models.py:71 core/models.py:64
|
||||
#: core/models.py:71
|
||||
msgid "Editor"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:72 core/models.py:72
|
||||
msgid "Administrator"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:73 core/models.py:73
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:84 core/models.py:84
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:90 core/models.py:90
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:112 core/models.py:112
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:113 core/models.py:113
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:119 core/models.py:119
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:120 core/models.py:120
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:125 core/models.py:125
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:126 core/models.py:126
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
msgid "short name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:195 core/models.py:195
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:200 core/models.py:200
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:207 core/models.py:207
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:208 core/models.py:208
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:214 core/models.py:214
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "device"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:219 core/models.py:219
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:222 core/models.py:222
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:224 core/models.py:224
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:227 core/models.py:227
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:230 core/models.py:230
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:243 core/models.py:243
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:382 build/lib/core/models.py:758 core/models.py:382
|
||||
#: core/models.py:758
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:408 core/models.py:408
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:633 core/models.py:633
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:637 core/models.py:637
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:640 core/models.py:640
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:663 core/models.py:663
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:664 core/models.py:664
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:670 core/models.py:670
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:700 core/models.py:700
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:723 core/models.py:723
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:729 core/models.py:729
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:735 core/models.py:735
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:741 build/lib/core/models.py:930 core/models.py:741
|
||||
#: core/models.py:930
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:759 core/models.py:759
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:760 core/models.py:760
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:761 core/models.py:761
|
||||
msgid "css"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:763 core/models.py:763
|
||||
msgid "public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:765 core/models.py:765
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:771 core/models.py:771
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:918 core/models.py:918
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:924 core/models.py:924
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:947 core/models.py:947
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:987 core/models.py:987
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/impress/settings.py:236 impress/settings.py:236
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/impress/settings.py:237 impress/settings.py:237
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/impress/settings.py:238 impress/settings.py:238
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "impress"
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -37,7 +37,7 @@ dependencies = [
|
||||
"django-redis==5.4.0",
|
||||
"django-storages[s3]==1.14.4",
|
||||
"django-timezone-field>=5.1",
|
||||
"django==5.1.4",
|
||||
"django==5.1.5",
|
||||
"djangorestframework==3.15.2",
|
||||
"drf_spectacular==0.28.0",
|
||||
"dockerflow==2024.4.2",
|
||||
@@ -50,13 +50,10 @@ dependencies = [
|
||||
"openai==1.58.1",
|
||||
"psycopg[binary]==3.2.3",
|
||||
"PyJWT==2.10.1",
|
||||
"pypandoc==1.14",
|
||||
"python-frontmatter==1.1.0",
|
||||
"python-magic==0.4.27",
|
||||
"requests==2.32.3",
|
||||
"sentry-sdk==2.19.2",
|
||||
"url-normalize==1.4.3",
|
||||
"WeasyPrint>=60.2",
|
||||
"whitenoise==6.8.2",
|
||||
"mozilla-django-oidc==4.0.1",
|
||||
]
|
||||
|
||||
@@ -16,6 +16,7 @@ const config = {
|
||||
['de-de', 'German'],
|
||||
],
|
||||
LANGUAGE_CODE: 'en-us',
|
||||
POSTHOG_KEY: {},
|
||||
SENTRY_DSN: null,
|
||||
};
|
||||
|
||||
|
||||
@@ -366,4 +366,27 @@ test.describe('Doc Editor', () => {
|
||||
|
||||
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
|
||||
});
|
||||
|
||||
test('it checks the multi columns', async ({ page, browserName }) => {
|
||||
await createDoc(page, 'doc-multi-columns', browserName, 1);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('/');
|
||||
|
||||
await page.getByText('Three Columns', { exact: true }).click();
|
||||
|
||||
await page.locator('.bn-block-column').first().fill('Column 1');
|
||||
await page.locator('.bn-block-column').nth(1).fill('Column 2');
|
||||
await page.locator('.bn-block-column').last().fill('Column 3');
|
||||
|
||||
expect(await page.locator('.bn-block-column').count()).toBe(3);
|
||||
await expect(
|
||||
page.locator('.bn-block-column[data-node-type="column"]').first(),
|
||||
).toHaveText('Column 1');
|
||||
await expect(
|
||||
page.locator('.bn-block-column[data-node-type="column"]').nth(1),
|
||||
).toHaveText('Column 2');
|
||||
await expect(
|
||||
page.locator('.bn-block-column[data-node-type="column"]').last(),
|
||||
).toHaveText('Column 3');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import path from 'path';
|
||||
|
||||
import { expect, test } from '@playwright/test';
|
||||
import cs from 'convert-stream';
|
||||
import jsdom from 'jsdom';
|
||||
import pdf from 'pdf-parse';
|
||||
|
||||
import { createDoc, verifyDocName } from './common';
|
||||
@@ -41,10 +42,8 @@ test.describe('Doc Export', () => {
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Download' })).toBeVisible();
|
||||
});
|
||||
test('it converts the doc to pdf with a template integrated', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
|
||||
test('it exports the doc to pdf', async ({ page, browserName }) => {
|
||||
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
|
||||
|
||||
const downloadPromise = page.waitForEvent('download', (download) => {
|
||||
@@ -77,10 +76,7 @@ test.describe('Doc Export', () => {
|
||||
expect(pdfText).toContain('Hello World'); // This is the doc text
|
||||
});
|
||||
|
||||
test('it converts the doc to docx with a template integrated', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
test('it exports the doc to docx', async ({ page, browserName }) => {
|
||||
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
|
||||
|
||||
const downloadPromise = page.waitForEvent('download', (download) => {
|
||||
@@ -111,152 +107,75 @@ test.describe('Doc Export', () => {
|
||||
expect(download.suggestedFilename()).toBe(`${randomDoc}.docx`);
|
||||
});
|
||||
|
||||
test('it converts the blocknote json in correct html for the export', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
test.setTimeout(60000);
|
||||
|
||||
/**
|
||||
* This test tell us that the export to pdf is working with images
|
||||
* but it does not tell us if the images are beeing displayed correctly
|
||||
* in the pdf.
|
||||
*
|
||||
* TODO: Check if the images are displayed correctly in the pdf
|
||||
*/
|
||||
test('it exports the docs with images', async ({ page, browserName }) => {
|
||||
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
|
||||
let body = '';
|
||||
|
||||
await page.route('**/templates/*/generate-document/', async (route) => {
|
||||
const request = route.request();
|
||||
body = request.postDataJSON().body;
|
||||
|
||||
await route.continue();
|
||||
const fileChooserPromise = page.waitForEvent('filechooser');
|
||||
const downloadPromise = page.waitForEvent('download', (download) => {
|
||||
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
|
||||
});
|
||||
|
||||
await verifyDocName(page, randomDoc);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('Hello World');
|
||||
await page.locator('.bn-block-outer').last().click();
|
||||
await page.keyboard.press('Enter');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.locator('.bn-block-outer').last().fill('Break');
|
||||
await expect(page.getByText('Break')).toBeVisible();
|
||||
await page.locator('.ProseMirror.bn-editor').click();
|
||||
await page.locator('.ProseMirror.bn-editor').fill('Hello World');
|
||||
|
||||
// Center the text
|
||||
await page.getByText('Break').dblclick();
|
||||
await page.locator('button[data-test="alignTextCenter"]').click();
|
||||
|
||||
// Change the background color
|
||||
await page.locator('button[data-test="colors"]').click();
|
||||
await page.locator('button[data-test="background-color-brown"]').click();
|
||||
|
||||
// Change the text color
|
||||
await page.getByText('Break').dblclick();
|
||||
await page.locator('button[data-test="colors"]').click();
|
||||
await page.locator('button[data-test="text-color-orange"]').click();
|
||||
|
||||
// Add a list
|
||||
await page.locator('.bn-block-outer').last().click();
|
||||
await page.keyboard.press('Enter');
|
||||
await page.locator('.bn-block-outer').last().fill('/');
|
||||
await page.getByText('Bullet List').click();
|
||||
await page
|
||||
.locator('.bn-block-content[data-content-type="bulletListItem"] p')
|
||||
.last()
|
||||
.fill('Test List 1');
|
||||
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||||
await page.waitForTimeout(300);
|
||||
await page.keyboard.press('Enter');
|
||||
await page
|
||||
.locator('.bn-block-content[data-content-type="bulletListItem"] p')
|
||||
.last()
|
||||
.fill('Test List 2');
|
||||
await page.keyboard.press('Enter');
|
||||
await page
|
||||
.locator('.bn-block-content[data-content-type="bulletListItem"] p')
|
||||
.last()
|
||||
.fill('Test List 3');
|
||||
await page.getByText('Resizable image with caption').click();
|
||||
await page.getByText('Upload image').click();
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
await page.keyboard.press('Backspace');
|
||||
const fileChooser = await fileChooserPromise;
|
||||
await fileChooser.setFiles(
|
||||
path.join(__dirname, 'assets/logo-suite-numerique.png'),
|
||||
);
|
||||
|
||||
// Add a number list
|
||||
await page.locator('.bn-block-outer').last().click();
|
||||
await page.keyboard.press('Enter');
|
||||
await page.locator('.bn-block-outer').last().fill('/');
|
||||
await page.getByText('Numbered List').click();
|
||||
await page
|
||||
.locator('.bn-block-content[data-content-type="numberedListItem"] p')
|
||||
.last()
|
||||
.fill('Test Number 1');
|
||||
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||||
await page.waitForTimeout(300);
|
||||
await page.keyboard.press('Enter');
|
||||
await page
|
||||
.locator('.bn-block-content[data-content-type="numberedListItem"] p')
|
||||
.last()
|
||||
.fill('Test Number 2');
|
||||
await page.keyboard.press('Enter');
|
||||
await page
|
||||
.locator('.bn-block-content[data-content-type="numberedListItem"] p')
|
||||
.last()
|
||||
.fill('Test Number 3');
|
||||
const image = page.getByRole('img', { name: 'logo-suite-numerique.png' });
|
||||
|
||||
// Add img
|
||||
await page.locator('.bn-block-outer').last().click();
|
||||
await page.keyboard.press('Enter');
|
||||
await page.locator('.bn-block-outer').last().fill('/');
|
||||
await page
|
||||
.getByRole('option', {
|
||||
name: 'Image',
|
||||
})
|
||||
.click();
|
||||
await page
|
||||
.getByRole('tab', {
|
||||
name: 'Embed',
|
||||
})
|
||||
.click();
|
||||
await page
|
||||
.getByPlaceholder('Enter URL')
|
||||
.fill('https://example.com/image.jpg');
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Embed image',
|
||||
})
|
||||
.click();
|
||||
await expect(image).toBeVisible();
|
||||
|
||||
// Download
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'download',
|
||||
})
|
||||
.click();
|
||||
|
||||
await page
|
||||
.getByRole('combobox', {
|
||||
name: 'Template',
|
||||
})
|
||||
.click();
|
||||
|
||||
await page
|
||||
.getByRole('option', {
|
||||
name: 'Demo Template',
|
||||
})
|
||||
.click({
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Download',
|
||||
})
|
||||
.click();
|
||||
|
||||
// Empty paragraph should be replaced by a <br/>
|
||||
expect(body.match(/<br>/g)?.length).toBeGreaterThanOrEqual(2);
|
||||
expect(body).toContain('style="color: orange;"');
|
||||
expect(body).toContain('custom-style="center"');
|
||||
expect(body).toContain('style="background-color: brown;"');
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
|
||||
|
||||
const { JSDOM } = jsdom;
|
||||
const DOMParser = new JSDOM().window.DOMParser;
|
||||
const parser = new DOMParser();
|
||||
const html = parser.parseFromString(body, 'text/html');
|
||||
const pdfBuffer = await cs.toBuffer(await download.createReadStream());
|
||||
const pdfExport = await pdf(pdfBuffer);
|
||||
const pdfText = pdfExport.text;
|
||||
|
||||
const ulLis = html.querySelectorAll('ul li');
|
||||
expect(ulLis.length).toBe(3);
|
||||
expect(ulLis[0].textContent).toBe('Test List 1');
|
||||
expect(ulLis[1].textContent).toBe('Test List 2');
|
||||
expect(ulLis[2].textContent).toBe('Test List 3');
|
||||
|
||||
const olLis = html.querySelectorAll('ol li');
|
||||
expect(olLis.length).toBe(3);
|
||||
expect(olLis[0].textContent).toBe('Test Number 1');
|
||||
expect(olLis[1].textContent).toBe('Test Number 2');
|
||||
expect(olLis[2].textContent).toBe('Test Number 3');
|
||||
|
||||
const img = html.querySelectorAll('img');
|
||||
expect(img.length).toBe(1);
|
||||
expect(img[0].src).toBe('https://example.com/image.jpg');
|
||||
expect(pdfText).toContain('Hello World');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,6 +47,7 @@ test.describe('Doc Header', () => {
|
||||
versions_list: true,
|
||||
versions_retrieve: true,
|
||||
accesses_manage: true,
|
||||
accesses_view: true,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
@@ -394,7 +395,38 @@ test.describe('Doc Header', () => {
|
||||
navigator.clipboard.readText(),
|
||||
);
|
||||
const clipboardContent = await handle.jsonValue();
|
||||
expect(clipboardContent.trim()).toBe(`<h1>Hello World</h1><p></p>`);
|
||||
expect(clipboardContent.trim()).toBe(
|
||||
`<h1 data-level=\"1\">Hello World</h1><p></p>`,
|
||||
);
|
||||
});
|
||||
|
||||
test('it checks the copy link button', async ({ page }) => {
|
||||
await mockedDocument(page, {
|
||||
abilities: {
|
||||
destroy: false, // Means owner
|
||||
link_configuration: true,
|
||||
versions_destroy: true,
|
||||
versions_list: true,
|
||||
versions_retrieve: true,
|
||||
accesses_manage: false,
|
||||
accesses_view: false,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
},
|
||||
});
|
||||
|
||||
await goToGridDoc(page);
|
||||
|
||||
const shareButton = page.getByRole('button', {
|
||||
name: 'Share',
|
||||
exact: true,
|
||||
});
|
||||
await expect(shareButton).toBeVisible();
|
||||
|
||||
await shareButton.click();
|
||||
await page.getByRole('button', { name: 'Copy link' }).click();
|
||||
await expect(page.getByText('Link Copied !')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -405,6 +437,46 @@ test.describe('Documents Header mobile', () => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('it checks the copy link button', async ({ page, browserName }) => {
|
||||
// eslint-disable-next-line playwright/no-skipped-test
|
||||
test.skip(
|
||||
browserName === 'webkit',
|
||||
'navigator.clipboard is not working with webkit and playwright',
|
||||
);
|
||||
await mockedDocument(page, {
|
||||
abilities: {
|
||||
destroy: false,
|
||||
link_configuration: true,
|
||||
versions_destroy: true,
|
||||
versions_list: true,
|
||||
versions_retrieve: true,
|
||||
accesses_manage: false,
|
||||
accesses_view: false,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
},
|
||||
});
|
||||
|
||||
await goToGridDoc(page);
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Copy link' })).toBeHidden();
|
||||
await page.getByLabel('Open the document options').click();
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
await page.getByRole('button', { name: 'Copy link' }).click();
|
||||
await expect(page.getByText('Link Copied !')).toBeVisible();
|
||||
// Test that clipboard is in HTML format
|
||||
const handle = await page.evaluateHandle(() =>
|
||||
navigator.clipboard.readText(),
|
||||
);
|
||||
const clipboardContent = await handle.jsonValue();
|
||||
|
||||
const origin = await page.evaluate(() => window.location.origin);
|
||||
expect(clipboardContent.trim()).toMatch(
|
||||
`${origin}/docs/mocked-document-id/`,
|
||||
);
|
||||
});
|
||||
|
||||
test('it checks the close button on Share modal', async ({ page }) => {
|
||||
await mockedDocument(page, {
|
||||
abilities: {
|
||||
@@ -414,6 +486,7 @@ test.describe('Documents Header mobile', () => {
|
||||
versions_list: true,
|
||||
versions_retrieve: true,
|
||||
accesses_manage: true,
|
||||
accesses_view: true,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
|
||||
@@ -250,7 +250,7 @@ test.describe('Doc Visibility: Public', () => {
|
||||
await expect(page.locator('h2').getByText(docTitle)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'search' })).toBeHidden();
|
||||
await expect(page.getByRole('button', { name: 'New doc' })).toBeHidden();
|
||||
await expect(page.getByRole('button', { name: 'Share' })).toBeHidden();
|
||||
await expect(page.getByRole('button', { name: 'Share' })).toBeVisible();
|
||||
const card = page.getByLabel('It is the card information');
|
||||
await expect(card).toBeVisible();
|
||||
|
||||
@@ -314,7 +314,7 @@ test.describe('Doc Visibility: Public', () => {
|
||||
await page.goto(urlDoc);
|
||||
|
||||
await verifyDocName(page, docTitle);
|
||||
await expect(page.getByRole('button', { name: 'Share' })).toBeHidden();
|
||||
await expect(page.getByRole('button', { name: 'Share' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -414,13 +414,8 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
|
||||
await expect(page.locator('h2').getByText(docTitle)).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
await expect(selectVisibility).toBeHidden();
|
||||
|
||||
const inputSearch = page.getByRole('combobox', {
|
||||
name: 'Quick search input',
|
||||
});
|
||||
await expect(inputSearch).toBeHidden();
|
||||
await page.getByRole('button', { name: 'Copy link' }).click();
|
||||
await expect(page.getByText('Link Copied !')).toBeVisible();
|
||||
});
|
||||
|
||||
test('It checks a authenticated doc in editable mode', async ({
|
||||
@@ -475,12 +470,7 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
|
||||
await verifyDocName(page, docTitle);
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
await expect(selectVisibility).toBeHidden();
|
||||
|
||||
const inputSearch = page.getByRole('combobox', {
|
||||
name: 'Quick search input',
|
||||
});
|
||||
await expect(inputSearch).toBeHidden();
|
||||
await page.getByRole('button', { name: 'Copy link' }).click();
|
||||
await expect(page.getByText('Link Copied !')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-e2e",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext .ts",
|
||||
@@ -22,7 +22,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"convert-stream": "1.0.2",
|
||||
"jsdom": "25.0.1",
|
||||
"pdf-parse": "1.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-impress",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -15,22 +15,28 @@
|
||||
"test:watch": "jest --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blocknote/core": "0.22.0",
|
||||
"@blocknote/mantine": "0.22.0",
|
||||
"@blocknote/react": "0.22.0",
|
||||
"@blocknote/core": "0.21.0",
|
||||
"@blocknote/mantine": "0.21.0",
|
||||
"@blocknote/react": "0.21.0",
|
||||
"@blocknote/xl-multi-column": "0.21.0",
|
||||
"@blocknote/xl-docx-exporter": "0.21.0",
|
||||
"@blocknote/xl-pdf-exporter": "0.21.0",
|
||||
"@gouvfr-lasuite/integration": "1.0.2",
|
||||
"@hocuspocus/provider": "2.15.0",
|
||||
"@openfun/cunningham-react": "2.9.4",
|
||||
"@react-pdf/renderer": "4.1.6",
|
||||
"@sentry/nextjs": "8.47.0",
|
||||
"@tanstack/react-query": "5.62.11",
|
||||
"cmdk": "1.0.4",
|
||||
"crisp-sdk-web": "1.0.25",
|
||||
"docx": "9.1.0",
|
||||
"i18next": "24.2.0",
|
||||
"i18next-browser-languagedetector": "8.0.2",
|
||||
"idb": "8.0.1",
|
||||
"lodash": "4.17.21",
|
||||
"luxon": "3.5.0",
|
||||
"next": "15.1.3",
|
||||
"posthog-js": "1.204.0",
|
||||
"react": "*",
|
||||
"react-aria-components": "1.5.0",
|
||||
"react-dom": "*",
|
||||
|
||||
@@ -20,12 +20,14 @@ export type DropdownMenuProps = {
|
||||
showArrow?: boolean;
|
||||
label?: string;
|
||||
arrowCss?: BoxProps['$css'];
|
||||
disabled?: boolean;
|
||||
topMessage?: string;
|
||||
};
|
||||
|
||||
export const DropdownMenu = ({
|
||||
options,
|
||||
children,
|
||||
disabled = false,
|
||||
showArrow = false,
|
||||
arrowCss,
|
||||
label,
|
||||
@@ -40,6 +42,10 @@ export const DropdownMenu = ({
|
||||
setIsOpen(isOpen);
|
||||
};
|
||||
|
||||
if (disabled) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropButton
|
||||
isOpen={isOpen}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Link from 'next/link';
|
||||
import styled from 'styled-components';
|
||||
import styled, { RuleSet } from 'styled-components';
|
||||
|
||||
export interface LinkProps {
|
||||
$css?: string;
|
||||
$css?: string | RuleSet<object>;
|
||||
}
|
||||
|
||||
export const StyledLink = styled(Link)<LinkProps>`
|
||||
@@ -12,5 +12,5 @@ export const StyledLink = styled(Link)<LinkProps>`
|
||||
color: #ffffff;
|
||||
}
|
||||
display: flex;
|
||||
${({ $css }) => $css && `${$css};`}
|
||||
${({ $css }) => $css && (typeof $css === 'string' ? `${$css};` : $css)}
|
||||
`;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { PropsWithChildren, useEffect } from 'react';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { configureCrispSession } from '@/services';
|
||||
import { PostHogProvider, configureCrispSession } from '@/services';
|
||||
import { useSentryStore } from '@/stores/useSentryStore';
|
||||
|
||||
import { useConfig } from './api/useConfig';
|
||||
@@ -45,5 +45,5 @@ export const ConfigProvider = ({ children }: PropsWithChildren) => {
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
return <PostHogProvider conf={conf.POSTHOG_KEY}>{children}</PostHogProvider>;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
import { Theme } from '@/cunningham/';
|
||||
import { PostHogConf } from '@/services';
|
||||
|
||||
interface ConfigResponse {
|
||||
LANGUAGES: [string, string][];
|
||||
@@ -11,6 +12,7 @@ interface ConfigResponse {
|
||||
CRISP_WEBSITE_ID?: string;
|
||||
FRONTEND_THEME?: Theme;
|
||||
MEDIA_BASE_URL?: string;
|
||||
POSTHOG_KEY?: PostHogConf;
|
||||
SENTRY_DSN?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -576,6 +576,22 @@ input:-webkit-autofill:focus {
|
||||
}
|
||||
}
|
||||
|
||||
.c__modal__scroller:has(.noPadding) {
|
||||
padding: 0 !important;
|
||||
|
||||
.c__modal__close .c__button {
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
|
||||
.c__modal__title {
|
||||
font-size: var(--c--theme--font--sizes--xs);
|
||||
padding: var(--c--theme--spacings--base);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toast
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
import { Dictionary, locales } from '@blocknote/core';
|
||||
import {
|
||||
Dictionary,
|
||||
combineByGroup,
|
||||
filterSuggestionItems,
|
||||
locales,
|
||||
} from '@blocknote/core';
|
||||
import '@blocknote/core/fonts/inter.css';
|
||||
import { BlockNoteView } from '@blocknote/mantine';
|
||||
import '@blocknote/mantine/style.css';
|
||||
import { useCreateBlockNote } from '@blocknote/react';
|
||||
import {
|
||||
SuggestionMenuController,
|
||||
getDefaultReactSlashMenuItems,
|
||||
useCreateBlockNote,
|
||||
} from '@blocknote/react';
|
||||
import {
|
||||
getMultiColumnSlashMenuItems,
|
||||
multiColumnDropCursor,
|
||||
locales as multiColumnLocales,
|
||||
} from '@blocknote/xl-multi-column';
|
||||
import { HocuspocusProvider } from '@hocuspocus/provider';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { Box, TextErrors } from '@/components';
|
||||
@@ -16,21 +31,23 @@ import { useUploadFile } from '../hook';
|
||||
import { useHeadings } from '../hook/useHeadings';
|
||||
import useSaveDoc from '../hook/useSaveDoc';
|
||||
import { useEditorStore } from '../stores';
|
||||
import { randomColor } from '../utils';
|
||||
import { blockNoteWithMultiColumn, randomColor } from '../utils';
|
||||
|
||||
import { BlockNoteToolbar } from './BlockNoteToolbar';
|
||||
|
||||
const cssEditor = (readonly: boolean) => `
|
||||
&, & > .bn-container, & .ProseMirror {
|
||||
height:100%;
|
||||
|
||||
.bn-side-menu[data-block-type=heading][data-level="1"] {
|
||||
height: 50px;
|
||||
}
|
||||
.bn-side-menu[data-block-type=heading][data-level="2"] {
|
||||
height: 43px;
|
||||
}
|
||||
.bn-side-menu[data-block-type=heading][data-level="3"] {
|
||||
const cssEditor = (readonly: boolean) => css`
|
||||
&,
|
||||
& > .bn-container,
|
||||
& .ProseMirror {
|
||||
height: 100%;
|
||||
|
||||
.bn-side-menu[data-block-type='heading'][data-level='1'] {
|
||||
height: 50px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='2'] {
|
||||
height: 43px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='3'] {
|
||||
height: 35px;
|
||||
}
|
||||
h1 {
|
||||
@@ -52,11 +69,11 @@ const cssEditor = (readonly: boolean) => `
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.bn-editor {
|
||||
|
||||
color: var(--c--theme--colors--greyscale-700);
|
||||
}
|
||||
|
||||
.bn-block-outer:not(:first-child) {
|
||||
&:has(h1) {
|
||||
padding-top: 32px;
|
||||
@@ -67,25 +84,25 @@ const cssEditor = (readonly: boolean) => `
|
||||
&:has(h3) {
|
||||
padding-top: 16px;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
& .bn-inline-content code {
|
||||
background-color: gainsboro;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@media screen and (width <= 560px) {
|
||||
& .bn-editor {
|
||||
|
||||
${readonly && `padding-left: 10px;`}
|
||||
};
|
||||
.bn-side-menu[data-block-type=heading][data-level="1"] {
|
||||
height: 46px;
|
||||
}
|
||||
.bn-side-menu[data-block-type=heading][data-level="2"] {
|
||||
.bn-side-menu[data-block-type='heading'][data-level='1'] {
|
||||
height: 46px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='2'] {
|
||||
height: 40px;
|
||||
}
|
||||
.bn-side-menu[data-block-type=heading][data-level="3"] {
|
||||
.bn-side-menu[data-block-type='heading'][data-level='3'] {
|
||||
height: 40px;
|
||||
}
|
||||
& .bn-editor h1 {
|
||||
@@ -97,7 +114,7 @@ const cssEditor = (readonly: boolean) => `
|
||||
& .bn-editor h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.bn-block-content[data-is-empty-and-focused][data-content-type="paragraph"]
|
||||
.bn-block-content[data-is-empty-and-focused][data-content-type='paragraph']
|
||||
.bn-inline-content:has(> .ProseMirror-trailingBreak:only-child)::before {
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -160,8 +177,14 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
||||
return cursor;
|
||||
},
|
||||
},
|
||||
dictionary: locales[lang as keyof typeof locales] as Dictionary,
|
||||
dictionary: {
|
||||
...(locales[lang as keyof typeof locales] as Dictionary),
|
||||
multi_column:
|
||||
multiColumnLocales[lang as keyof typeof multiColumnLocales],
|
||||
},
|
||||
uploadFile,
|
||||
schema: blockNoteWithMultiColumn,
|
||||
dropCursor: multiColumnDropCursor,
|
||||
},
|
||||
[collabName, lang, provider, uploadFile],
|
||||
);
|
||||
@@ -175,8 +198,24 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
||||
};
|
||||
}, [setEditor, editor]);
|
||||
|
||||
const getSlashMenuItems = useMemo(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
return async (query: string) =>
|
||||
filterSuggestionItems(
|
||||
combineByGroup(
|
||||
getDefaultReactSlashMenuItems(editor),
|
||||
getMultiColumnSlashMenuItems(editor),
|
||||
),
|
||||
query,
|
||||
);
|
||||
}, [editor]);
|
||||
|
||||
return (
|
||||
<Box $css={cssEditor(readOnly)}>
|
||||
<Box
|
||||
$padding={{ top: 'md' }}
|
||||
$background="white"
|
||||
$css={cssEditor(readOnly)}
|
||||
>
|
||||
{errorAttachment && (
|
||||
<Box $margin={{ bottom: 'big' }}>
|
||||
<TextErrors
|
||||
@@ -192,7 +231,12 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
||||
formattingToolbar={false}
|
||||
editable={!readOnly}
|
||||
theme="light"
|
||||
slashMenu={false}
|
||||
>
|
||||
<SuggestionMenuController
|
||||
triggerCharacter="/"
|
||||
getItems={getSlashMenuItems}
|
||||
/>
|
||||
<BlockNoteToolbar />
|
||||
</BlockNoteView>
|
||||
</Box>
|
||||
@@ -218,6 +262,7 @@ export const BlockNoteEditorVersion = ({
|
||||
},
|
||||
provider: undefined,
|
||||
},
|
||||
schema: blockNoteWithMultiColumn,
|
||||
},
|
||||
[initialContent],
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { BlockNoteEditor } from '@blocknote/core';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useHeadingStore } from '../stores';
|
||||
import { DocsBlockNoteEditor } from '../types';
|
||||
|
||||
export const useHeadings = (editor: BlockNoteEditor) => {
|
||||
export const useHeadings = (editor: DocsBlockNoteEditor) => {
|
||||
const { setHeadings, resetHeadings } = useHeadingStore();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { BlockNoteEditor } from '@blocknote/core';
|
||||
import { create } from 'zustand';
|
||||
|
||||
import { DocsBlockNoteEditor } from '../types';
|
||||
|
||||
export interface UseEditorstore {
|
||||
editor?: BlockNoteEditor;
|
||||
setEditor: (editor: BlockNoteEditor | undefined) => void;
|
||||
editor?: DocsBlockNoteEditor;
|
||||
setEditor: (editor: DocsBlockNoteEditor | undefined) => void;
|
||||
}
|
||||
|
||||
export const useEditorStore = create<UseEditorstore>((set) => ({
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { BlockNoteEditor } from '@blocknote/core';
|
||||
import { create } from 'zustand';
|
||||
|
||||
import { HeadingBlock } from '../types';
|
||||
import { DocsBlockNoteEditor, HeadingBlock } from '../types';
|
||||
|
||||
const recursiveTextContent = (content: HeadingBlock['content']): string => {
|
||||
if (!content) {
|
||||
@@ -21,7 +20,7 @@ const recursiveTextContent = (content: HeadingBlock['content']): string => {
|
||||
|
||||
export interface UseHeadingStore {
|
||||
headings: HeadingBlock[];
|
||||
setHeadings: (editor: BlockNoteEditor) => void;
|
||||
setHeadings: (editor: DocsBlockNoteEditor) => void;
|
||||
resetHeadings: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { BlockNoteEditor } from '@blocknote/core';
|
||||
|
||||
import { blockNoteWithMultiColumn } from './utils';
|
||||
|
||||
export interface DocAttachment {
|
||||
file: string;
|
||||
}
|
||||
@@ -12,3 +16,9 @@ export type HeadingBlock = {
|
||||
level: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type DocsBlockNoteEditor = BlockNoteEditor<
|
||||
typeof blockNoteWithMultiColumn.blockSchema,
|
||||
typeof blockNoteWithMultiColumn.inlineContentSchema,
|
||||
typeof blockNoteWithMultiColumn.styleSchema
|
||||
>;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { BlockNoteSchema } from '@blocknote/core';
|
||||
import { withMultiColumn } from '@blocknote/xl-multi-column';
|
||||
|
||||
export const randomColor = () => {
|
||||
const randomInt = (min: number, max: number) => {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
@@ -25,3 +28,7 @@ function hslToHex(h: number, s: number, l: number) {
|
||||
|
||||
export const toBase64 = (str: Uint8Array) =>
|
||||
Buffer.from(str).toString('base64');
|
||||
|
||||
export const blockNoteWithMultiColumn = withMultiColumn(
|
||||
BlockNoteSchema.create(),
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
|
||||
<Box
|
||||
aria-label={t('Public document')}
|
||||
$color={colors['primary-800']}
|
||||
$background={colors['primary-100']}
|
||||
$background={colors['primary-050']}
|
||||
$radius={spacings['3xs']}
|
||||
$direction="row"
|
||||
$padding="xs"
|
||||
@@ -64,7 +64,12 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
<Box $direction="row" $align="center" $width="100%">
|
||||
<Box
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$width="100%"
|
||||
$padding={{ bottom: 'xs' }}
|
||||
>
|
||||
<Box
|
||||
$direction="row"
|
||||
$justify="space-between"
|
||||
@@ -98,7 +103,7 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
|
||||
<DocToolBox doc={doc} />
|
||||
</Box>
|
||||
</Box>
|
||||
<HorizontalSeparator $withPadding={true} />
|
||||
<HorizontalSeparator $withPadding={false} />
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
Icon,
|
||||
IconOptions,
|
||||
} from '@/components';
|
||||
import { useAuthStore } from '@/core';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { useEditorStore } from '@/features/docs/doc-editor/';
|
||||
import { Doc, ModalRemoveDoc } from '@/features/docs/doc-management';
|
||||
@@ -27,7 +26,7 @@ import {
|
||||
} from '@/features/docs/doc-versioning';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { ModalPDF } from './ModalExport';
|
||||
import { ModalExport } from './ModalExport';
|
||||
|
||||
interface DocToolBoxProps {
|
||||
doc: Doc;
|
||||
@@ -37,18 +36,18 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
const { t } = useTranslation();
|
||||
const hasAccesses = doc.nb_accesses > 1;
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||
|
||||
const spacings = spacingsTokens();
|
||||
const colors = colorsTokens();
|
||||
|
||||
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
|
||||
const [isModalPDFOpen, setIsModalPDFOpen] = useState(false);
|
||||
const [isModalExportOpen, setIsModalExportOpen] = useState(false);
|
||||
const selectHistoryModal = useModal();
|
||||
const modalShare = useModal();
|
||||
|
||||
const { isSmallMobile, isDesktop } = useResponsiveStore();
|
||||
const { authenticated } = useAuthStore();
|
||||
const { editor } = useEditorStore();
|
||||
const { toast } = useToastProvider();
|
||||
|
||||
@@ -57,16 +56,14 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
? [
|
||||
{
|
||||
label: t('Share'),
|
||||
icon: 'upload',
|
||||
callback: () => {
|
||||
modalShare.open();
|
||||
},
|
||||
icon: 'group',
|
||||
callback: modalShare.open,
|
||||
},
|
||||
{
|
||||
label: t('Export'),
|
||||
icon: 'download',
|
||||
callback: () => {
|
||||
setIsModalPDFOpen(true);
|
||||
setIsModalExportOpen(true);
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -153,7 +150,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
$margin={{ left: 'auto' }}
|
||||
$gap={spacings['2xs']}
|
||||
>
|
||||
{authenticated && !isSmallMobile && (
|
||||
{!isSmallMobile && (
|
||||
<>
|
||||
{!hasAccesses && (
|
||||
<Button
|
||||
@@ -193,6 +190,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isSmallMobile && (
|
||||
<Button
|
||||
color="tertiary-text"
|
||||
@@ -200,7 +198,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
<Icon iconName="download" $theme="primary" $variation="800" />
|
||||
}
|
||||
onClick={() => {
|
||||
setIsModalPDFOpen(true);
|
||||
setIsModalExportOpen(true);
|
||||
}}
|
||||
size={isSmallMobile ? 'small' : 'medium'}
|
||||
/>
|
||||
@@ -230,8 +228,8 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
{modalShare.isOpen && (
|
||||
<DocShareModal onClose={() => modalShare.close()} doc={doc} />
|
||||
)}
|
||||
{isModalPDFOpen && (
|
||||
<ModalPDF onClose={() => setIsModalPDFOpen(false)} doc={doc} />
|
||||
{isModalExportOpen && (
|
||||
<ModalExport onClose={() => setIsModalExportOpen(false)} doc={doc} />
|
||||
)}
|
||||
{isModalRemoveOpen && (
|
||||
<ModalRemoveDoc onClose={() => setIsModalRemoveOpen(false)} doc={doc} />
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import {
|
||||
DOCXExporter,
|
||||
docxDefaultSchemaMappings,
|
||||
} from '@blocknote/xl-docx-exporter';
|
||||
import {
|
||||
PDFExporter,
|
||||
pdfDefaultSchemaMappings,
|
||||
} from '@blocknote/xl-pdf-exporter';
|
||||
import {
|
||||
Button,
|
||||
Loader,
|
||||
@@ -7,91 +15,116 @@ import {
|
||||
VariantType,
|
||||
useToastProvider,
|
||||
} from '@openfun/cunningham-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { pdf } from '@react-pdf/renderer';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, Text } from '@/components';
|
||||
import { useEditorStore } from '@/features/docs/doc-editor';
|
||||
import { Doc } from '@/features/docs/doc-management';
|
||||
|
||||
import { useExport } from '../api/useExport';
|
||||
import { TemplatesOrdering, useTemplates } from '../api/useTemplates';
|
||||
import { adaptBlockNoteHTML, downloadFile } from '../utils';
|
||||
import { downloadFile, exportResolveFileUrl } from '../utils';
|
||||
|
||||
export enum DocDownloadFormat {
|
||||
enum DocDownloadFormat {
|
||||
PDF = 'pdf',
|
||||
DOCX = 'docx',
|
||||
}
|
||||
|
||||
interface ModalPDFProps {
|
||||
interface ModalExportProps {
|
||||
onClose: () => void;
|
||||
doc: Doc;
|
||||
}
|
||||
|
||||
export const ModalPDF = ({ onClose, doc }: ModalPDFProps) => {
|
||||
export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { data: templates } = useTemplates({
|
||||
ordering: TemplatesOrdering.BY_CREATED_ON_DESC,
|
||||
});
|
||||
const { toast } = useToastProvider();
|
||||
const { editor } = useEditorStore();
|
||||
const {
|
||||
mutate: createExport,
|
||||
data: documentGenerated,
|
||||
isSuccess,
|
||||
isPending,
|
||||
error,
|
||||
} = useExport();
|
||||
const [templateIdSelected, setTemplateIdSelected] = useState<string>();
|
||||
const [templateSelected, setTemplateSelected] = useState<string>('');
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
const [format, setFormat] = useState<DocDownloadFormat>(
|
||||
DocDownloadFormat.PDF,
|
||||
);
|
||||
|
||||
const templateOptions = useMemo(() => {
|
||||
if (!templates?.pages) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const templateOptions = templates.pages
|
||||
const templateOptions = (templates?.pages || [])
|
||||
.map((page) =>
|
||||
page.results.map((template) => ({
|
||||
label: template.title,
|
||||
value: template.id,
|
||||
value: template.code,
|
||||
})),
|
||||
)
|
||||
.flat();
|
||||
|
||||
if (templateOptions.length) {
|
||||
setTemplateIdSelected(templateOptions[0].value);
|
||||
}
|
||||
templateOptions.unshift({
|
||||
label: t('Empty template'),
|
||||
value: '',
|
||||
});
|
||||
|
||||
return templateOptions;
|
||||
}, [templates?.pages]);
|
||||
}, [t, templates?.pages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!error) {
|
||||
async function onSubmit() {
|
||||
if (!editor) {
|
||||
toast(t('The export failed'), VariantType.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
toast(error.message, VariantType.ERROR);
|
||||
setIsExporting(true);
|
||||
|
||||
onClose();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [error, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!documentGenerated || !isSuccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
// normalize title
|
||||
const title = doc.title
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.replace(/\s/g, '-');
|
||||
|
||||
downloadFile(documentGenerated, `${title}.${format}`);
|
||||
const html = templateSelected;
|
||||
let exportDocument = editor.document;
|
||||
if (html) {
|
||||
const blockTemplate = await editor.tryParseHTMLToBlocks(html);
|
||||
exportDocument = [...blockTemplate, ...editor.document];
|
||||
}
|
||||
|
||||
let blobExport: Blob;
|
||||
if (format === DocDownloadFormat.PDF) {
|
||||
const defaultExporter = new PDFExporter(
|
||||
editor.schema,
|
||||
pdfDefaultSchemaMappings,
|
||||
);
|
||||
|
||||
const exporter = new PDFExporter(
|
||||
editor.schema,
|
||||
pdfDefaultSchemaMappings,
|
||||
{
|
||||
resolveFileUrl: async (url) =>
|
||||
exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl),
|
||||
},
|
||||
);
|
||||
const pdfDocument = await exporter.toReactPDFDocument(exportDocument);
|
||||
blobExport = await pdf(pdfDocument).toBlob();
|
||||
} else {
|
||||
const defaultExporter = new DOCXExporter(
|
||||
editor.schema,
|
||||
docxDefaultSchemaMappings,
|
||||
);
|
||||
|
||||
const exporter = new DOCXExporter(
|
||||
editor.schema,
|
||||
docxDefaultSchemaMappings,
|
||||
{
|
||||
resolveFileUrl: async (url) =>
|
||||
exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl),
|
||||
},
|
||||
);
|
||||
|
||||
blobExport = await exporter.toBlob(exportDocument);
|
||||
}
|
||||
|
||||
downloadFile(blobExport, `${title}.${format}`);
|
||||
|
||||
toast(
|
||||
t('Your {{format}} was downloaded succesfully', {
|
||||
@@ -100,29 +133,9 @@ export const ModalPDF = ({ onClose, doc }: ModalPDFProps) => {
|
||||
VariantType.SUCCESS,
|
||||
);
|
||||
|
||||
setIsExporting(false);
|
||||
|
||||
onClose();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [documentGenerated, isSuccess, t]);
|
||||
|
||||
async function onSubmit() {
|
||||
if (!templateIdSelected || !format) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editor) {
|
||||
toast(t('No editor found'), VariantType.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
let body = await editor.blocksToFullHTML(editor.document);
|
||||
body = adaptBlockNoteHTML(body);
|
||||
|
||||
createExport({
|
||||
templateId: templateIdSelected,
|
||||
body,
|
||||
body_type: 'html',
|
||||
format,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -138,6 +151,7 @@ export const ModalPDF = ({ onClose, doc }: ModalPDFProps) => {
|
||||
color="secondary"
|
||||
fullWidth
|
||||
onClick={() => onClose()}
|
||||
disabled={isExporting}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
@@ -146,7 +160,7 @@ export const ModalPDF = ({ onClose, doc }: ModalPDFProps) => {
|
||||
color="primary"
|
||||
fullWidth
|
||||
onClick={() => void onSubmit()}
|
||||
disabled={isPending || !templateIdSelected}
|
||||
disabled={isExporting}
|
||||
>
|
||||
{t('Download')}
|
||||
</Button>
|
||||
@@ -173,9 +187,9 @@ export const ModalPDF = ({ onClose, doc }: ModalPDFProps) => {
|
||||
clearable={false}
|
||||
label={t('Template')}
|
||||
options={templateOptions}
|
||||
value={templateIdSelected}
|
||||
value={templateSelected}
|
||||
onChange={(options) =>
|
||||
setTemplateIdSelected(options.target.value as string)
|
||||
setTemplateSelected(options.target.value as string)
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
@@ -192,8 +206,17 @@ export const ModalPDF = ({ onClose, doc }: ModalPDFProps) => {
|
||||
}
|
||||
/>
|
||||
|
||||
{isPending && (
|
||||
<Box $align="center" $margin={{ top: 'big' }}>
|
||||
{isExporting && (
|
||||
<Box
|
||||
$align="center"
|
||||
$margin={{ top: 'big' }}
|
||||
$css={css`
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -100%);
|
||||
`}
|
||||
>
|
||||
<Loader />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -10,137 +10,23 @@ export function downloadFile(blob: Blob, filename: string) {
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
const convertToLi = (html: string) => {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
const divs = doc.querySelectorAll(
|
||||
'div[data-content-type="bulletListItem"] , div[data-content-type="numberedListItem"]',
|
||||
);
|
||||
export const exportResolveFileUrl = async (
|
||||
url: string,
|
||||
resolveFileUrl: ((url: string) => Promise<string | Blob>) | undefined,
|
||||
) => {
|
||||
if (!url.includes(window.location.hostname) && resolveFileUrl) {
|
||||
return resolveFileUrl(url);
|
||||
}
|
||||
|
||||
// Loop through each div and replace it with a li
|
||||
divs.forEach((div) => {
|
||||
// Create a new li element
|
||||
const li = document.createElement('li');
|
||||
|
||||
// Copy the attributes from the div to the li
|
||||
for (let i = 0; i < div.attributes.length; i++) {
|
||||
li.setAttribute(div.attributes[i].name, div.attributes[i].value);
|
||||
}
|
||||
|
||||
// Move all child elements of the div to the li
|
||||
while (div.firstChild) {
|
||||
li.appendChild(div.firstChild);
|
||||
}
|
||||
|
||||
// Replace the div with the li in the DOM
|
||||
if (div.parentNode) {
|
||||
div.parentNode.replaceChild(li, div);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Convert the blocknote content to a simplified version to be
|
||||
* correctly parsed by our pdf and docx parser
|
||||
*/
|
||||
const newContent: string[] = [];
|
||||
let currentList: HTMLUListElement | HTMLOListElement | null = null;
|
||||
|
||||
// Iterate over all the children of the bn-block-group
|
||||
doc.body
|
||||
.querySelectorAll('.bn-block-group .bn-block-outer')
|
||||
.forEach((outerDiv) => {
|
||||
const blockContent = outerDiv.querySelector('.bn-block-content');
|
||||
|
||||
if (blockContent) {
|
||||
const contentType = blockContent.getAttribute('data-content-type');
|
||||
|
||||
if (contentType === 'bulletListItem') {
|
||||
// If a list is not started, start a new one
|
||||
if (!currentList) {
|
||||
currentList = document.createElement('ul');
|
||||
}
|
||||
|
||||
currentList.appendChild(blockContent);
|
||||
} else if (contentType === 'numberedListItem') {
|
||||
// If a numbered list is not started, start a new one
|
||||
if (!currentList) {
|
||||
currentList = document.createElement('ol');
|
||||
}
|
||||
|
||||
currentList.appendChild(blockContent);
|
||||
} else {
|
||||
/***
|
||||
* If there is a current list, add it to the new content
|
||||
* It means that the current list has ended
|
||||
*/
|
||||
if (currentList) {
|
||||
newContent.push(currentList.outerHTML);
|
||||
}
|
||||
|
||||
currentList = null;
|
||||
newContent.push(outerDiv.outerHTML);
|
||||
}
|
||||
} else {
|
||||
// In case there is no content-type, add the outerDiv as is
|
||||
newContent.push(outerDiv.outerHTML);
|
||||
}
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
return newContent.join('');
|
||||
};
|
||||
|
||||
const convertToImg = (html: string) => {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
const divs = doc.querySelectorAll('div[data-content-type="image"]');
|
||||
|
||||
// Loop through each div and replace it with a img
|
||||
divs.forEach((div) => {
|
||||
const img = document.createElement('img');
|
||||
|
||||
// Copy the attributes from the div to the img
|
||||
for (let i = 0; i < div.attributes.length; i++) {
|
||||
img.setAttribute(div.attributes[i].name, div.attributes[i].value);
|
||||
|
||||
if (div.attributes[i].name === 'data-url') {
|
||||
img.setAttribute('src', div.attributes[i].value);
|
||||
}
|
||||
|
||||
if (div.attributes[i].name === 'data-preview-width') {
|
||||
img.setAttribute('width', div.attributes[i].value);
|
||||
}
|
||||
}
|
||||
|
||||
// Move all child elements of the div to the img
|
||||
while (div.firstChild) {
|
||||
img.appendChild(div.firstChild);
|
||||
}
|
||||
|
||||
// Replace the div with the img in the DOM
|
||||
if (div.parentNode) {
|
||||
div.parentNode.replaceChild(img, div);
|
||||
}
|
||||
});
|
||||
|
||||
return doc.body.innerHTML;
|
||||
};
|
||||
|
||||
export const adaptBlockNoteHTML = (html: string) => {
|
||||
html = html.replaceAll('<p class="bn-inline-content"></p>', '<br/>');
|
||||
|
||||
// custom-style is used by pandoc to convert the style
|
||||
html = html.replaceAll(
|
||||
/data-text-alignment=\"([a-z]+)\"/g,
|
||||
'custom-style="$1"',
|
||||
);
|
||||
html = html.replaceAll(/data-text-color=\"([a-z]+)\"/g, 'style="color: $1;"');
|
||||
html = html.replaceAll(
|
||||
/data-background-color=\"([a-z]+)\"/g,
|
||||
'style="background-color: $1;"',
|
||||
);
|
||||
|
||||
html = convertToLi(html);
|
||||
html = convertToImg(html);
|
||||
|
||||
return html;
|
||||
return response.blob();
|
||||
} catch {
|
||||
console.error(`Failed to fetch image: ${url}`);
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './useCollaboration';
|
||||
export * from './useTrans';
|
||||
export * from './useCopyDocLink';
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useClipboard } from '@/hook';
|
||||
|
||||
import { Doc } from '../types';
|
||||
|
||||
export const useCopyDocLink = (docId: Doc['id']) => {
|
||||
const { t } = useTranslation();
|
||||
const copyToClipboard = useClipboard();
|
||||
|
||||
return useCallback(() => {
|
||||
copyToClipboard(
|
||||
`${window.location.origin}/docs/${docId}/`,
|
||||
t('Link Copied !'),
|
||||
t('Failed to copy link'),
|
||||
);
|
||||
}, [copyToClipboard, docId, t]);
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { createGlobalStyle, css } from 'styled-components';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { Box, LoadMoreText } from '@/components';
|
||||
import { Box, HorizontalSeparator, LoadMoreText, Text } from '@/components';
|
||||
import {
|
||||
QuickSearch,
|
||||
QuickSearchData,
|
||||
@@ -58,6 +58,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
|
||||
const [listHeight, setListHeight] = useState<string>('400px');
|
||||
const canShare = doc.abilities.accesses_manage;
|
||||
const canViewAccesses = doc.abilities.accesses_view;
|
||||
const showMemberSection = inputValue === '' && selectedUsers.length === 0;
|
||||
const showFooter = selectedUsers.length === 0 && !inputValue;
|
||||
|
||||
@@ -94,7 +95,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
count === 1
|
||||
? t('Document owner')
|
||||
: t('Share with {{count}} users', {
|
||||
count,
|
||||
count: count,
|
||||
}),
|
||||
elements: members,
|
||||
endActions: membersQuery.hasNextPage
|
||||
@@ -191,8 +192,9 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
<ShareModalStyle />
|
||||
<Box
|
||||
aria-label={t('Share modal')}
|
||||
$height={modalContentHeight}
|
||||
$height={canViewAccesses ? modalContentHeight : 'auto'}
|
||||
$overflow="hidden"
|
||||
className="noPadding"
|
||||
$justify="space-between"
|
||||
>
|
||||
<Box
|
||||
@@ -204,7 +206,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div ref={selectedUsersRef}>
|
||||
<Box ref={selectedUsersRef}>
|
||||
{canShare && selectedUsers.length > 0 && (
|
||||
<Box
|
||||
$padding={{ horizontal: 'base' }}
|
||||
@@ -222,55 +224,76 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
{!canViewAccesses && <HorizontalSeparator />}
|
||||
</Box>
|
||||
|
||||
<Box data-testid="doc-share-quick-search">
|
||||
<QuickSearch
|
||||
onFilter={(str) => {
|
||||
setInputValue(str);
|
||||
onFilter(str);
|
||||
}}
|
||||
inputValue={inputValue}
|
||||
showInput={canShare}
|
||||
loading={searchUsersQuery.isLoading}
|
||||
placeholder={t('Type a name or email')}
|
||||
>
|
||||
{!showMemberSection && inputValue !== '' && (
|
||||
<QuickSearchGroup
|
||||
group={searchUserData}
|
||||
onSelect={onSelect}
|
||||
renderElement={(user) => (
|
||||
<DocShareModalInviteUserRow user={user} />
|
||||
{!canViewAccesses && (
|
||||
<Box $height={listHeight} $align="center" $justify="center">
|
||||
<Text
|
||||
$maxWidth="320px"
|
||||
$textAlign="center"
|
||||
$variation="600"
|
||||
$size="sm"
|
||||
>
|
||||
{t(
|
||||
'You do not have permission to view users sharing this document or modify link settings.',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{showMemberSection && (
|
||||
<>
|
||||
{invitationsData.elements.length > 0 && (
|
||||
<Box aria-label={t('List invitation card')}>
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{canViewAccesses && (
|
||||
<QuickSearch
|
||||
onFilter={(str) => {
|
||||
setInputValue(str);
|
||||
onFilter(str);
|
||||
}}
|
||||
inputValue={inputValue}
|
||||
showInput={canShare}
|
||||
loading={searchUsersQuery.isLoading}
|
||||
placeholder={t('Type a name or email')}
|
||||
>
|
||||
{canViewAccesses && (
|
||||
<>
|
||||
{!showMemberSection && inputValue !== '' && (
|
||||
<QuickSearchGroup
|
||||
group={invitationsData}
|
||||
renderElement={(invitation) => (
|
||||
<DocShareInvitationItem
|
||||
doc={doc}
|
||||
invitation={invitation}
|
||||
/>
|
||||
group={searchUserData}
|
||||
onSelect={onSelect}
|
||||
renderElement={(user) => (
|
||||
<DocShareModalInviteUserRow user={user} />
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
)}
|
||||
{showMemberSection && (
|
||||
<>
|
||||
{invitationsData.elements.length > 0 && (
|
||||
<Box aria-label={t('List invitation card')}>
|
||||
<QuickSearchGroup
|
||||
group={invitationsData}
|
||||
renderElement={(invitation) => (
|
||||
<DocShareInvitationItem
|
||||
doc={doc}
|
||||
invitation={invitation}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box aria-label={t('List members card')}>
|
||||
<QuickSearchGroup
|
||||
group={membersData}
|
||||
renderElement={(access) => (
|
||||
<DocShareMemberItem doc={doc} access={access} />
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</QuickSearch>
|
||||
<Box aria-label={t('List members card')}>
|
||||
<QuickSearchGroup
|
||||
group={membersData}
|
||||
renderElement={(access) => (
|
||||
<DocShareMemberItem doc={doc} access={access} />
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</QuickSearch>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import {
|
||||
Button,
|
||||
VariantType,
|
||||
useToastProvider,
|
||||
} from '@openfun/cunningham-react';
|
||||
import { Button } from '@openfun/cunningham-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, HorizontalSeparator } from '@/components';
|
||||
import { Doc } from '@/features/docs';
|
||||
import { Doc, useCopyDocLink } from '@/features/docs';
|
||||
|
||||
import { DocVisibility } from './DocVisibility';
|
||||
|
||||
@@ -17,8 +13,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export const DocShareModalFooter = ({ doc, onClose }: Props) => {
|
||||
const canShare = doc.abilities.accesses_manage;
|
||||
const { toast } = useToastProvider();
|
||||
const copyDocLink = useCopyDocLink(doc.id);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box
|
||||
@@ -27,12 +22,10 @@ export const DocShareModalFooter = ({ doc, onClose }: Props) => {
|
||||
`}
|
||||
>
|
||||
<HorizontalSeparator $withPadding={true} />
|
||||
{canShare && (
|
||||
<>
|
||||
<DocVisibility doc={doc} />
|
||||
<HorizontalSeparator />
|
||||
</>
|
||||
)}
|
||||
|
||||
<DocVisibility doc={doc} />
|
||||
<HorizontalSeparator />
|
||||
|
||||
<Box
|
||||
$direction="row"
|
||||
$justify="space-between"
|
||||
@@ -41,18 +34,7 @@ export const DocShareModalFooter = ({ doc, onClose }: Props) => {
|
||||
<Button
|
||||
fullWidth={false}
|
||||
onClick={() => {
|
||||
navigator.clipboard
|
||||
.writeText(window.location.href)
|
||||
.then(() => {
|
||||
toast(t('Link Copied !'), VariantType.SUCCESS, {
|
||||
duration: 3000,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
toast(t('Failed to copy link'), VariantType.ERROR, {
|
||||
duration: 3000,
|
||||
});
|
||||
});
|
||||
copyDocLink();
|
||||
}}
|
||||
color="tertiary"
|
||||
icon={<span className="material-icons">add_link</span>}
|
||||
|
||||
@@ -26,10 +26,10 @@ export const DocShareModalInviteUserRow = ({ user }: Props) => {
|
||||
color: var(--c--theme--colors--greyscale-400);
|
||||
`}
|
||||
>
|
||||
<Text $theme="primary" $variation="600">
|
||||
<Text $theme="primary" $variation="800">
|
||||
{t('Add')}
|
||||
</Text>
|
||||
<Icon $theme="primary" $variation="600" iconName="add" />
|
||||
<Icon $theme="primary" $variation="800" iconName="add" />
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -34,6 +34,7 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
|
||||
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||
const spacing = spacingsTokens();
|
||||
const colors = colorsTokens();
|
||||
const canManage = doc.abilities.accesses_manage;
|
||||
const [linkReach, setLinkReach] = useState<LinkReach>(doc.link_reach);
|
||||
const [docLinkRole, setDocLinkRole] = useState<LinkRole>(doc.link_role);
|
||||
const { linkModeTranslations, linkReachChoices, linkReachTranslations } =
|
||||
@@ -106,29 +107,35 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
|
||||
$direction="row"
|
||||
$align={isDesktop ? 'center' : undefined}
|
||||
$padding={{ horizontal: '2xs' }}
|
||||
$gap={spacing['3xs']}
|
||||
$gap={canManage ? spacing['3xs'] : spacing['base']}
|
||||
>
|
||||
<DropdownMenu
|
||||
label={t('Visibility')}
|
||||
arrowCss={css`
|
||||
color: ${colors['primary-800']} !important;
|
||||
`}
|
||||
disabled={!canManage}
|
||||
showArrow={true}
|
||||
options={linkReachOptions}
|
||||
>
|
||||
<Box $direction="row" $align="center" $gap={spacing['3xs']}>
|
||||
<Icon
|
||||
$theme="primary"
|
||||
$variation="800"
|
||||
$theme={canManage ? 'primary' : 'greyscale'}
|
||||
$variation={canManage ? '800' : '600'}
|
||||
iconName={linkReachChoices[linkReach].icon}
|
||||
/>
|
||||
<Text $theme="primary" $variation="800">
|
||||
<Text
|
||||
$theme={canManage ? 'primary' : 'greyscale'}
|
||||
$variation={canManage ? '800' : '600'}
|
||||
$weight="500"
|
||||
$size="md"
|
||||
>
|
||||
{linkReachChoices[linkReach].label}
|
||||
</Text>
|
||||
</Box>
|
||||
</DropdownMenu>
|
||||
{isDesktop && (
|
||||
<Text $size="xs" $variation="600">
|
||||
<Text $size="xs" $variation="600" $weight="400">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
@@ -137,6 +144,7 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
|
||||
<Box $direction="row" $align="center" $gap={spacing['3xs']}>
|
||||
{linkReach !== LinkReach.RESTRICTED && (
|
||||
<DropdownMenu
|
||||
disabled={!canManage}
|
||||
showArrow={true}
|
||||
options={linkMode}
|
||||
label={t('Visibility mode')}
|
||||
|
||||
@@ -26,7 +26,7 @@ export const useTranslatedShareSettings = () => {
|
||||
},
|
||||
[LinkReach.AUTHENTICATED]: {
|
||||
label: linkReachTranslations[LinkReach.AUTHENTICATED],
|
||||
icon: 'corporate_fare',
|
||||
icon: 'vpn_lock',
|
||||
value: LinkReach.AUTHENTICATED,
|
||||
descriptionReadOnly: t(
|
||||
'Anyone with the link can view the document if they are logged in',
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { BlockNoteEditor } from '@blocknote/core';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { BoxButton, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { DocsBlockNoteEditor } from '@/features/docs/doc-editor';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
const leftPaddingMap: { [key: number]: string } = {
|
||||
3: '1.5rem',
|
||||
2: '0.9rem',
|
||||
1: '0.3',
|
||||
1: '0.3rem',
|
||||
};
|
||||
|
||||
export type HeadingsHighlight = {
|
||||
@@ -17,7 +17,7 @@ export type HeadingsHighlight = {
|
||||
}[];
|
||||
|
||||
interface HeadingProps {
|
||||
editor: BlockNoteEditor;
|
||||
editor: DocsBlockNoteEditor;
|
||||
level: number;
|
||||
text: string;
|
||||
headingId: string;
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from '@/features/docs/doc-management';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useResponsiveDocGrid } from '../hooks/useResponsiveDocGrid';
|
||||
|
||||
import { DocsGridItem } from './DocsGridItem';
|
||||
import { DocsGridLoader } from './DocsGridLoader';
|
||||
|
||||
@@ -22,6 +24,7 @@ export const DocsGrid = ({
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const { flexLeft, flexRight } = useResponsiveDocGrid();
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -101,23 +104,21 @@ export const DocsGrid = ({
|
||||
<Box
|
||||
$direction="row"
|
||||
$padding={{ horizontal: 'xs' }}
|
||||
$gap="20px"
|
||||
$gap="10px"
|
||||
data-testid="docs-grid-header"
|
||||
>
|
||||
<Box $flex={6} $padding="3xs">
|
||||
<Box $flex={flexLeft} $padding="3xs">
|
||||
<Text $size="xs" $variation="600" $weight="500">
|
||||
{t('Name')}
|
||||
</Text>
|
||||
</Box>
|
||||
{isDesktop && (
|
||||
<Box $flex={2} $padding="3xs">
|
||||
<Box $flex={flexRight} $padding={{ vertical: '3xs' }}>
|
||||
<Text $size="xs" $weight="500" $variation="600">
|
||||
{t('Updated at')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box $flex={1.15} $align="flex-end" $padding="3xs" />
|
||||
</Box>
|
||||
|
||||
{/* Body */}
|
||||
|
||||
@@ -20,6 +20,7 @@ export const DocsGridActions = ({
|
||||
openShareModal,
|
||||
}: DocsGridActionsProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const deleteModal = useModal();
|
||||
|
||||
const removeFavoriteDoc = useDeleteFavoriteDoc({
|
||||
@@ -45,7 +46,10 @@ export const DocsGridActions = ({
|
||||
{
|
||||
label: t('Share'),
|
||||
icon: 'group',
|
||||
callback: () => openShareModal?.(),
|
||||
callback: () => {
|
||||
openShareModal?.();
|
||||
},
|
||||
|
||||
testId: `docs-grid-actions-share-${doc.id}`,
|
||||
},
|
||||
|
||||
|
||||
@@ -1,27 +1,33 @@
|
||||
import { Button, useModal } from '@openfun/cunningham-react';
|
||||
import { Tooltip, useModal } from '@openfun/cunningham-react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, Icon, StyledLink, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc, LinkReach } from '@/features/docs/doc-management';
|
||||
import { DocShareModal } from '@/features/docs/doc-share';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { DocsGridActions } from './DocsGridActions';
|
||||
import { SimpleDocItem } from './SimpleDocItem';
|
||||
import { useResponsiveDocGrid } from '../hooks/useResponsiveDocGrid';
|
||||
|
||||
import { DocsGridActions } from './DocsGridActions';
|
||||
import { DocsGridItemSharedButton } from './DocsGridItemSharedButton';
|
||||
import { SimpleDocItem } from './SimpleDocItem';
|
||||
type DocsGridItemProps = {
|
||||
doc: Doc;
|
||||
};
|
||||
export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
|
||||
const { flexLeft, flexRight } = useResponsiveDocGrid();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const spacings = spacingsTokens();
|
||||
const shareModal = useModal();
|
||||
const isPublic = doc.link_reach === LinkReach.PUBLIC;
|
||||
const isAuthenticated = doc.link_reach === LinkReach.AUTHENTICATED;
|
||||
const isRestricted = doc.link_reach === LinkReach.RESTRICTED;
|
||||
const sharedCount = doc.nb_accesses - 1;
|
||||
const isShared = sharedCount > 0;
|
||||
|
||||
const showAccesses = isPublic || isAuthenticated;
|
||||
|
||||
const handleShareClick = () => {
|
||||
shareModal.open();
|
||||
@@ -33,8 +39,8 @@ export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
|
||||
$direction="row"
|
||||
$width="100%"
|
||||
$align="center"
|
||||
$gap="20px"
|
||||
role="row"
|
||||
$gap="20px"
|
||||
$padding={{ vertical: '2xs', horizontal: isDesktop ? 'base' : 'xs' }}
|
||||
$css={css`
|
||||
cursor: pointer;
|
||||
@@ -45,75 +51,80 @@ export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
|
||||
`}
|
||||
>
|
||||
<StyledLink
|
||||
$css="flex: 8; align-items: center;"
|
||||
$css={css`
|
||||
flex: ${flexLeft};
|
||||
align-items: center;
|
||||
`}
|
||||
href={`/docs/${doc.id}`}
|
||||
>
|
||||
<Box
|
||||
data-testid={`docs-grid-name-${doc.id}`}
|
||||
$flex={6}
|
||||
$padding={{ right: 'base' }}
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$gap={spacings.xs}
|
||||
$flex={flexLeft}
|
||||
$padding={{ right: isDesktop ? 'md' : '3xs' }}
|
||||
>
|
||||
<SimpleDocItem isPinned={doc.is_favorite} doc={doc} />
|
||||
{showAccesses && (
|
||||
<Box
|
||||
$padding={{ top: !isDesktop ? '4xs' : undefined }}
|
||||
$css={
|
||||
!isDesktop
|
||||
? css`
|
||||
align-self: flex-start;
|
||||
`
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Tooltip
|
||||
content={
|
||||
<Text $textAlign="center" $variation="000">
|
||||
{isPublic
|
||||
? t('Accessible to anyone')
|
||||
: t('Accessible to authenticated users')}
|
||||
</Text>
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<div>
|
||||
<Icon
|
||||
$theme="greyscale"
|
||||
$variation="600"
|
||||
$size="14px"
|
||||
iconName={isPublic ? 'public' : 'vpn_lock'}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</StyledLink>
|
||||
|
||||
<Box
|
||||
$flex={flexRight}
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$justify={isDesktop ? 'space-between' : 'flex-end'}
|
||||
$gap="32px"
|
||||
>
|
||||
{isDesktop && (
|
||||
<Box $flex={2}>
|
||||
<StyledLink href={`/docs/${doc.id}`}>
|
||||
<Text $variation="600" $size="xs">
|
||||
{DateTime.fromISO(doc.updated_at).toRelative()}
|
||||
</Text>
|
||||
</Box>
|
||||
</StyledLink>
|
||||
)}
|
||||
</StyledLink>
|
||||
<Box
|
||||
$flex={1.15}
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$justify="flex-end"
|
||||
$gap="32px"
|
||||
>
|
||||
{isDesktop && isPublic && (
|
||||
<Button
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleShareClick();
|
||||
}}
|
||||
size="nano"
|
||||
fullWidth
|
||||
icon={<Icon $variation="000" iconName="public" />}
|
||||
>
|
||||
{isShared ? sharedCount : undefined}
|
||||
</Button>
|
||||
)}
|
||||
{isDesktop && !isPublic && isRestricted && isShared && (
|
||||
<Button
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleShareClick();
|
||||
}}
|
||||
fullWidth
|
||||
color="tertiary"
|
||||
size="nano"
|
||||
icon={<Icon $variation="800" $theme="primary" iconName="group" />}
|
||||
>
|
||||
{sharedCount}
|
||||
</Button>
|
||||
)}
|
||||
{isDesktop && !isPublic && isAuthenticated && (
|
||||
<Button
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleShareClick();
|
||||
}}
|
||||
fullWidth
|
||||
size="nano"
|
||||
icon={<Icon $variation="000" iconName="corporate_fare" />}
|
||||
>
|
||||
{sharedCount}
|
||||
</Button>
|
||||
)}
|
||||
<DocsGridActions doc={doc} openShareModal={handleShareClick} />
|
||||
|
||||
<Box $direction="row" $align="center" $gap={spacings.lg}>
|
||||
{isDesktop && (
|
||||
<DocsGridItemSharedButton
|
||||
doc={doc}
|
||||
handleClick={handleShareClick}
|
||||
/>
|
||||
)}
|
||||
<DocsGridActions doc={doc} openShareModal={handleShareClick} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
{shareModal.isOpen && (
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Button, Tooltip } from '@openfun/cunningham-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, Icon, Text } from '@/components';
|
||||
|
||||
import { Doc } from '../../doc-management';
|
||||
|
||||
type Props = {
|
||||
doc: Doc;
|
||||
handleClick: () => void;
|
||||
};
|
||||
export const DocsGridItemSharedButton = ({ doc, handleClick }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const sharedCount = doc.nb_accesses;
|
||||
const isShared = sharedCount - 1 > 0;
|
||||
|
||||
if (!isShared) {
|
||||
return <Box $minWidth="50px"> </Box>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={
|
||||
<Text $textAlign="center" $variation="000">
|
||||
{t('Shared with {{count}} users', { count: sharedCount })}
|
||||
</Text>
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<Button
|
||||
style={{ minWidth: '50px', justifyContent: 'center' }}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleClick();
|
||||
}}
|
||||
color="tertiary"
|
||||
size="nano"
|
||||
icon={<Icon $variation="800" $theme="primary" iconName="group" />}
|
||||
>
|
||||
{sharedCount}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, Icon, Text } from '@/components';
|
||||
import { Box, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc, LinkReach } from '@/features/docs/doc-management';
|
||||
import { Doc } from '@/features/docs/doc-management';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import PinnedDocumentIcon from '../assets/pinned-document.svg';
|
||||
@@ -34,11 +34,6 @@ export const SimpleDocItem = ({
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const spacings = spacingsTokens();
|
||||
|
||||
const isPublic = doc?.link_reach === LinkReach.PUBLIC;
|
||||
const isShared = !isPublic && doc.nb_accesses > 1;
|
||||
const accessCount = doc.nb_accesses - 1;
|
||||
const isSharedOrPublic = isShared || isPublic;
|
||||
|
||||
return (
|
||||
<Box $direction="row" $gap={spacings.sm}>
|
||||
<Box
|
||||
@@ -69,22 +64,6 @@ export const SimpleDocItem = ({
|
||||
$gap={spacings['3xs']}
|
||||
$margin={{ top: '-2px' }}
|
||||
>
|
||||
{isPublic && (
|
||||
<Icon iconName="public" $size="16px" $variation="600" />
|
||||
)}
|
||||
{isShared && (
|
||||
<Icon iconName="group" $size="16px" $variation="600" />
|
||||
)}
|
||||
{isSharedOrPublic && accessCount > 0 && (
|
||||
<Text $size="12px" $weight="bold" $variation="600">
|
||||
{accessCount}
|
||||
</Text>
|
||||
)}
|
||||
{isSharedOrPublic && (
|
||||
<Text $size="12px" $variation="600">
|
||||
·
|
||||
</Text>
|
||||
)}
|
||||
<Text $variation="600" $size="xs">
|
||||
{DateTime.fromISO(doc.updated_at).toRelative()}
|
||||
</Text>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
export const useResponsiveDocGrid = () => {
|
||||
const { isDesktop, screenWidth } = useResponsiveStore();
|
||||
|
||||
const flexLeft = useMemo(() => {
|
||||
if (!isDesktop) {
|
||||
return 1;
|
||||
} else if (screenWidth <= 1100) {
|
||||
return 6;
|
||||
} else if (screenWidth < 1200) {
|
||||
return 8;
|
||||
}
|
||||
return 8;
|
||||
}, [isDesktop, screenWidth]);
|
||||
|
||||
const flexRight = useMemo(() => {
|
||||
if (!isDesktop) {
|
||||
return undefined;
|
||||
} else if (screenWidth <= 1200) {
|
||||
return 5;
|
||||
}
|
||||
return 4;
|
||||
}, [isDesktop, screenWidth]);
|
||||
|
||||
return { flexLeft, flexRight };
|
||||
};
|
||||
@@ -1 +1,2 @@
|
||||
export * from './useDate';
|
||||
export * from './useClipboard';
|
||||
|
||||
34
src/frontend/apps/impress/src/hook/useClipboard.tsx
Normal file
34
src/frontend/apps/impress/src/hook/useClipboard.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const useClipboard = () => {
|
||||
const { toast } = useToastProvider();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useCallback(
|
||||
(text: string, successMessage?: string, errorMessage?: string) => {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
toast(
|
||||
successMessage ?? t('Copied to clipboard'),
|
||||
VariantType.SUCCESS,
|
||||
{
|
||||
duration: 3000,
|
||||
},
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
toast(
|
||||
errorMessage ?? t('Failed to copy to clipboard'),
|
||||
VariantType.ERROR,
|
||||
{
|
||||
duration: 3000,
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
[t, toast],
|
||||
);
|
||||
};
|
||||
@@ -9,7 +9,6 @@
|
||||
"Accessibility statement": "Erklärung zur Barrierefreiheit",
|
||||
"Add": "Hinzufügen",
|
||||
"Address:": "Anschrift:",
|
||||
"Administrator": "Administrator",
|
||||
"All docs": "Alle Dokumente",
|
||||
"Anonymous": "Gast",
|
||||
"Anyone with the link can edit the document": "Jeder mit dem Link kann das Dokument bearbeiten",
|
||||
@@ -27,6 +26,7 @@
|
||||
"Content modal to delete document": "Inhalts-Modal zum Löschen des Dokuments",
|
||||
"Content modal to export the document": "Inhalte zum Exportieren des Dokuments",
|
||||
"Convert Markdown": "Markdown konvertieren",
|
||||
"Cookies placed": "Cookies gesetzt",
|
||||
"Copied to clipboard": "In die Zwischenablage kopiert",
|
||||
"Copy as {{format}}": "Als {{format}} kopieren",
|
||||
"Copy link": "Link kopieren",
|
||||
@@ -35,33 +35,33 @@
|
||||
"Delete a doc": "Dokument löschen",
|
||||
"Delete document": "Dokument löschen",
|
||||
"Doc visibility card": "Dokumenten-Sichtbarkeitskarte",
|
||||
"Docs": "Docs",
|
||||
"Docs Logo": "Docs Logo",
|
||||
"Docs: Your new companion to collaborate on documents efficiently, intuitively, and securely.": "Pages: Ihr neuer Begleiter für eine effiziente, intuitive und sichere Zusammenarbeit bei Dokumenten.",
|
||||
"Document owner": "Besitzer des Dokuments",
|
||||
"Document title updated successfully": "Titel des Dokuments erfolgreich aktualisiert",
|
||||
"Download": "Herunterladen",
|
||||
"E-mail:": "E-Mail:",
|
||||
"Edition": "Bearbeiten",
|
||||
"Editor": "Editor",
|
||||
"Editor unavailable": "Editor nicht verfügbar",
|
||||
"Error during delete invitation": "Fehler beim Löschen der Einladung",
|
||||
"Error during invitation update": "Fehler beim Aktualisieren der Einladung",
|
||||
"Error during update invitation": "Fehler beim Aktualisieren der Einladung",
|
||||
"Error while deleting invitation": "Fehler beim Löschen der Einladung",
|
||||
"Established on December 20, 2023.": "Gegründet am 20. Dezember 2023.",
|
||||
"Export": "Exportieren",
|
||||
"Failed to add the member in the document.": "Fehler beim Hinzufügen des Mitglieds zum Dokument.",
|
||||
"Failed to copy link": "Link konnte nicht kopiert werden",
|
||||
"Failed to copy to clipboard": "Fehler beim Kopieren in die Zwischenablage",
|
||||
"Failed to create the invitation for {{email}}.": "Fehler beim Erstellen der Einladung für {{email}}.",
|
||||
"Format": "Format",
|
||||
"History": "Versionsverlauf",
|
||||
"If a member is editing, his works can be lost.": "Wenn ein Mitglied editiert, können seine Änderungen verloren gehen.",
|
||||
"If you are unable to access a content or a service, you can contact the person responsible for https://lasuite.numerique.gouv.fr to be directed to an accessible alternative or to obtain the content in another form.": "Wenn Sie keinen Zugriff auf Inhalte oder einen Service haben, können Sie sich an die für https://lasuite.numerique.gouv.fr verantwortliche Person wenden, um eine zugängliche Alternative zu erhalten oder den Inhalt in einer anderen Form zu erhalten.",
|
||||
"Illustration:": "Abbildung:",
|
||||
"Improvement and contact": "Verbesserungen und Kontakt",
|
||||
"Invite": "Einladen",
|
||||
"It is the card information about the document.": "Es handelt sich um die Karteninformationen zum Dokument.",
|
||||
"It is the document title": "Es ist der Titel des Dokuments",
|
||||
"It seems that the page you are looking for does not exist or cannot be displayed correctly.": "Es scheint, dass die von Ihnen gesuchte Seite nicht existiert oder nicht korrekt angezeigt werden kann.",
|
||||
"It's true, you didn't have to click on a block that covers half the page to say you agree to the placement of cookies — even if you don't know what it means!": "Es stimmt, Sie mussten nicht auf einen Block klicken, der die halbe Seite bedeckt, um zu sagen, dass Sie der Platzierung von Cookies zustimmen - auch wenn Sie nicht wissen, was es bedeutet!",
|
||||
"Language": "Sprache",
|
||||
"Last update: {{update}}": "Zuletzt aktualisiert: {{update}}",
|
||||
"Legal Notice": "Impressum",
|
||||
@@ -75,22 +75,21 @@
|
||||
"Logout": "Abmelden",
|
||||
"Modal confirmation to restore the version": "Modale Bestätigung um die Version wiederherzustellen",
|
||||
"More docs": "Weitere Dokumente",
|
||||
"More info?": "Mehr Informationen",
|
||||
"My docs": "Meine Dokumente",
|
||||
"Name": "Name",
|
||||
"New doc": "Neues Dokument",
|
||||
"No active search": "Keine aktive Suche",
|
||||
"No document found": "Kein Dokument gefunden",
|
||||
"No documents found": "Keine Dokumente gefunden",
|
||||
"No editor found": "Kein Editor gefunden",
|
||||
"No versions": "Keine Versionen",
|
||||
"OK": "OK",
|
||||
"Nothing exceptional, no special privileges related to a .gouv.fr.": "Nichts Außergewöhnliches, keine besonderen Privilegien im Zusammenhang mit .gouv.fr.",
|
||||
"Offline ?!": "Offline?!",
|
||||
"Only invited people can access": "Nur eingeladene Personen haben Zugriff",
|
||||
"Open the document options": "Öffnen Sie die Dokumentoptionen",
|
||||
"Open the header menu": "Öffne das Kopfzeilen-Menü",
|
||||
"Ouch !": "Autsch!",
|
||||
"Owner": "Besitzer",
|
||||
"PDF": "PDF",
|
||||
"Pending invitations": "Ausstehende Einladungen",
|
||||
"Personal data and cookies": "Personenbezogene Daten und Cookies",
|
||||
"Pin": "Anheften",
|
||||
@@ -98,9 +97,12 @@
|
||||
"Private": "Privat",
|
||||
"Public": "Öffentlich",
|
||||
"Public document": "Öffentliches Dokument",
|
||||
"Publication Director": "Verantwortlicher Herausgeber",
|
||||
"Publisher": "Herausgeber",
|
||||
"Quick search input": "Schnellsuche-Eingabe",
|
||||
"Reader": "Leser",
|
||||
"Reading": "Lesen",
|
||||
"Remedies": "Rechtsbehelfe",
|
||||
"Remove": "Löschen",
|
||||
"Rename": "Umbenennen",
|
||||
"Rephrase": "Umformulieren",
|
||||
@@ -110,6 +112,7 @@
|
||||
"Search user result": "Suchergebnis",
|
||||
"Select a document": "Dokument auswählen",
|
||||
"Select a version on the right to restore": "Wählen Sie rechts eine Version zum Wiederherstellen aus",
|
||||
"Send a letter by post (free of charge, no stamp needed):": "Senden Sie einen Brief per Post (kostenlos, kein Porto erforderlich):",
|
||||
"Share": "Teilen",
|
||||
"Share modal": "Teilen-Modal",
|
||||
"Share the document": "Dokument teilen",
|
||||
@@ -118,13 +121,18 @@
|
||||
"Share with {{count}} users_other": "Teilen mit {{count}} Benutzern",
|
||||
"Shared with me": "Mit mir geteilt",
|
||||
"Something bad happens, please retry.": "Etwas ist schiefgelaufen, bitte versuchen Sie es erneut.",
|
||||
"Stéphanie Schaer: Interministerial Digital Director (DINUM).": "Stéphanie Schaer: Interministerielle Digitaldirektorin (DINUM).",
|
||||
"Summarize": "Zusammenfassen",
|
||||
"Summary": "Zusammenfassung",
|
||||
"Template": "Vorlage",
|
||||
"The document has been deleted.": "Das Dokument wurde gelöscht.",
|
||||
"The document visibility has been updated.": "Die Sichtbarkeit des Dokuments wurde aktualisiert.",
|
||||
"The team in charge of the digital workspace \"La Suite numérique\" can be contacted directly at": "Das Team, das für den digitalen Arbeitsbereich \"La Suite numérique\" zuständig ist, kann direkt kontaktiert werden unter",
|
||||
"This accessibility statement applies to the site hosted on": "Diese Erklärung zur Barrierefreiheit gilt für die gehostete Seite",
|
||||
"This site does not display a cookie consent banner, why?": "",
|
||||
"This allows us to measure the number of visits and understand which pages are the most viewed.": "Dies ermöglicht es uns, die Anzahl der Besuche zu messen und zu verstehen, welche Seiten am häufigsten angesehen werden.",
|
||||
"This procedure should be used in the following case:": "Dieses Verfahren sollte in folgendem Fall verwendet werden:",
|
||||
"This site places a small text file (a \"cookie\") on your computer when you visit it.": "Diese Website platziert beim Besuch auf Ihrem Computer eine kleine Textdatei (ein \"Cookie\").",
|
||||
"This will protect your privacy, but will also prevent the owner from learning from your actions and creating a better experience for you and other users.": "Dies schützt Ihre Privatsphäre, verhindert jedoch auch, dass der Eigentümer aus Ihren Aktionen lernt und eine bessere Erfahrung für Sie und andere Benutzer schafft.",
|
||||
"Too many requests. Please wait 60 seconds.": "Zu viele Anfragen. Bitte warten Sie 60 Sekunden.",
|
||||
"Type a name or email": "Geben Sie einen Namen oder eine E-Mail-Adresse ein",
|
||||
"Type the name of a document": "Geben Sie den Namen eines Dokuments ein",
|
||||
@@ -139,12 +147,15 @@
|
||||
"Visibility": "Sichtbarkeit",
|
||||
"Visibility mode": "Sichtbarkeitseinstellungen",
|
||||
"Warning": "Warnung",
|
||||
"We simply comply with the law, which states that certain audience measurement tools, properly configured to respect privacy, are exempt from prior authorization.": "Wir halten uns einfach an das Gesetz, das besagt, dass bestimmte Publikumsmessungstools, die ordnungsgemäß konfiguriert sind, um die Privatsphäre zu respektieren, von einer vorherigen Genehmigung befreit sind.",
|
||||
"We try to respond within 2 working days.": "Wir versuchen, innerhalb von 2 Arbeitstagen zu antworten.",
|
||||
"Word / Open Office": "Word / Open Office",
|
||||
"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.": "Sie sind der einzige Besitzer dieser Gruppe. Machen Sie ein anderes Mitglied zum Gruppenbesitzer, bevor Sie Ihre eigene Rolle ändern oder aus Ihrem Dokument entfernen können.",
|
||||
"You can oppose the tracking of your browsing on this website.": "Sie können der Verfolgung Ihres Surfverhaltens auf dieser Website widersprechen.",
|
||||
"You can:": "Sie können:",
|
||||
"You cannot update the role or remove other owner.": "Sie können die Rolle nicht aktualisieren oder einen anderen Besitzer entfernen.",
|
||||
"Your current document will revert to this version.": "Ihr aktuelles Dokument wird auf diese Version zurückgesetzt.",
|
||||
"Your {{format}} was downloaded succesfully": "Ihr {{format}} wurde erfolgreich heruntergeladen"
|
||||
"Your {{format}} was downloaded succesfully": "Ihr {{format}} wurde erfolgreich heruntergeladen",
|
||||
"you have reported to the website manager a lack of accessibility that prevents you from accessing content or one of the services of the portal and you have not received a satisfactory response.": "sie haben dem Website-Manager einen Mangel an Barrierefreiheit gemeldet, der Ihnen den Zugriff auf Inhalte oder einen der Dienste des Portals verwehrt, und Sie haben keine zufriedenstellende Antwort erhalten."
|
||||
}
|
||||
},
|
||||
"en": { "translation": {} },
|
||||
@@ -323,5 +334,6 @@
|
||||
"accessibility-not-audit": "<strong>docs.numerique.gouv.fr</strong> n'est pas en conformité avec le RGAA 4.1. Le site n'a <strong>pas encore été audité.</strong>",
|
||||
"you have reported to the website manager a lack of accessibility that prevents you from accessing content or one of the services of the portal and you have not received a satisfactory response.": "vous avez signalé au responsable du site internet un défaut d'accessibilité qui vous empêche d'accéder à un contenu ou à un des services du portail et vous n'avez pas obtenu de réponse satisfaisante."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nl": { "translation": {} }
|
||||
}
|
||||
|
||||
46
src/frontend/apps/impress/src/services/Posthog.tsx
Normal file
46
src/frontend/apps/impress/src/services/Posthog.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Router } from 'next/router';
|
||||
import posthog from 'posthog-js';
|
||||
import { PostHogProvider as PHProvider } from 'posthog-js/react';
|
||||
import { PropsWithChildren, useEffect } from 'react';
|
||||
|
||||
export interface PostHogConf {
|
||||
id: string;
|
||||
host: string;
|
||||
}
|
||||
|
||||
interface PostHogProviderProps {
|
||||
conf?: PostHogConf;
|
||||
}
|
||||
|
||||
export function PostHogProvider({
|
||||
children,
|
||||
conf,
|
||||
}: PropsWithChildren<PostHogProviderProps>) {
|
||||
useEffect(() => {
|
||||
if (!conf?.id || !conf?.host || posthog.__loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
posthog.init(conf.id, {
|
||||
api_host: conf.host,
|
||||
person_profiles: 'always',
|
||||
loaded: (posthog) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
posthog.debug();
|
||||
}
|
||||
},
|
||||
capture_pageview: false,
|
||||
capture_pageleave: true,
|
||||
});
|
||||
|
||||
const handleRouteChange = () => posthog?.capture('$pageview');
|
||||
|
||||
Router.events.on('routeChangeComplete', handleRouteChange);
|
||||
|
||||
return () => {
|
||||
Router.events.off('routeChangeComplete', handleRouteChange);
|
||||
};
|
||||
}, [conf?.host, conf?.id]);
|
||||
|
||||
return <PHProvider client={posthog}>{children}</PHProvider>;
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './Crisp';
|
||||
export * from './Posthog';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "impress",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"private": true,
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eslint-config-impress",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js ."
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "packages-i18n",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"extract-translation": "yarn extract-translation:impress",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "server-y-provider",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"description": "Y.js provider for docs",
|
||||
"repository": "https://github.com/numerique-gouv/impress",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -988,7 +988,56 @@
|
||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@blocknote/core@0.22.0", "@blocknote/core@^0.22.0":
|
||||
"@blocknote/core@0.21.0", "@blocknote/core@^0.21.0":
|
||||
version "0.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@blocknote/core/-/core-0.21.0.tgz#b54baaa3eca3b700c80c59113a837c3d4153dca2"
|
||||
integrity sha512-TQAN0qRCkXpz5AwfdxjuFvIKWsU4bBI5d/e5iX7PEkhwf4PFgPHsMUl2uuGw5o/hhjzoU54YKaAjlJlWyw4goA==
|
||||
dependencies:
|
||||
"@emoji-mart/data" "^1.2.1"
|
||||
"@tiptap/core" "^2.7.1"
|
||||
"@tiptap/extension-bold" "^2.7.1"
|
||||
"@tiptap/extension-code" "^2.7.1"
|
||||
"@tiptap/extension-collaboration" "^2.7.1"
|
||||
"@tiptap/extension-collaboration-cursor" "^2.7.1"
|
||||
"@tiptap/extension-gapcursor" "^2.7.1"
|
||||
"@tiptap/extension-hard-break" "^2.7.1"
|
||||
"@tiptap/extension-history" "^2.7.1"
|
||||
"@tiptap/extension-horizontal-rule" "^2.7.1"
|
||||
"@tiptap/extension-italic" "^2.7.1"
|
||||
"@tiptap/extension-link" "^2.7.1"
|
||||
"@tiptap/extension-paragraph" "^2.7.1"
|
||||
"@tiptap/extension-strike" "^2.7.1"
|
||||
"@tiptap/extension-table-cell" "^2.7.1"
|
||||
"@tiptap/extension-table-header" "^2.7.1"
|
||||
"@tiptap/extension-table-row" "^2.7.1"
|
||||
"@tiptap/extension-text" "^2.7.1"
|
||||
"@tiptap/extension-underline" "^2.7.1"
|
||||
"@tiptap/pm" "^2.7.1"
|
||||
emoji-mart "^5.6.0"
|
||||
hast-util-from-dom "^4.2.0"
|
||||
prosemirror-dropcursor "^1.8.1"
|
||||
prosemirror-highlight "^0.9.0"
|
||||
prosemirror-model "^1.23.0"
|
||||
prosemirror-state "^1.4.3"
|
||||
prosemirror-tables "^1.6.1"
|
||||
prosemirror-transform "^1.9.0"
|
||||
prosemirror-view "^1.33.7"
|
||||
rehype-format "^5.0.0"
|
||||
rehype-parse "^8.0.4"
|
||||
rehype-remark "^9.1.2"
|
||||
rehype-stringify "^9.0.3"
|
||||
remark-gfm "^3.0.1"
|
||||
remark-parse "^10.0.1"
|
||||
remark-rehype "^10.1.0"
|
||||
remark-stringify "^10.0.2"
|
||||
shiki "^1.22.0"
|
||||
unified "^10.1.2"
|
||||
uuid "^8.3.2"
|
||||
y-prosemirror "1.2.13"
|
||||
y-protocols "^1.0.6"
|
||||
yjs "^13.6.15"
|
||||
|
||||
"@blocknote/core@^0.22.0":
|
||||
version "0.22.0"
|
||||
resolved "https://registry.yarnpkg.com/@blocknote/core/-/core-0.22.0.tgz#2f363f9677d4fa5f20299b22850f5f34a6340a55"
|
||||
integrity sha512-AAEx01zK6u+b1SsZniMm/aogEMjasF4vA9ZHgFGj04G7AwK5Hjwa0Sxre58qcW+KzuvR09CQHTkwjmgVmJX/HA==
|
||||
@@ -1037,19 +1086,31 @@
|
||||
y-protocols "^1.0.6"
|
||||
yjs "^13.6.15"
|
||||
|
||||
"@blocknote/mantine@0.22.0":
|
||||
version "0.22.0"
|
||||
resolved "https://registry.yarnpkg.com/@blocknote/mantine/-/mantine-0.22.0.tgz#15509aaefe88c3efd73a884b9fb1e0584a6223ec"
|
||||
integrity sha512-6irIKCGUpE47X8qWLx9oa5ndztSrvLEHgVRp+fdVUHMJCx0/OzijJyYTTFKw8yEI9qc01pjmwdYMZrMMZybyGw==
|
||||
"@blocknote/mantine@0.21.0":
|
||||
version "0.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@blocknote/mantine/-/mantine-0.21.0.tgz#b8a640f498a4884129fe33f854be8d2bb842ea41"
|
||||
integrity sha512-GAxgvn/87wDyE8qdkystTkEbqE8AFO81gaMJ6df0P6ZAdfIH3sFYUf9MffVOjtq7T6NSCM9vHNnhHsC9K8m/fg==
|
||||
dependencies:
|
||||
"@blocknote/core" "^0.22.0"
|
||||
"@blocknote/react" "^0.22.0"
|
||||
"@blocknote/core" "^0.21.0"
|
||||
"@blocknote/react" "^0.21.0"
|
||||
"@mantine/core" "^7.10.1"
|
||||
"@mantine/hooks" "^7.10.1"
|
||||
"@mantine/utils" "^6.0.21"
|
||||
react-icons "^5.2.1"
|
||||
|
||||
"@blocknote/react@0.22.0", "@blocknote/react@^0.22.0":
|
||||
"@blocknote/react@0.21.0", "@blocknote/react@^0.21.0":
|
||||
version "0.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@blocknote/react/-/react-0.21.0.tgz#ad8907f89575e8c139d07d75bdb66ef4e33f84f9"
|
||||
integrity sha512-eBKe3hihGNeO4G/qKKJ/B5uuEmWm8XMbT8SxJ2zpNTjHx5lLP45vhtjAM+HCzQqz4xYacc2NphUIdjPPH5eXrQ==
|
||||
dependencies:
|
||||
"@blocknote/core" "^0.21.0"
|
||||
"@floating-ui/react" "^0.26.4"
|
||||
"@tiptap/core" "^2.7.1"
|
||||
"@tiptap/react" "^2.7.1"
|
||||
lodash.merge "^4.6.2"
|
||||
react-icons "^5.2.1"
|
||||
|
||||
"@blocknote/react@^0.22.0":
|
||||
version "0.22.0"
|
||||
resolved "https://registry.yarnpkg.com/@blocknote/react/-/react-0.22.0.tgz#a17167a26b70ef421218ae3e49d15cca751291f0"
|
||||
integrity sha512-Y6Oj99iOKnlh2FE/lgy8kO5PziPnA8MyEJyjCH9Jbvlc9t493L9EFmLK8iKBZek7sh0TOzhXGBOA6lIpk02X6A==
|
||||
@@ -1075,6 +1136,42 @@
|
||||
y-protocols "^1.0.6"
|
||||
yjs "^13.6.15"
|
||||
|
||||
"@blocknote/xl-docx-exporter@0.21.0":
|
||||
version "0.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@blocknote/xl-docx-exporter/-/xl-docx-exporter-0.21.0.tgz#f7f50bac0e3110d8851b3969f704663542d28f59"
|
||||
integrity sha512-uJiyfAXlONFu6ty6KbH7zrQUYxlZS2nya3fhCAWFTB3zEz6HiwiVE4HeC5jqDgMAw/i2zzluEGOQv3O5qW4KnQ==
|
||||
dependencies:
|
||||
"@blocknote/core" "^0.21.0"
|
||||
buffer "^6.0.3"
|
||||
docx "^9.0.2"
|
||||
sharp "^0.33.5"
|
||||
|
||||
"@blocknote/xl-multi-column@0.21.0":
|
||||
version "0.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@blocknote/xl-multi-column/-/xl-multi-column-0.21.0.tgz#913bf07d6fb2c5445a6e4a375646997035e351e2"
|
||||
integrity sha512-nWRZCP14pcbbA4F274kBL4UXI6RNmxZRKynsqACBPC/B3bns8GDh0GWMEPz/KN/6kuOkm/bYHHCkglDlEIEF1Q==
|
||||
dependencies:
|
||||
"@blocknote/core" "^0.21.0"
|
||||
"@blocknote/react" "^0.21.0"
|
||||
"@tiptap/core" "^2.7.1"
|
||||
prosemirror-model "^1.23.0"
|
||||
prosemirror-state "^1.4.3"
|
||||
prosemirror-tables "^1.3.7"
|
||||
prosemirror-transform "^1.9.0"
|
||||
prosemirror-view "^1.33.7"
|
||||
react-icons "^5.2.1"
|
||||
|
||||
"@blocknote/xl-pdf-exporter@0.21.0":
|
||||
version "0.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@blocknote/xl-pdf-exporter/-/xl-pdf-exporter-0.21.0.tgz#6e05b20ba331cdd17162f7d42678bdfc54e9914f"
|
||||
integrity sha512-OAw8nIuDDBXeMSdMkjBIYvsu6i3qhxo5OwqzcQzAV8wcHXvPnkcJiBE7RNTcawPbCdgBybx3i/oW9RT+dWcOrA==
|
||||
dependencies:
|
||||
"@blocknote/core" "^0.21.0"
|
||||
"@blocknote/react" "^0.21.0"
|
||||
"@react-pdf/renderer" "^4.0.0"
|
||||
buffer "^6.0.3"
|
||||
docx "^9.0.2"
|
||||
|
||||
"@cspotcode/source-map-support@^0.8.0":
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
|
||||
@@ -3281,6 +3378,145 @@
|
||||
"@react-types/shared" "^3.26.0"
|
||||
"@swc/helpers" "^0.5.0"
|
||||
|
||||
"@react-pdf/fns@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/fns/-/fns-3.0.0.tgz#2e0137d48b14c531b2f6a9214cb36ea2a7aea3ba"
|
||||
integrity sha512-ICbIWR93PE6+xf2Xd/fXYO1dAuiOAJaszEuGGv3wp5lLSeeelDXlEYLh6R05okxh28YqMzc0Qd85x6n6MtaLUQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.13"
|
||||
|
||||
"@react-pdf/font@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/font/-/font-3.0.1.tgz#ea831f272e3b1418ffd2d9f70c835236e6564354"
|
||||
integrity sha512-s+0xrQabGoYDDZwVpz8PXp1ylwabqiMhzfyetvxBqjDuQ15PuoSkmUkKUOkfDzauuAqs0MLMvt+Pcv+NioLfzw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.13"
|
||||
"@react-pdf/types" "^2.7.0"
|
||||
fontkit "^2.0.2"
|
||||
is-url "^1.2.4"
|
||||
|
||||
"@react-pdf/image@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/image/-/image-3.0.1.tgz#5c08a7ddf5d07c53ea3377348e7bb07d17efe7c2"
|
||||
integrity sha512-Hd5F1LzjuzG4bL/ytaOYxwN/5ip8oFBYDHdpccOfYY87J/Ca7AL31SsuneLk9DtnwNM1BSAKXtBo/WDFY3r57A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.13"
|
||||
"@react-pdf/png-js" "^3.0.0"
|
||||
jay-peg "^1.1.0"
|
||||
|
||||
"@react-pdf/layout@^4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/layout/-/layout-4.2.0.tgz#e2489992d4b6409a6cecf62b1b9391143ad30495"
|
||||
integrity sha512-/0jMhDKwZH0lQs3umNsOduaPtkK0IUpaBRUEv4udHVD9lB2VzYoSNeYsCu+MJMPJyByXj70OSWV7IMjWTCKwWw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.13"
|
||||
"@react-pdf/fns" "3.0.0"
|
||||
"@react-pdf/image" "^3.0.1"
|
||||
"@react-pdf/pdfkit" "^4.0.0"
|
||||
"@react-pdf/primitives" "^4.0.0"
|
||||
"@react-pdf/stylesheet" "^5.2.0"
|
||||
"@react-pdf/textkit" "^5.0.1"
|
||||
"@react-pdf/types" "^2.7.0"
|
||||
emoji-regex "^10.3.0"
|
||||
queue "^6.0.1"
|
||||
yoga-layout "^3.1.0"
|
||||
|
||||
"@react-pdf/pdfkit@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/pdfkit/-/pdfkit-4.0.0.tgz#8dbbfc8e546ebd0b76e88bd525fd6bec43c19df4"
|
||||
integrity sha512-HaaAoBpoRGJ6c1ZOANNQZ3q6Ehmagqa8n40x+OZ5s9HcmUviZ34SCm+QBa42s1o4299M+Lgw3UoqpW7sHv3/Hg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.13"
|
||||
"@react-pdf/png-js" "^3.0.0"
|
||||
browserify-zlib "^0.2.0"
|
||||
crypto-js "^4.2.0"
|
||||
fontkit "^2.0.2"
|
||||
jay-peg "^1.1.0"
|
||||
vite-compatible-readable-stream "^3.6.1"
|
||||
|
||||
"@react-pdf/png-js@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/png-js/-/png-js-3.0.0.tgz#c0b7dc7c77e36f0830e9b7bccca7ddd64ada1c5e"
|
||||
integrity sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA==
|
||||
dependencies:
|
||||
browserify-zlib "^0.2.0"
|
||||
|
||||
"@react-pdf/primitives@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/primitives/-/primitives-4.0.0.tgz#0a710664923547c315386e82a643e58008612844"
|
||||
integrity sha512-yp4E0rDL03NaUp/CnDBz3HQNfH2Mzdlgku57yhTMGNzetwB0NJusXcjYg5XsTGIXnR7Tv80JKI4O4ajj+oaLeQ==
|
||||
|
||||
"@react-pdf/reconciler@^1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/reconciler/-/reconciler-1.1.3.tgz#6fbd0f601fcb4f7ff1ff7c4dcc4df6afbccd9129"
|
||||
integrity sha512-4vqY0klmUH32kTFvuqdAszkOpwfZYKMLO4VpJ5xZWTsoUOLQSyhC2QM2QCj9eaxpB2Nd5Kl9uW+KfyutvZnMzQ==
|
||||
dependencies:
|
||||
object-assign "^4.1.1"
|
||||
scheduler "0.25.0-rc-603e6108-20241029"
|
||||
|
||||
"@react-pdf/render@^4.0.2":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/render/-/render-4.0.2.tgz#66284862329a926589f62170b8c3adff55d76157"
|
||||
integrity sha512-5QJB9sS0uU5ALTLxrtT073VT1imZhrzuOun+7kvo0nykeAr9I4lv0Shmy8rS4QhpmXn8ASmhd17WjCVm4DcJlw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.13"
|
||||
"@react-pdf/fns" "3.0.0"
|
||||
"@react-pdf/primitives" "^4.0.0"
|
||||
"@react-pdf/textkit" "^5.0.1"
|
||||
"@react-pdf/types" "^2.7.0"
|
||||
abs-svg-path "^0.1.1"
|
||||
color-string "^1.9.1"
|
||||
normalize-svg-path "^1.1.0"
|
||||
parse-svg-path "^0.1.2"
|
||||
svg-arc-to-cubic-bezier "^3.2.0"
|
||||
|
||||
"@react-pdf/renderer@4.1.6", "@react-pdf/renderer@^4.0.0":
|
||||
version "4.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/renderer/-/renderer-4.1.6.tgz#257d4871192edb4d1716bc6399b5a7cf73ee3db3"
|
||||
integrity sha512-hfQ0PsuVqfoYxkYgmkj+HFkylbB1QTpXY1rnlgnzJlrlSoNXjzPrCa/ty8jcHOwYA2lNoazIAoDatBIsc8K5pw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.13"
|
||||
"@react-pdf/font" "^3.0.1"
|
||||
"@react-pdf/layout" "^4.2.0"
|
||||
"@react-pdf/pdfkit" "^4.0.0"
|
||||
"@react-pdf/primitives" "^4.0.0"
|
||||
"@react-pdf/reconciler" "^1.1.3"
|
||||
"@react-pdf/render" "^4.0.2"
|
||||
"@react-pdf/types" "^2.7.0"
|
||||
events "^3.3.0"
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.2"
|
||||
queue "^6.0.1"
|
||||
|
||||
"@react-pdf/stylesheet@^5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/stylesheet/-/stylesheet-5.2.0.tgz#01ed0462c363842babf5e21de913bbfe3fff23c6"
|
||||
integrity sha512-ST19VumM9iRG0z8EjDJnyQCG+NhPFtYUCAh5B8HY237MrsRGvMgzcwrpyyqcyuLwHHYy7S4iw8EY0mK9+Qa2XQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.13"
|
||||
"@react-pdf/fns" "3.0.0"
|
||||
"@react-pdf/types" "^2.7.0"
|
||||
color-string "^1.9.1"
|
||||
hsl-to-hex "^1.0.0"
|
||||
media-engine "^1.0.3"
|
||||
postcss-value-parser "^4.1.0"
|
||||
|
||||
"@react-pdf/textkit@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/textkit/-/textkit-5.0.1.tgz#284e55ff016f46c8bf364aa1d7bcef772c913662"
|
||||
integrity sha512-4GdDiPA9l+If203hkh48slvRQmcmM3ecPLFTpXNMPrep/3retgvxUEXKMxI+xKclpw8tMzK/W6Z4hN9DgnxWMg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.13"
|
||||
"@react-pdf/fns" "3.0.0"
|
||||
bidi-js "^1.0.2"
|
||||
hyphen "^1.6.4"
|
||||
unicode-properties "^1.4.1"
|
||||
|
||||
"@react-pdf/types@^2.7.0":
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/types/-/types-2.7.0.tgz#56a0232ce420c313fe67cef193cbf57870a01477"
|
||||
integrity sha512-7KrPPCpgRPKR+g+T127PE4bpw9Q84ZiY07EYRwXKVtTEVW9wJ5BZiF9smT9IvH19s+MQaDLmYRgjESsnqlyH0Q==
|
||||
|
||||
"@react-stately/calendar@3.5.4":
|
||||
version "3.5.4"
|
||||
resolved "https://registry.yarnpkg.com/@react-stately/calendar/-/calendar-3.5.4.tgz#847b2a2e5cf13a81b3344f1ef4e9a0d10138191e"
|
||||
@@ -4317,7 +4553,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9"
|
||||
integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==
|
||||
|
||||
"@swc/helpers@0.5.15", "@swc/helpers@^0.5.0":
|
||||
"@swc/helpers@0.5.15", "@swc/helpers@^0.5.0", "@swc/helpers@^0.5.12":
|
||||
version "0.5.15"
|
||||
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7"
|
||||
integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==
|
||||
@@ -4857,7 +5093,7 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*", "@types/node@22.10.3":
|
||||
"@types/node@*", "@types/node@22.10.3", "@types/node@^22.7.5":
|
||||
version "22.10.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.3.tgz#cdc2a89bf6e5d5e593fad08e83f74d7348d5dd10"
|
||||
integrity sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==
|
||||
@@ -5286,6 +5522,11 @@ abab@^2.0.6:
|
||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
|
||||
integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
|
||||
|
||||
abs-svg-path@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abs-svg-path/-/abs-svg-path-0.1.1.tgz#df601c8e8d2ba10d4a76d625e236a9a39c2723bf"
|
||||
integrity sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==
|
||||
|
||||
accepts@~1.3.8:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
|
||||
@@ -5714,11 +5955,18 @@ bare-events@^2.2.0:
|
||||
resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.0.tgz#305b511e262ffd8b9d5616b056464f8e1b3329cc"
|
||||
integrity sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==
|
||||
|
||||
base64-js@^1.3.1:
|
||||
base64-js@^1.1.2, base64-js@^1.3.0, base64-js@^1.3.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
bidi-js@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/bidi-js/-/bidi-js-1.0.3.tgz#6f8bcf3c877c4d9220ddf49b9bb6930c88f877d2"
|
||||
integrity sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==
|
||||
dependencies:
|
||||
require-from-string "^2.0.2"
|
||||
|
||||
binary-extensions@^2.0.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
||||
@@ -5815,6 +6063,20 @@ broccoli-plugin@^4.0.7:
|
||||
rimraf "^3.0.2"
|
||||
symlink-or-copy "^1.3.1"
|
||||
|
||||
brotli@^1.3.2:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/brotli/-/brotli-1.3.3.tgz#7365d8cc00f12cf765d2b2c898716bcf4b604d48"
|
||||
integrity sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==
|
||||
dependencies:
|
||||
base64-js "^1.1.2"
|
||||
|
||||
browserify-zlib@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
|
||||
integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==
|
||||
dependencies:
|
||||
pako "~1.0.5"
|
||||
|
||||
browserslist@^4.24.0, browserslist@^4.24.2:
|
||||
version "4.24.3"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.3.tgz#5fc2725ca8fb3c1432e13dac278c7cc103e026d2"
|
||||
@@ -6086,7 +6348,7 @@ color-name@^1.0.0, color-name@~1.1.4:
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
color-string@^1.9.0:
|
||||
color-string@^1.9.0, color-string@^1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
|
||||
integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
|
||||
@@ -6225,7 +6487,7 @@ core-js-compat@^3.38.0, core-js-compat@^3.38.1:
|
||||
dependencies:
|
||||
browserslist "^4.24.2"
|
||||
|
||||
core-js@^3.0.0:
|
||||
core-js@^3.0.0, core-js@^3.38.1:
|
||||
version "3.39.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.39.0.tgz#57f7647f4d2d030c32a72ea23a0555b2eaa30f83"
|
||||
integrity sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==
|
||||
@@ -6318,6 +6580,11 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
crypto-js@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
|
||||
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
|
||||
|
||||
crypto-random-string@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
||||
@@ -6593,6 +6860,11 @@ dezalgo@^1.0.4:
|
||||
asap "^2.0.0"
|
||||
wrappy "1"
|
||||
|
||||
dfa@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dfa/-/dfa-1.2.0.tgz#96ac3204e2d29c49ea5b57af8d92c2ae12790657"
|
||||
integrity sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==
|
||||
|
||||
diff-sequences@^29.6.3:
|
||||
version "29.6.3"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
|
||||
@@ -6629,6 +6901,30 @@ doctrine@^3.0.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
docx@9.1.0:
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/docx/-/docx-9.1.0.tgz#e089b86048df0fb777f0a957bc45c7a591b181f8"
|
||||
integrity sha512-XOtseSTRrkKN/sV5jNBqyLazyhNpWfaUhpuKc22cs+5DavNjRQvchnohb0g0S+x/96/D06U/i0/U/Gc4E5kwuQ==
|
||||
dependencies:
|
||||
"@types/node" "^22.7.5"
|
||||
hash.js "^1.1.7"
|
||||
jszip "^3.10.1"
|
||||
nanoid "^5.0.4"
|
||||
xml "^1.0.1"
|
||||
xml-js "^1.6.8"
|
||||
|
||||
docx@^9.0.2:
|
||||
version "9.1.1"
|
||||
resolved "https://registry.yarnpkg.com/docx/-/docx-9.1.1.tgz#0c72d8000e9bb5bb380c5b48656a3277eb53322d"
|
||||
integrity sha512-jz941pdz4+gMljZ1pV+95FwuWEouKi4u1Elhv3ptqeytGOSyX+u131hRYg4wgqLU+x2CbGsz9eTYgo2uMMz65Q==
|
||||
dependencies:
|
||||
"@types/node" "^22.7.5"
|
||||
hash.js "^1.1.7"
|
||||
jszip "^3.10.1"
|
||||
nanoid "^5.0.4"
|
||||
xml "^1.0.1"
|
||||
xml-js "^1.6.8"
|
||||
|
||||
dom-accessibility-api@^0.5.9:
|
||||
version "0.5.16"
|
||||
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453"
|
||||
@@ -6749,6 +7045,11 @@ emoji-regex-xs@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz#e8af22e5d9dbd7f7f22d280af3d19d2aab5b0724"
|
||||
integrity sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==
|
||||
|
||||
emoji-regex@^10.3.0:
|
||||
version "10.4.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4"
|
||||
integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
@@ -7288,7 +7589,7 @@ etag@~1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
|
||||
|
||||
events@^3.2.0:
|
||||
events@^3.2.0, events@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
||||
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
|
||||
@@ -7475,6 +7776,11 @@ fetch-mock@9.11.0:
|
||||
querystring "^0.2.0"
|
||||
whatwg-url "^6.5.0"
|
||||
|
||||
fflate@^0.4.8:
|
||||
version "0.4.8"
|
||||
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
|
||||
integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
|
||||
|
||||
figlet@1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.7.0.tgz#46903a04603fd19c3e380358418bb2703587a72e"
|
||||
@@ -7564,6 +7870,21 @@ flatted@^3.2.9, flatted@^3.3.1:
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27"
|
||||
integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==
|
||||
|
||||
fontkit@^2.0.2:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/fontkit/-/fontkit-2.0.4.tgz#4765d664c68b49b5d6feb6bd1051ee49d8ec5ab0"
|
||||
integrity sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==
|
||||
dependencies:
|
||||
"@swc/helpers" "^0.5.12"
|
||||
brotli "^1.3.2"
|
||||
clone "^2.1.2"
|
||||
dfa "^1.2.0"
|
||||
fast-deep-equal "^3.1.3"
|
||||
restructure "^3.0.0"
|
||||
tiny-inflate "^1.0.3"
|
||||
unicode-properties "^1.4.0"
|
||||
unicode-trie "^2.0.0"
|
||||
|
||||
for-each@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
|
||||
@@ -7940,6 +8261,14 @@ has-tostringtag@^1.0.0, has-tostringtag@^1.0.2:
|
||||
dependencies:
|
||||
has-symbols "^1.0.3"
|
||||
|
||||
hash.js@^1.1.7:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
|
||||
integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
minimalistic-assert "^1.0.1"
|
||||
|
||||
hasown@^2.0.0, hasown@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
|
||||
@@ -8223,6 +8552,18 @@ hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
|
||||
dependencies:
|
||||
react-is "^16.7.0"
|
||||
|
||||
hsl-to-hex@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz#c58c826dc6d2f1e0a5ff1da5a7ecbf03faac1352"
|
||||
integrity sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==
|
||||
dependencies:
|
||||
hsl-to-rgb-for-reals "^1.1.0"
|
||||
|
||||
hsl-to-rgb-for-reals@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz#e1eb23f6b78016e3722431df68197e6dcdc016d9"
|
||||
integrity sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==
|
||||
|
||||
html-encoding-sniffer@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
|
||||
@@ -8328,6 +8669,11 @@ human-signals@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
||||
|
||||
hyphen@^1.6.4:
|
||||
version "1.10.6"
|
||||
resolved "https://registry.yarnpkg.com/hyphen/-/hyphen-1.10.6.tgz#0e779d280e696102b97d7e42f5ca5de2cc97e274"
|
||||
integrity sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==
|
||||
|
||||
i18next-browser-languagedetector@8.0.2:
|
||||
version "8.0.2"
|
||||
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.2.tgz#037ca25c26877cad778f060a9e177054d9f8eaa3"
|
||||
@@ -8409,6 +8755,11 @@ ignore@^6.0.2:
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-6.0.2.tgz#77cccb72a55796af1b6d2f9eb14fa326d24f4283"
|
||||
integrity sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==
|
||||
|
||||
immediate@~3.0.5:
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
||||
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
|
||||
|
||||
import-fresh@^3.2.1, import-fresh@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
||||
@@ -8733,6 +9084,11 @@ is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15:
|
||||
dependencies:
|
||||
which-typed-array "^1.1.16"
|
||||
|
||||
is-url@^1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
|
||||
integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==
|
||||
|
||||
is-valid-glob@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa"
|
||||
@@ -8853,6 +9209,13 @@ jake@^10.8.5:
|
||||
filelist "^1.0.4"
|
||||
minimatch "^3.1.2"
|
||||
|
||||
jay-peg@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jay-peg/-/jay-peg-1.1.1.tgz#fdf410b89fa7a295bf74424ffe4c9083dbe7c363"
|
||||
integrity sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==
|
||||
dependencies:
|
||||
restructure "^3.0.0"
|
||||
|
||||
jest-changed-files@^29.7.0:
|
||||
version "29.7.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a"
|
||||
@@ -9254,33 +9617,6 @@ js-yaml@^4.1.0:
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
jsdom@25.0.1, jsdom@^25.0.1:
|
||||
version "25.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef"
|
||||
integrity sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==
|
||||
dependencies:
|
||||
cssstyle "^4.1.0"
|
||||
data-urls "^5.0.0"
|
||||
decimal.js "^10.4.3"
|
||||
form-data "^4.0.0"
|
||||
html-encoding-sniffer "^4.0.0"
|
||||
http-proxy-agent "^7.0.2"
|
||||
https-proxy-agent "^7.0.5"
|
||||
is-potential-custom-element-name "^1.0.1"
|
||||
nwsapi "^2.2.12"
|
||||
parse5 "^7.1.2"
|
||||
rrweb-cssom "^0.7.1"
|
||||
saxes "^6.0.0"
|
||||
symbol-tree "^3.2.4"
|
||||
tough-cookie "^5.0.0"
|
||||
w3c-xmlserializer "^5.0.0"
|
||||
webidl-conversions "^7.0.0"
|
||||
whatwg-encoding "^3.1.1"
|
||||
whatwg-mimetype "^4.0.0"
|
||||
whatwg-url "^14.0.0"
|
||||
ws "^8.18.0"
|
||||
xml-name-validator "^5.0.0"
|
||||
|
||||
jsdom@^20.0.0:
|
||||
version "20.0.3"
|
||||
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db"
|
||||
@@ -9313,6 +9649,33 @@ jsdom@^20.0.0:
|
||||
ws "^8.11.0"
|
||||
xml-name-validator "^4.0.0"
|
||||
|
||||
jsdom@^25.0.1:
|
||||
version "25.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef"
|
||||
integrity sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==
|
||||
dependencies:
|
||||
cssstyle "^4.1.0"
|
||||
data-urls "^5.0.0"
|
||||
decimal.js "^10.4.3"
|
||||
form-data "^4.0.0"
|
||||
html-encoding-sniffer "^4.0.0"
|
||||
http-proxy-agent "^7.0.2"
|
||||
https-proxy-agent "^7.0.5"
|
||||
is-potential-custom-element-name "^1.0.1"
|
||||
nwsapi "^2.2.12"
|
||||
parse5 "^7.1.2"
|
||||
rrweb-cssom "^0.7.1"
|
||||
saxes "^6.0.0"
|
||||
symbol-tree "^3.2.4"
|
||||
tough-cookie "^5.0.0"
|
||||
w3c-xmlserializer "^5.0.0"
|
||||
webidl-conversions "^7.0.0"
|
||||
whatwg-encoding "^3.1.1"
|
||||
whatwg-mimetype "^4.0.0"
|
||||
whatwg-url "^14.0.0"
|
||||
ws "^8.18.0"
|
||||
xml-name-validator "^5.0.0"
|
||||
|
||||
jsesc@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
|
||||
@@ -9396,6 +9759,16 @@ jsonpointer@^5.0.0:
|
||||
object.assign "^4.1.4"
|
||||
object.values "^1.1.6"
|
||||
|
||||
jszip@^3.10.1:
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
|
||||
integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
|
||||
dependencies:
|
||||
lie "~3.3.0"
|
||||
pako "~1.0.2"
|
||||
readable-stream "~2.3.6"
|
||||
setimmediate "^1.0.5"
|
||||
|
||||
keyv@^4.5.3, keyv@^4.5.4:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
|
||||
@@ -9460,6 +9833,13 @@ lib0@^0.2.42, lib0@^0.2.47, lib0@^0.2.85, lib0@^0.2.87, lib0@^0.2.98:
|
||||
dependencies:
|
||||
isomorphic.js "^0.2.4"
|
||||
|
||||
lie@~3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
|
||||
integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
|
||||
dependencies:
|
||||
immediate "~3.0.5"
|
||||
|
||||
lilconfig@^3.1.2:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4"
|
||||
@@ -9825,6 +10205,11 @@ mdurl@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0"
|
||||
integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==
|
||||
|
||||
media-engine@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/media-engine/-/media-engine-1.0.3.tgz#be3188f6cd243ea2a40804a35de5a5b032f58dad"
|
||||
integrity sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
@@ -10205,6 +10590,11 @@ min-indent@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
|
||||
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
|
||||
|
||||
minimalistic-assert@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
||||
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
|
||||
|
||||
minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||
@@ -10283,6 +10673,11 @@ nanoid@^3.3.6, nanoid@^3.3.7:
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
|
||||
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
|
||||
|
||||
nanoid@^5.0.4:
|
||||
version "5.0.9"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.9.tgz#977dcbaac055430ce7b1e19cf0130cea91a20e50"
|
||||
integrity sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
@@ -10379,6 +10774,13 @@ normalize-path@3.0.0, normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
normalize-svg-path@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz#0e614eca23c39f0cffe821d6be6cd17e569a766c"
|
||||
integrity sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==
|
||||
dependencies:
|
||||
svg-arc-to-cubic-bezier "^3.0.0"
|
||||
|
||||
now-and-later@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-3.0.0.tgz#cdc045dc5b894b35793cf276cc3206077bb7302d"
|
||||
@@ -10564,6 +10966,16 @@ p-try@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||
|
||||
pako@^0.2.5:
|
||||
version "0.2.9"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
|
||||
integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==
|
||||
|
||||
pako@~1.0.2, pako@~1.0.5:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
@@ -10581,6 +10993,11 @@ parse-json@^5.0.0, parse-json@^5.2.0:
|
||||
json-parse-even-better-errors "^2.3.0"
|
||||
lines-and-columns "^1.1.6"
|
||||
|
||||
parse-svg-path@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/parse-svg-path/-/parse-svg-path-0.1.2.tgz#7a7ec0d1eb06fa5325c7d3e009b859a09b5d49eb"
|
||||
integrity sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==
|
||||
|
||||
parse5-htmlparser2-tree-adapter@^7.0.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz#b5a806548ed893a43e24ccb42fbb78069311e81b"
|
||||
@@ -10779,7 +11196,7 @@ postcss-selector-parser@^7.0.0:
|
||||
cssesc "^3.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-value-parser@^4.0.2, postcss-value-parser@^4.2.0:
|
||||
postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
@@ -10860,6 +11277,21 @@ postgres-range@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.4.tgz#a59c5f9520909bcec5e63e8cf913a92e4c952863"
|
||||
integrity sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==
|
||||
|
||||
posthog-js@1.204.0:
|
||||
version "1.204.0"
|
||||
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.204.0.tgz#73843af471fcc484ca1e8e1bcc927887cf81b4ba"
|
||||
integrity sha512-wVt948wKPPztCZ3OeDq8y0dtaPbhbY8vFuEVBUNHOn7PohbTXr7HZ4CNhH8fXgFkx5COEzz/20wWJmEsSU5oCA==
|
||||
dependencies:
|
||||
core-js "^3.38.1"
|
||||
fflate "^0.4.8"
|
||||
preact "^10.19.3"
|
||||
web-vitals "^4.2.0"
|
||||
|
||||
preact@^10.19.3:
|
||||
version "10.25.4"
|
||||
resolved "https://registry.yarnpkg.com/preact/-/preact-10.25.4.tgz#c1d00bee9d7b9dcd06a2311d9951973b506ae8ac"
|
||||
integrity sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==
|
||||
|
||||
prelude-ls@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
@@ -11061,7 +11493,7 @@ prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3:
|
||||
prosemirror-transform "^1.0.0"
|
||||
prosemirror-view "^1.27.0"
|
||||
|
||||
prosemirror-tables@^1.6.1:
|
||||
prosemirror-tables@^1.3.7, prosemirror-tables@^1.6.1:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.6.2.tgz#cec9e9ac6ecf81d67147c19ab39125d56c8351ae"
|
||||
integrity sha512-97dKocVLrEVTQjZ4GBLdrrMw7Gv3no8H8yMwf5IRM9OoHrzbWpcH5jJxYgNQIRCtdIqwDctT1HdMHrGTiwp1dQ==
|
||||
@@ -11080,7 +11512,7 @@ prosemirror-trailing-node@^3.0.0:
|
||||
"@remirror/core-constants" "3.0.0"
|
||||
escape-string-regexp "^4.0.0"
|
||||
|
||||
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.7.3:
|
||||
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.7.3, prosemirror-transform@^1.9.0:
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz#8ebac4e305b586cd96595aa028118c9191bbf052"
|
||||
integrity sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==
|
||||
@@ -11175,6 +11607,13 @@ queue-tick@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142"
|
||||
integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==
|
||||
|
||||
queue@^6.0.1:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65"
|
||||
integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==
|
||||
dependencies:
|
||||
inherits "~2.0.3"
|
||||
|
||||
quick-temp@^0.1.8:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/quick-temp/-/quick-temp-0.1.8.tgz#bab02a242ab8fb0dd758a3c9776b32f9a5d94408"
|
||||
@@ -11871,6 +12310,11 @@ resolve@^2.0.0-next.5:
|
||||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
restructure@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/restructure/-/restructure-3.0.2.tgz#e6b2fad214f78edee21797fa8160fef50eb9b49a"
|
||||
integrity sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==
|
||||
|
||||
reusify@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||
@@ -11981,6 +12425,11 @@ safe-regex-test@^1.0.3, safe-regex-test@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
sax@^1.2.4:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"
|
||||
integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
|
||||
|
||||
saxes@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5"
|
||||
@@ -11988,6 +12437,11 @@ saxes@^6.0.0:
|
||||
dependencies:
|
||||
xmlchars "^2.2.0"
|
||||
|
||||
scheduler@0.25.0-rc-603e6108-20241029:
|
||||
version "0.25.0-rc-603e6108-20241029"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz#684dd96647e104d23e0d29a37f18937daf82df19"
|
||||
integrity sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==
|
||||
|
||||
scheduler@^0.23.2:
|
||||
version "0.23.2"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
|
||||
@@ -12082,6 +12536,11 @@ set-function-name@^2.0.2:
|
||||
functions-have-names "^1.2.3"
|
||||
has-property-descriptors "^1.0.2"
|
||||
|
||||
setimmediate@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||
integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
|
||||
|
||||
setprototypeof@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
|
||||
@@ -12672,6 +13131,11 @@ supports-preserve-symlinks-flag@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
svg-arc-to-cubic-bezier@^3.0.0, svg-arc-to-cubic-bezier@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz#390c450035ae1c4a0104d90650304c3bc814abe6"
|
||||
integrity sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==
|
||||
|
||||
svg-parser@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5"
|
||||
@@ -12806,6 +13270,11 @@ through2@^2.0.1:
|
||||
readable-stream "~2.3.6"
|
||||
xtend "~4.0.1"
|
||||
|
||||
tiny-inflate@^1.0.0, tiny-inflate@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4"
|
||||
integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==
|
||||
|
||||
tippy.js@^6.3.7:
|
||||
version "6.3.7"
|
||||
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c"
|
||||
@@ -13134,11 +13603,27 @@ unicode-match-property-value-ecmascript@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz#a0401aee72714598f739b68b104e4fe3a0cb3c71"
|
||||
integrity sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==
|
||||
|
||||
unicode-properties@^1.4.0, unicode-properties@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/unicode-properties/-/unicode-properties-1.4.1.tgz#96a9cffb7e619a0dc7368c28da27e05fc8f9be5f"
|
||||
integrity sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==
|
||||
dependencies:
|
||||
base64-js "^1.3.0"
|
||||
unicode-trie "^2.0.0"
|
||||
|
||||
unicode-property-aliases-ecmascript@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd"
|
||||
integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==
|
||||
|
||||
unicode-trie@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-2.0.0.tgz#8fd8845696e2e14a8b67d78fa9e0dd2cad62fec8"
|
||||
integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==
|
||||
dependencies:
|
||||
pako "^0.2.5"
|
||||
tiny-inflate "^1.0.0"
|
||||
|
||||
unified@^10.0.0, unified@^10.1.2:
|
||||
version "10.1.2"
|
||||
resolved "https://registry.yarnpkg.com/unified/-/unified-10.1.2.tgz#b1d64e55dafe1f0b98bb6c719881103ecf6c86df"
|
||||
@@ -13500,6 +13985,15 @@ vinyl@^3.0.0:
|
||||
replace-ext "^2.0.0"
|
||||
teex "^1.0.1"
|
||||
|
||||
vite-compatible-readable-stream@^3.6.1:
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz#27267aebbdc9893c0ddf65a421279cbb1e31d8cd"
|
||||
integrity sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
void-elements@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||
@@ -13561,6 +14055,11 @@ web-namespaces@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692"
|
||||
integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==
|
||||
|
||||
web-vitals@^4.2.0:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.4.tgz#1d20bc8590a37769bd0902b289550936069184b7"
|
||||
integrity sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||
@@ -13969,6 +14468,13 @@ ws@^7.4.6:
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
|
||||
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
|
||||
|
||||
xml-js@^1.6.8:
|
||||
version "1.6.11"
|
||||
resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
|
||||
integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
|
||||
dependencies:
|
||||
sax "^1.2.4"
|
||||
|
||||
xml-name-validator@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
|
||||
@@ -13979,6 +14485,11 @@ xml-name-validator@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673"
|
||||
integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==
|
||||
|
||||
xml@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
|
||||
integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==
|
||||
|
||||
xmlchars@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||
@@ -14053,6 +14564,11 @@ yocto-queue@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
yoga-layout@^3.1.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/yoga-layout/-/yoga-layout-3.2.1.tgz#d2d1ba06f0e81c2eb650c3e5ad8b0b4adde1e843"
|
||||
integrity sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==
|
||||
|
||||
zustand@5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.2.tgz#f7595ada55a565f1fd6464f002a91e701ee0cfca"
|
||||
|
||||
@@ -148,6 +148,13 @@ ingressAdmin:
|
||||
enabled: true
|
||||
host: impress.127.0.0.1.nip.io
|
||||
|
||||
posthog:
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
ingressAssets:
|
||||
enabled: false
|
||||
|
||||
ingressMedia:
|
||||
enabled: true
|
||||
host: impress.127.0.0.1.nip.io
|
||||
|
||||
@@ -93,4 +93,4 @@ releases:
|
||||
environments:
|
||||
dev:
|
||||
values:
|
||||
- version: 2.0.0
|
||||
- version: 2.0.1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
type: application
|
||||
name: docs
|
||||
version: 0.0.2
|
||||
version: 2.0.1-beta.8
|
||||
appVersion: latest
|
||||
|
||||
@@ -104,6 +104,11 @@
|
||||
| `backend.migrate.restartPolicy` | backend migrate job restart policy | `Never` |
|
||||
| `backend.createsuperuser.command` | backend migrate command | `["/bin/sh","-c","python manage.py createsuperuser --email $DJANGO_SUPERUSER_EMAIL --password $DJANGO_SUPERUSER_PASSWORD\n"]` |
|
||||
| `backend.createsuperuser.restartPolicy` | backend migrate job restart policy | `Never` |
|
||||
| `backend.job` | job dedicated to run a random management command, for example after a deployment | |
|
||||
| `backend.job.name` | The name to use to describe this job | `""` |
|
||||
| `backend.job.command` | The management command to execute | `[]` |
|
||||
| `backend.job.restartPolicy` | The restart policy for the job. | `Never` |
|
||||
| `backend.job.annotations` | Annotations to add to the job [default: argocd.argoproj.io/hook: PostSync] | |
|
||||
| `backend.probes.liveness.path` | Configure path for backend HTTP liveness probe | `/__heartbeat__` |
|
||||
| `backend.probes.liveness.targetPort` | Configure port for backend HTTP liveness probe | `undefined` |
|
||||
| `backend.probes.liveness.initialDelaySeconds` | Configure initial delay for backend liveness probe | `10` |
|
||||
@@ -176,6 +181,37 @@
|
||||
| `frontend.extraVolumeMounts` | Additional volumes to mount on the frontend. | `[]` |
|
||||
| `frontend.extraVolumes` | Additional volumes to mount on the frontend. | `[]` |
|
||||
|
||||
### posthog
|
||||
|
||||
| Name | Description | Value |
|
||||
| -------------------------------------- | ----------------------------------------------------------- | ------------------------- |
|
||||
| `posthog.ingress.enabled` | Enable or disable the ingress resource creation | `false` |
|
||||
| `posthog.ingress.className` | Kubernetes ingress class name to use (e.g., nginx, traefik) | `nil` |
|
||||
| `posthog.ingress.host` | Primary hostname for the ingress resource | `impress.example.com` |
|
||||
| `posthog.ingress.path` | URL path prefix for the ingress routes (e.g., /) | `/` |
|
||||
| `posthog.ingress.hosts` | Additional hostnames array to be included in the ingress | `[]` |
|
||||
| `posthog.ingress.tls.enabled` | Enable or disable TLS/HTTPS for the ingress | `true` |
|
||||
| `posthog.ingress.tls.additional` | Additional TLS configurations for extra hosts/certificates | `[]` |
|
||||
| `posthog.ingress.customBackends` | Custom backend service configurations for the ingress | `[]` |
|
||||
| `posthog.ingress.annotations` | Additional Kubernetes annotations to apply to the ingress | `{}` |
|
||||
| `posthog.ingressAssets.enabled` | Enable or disable the ingress resource creation | `false` |
|
||||
| `posthog.ingressAssets.className` | Kubernetes ingress class name to use (e.g., nginx, traefik) | `nil` |
|
||||
| `posthog.ingressAssets.host` | Primary hostname for the ingress resource | `impress.example.com` |
|
||||
| `posthog.ingressAssets.paths` | URL paths prefix for the ingress routes (e.g., /static) | `["/static","/array"]` |
|
||||
| `posthog.ingressAssets.hosts` | Additional hostnames array to be included in the ingress | `[]` |
|
||||
| `posthog.ingressAssets.tls.enabled` | Enable or disable TLS/HTTPS for the ingress | `true` |
|
||||
| `posthog.ingressAssets.tls.additional` | Additional TLS configurations for extra hosts/certificates | `[]` |
|
||||
| `posthog.ingressAssets.customBackends` | Custom backend service configurations for the ingress | `[]` |
|
||||
| `posthog.ingressAssets.annotations` | Additional Kubernetes annotations to apply to the ingress | `{}` |
|
||||
| `posthog.service.type` | Service type (e.g. ExternalName, ClusterIP, LoadBalancer) | `ExternalName` |
|
||||
| `posthog.service.externalName` | External service hostname when type is ExternalName | `eu.i.posthog.com` |
|
||||
| `posthog.service.port` | Port number for the service | `443` |
|
||||
| `posthog.service.annotations` | Additional annotations to apply to the service | `{}` |
|
||||
| `posthog.assetsService.type` | Service type (e.g. ExternalName, ClusterIP, LoadBalancer) | `ExternalName` |
|
||||
| `posthog.assetsService.externalName` | External service hostname when type is ExternalName | `eu-assets.i.posthog.com` |
|
||||
| `posthog.assetsService.port` | Port number for the service | `443` |
|
||||
| `posthog.assetsService.annotations` | Additional annotations to apply to the service | `{}` |
|
||||
|
||||
### yProvider
|
||||
|
||||
| Name | Description | Value |
|
||||
|
||||
@@ -148,6 +148,15 @@ Requires top level scope
|
||||
{{ include "impress.fullname" . }}-frontend
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Full name for the Posthog
|
||||
|
||||
Requires top level scope
|
||||
*/}}
|
||||
{{- define "impress.posthog.fullname" -}}
|
||||
{{ include "impress.fullname" . }}-posthog
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Full name for the yProvider
|
||||
|
||||
|
||||
124
src/helm/impress/templates/backend_job.yml
Normal file
124
src/helm/impress/templates/backend_job.yml
Normal file
@@ -0,0 +1,124 @@
|
||||
{{- if .Values.backend.job.command -}}
|
||||
{{- $envVars := include "impress.common.env" (list . .Values.backend) -}}
|
||||
{{- $fullName := include "impress.backend.fullname" . -}}
|
||||
{{- $component := "backend" -}}
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: {{ $fullName }}-{{ .Values.backend.job.name | default "random" | replace "_" "-" }}
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-options: Replace=true,Force=true
|
||||
{{- with .Values.backend.job.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
{{- with .Values.backend.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "impress.common.selectorLabels" (list . $component) | nindent 8 }}
|
||||
spec:
|
||||
{{- if $.Values.image.credentials }}
|
||||
imagePullSecrets:
|
||||
- name: {{ include "impress.secret.dockerconfigjson.name" (dict "fullname" (include "impress.fullname" .) "imageCredentials" $.Values.image.credentials) }}
|
||||
{{- end}}
|
||||
shareProcessNamespace: {{ .Values.backend.shareProcessNamespace }}
|
||||
containers:
|
||||
{{- with .Values.backend.sidecars }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ (.Values.backend.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.backend.image | default dict).tag | default .Values.image.tag }}"
|
||||
imagePullPolicy: {{ (.Values.backend.image | default dict).pullPolicy | default .Values.image.pullPolicy }}
|
||||
{{- with .Values.backend.job.command }}
|
||||
command:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.backend.args }}
|
||||
args:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
env:
|
||||
{{- if $envVars}}
|
||||
{{- $envVars | indent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.backend.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.backend.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
{{- range $index, $value := .Values.mountFiles }}
|
||||
- name: "files-{{ $index }}"
|
||||
mountPath: {{ $value.path }}
|
||||
subPath: content
|
||||
{{- end }}
|
||||
{{- range $name, $volume := .Values.backend.persistence }}
|
||||
- name: "{{ $name }}"
|
||||
mountPath: "{{ $volume.mountPath }}"
|
||||
{{- end }}
|
||||
{{- range .Values.backend.extraVolumeMounts }}
|
||||
- name: {{ .name }}
|
||||
mountPath: {{ .mountPath }}
|
||||
subPath: {{ .subPath | default "" }}
|
||||
readOnly: {{ .readOnly }}
|
||||
{{- end }}
|
||||
{{- with .Values.backend.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.backend.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.backend.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
restartPolicy: {{ .Values.backend.job.restartPolicy }}
|
||||
volumes:
|
||||
{{- range $index, $value := .Values.mountFiles }}
|
||||
- name: "files-{{ $index }}"
|
||||
configMap:
|
||||
name: "{{ include "impress.fullname" $ }}-files-{{ $index }}"
|
||||
{{- end }}
|
||||
{{- range $name, $volume := .Values.backend.persistence }}
|
||||
- name: "{{ $name }}"
|
||||
{{- if eq $volume.type "emptyDir" }}
|
||||
emptyDir: {}
|
||||
{{- else }}
|
||||
persistentVolumeClaim:
|
||||
claimName: "{{ $fullName }}-{{ $name }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range .Values.backend.extraVolumes }}
|
||||
- name: {{ .name }}
|
||||
{{- if .existingClaim }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ .existingClaim }}
|
||||
{{- else if .hostPath }}
|
||||
hostPath:
|
||||
{{ toYaml .hostPath | nindent 12 }}
|
||||
{{- else if .csi }}
|
||||
csi:
|
||||
{{- toYaml .csi | nindent 12 }}
|
||||
{{- else if .configMap }}
|
||||
configMap:
|
||||
{{- toYaml .configMap | nindent 12 }}
|
||||
{{- else if .emptyDir }}
|
||||
emptyDir:
|
||||
{{- toYaml .emptyDir | nindent 12 }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
86
src/helm/impress/templates/ingress_posthog.yaml
Normal file
86
src/helm/impress/templates/ingress_posthog.yaml
Normal file
@@ -0,0 +1,86 @@
|
||||
{{- if .Values.posthog.ingress.enabled -}}
|
||||
{{- $fullName := include "impress.fullname" . -}}
|
||||
{{- if and .Values.posthog.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.posthog.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.posthog.ingress.annotations "kubernetes.io/ingress.class" .Values.posthog.ingress.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}-posthog
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
labels:
|
||||
{{- include "impress.labels" . | nindent 4 }}
|
||||
{{- with .Values.posthog.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.posthog.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.posthog.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.posthog.ingress.tls.enabled }}
|
||||
tls:
|
||||
{{- if .Values.posthog.ingress.host }}
|
||||
- secretName: {{ $fullName }}-posthog-tls
|
||||
hosts:
|
||||
- {{ .Values.posthog.ingress.host | quote }}
|
||||
{{- end }}
|
||||
{{- range .Values.posthog.ingress.tls.additional }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- if .Values.posthog.ingress.host }}
|
||||
- host: {{ .Values.posthog.ingress.host | quote }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ .Values.posthog.ingress.path }}
|
||||
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
pathType: Prefix
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ include "impress.posthog.fullname" . }}-proxy
|
||||
port:
|
||||
number: {{ .Values.posthog.service.port }}
|
||||
{{- else }}
|
||||
serviceName: {{ include "impress.posthog.fullname" . }}-proxy
|
||||
servicePort: {{ .Values.posthog.service.port }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range .Values.posthog.ingress.hosts }}
|
||||
- host: {{ . | quote }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ $.Values.posthog.ingress.path | quote }}
|
||||
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
pathType: Prefix
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ include "impress.posthog.fullname" . }}-proxy
|
||||
port:
|
||||
number: {{ $.Values.posthog.service.port }}
|
||||
{{- else }}
|
||||
serviceName: {{ include "impress.posthog.fullname" . }}-proxy
|
||||
servicePort: {{ $.Values.posthog.service.port }}
|
||||
{{- end }}
|
||||
{{- with $.Values.posthog.service.customBackends }}
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
66
src/helm/impress/templates/ingress_posthog_assets.yaml
Normal file
66
src/helm/impress/templates/ingress_posthog_assets.yaml
Normal file
@@ -0,0 +1,66 @@
|
||||
{{- if .Values.posthog.ingressAssets.enabled -}}
|
||||
{{- $fullName := include "impress.fullname" . -}}
|
||||
{{- if and .Values.posthog.ingressAssets.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.posthog.ingressAssets.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.posthog.ingressAssets.annotations "kubernetes.io/ingress.class" .Values.posthog.ingressAssets.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}-posthog-assets
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
labels:
|
||||
{{- include "impress.labels" . | nindent 4 }}
|
||||
{{- with .Values.posthog.ingressAssets.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.posthog.ingressAssets.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.posthog.ingressAssets.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.posthog.ingressAssets.tls.enabled }}
|
||||
tls:
|
||||
{{- if .Values.posthog.ingressAssets.host }}
|
||||
- secretName: {{ $fullName }}-posthog-tls
|
||||
hosts:
|
||||
- {{ .Values.posthog.ingressAssets.host | quote }}
|
||||
{{- end }}
|
||||
{{- range .Values.posthog.ingressAssets.tls.additional }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- if .Values.posthog.ingressAssets.host }}
|
||||
- host: {{ .Values.posthog.ingressAssets.host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .Values.posthog.ingressAssets.paths }}
|
||||
- path: {{ . | quote }}
|
||||
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
pathType: Prefix
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ include "impress.posthog.fullname" $ }}-assets-proxy
|
||||
port:
|
||||
number: {{ $.Values.posthog.assetsService.port }}
|
||||
{{- else }}
|
||||
serviceName: {{ include "impress.posthog.fullname" $ }}-assets-proxy
|
||||
servicePort: {{ $.Values.posthog.assetsService.port }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
24
src/helm/impress/templates/posthog_assets_svc.yaml
Normal file
24
src/helm/impress/templates/posthog_assets_svc.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
{{- if .Values.posthog.ingressAssets.enabled -}}
|
||||
{{- $envVars := include "impress.common.env" (list . .Values.posthog) -}}
|
||||
{{- $fullName := include "impress.posthog.fullname" . -}}
|
||||
{{- $component := "posthog" -}}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ $fullName }}-assets-proxy
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
labels:
|
||||
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
|
||||
annotations:
|
||||
{{- toYaml $.Values.posthog.assetsService.annotations | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.posthog.assetsService.type }}
|
||||
externalName: {{ .Values.posthog.assetsService.externalName }}
|
||||
ports:
|
||||
- port: {{ .Values.posthog.assetsService.port }}
|
||||
targetPort: {{ .Values.posthog.assetsService.targetPort }}
|
||||
protocol: TCP
|
||||
name: https
|
||||
selector:
|
||||
{{- include "impress.common.selectorLabels" (list . $component) | nindent 4 }}
|
||||
{{- end }}
|
||||
24
src/helm/impress/templates/posthog_svc.yaml
Normal file
24
src/helm/impress/templates/posthog_svc.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
{{- if .Values.posthog.ingress.enabled -}}
|
||||
{{- $envVars := include "impress.common.env" (list . .Values.posthog) -}}
|
||||
{{- $fullName := include "impress.posthog.fullname" . -}}
|
||||
{{- $component := "posthog" -}}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ $fullName }}-proxy
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
labels:
|
||||
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
|
||||
annotations:
|
||||
{{- toYaml $.Values.posthog.service.annotations | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.posthog.service.type }}
|
||||
externalName: {{ .Values.posthog.service.externalName }}
|
||||
ports:
|
||||
- port: {{ .Values.posthog.service.port }}
|
||||
targetPort: {{ .Values.posthog.service.targetPort }}
|
||||
protocol: TCP
|
||||
name: https
|
||||
selector:
|
||||
{{- include "impress.common.selectorLabels" (list . $component) | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -251,6 +251,19 @@ backend:
|
||||
python manage.py createsuperuser --email $DJANGO_SUPERUSER_EMAIL --password $DJANGO_SUPERUSER_PASSWORD
|
||||
restartPolicy: Never
|
||||
|
||||
## @extra backend.job job dedicated to run a random management command, for example after a deployment
|
||||
## @param backend.job.name The name to use to describe this job
|
||||
## @param backend.job.command The management command to execute
|
||||
## @param backend.job.restartPolicy The restart policy for the job.
|
||||
## @extra backend.job.annotations Annotations to add to the job [default: argocd.argoproj.io/hook: PostSync]
|
||||
## @skip backend.job.annotations.argocd.argoproj.io/hook
|
||||
job:
|
||||
name: ""
|
||||
command: []
|
||||
restartPolicy: Never
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PostSync
|
||||
|
||||
## @param backend.probes.liveness.path [nullable] Configure path for backend HTTP liveness probe
|
||||
## @param backend.probes.liveness.targetPort [nullable] Configure port for backend HTTP liveness probe
|
||||
## @param backend.probes.liveness.initialDelaySeconds [nullable] Configure initial delay for backend liveness probe
|
||||
@@ -390,6 +403,77 @@ frontend:
|
||||
## @param frontend.extraVolumes Additional volumes to mount on the frontend.
|
||||
extraVolumes: []
|
||||
|
||||
## @section posthog
|
||||
|
||||
posthog:
|
||||
|
||||
## @param posthog.ingress.enabled Enable or disable the ingress resource creation
|
||||
## @param posthog.ingress.className Kubernetes ingress class name to use (e.g., nginx, traefik)
|
||||
## @param posthog.ingress.host Primary hostname for the ingress resource
|
||||
## @param posthog.ingress.path URL path prefix for the ingress routes (e.g., /)
|
||||
## @param posthog.ingress.hosts Additional hostnames array to be included in the ingress
|
||||
## @param posthog.ingress.tls.enabled Enable or disable TLS/HTTPS for the ingress
|
||||
## @param posthog.ingress.tls.additional Additional TLS configurations for extra hosts/certificates
|
||||
## @param posthog.ingress.customBackends Custom backend service configurations for the ingress
|
||||
## @param posthog.ingress.annotations Additional Kubernetes annotations to apply to the ingress
|
||||
ingress:
|
||||
enabled: false
|
||||
className: null
|
||||
host: impress.example.com
|
||||
path: /
|
||||
hosts: [ ]
|
||||
tls:
|
||||
enabled: true
|
||||
additional: [ ]
|
||||
|
||||
customBackends: [ ]
|
||||
annotations: {}
|
||||
|
||||
## @param posthog.ingressAssets.enabled Enable or disable the ingress resource creation
|
||||
## @param posthog.ingressAssets.className Kubernetes ingress class name to use (e.g., nginx, traefik)
|
||||
## @param posthog.ingressAssets.host Primary hostname for the ingress resource
|
||||
## @param posthog.ingressAssets.paths URL paths prefix for the ingress routes (e.g., /static)
|
||||
## @param posthog.ingressAssets.hosts Additional hostnames array to be included in the ingress
|
||||
## @param posthog.ingressAssets.tls.enabled Enable or disable TLS/HTTPS for the ingress
|
||||
## @param posthog.ingressAssets.tls.additional Additional TLS configurations for extra hosts/certificates
|
||||
## @param posthog.ingressAssets.customBackends Custom backend service configurations for the ingress
|
||||
## @param posthog.ingressAssets.annotations Additional Kubernetes annotations to apply to the ingress
|
||||
ingressAssets:
|
||||
enabled: false
|
||||
className: null
|
||||
host: impress.example.com
|
||||
paths:
|
||||
- /static
|
||||
- /array
|
||||
hosts: [ ]
|
||||
tls:
|
||||
enabled: true
|
||||
additional: [ ]
|
||||
|
||||
customBackends: [ ]
|
||||
annotations: {}
|
||||
|
||||
## @param posthog.service.type Service type (e.g. ExternalName, ClusterIP, LoadBalancer)
|
||||
## @param posthog.service.externalName External service hostname when type is ExternalName
|
||||
## @param posthog.service.port Port number for the service
|
||||
## @param posthog.service.annotations Additional annotations to apply to the service
|
||||
service:
|
||||
type: ExternalName
|
||||
externalName: eu.i.posthog.com
|
||||
port: 443
|
||||
annotations: {}
|
||||
|
||||
## @param posthog.assetsService.type Service type (e.g. ExternalName, ClusterIP, LoadBalancer)
|
||||
## @param posthog.assetsService.externalName External service hostname when type is ExternalName
|
||||
## @param posthog.assetsService.port Port number for the service
|
||||
## @param posthog.assetsService.annotations Additional annotations to apply to the service
|
||||
assetsService:
|
||||
type: ExternalName
|
||||
externalName: eu-assets.i.posthog.com
|
||||
port: 443
|
||||
annotations: {}
|
||||
|
||||
|
||||
## @section yProvider
|
||||
|
||||
yProvider:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"description": "An util to generate html and text django's templates from mjml templates",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
Reference in New Issue
Block a user