mirror of
https://github.com/suitenumerique/docs.git
synced 2026-05-06 23:22:15 +02:00
Compare commits
62 Commits
v3.4.1
...
packages/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3d27793a1 | ||
|
|
2ebb1d0e91 | ||
|
|
0cf8b9da1a | ||
|
|
7be761ce84 | ||
|
|
5181bba083 | ||
|
|
f434d78b5d | ||
|
|
e07f709dd4 | ||
|
|
afbacb0a24 | ||
|
|
409e073192 | ||
|
|
886dcb75d5 | ||
|
|
bb4d2a9fea | ||
|
|
5e5054282e | ||
|
|
f497e75426 | ||
|
|
97ab13ded6 | ||
|
|
99d674c615 | ||
|
|
1cdb6b62c8 | ||
|
|
2bf53301d2 | ||
|
|
ec84f31bc7 | ||
|
|
7813219b86 | ||
|
|
cecb4f5756 | ||
|
|
63efe40a7b | ||
|
|
e26c3dff35 | ||
|
|
f5f9d8a877 | ||
|
|
e7709badbb | ||
|
|
2a7c0ef800 | ||
|
|
155e7dfe22 | ||
|
|
afa48b6675 | ||
|
|
f12d30cffa | ||
|
|
30dfea744a | ||
|
|
2cbe363a5f | ||
|
|
7f450e8aa8 | ||
|
|
7021c0f849 | ||
|
|
e8d18d85e9 | ||
|
|
67a195f89c | ||
|
|
09b6fef63f | ||
|
|
11d0bafc94 | ||
|
|
1ae831cabd | ||
|
|
f1c2219270 | ||
|
|
8c9380c356 | ||
|
|
3ff6d2541c | ||
|
|
34ce276222 | ||
|
|
04273c3b3e | ||
|
|
0b301b95c8 | ||
|
|
228bdf733e | ||
|
|
bbf48f088f | ||
|
|
b28ff8f632 | ||
|
|
14b7cdf561 | ||
|
|
c534fed196 | ||
|
|
c1a740b7d4 | ||
|
|
83f2b3886e | ||
|
|
966e514c5a | ||
|
|
ef6d6c6a59 | ||
|
|
e79f3281b1 | ||
|
|
b78550b513 | ||
|
|
5a23c97681 | ||
|
|
040eddbe6b | ||
|
|
f2e54308d2 | ||
|
|
cd6e0ef9e1 | ||
|
|
02acc7233f | ||
|
|
1c71e830a2 | ||
|
|
ac0c16a44a | ||
|
|
ca09f9a158 |
6
.github/ISSUE_TEMPLATE.md
vendored
6
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,6 +0,0 @@
|
||||
<!---
|
||||
Thanks for filing an issue 😄 ! Before you submit, please read the following:
|
||||
|
||||
Check the other issue templates if you are trying to submit a bug report, feature request, or question
|
||||
Search open/closed issues before submitting since someone might have asked the same thing before!
|
||||
-->
|
||||
4
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -6,6 +6,10 @@ labels: ["bug", "triage"]
|
||||
|
||||
## Bug Report
|
||||
|
||||
**Before you file your issue**
|
||||
- Check the other [issues](https://github.com/suitenumerique/docs/issues) before filing your own
|
||||
- If your report is related to the ([BlockNote](https://github.com/TypeCellOS/BlockNote)) text editor, [file it on their repo](https://github.com/TypeCellOS/BlockNote/issues). If you're not sure whether your issue is with BlockNote or Docs, file it on our repo: if we support it, we'll backport it upstream ourselves 😊, otherwise we'll ask you to do so.
|
||||
|
||||
**Problematic behavior**
|
||||
A clear and concise description of the behavior.
|
||||
|
||||
|
||||
4
.github/workflows/impress-frontend.yml
vendored
4
.github/workflows/impress-frontend.yml
vendored
@@ -80,7 +80,7 @@ jobs:
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Set e2e env variables
|
||||
run: cat env.d/development/common.e2e.dist >> env.d/development/common.dist
|
||||
run: cat env.d/development/common.e2e >> env.d/development/common.local
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: cd src/frontend/apps/e2e && yarn install --frozen-lockfile && yarn install-playwright chromium
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Set e2e env variables
|
||||
run: cat env.d/development/common.e2e.dist >> env.d/development/common.dist
|
||||
run: cat env.d/development/common.e2e >> env.d/development/common.local
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: cd src/frontend/apps/e2e && yarn install --frozen-lockfile && yarn install-playwright firefox webkit chromium
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -40,8 +40,7 @@ venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
env.d/development/*
|
||||
!env.d/development/*.dist
|
||||
env.d/development/*.local
|
||||
env.d/terraform
|
||||
|
||||
# npm
|
||||
|
||||
150
CHANGELOG.md
150
CHANGELOG.md
@@ -8,15 +8,70 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- ⚡️(frontend) improve accessibility:
|
||||
- #1248
|
||||
- #1235
|
||||
- #1255
|
||||
- #1262
|
||||
- #1244
|
||||
- #1270
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(makefile) Windows compatibility fix for Docker volume mounting #1264
|
||||
- 🐛(minio) fix user permission error with Minio and Windows #1264
|
||||
|
||||
|
||||
## [3.5.0] - 2025-07-31
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(helm) Service Account support for K8s Resources in Helm Charts #780
|
||||
- ✨(backend) allow masking documents from the list view #1172
|
||||
- ✨(frontend) subdocs can manage link reach #1190
|
||||
- ✨(frontend) add duplicate action to doc tree #1175
|
||||
- ✨(frontend) Interlinking doc #904
|
||||
- ✨(frontend) add multi columns support for editor #1219
|
||||
|
||||
### Changed
|
||||
|
||||
- ♻️(frontend) search on all docs if no children #1184
|
||||
- ♻️(frontend) redirect to doc after duplicate #1175
|
||||
- 🔧(project) change env.d system by using local files #1200
|
||||
- ⚡️(frontend) improve tree stability #1207
|
||||
- ⚡️(frontend) improve accessibility #1232
|
||||
- 🛂(frontend) block drag n drop when not desktop #1239
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(service-worker) Fix useOffline Maximum update depth exceeded #1196
|
||||
- 🐛(frontend) fix empty left panel after deleting root doc #1197
|
||||
- 🐛(helm) charts generate invalid YAML for collaboration API / WS #890
|
||||
- 🐛(frontend) 401 redirection overridden #1214
|
||||
- 🐛(frontend) include root parent in search #1243
|
||||
|
||||
## [3.4.2] - 2025-07-18
|
||||
|
||||
### Changed
|
||||
|
||||
- ⚡️(docker) Optimize Dockerfile to use apk with --no-cache #743
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(backend) improve prompt to not use code blocks delimiter #1188
|
||||
|
||||
## [3.4.1] - 2025-07-15
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🌐(frontend) keep simple tag during export #1154
|
||||
- 🐛(back) manage can-edit endpoint without created room
|
||||
- 🐛(back) manage can-edit endpoint without created room
|
||||
in the ws #1152
|
||||
- 🐛(frontend) fix action buttons not clickable #1162
|
||||
- 🐛(frontend) fix crash share modal on grid options #1174
|
||||
- 🐛(frontend) fix unfold subdocs not clickable at the bottom #1179
|
||||
|
||||
## [3.4.0] - 2025-07-09
|
||||
|
||||
@@ -27,16 +82,16 @@ and this project adheres to
|
||||
- ✨Ask for access #1081
|
||||
- ✨(frontend) add customization for translations #857
|
||||
- ✨(backend) add ancestors links definitions to document abilities #846
|
||||
- ✨(backend) include ancestors accesses on document accesses list view # 846
|
||||
- ✨(backend) include ancestors accesses on document accesses list view #846
|
||||
- ✨(backend) add ancestors links reach and role to document API #846
|
||||
- 📝(project) add troubleshoot doc #1066
|
||||
- 📝(project) add system-requirement doc #1066
|
||||
- 🔧(front) configure x-frame-options to DENY in nginx conf #1084
|
||||
- ✨(backend) allow to disable checking unsafe mimetype on
|
||||
- 🔧(frontend) configure x-frame-options to DENY in nginx conf #1084
|
||||
- ✨(backend) allow to disable checking unsafe mimetype on
|
||||
attachment upload #1099
|
||||
- ✨(doc) add documentation to install with compose #855
|
||||
- ✨ Give priority to users connected to collaboration server
|
||||
(aka no websocket feature) #1093
|
||||
- ✨ Give priority to users connected to collaboration server
|
||||
(aka no websocket feature) #1093
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -62,7 +117,6 @@ and this project adheres to
|
||||
|
||||
- 🔥(frontend) remove Beta from logo #1095
|
||||
|
||||
|
||||
## [3.3.0] - 2025-05-06
|
||||
|
||||
### Added
|
||||
@@ -88,13 +142,13 @@ and this project adheres to
|
||||
- ⬆️(docker) upgrade node images to alpine 3.21 #973
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(y-provider) increase JSON size limits for transcription conversion #989
|
||||
|
||||
### Removed
|
||||
|
||||
- 🔥(back) remove footer endpoint #948
|
||||
|
||||
|
||||
## [3.2.1] - 2025-05-06
|
||||
|
||||
## Fixed
|
||||
@@ -102,7 +156,6 @@ and this project adheres to
|
||||
- 🐛(frontend) fix list copy paste #943
|
||||
- 📝(doc) update contributing policy (commit signatures are now mandatory) #895
|
||||
|
||||
|
||||
## [3.2.0] - 2025-05-05
|
||||
|
||||
## Added
|
||||
@@ -113,7 +166,7 @@ and this project adheres to
|
||||
- ✨(settings) Allow configuring PKCE for the SSO #886
|
||||
- 🌐(i18n) activate chinese and spanish languages #884
|
||||
- 🔧(backend) allow overwriting the data directory #893
|
||||
- ➕(backend) add `django-lasuite` dependency #839
|
||||
- ➕(backend) add `django-lasuite` dependency #839
|
||||
- ✨(frontend) advanced table features #908
|
||||
|
||||
## Changed
|
||||
@@ -130,7 +183,6 @@ and this project adheres to
|
||||
- 🐛(backend) race condition create doc #633
|
||||
- 🐛(frontend) fix breaklines in custom blocks #908
|
||||
|
||||
|
||||
## [3.1.0] - 2025-04-07
|
||||
|
||||
## Added
|
||||
@@ -165,7 +217,6 @@ and this project adheres to
|
||||
- 🐛(backend) compute ancestor_links in get_abilities if needed #725
|
||||
- 🔒️(back) restrict access to document accesses #801
|
||||
|
||||
|
||||
## [2.6.0] - 2025-03-21
|
||||
|
||||
## Added
|
||||
@@ -184,7 +235,6 @@ and this project adheres to
|
||||
- 🔒️(back) throttle user list endpoint #636
|
||||
- 🔒️(back) remove pagination and limit to 5 for user list endpoint #636
|
||||
|
||||
|
||||
## [2.5.0] - 2025-03-18
|
||||
|
||||
## Added
|
||||
@@ -207,15 +257,14 @@ and this project adheres to
|
||||
## Fixed
|
||||
|
||||
- 🐛(frontend) SVG export #706
|
||||
- 🐛(frontend) remove scroll listener table content #688
|
||||
- 🐛(frontend) remove scroll listener table content #688
|
||||
- 🔒️(back) restrict access to favorite_list endpoint #690
|
||||
- 🐛(backend) refactor to fix filtering on children
|
||||
and descendants views #695
|
||||
- 🐛(backend) refactor to fix filtering on children
|
||||
and descendants views #695
|
||||
- 🐛(action) fix notify-argocd workflow #713
|
||||
- 🚨(helm) fix helmfile lint #736
|
||||
- 🚚(frontend) redirect to 401 page when 401 error #759
|
||||
|
||||
|
||||
## [2.4.0] - 2025-03-06
|
||||
|
||||
## Added
|
||||
@@ -230,7 +279,6 @@ and this project adheres to
|
||||
|
||||
- 🐛(frontend) fix collaboration error #684
|
||||
|
||||
|
||||
## [2.3.0] - 2025-03-03
|
||||
|
||||
## Added
|
||||
@@ -646,35 +694,37 @@ and this project adheres to
|
||||
- ✨(frontend) Coming Soon page (#67)
|
||||
- 🚀 Impress, project to manage your documents easily and collaboratively.
|
||||
|
||||
[unreleased]: https://github.com/numerique-gouv/impress/compare/v3.4.1...main
|
||||
[v3.4.1]: https://github.com/numerique-gouv/impress/releases/v3.4.1
|
||||
[v3.4.0]: https://github.com/numerique-gouv/impress/releases/v3.4.0
|
||||
[v3.3.0]: https://github.com/numerique-gouv/impress/releases/v3.3.0
|
||||
[v3.2.1]: https://github.com/numerique-gouv/impress/releases/v3.2.1
|
||||
[v3.2.0]: https://github.com/numerique-gouv/impress/releases/v3.2.0
|
||||
[v3.1.0]: https://github.com/numerique-gouv/impress/releases/v3.1.0
|
||||
[v3.0.0]: https://github.com/numerique-gouv/impress/releases/v3.0.0
|
||||
[v2.6.0]: https://github.com/numerique-gouv/impress/releases/v2.6.0
|
||||
[v2.5.0]: https://github.com/numerique-gouv/impress/releases/v2.5.0
|
||||
[v2.4.0]: https://github.com/numerique-gouv/impress/releases/v2.4.0
|
||||
[v2.3.0]: https://github.com/numerique-gouv/impress/releases/v2.3.0
|
||||
[v2.2.0]: https://github.com/numerique-gouv/impress/releases/v2.2.0
|
||||
[v2.1.0]: https://github.com/numerique-gouv/impress/releases/v2.1.0
|
||||
[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
|
||||
[v1.8.2]: https://github.com/numerique-gouv/impress/releases/v1.8.2
|
||||
[v1.8.1]: https://github.com/numerique-gouv/impress/releases/v1.8.1
|
||||
[v1.8.0]: https://github.com/numerique-gouv/impress/releases/v1.8.0
|
||||
[v1.7.0]: https://github.com/numerique-gouv/impress/releases/v1.7.0
|
||||
[v1.6.0]: https://github.com/numerique-gouv/impress/releases/v1.6.0
|
||||
[1.5.1]: https://github.com/numerique-gouv/impress/releases/v1.5.1
|
||||
[1.5.0]: https://github.com/numerique-gouv/impress/releases/v1.5.0
|
||||
[1.4.0]: https://github.com/numerique-gouv/impress/releases/v1.4.0
|
||||
[1.3.0]: https://github.com/numerique-gouv/impress/releases/v1.3.0
|
||||
[1.2.1]: https://github.com/numerique-gouv/impress/releases/v1.2.1
|
||||
[1.2.0]: https://github.com/numerique-gouv/impress/releases/v1.2.0
|
||||
[1.1.0]: https://github.com/numerique-gouv/impress/releases/v1.1.0
|
||||
[1.0.0]: https://github.com/numerique-gouv/impress/releases/v1.0.0
|
||||
[0.1.0]: https://github.com/numerique-gouv/impress/releases/v0.1.0
|
||||
[unreleased]: https://github.com/suitenumerique/docs/compare/v3.5.0...main
|
||||
[v3.5.0]: https://github.com/suitenumerique/docs/releases/v3.5.0
|
||||
[v3.4.2]: https://github.com/suitenumerique/docs/releases/v3.4.2
|
||||
[v3.4.1]: https://github.com/suitenumerique/docs/releases/v3.4.1
|
||||
[v3.4.0]: https://github.com/suitenumerique/docs/releases/v3.4.0
|
||||
[v3.3.0]: https://github.com/suitenumerique/docs/releases/v3.3.0
|
||||
[v3.2.1]: https://github.com/suitenumerique/docs/releases/v3.2.1
|
||||
[v3.2.0]: https://github.com/suitenumerique/docs/releases/v3.2.0
|
||||
[v3.1.0]: https://github.com/suitenumerique/docs/releases/v3.1.0
|
||||
[v3.0.0]: https://github.com/suitenumerique/docs/releases/v3.0.0
|
||||
[v2.6.0]: https://github.com/suitenumerique/docs/releases/v2.6.0
|
||||
[v2.5.0]: https://github.com/suitenumerique/docs/releases/v2.5.0
|
||||
[v2.4.0]: https://github.com/suitenumerique/docs/releases/v2.4.0
|
||||
[v2.3.0]: https://github.com/suitenumerique/docs/releases/v2.3.0
|
||||
[v2.2.0]: https://github.com/suitenumerique/docs/releases/v2.2.0
|
||||
[v2.1.0]: https://github.com/suitenumerique/docs/releases/v2.1.0
|
||||
[v2.0.1]: https://github.com/suitenumerique/docs/releases/v2.0.1
|
||||
[v2.0.0]: https://github.com/suitenumerique/docs/releases/v2.0.0
|
||||
[v1.10.0]: https://github.com/suitenumerique/docs/releases/v1.10.0
|
||||
[v1.9.0]: https://github.com/suitenumerique/docs/releases/v1.9.0
|
||||
[v1.8.2]: https://github.com/suitenumerique/docs/releases/v1.8.2
|
||||
[v1.8.1]: https://github.com/suitenumerique/docs/releases/v1.8.1
|
||||
[v1.8.0]: https://github.com/suitenumerique/docs/releases/v1.8.0
|
||||
[v1.7.0]: https://github.com/suitenumerique/docs/releases/v1.7.0
|
||||
[v1.6.0]: https://github.com/suitenumerique/docs/releases/v1.6.0
|
||||
[1.5.1]: https://github.com/suitenumerique/docs/releases/v1.5.1
|
||||
[1.5.0]: https://github.com/suitenumerique/docs/releases/v1.5.0
|
||||
[1.4.0]: https://github.com/suitenumerique/docs/releases/v1.4.0
|
||||
[1.3.0]: https://github.com/suitenumerique/docs/releases/v1.3.0
|
||||
[1.2.1]: https://github.com/suitenumerique/docs/releases/v1.2.1
|
||||
[1.2.0]: https://github.com/suitenumerique/docs/releases/v1.2.0
|
||||
[1.1.0]: https://github.com/suitenumerique/docs/releases/v1.1.0
|
||||
[1.0.0]: https://github.com/suitenumerique/docs/releases/v1.0.0
|
||||
[0.1.0]: https://github.com/suitenumerique/docs/releases/v0.1.0
|
||||
|
||||
@@ -7,8 +7,7 @@ FROM python:3.13.3-alpine AS base
|
||||
RUN python -m pip install --upgrade pip setuptools
|
||||
|
||||
# Upgrade system packages to install security updates
|
||||
RUN apk update && \
|
||||
apk upgrade
|
||||
RUN apk update && apk upgrade --no-cache
|
||||
|
||||
# ---- Back-end builder image ----
|
||||
FROM base AS back-builder
|
||||
@@ -45,7 +44,7 @@ FROM base AS link-collector
|
||||
ARG IMPRESS_STATIC_ROOT=/data/static
|
||||
|
||||
# Install pango & rdfind
|
||||
RUN apk add \
|
||||
RUN apk add --no-cache \
|
||||
pango \
|
||||
rdfind
|
||||
|
||||
@@ -71,7 +70,7 @@ FROM base AS core
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# Install required system libs
|
||||
RUN apk add \
|
||||
RUN apk add --no-cache \
|
||||
cairo \
|
||||
file \
|
||||
font-noto \
|
||||
@@ -117,7 +116,7 @@ FROM core AS backend-development
|
||||
USER root:root
|
||||
|
||||
# Install psql
|
||||
RUN apk add postgresql-client
|
||||
RUN apk add --no-cache postgresql-client
|
||||
|
||||
# Uninstall impress and re-install it in editable mode along with development
|
||||
# dependencies
|
||||
|
||||
42
Makefile
42
Makefile
@@ -35,9 +35,13 @@ DB_PORT = 5432
|
||||
|
||||
# -- Docker
|
||||
# Get the current user ID to use for docker run and docker exec commands
|
||||
DOCKER_UID = $(shell id -u)
|
||||
DOCKER_GID = $(shell id -g)
|
||||
DOCKER_USER = $(DOCKER_UID):$(DOCKER_GID)
|
||||
ifeq ($(OS),Windows_NT)
|
||||
DOCKER_USER := 0:0 # run containers as root on Windows
|
||||
else
|
||||
DOCKER_UID := $(shell id -u)
|
||||
DOCKER_GID := $(shell id -g)
|
||||
DOCKER_USER := $(DOCKER_UID):$(DOCKER_GID)
|
||||
endif
|
||||
COMPOSE = DOCKER_USER=$(DOCKER_USER) docker compose
|
||||
COMPOSE_E2E = DOCKER_USER=$(DOCKER_USER) docker compose -f compose.yml -f compose-e2e.yml
|
||||
COMPOSE_EXEC = $(COMPOSE) exec
|
||||
@@ -48,7 +52,7 @@ COMPOSE_RUN_CROWDIN = $(COMPOSE_RUN) crowdin crowdin
|
||||
|
||||
# -- Backend
|
||||
MANAGE = $(COMPOSE_RUN_APP) python manage.py
|
||||
MAIL_YARN = $(COMPOSE_RUN) -w /app/src/mail node yarn
|
||||
MAIL_YARN = $(COMPOSE_RUN) -w //app/src/mail node yarn
|
||||
|
||||
# -- Frontend
|
||||
PATH_FRONT = ./src/frontend
|
||||
@@ -67,18 +71,18 @@ data/static:
|
||||
|
||||
# -- Project
|
||||
|
||||
create-env-files: ## Copy the dist env files to env files
|
||||
create-env-files: \
|
||||
env.d/development/common \
|
||||
env.d/development/crowdin \
|
||||
env.d/development/postgresql \
|
||||
env.d/development/kc_postgresql
|
||||
.PHONY: create-env-files
|
||||
create-env-local-files: ## create env.local files in env.d/development
|
||||
create-env-local-files:
|
||||
@touch env.d/development/crowdin.local
|
||||
@touch env.d/development/common.local
|
||||
@touch env.d/development/postgresql.local
|
||||
@touch env.d/development/kc_postgresql.local
|
||||
.PHONY: create-env-local-files
|
||||
|
||||
pre-bootstrap: \
|
||||
data/media \
|
||||
data/static \
|
||||
create-env-files
|
||||
create-env-local-files
|
||||
.PHONY: pre-bootstrap
|
||||
|
||||
post-bootstrap: \
|
||||
@@ -258,20 +262,6 @@ resetdb: ## flush database and create a superuser "admin"
|
||||
@${MAKE} superuser
|
||||
.PHONY: resetdb
|
||||
|
||||
env.d/development/common:
|
||||
cp -n env.d/development/common.dist env.d/development/common
|
||||
|
||||
env.d/development/postgresql:
|
||||
cp -n env.d/development/postgresql.dist env.d/development/postgresql
|
||||
|
||||
env.d/development/kc_postgresql:
|
||||
cp -n env.d/development/kc_postgresql.dist env.d/development/kc_postgresql
|
||||
|
||||
# -- Internationalization
|
||||
|
||||
env.d/development/crowdin:
|
||||
cp -n env.d/development/crowdin.dist env.d/development/crowdin
|
||||
|
||||
crowdin-download: ## Download translated message from crowdin
|
||||
@$(COMPOSE_RUN_CROWDIN) download -c crowdin/config.yml
|
||||
.PHONY: crowdin-download
|
||||
|
||||
@@ -38,6 +38,10 @@ function _set_user() {
|
||||
# options: docker compose command options
|
||||
# ARGS : docker compose command arguments
|
||||
function _docker_compose() {
|
||||
# Set DOCKER_USER for Windows compatibility with MinIO
|
||||
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || -n "${WSL_DISTRO_NAME:-}" ]]; then
|
||||
export DOCKER_USER="0:0"
|
||||
fi
|
||||
|
||||
echo "🐳(compose) file: '${COMPOSE_FILE}'"
|
||||
docker compose \
|
||||
|
||||
@@ -24,5 +24,6 @@ services:
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- env.d/development/common
|
||||
- env.d/development/common.local
|
||||
ports:
|
||||
- "4444:4444"
|
||||
@@ -10,6 +10,7 @@ services:
|
||||
retries: 300
|
||||
env_file:
|
||||
- env.d/development/postgresql
|
||||
- env.d/development/postgresql.local
|
||||
ports:
|
||||
- "15432:5432"
|
||||
|
||||
@@ -66,7 +67,9 @@ services:
|
||||
- DJANGO_CONFIGURATION=Development
|
||||
env_file:
|
||||
- env.d/development/common
|
||||
- env.d/development/common.local
|
||||
- env.d/development/postgresql
|
||||
- env.d/development/postgresql.local
|
||||
ports:
|
||||
- "8071:8000"
|
||||
volumes:
|
||||
@@ -91,7 +94,9 @@ services:
|
||||
- DJANGO_CONFIGURATION=Development
|
||||
env_file:
|
||||
- env.d/development/common
|
||||
- env.d/development/common.local
|
||||
- env.d/development/postgresql
|
||||
- env.d/development/postgresql.local
|
||||
volumes:
|
||||
- ./src/backend:/app
|
||||
- ./data/static:/data/static
|
||||
@@ -135,6 +140,7 @@ services:
|
||||
- ".:/app"
|
||||
env_file:
|
||||
- env.d/development/crowdin
|
||||
- env.d/development/crowdin.local
|
||||
user: "${DOCKER_USER:-1000}"
|
||||
working_dir: /app
|
||||
|
||||
@@ -156,6 +162,7 @@ services:
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- env.d/development/common
|
||||
- env.d/development/common.local
|
||||
ports:
|
||||
- "4444:4444"
|
||||
volumes:
|
||||
@@ -174,6 +181,7 @@ services:
|
||||
- "5433:5432"
|
||||
env_file:
|
||||
- env.d/development/kc_postgresql
|
||||
- env.d/development/kc_postgresql.local
|
||||
|
||||
keycloak:
|
||||
image: quay.io/keycloak/keycloak:20.0.1
|
||||
|
||||
@@ -136,9 +136,10 @@ NODE_ENV=production NEXT_PUBLIC_PUBLISH_AS_MIT=false yarn build
|
||||
|
||||
Packages with licences incompatible with the MIT licence:
|
||||
* `xl-docx-exporter`: [AGPL-3.0](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-docx-exporter/LICENSE),
|
||||
* `xl-pdf-exporter`: [AGPL-3.0](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-pdf-exporter/LICENSE)
|
||||
* `xl-pdf-exporter`: [AGPL-3.0](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-pdf-exporter/LICENSE),
|
||||
* `xl-multi-column`: [AGPL-3.0](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-multi-column/LICENSE).
|
||||
|
||||
In `.env.development`, `PUBLISH_AS_MIT` is set to `false`, allowing developers to test Docs with all its features.
|
||||
|
||||
⚠️ If you run Docs in production with `PUBLISH_AS_MIT` set to `false` make sure you fulfill your [BlockNote licensing](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-pdf-exporter/LICENSE) or [subscription](https://www.blocknotejs.org/about#partner-with-us) obligations.
|
||||
⚠️ If you run Docs in production with `PUBLISH_AS_MIT` set to `false` make sure you fulfill your BlockNote licensing or [subscription](https://www.blocknotejs.org/about#partner-with-us) obligations.
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
```bash
|
||||
mkdir keycloak
|
||||
curl -o compose.yaml https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/docs/examples/compose/keycloak/compose.yaml
|
||||
curl -o env.d/kc_postgresql https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/env.d/production.dist/kc_postgresql
|
||||
curl -o env.d/keycloak https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/env.d/production.dist/keycloak
|
||||
curl -o keycloak/compose.yaml https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/docs/examples/compose/keycloak/compose.yaml
|
||||
curl -o keycloak/env.d/kc_postgresql https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/env.d/production.dist/kc_postgresql
|
||||
curl -o keycloak/env.d/keycloak https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/env.d/production.dist/keycloak
|
||||
```
|
||||
|
||||
### Step 2:. Update `env.d/` files
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
```bash
|
||||
mkdir minio
|
||||
curl -o compose.yaml https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/docs/examples/compose/minio/compose.yaml
|
||||
curl -o minio/compose.yaml https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/docs/examples/compose/minio/compose.yaml
|
||||
```
|
||||
|
||||
### Step 2:. Update compose file with your own values
|
||||
|
||||
@@ -13,7 +13,7 @@ Acme-companion is a lightweight companion container for nginx-proxy. It handles
|
||||
|
||||
```bash
|
||||
mkdir nginx-proxy
|
||||
curl -o compose.yaml https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/docs/examples/compose/nginx-proxy/compose.yaml
|
||||
curl -o nginx-proxy/compose.yaml https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/docs/examples/compose/nginx-proxy/compose.yaml
|
||||
```
|
||||
|
||||
### Step 2: Edit `DEFAULT_EMAIL` in the compose file.
|
||||
|
||||
@@ -46,9 +46,6 @@ backend:
|
||||
DB_USER: dinum
|
||||
DB_PASSWORD: pass
|
||||
DB_PORT: 5432
|
||||
POSTGRES_DB: impress
|
||||
POSTGRES_USER: dinum
|
||||
POSTGRES_PASSWORD: pass
|
||||
REDIS_URL: redis://default:pass@redis-master:6379/1
|
||||
AWS_S3_ENDPOINT_URL: http://minio.impress.svc.cluster.local:9000
|
||||
AWS_S3_ACCESS_KEY_ID: root
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Installation with docker compose
|
||||
|
||||
We provide a sample configuration for running Docs using Docker Compose. Please note that this configuration is experimental, and the official way to deploy Docs in production is to use [k8s](../installation/k8s.md)
|
||||
We provide a sample configuration for running Docs using Docker Compose. Please note that this configuration is experimental, and the official way to deploy Docs in production is to use [k8s](../installation/kubernetes.md)
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -31,11 +31,17 @@ For older versions of Docker Engine that do not include Docker Compose:
|
||||
|
||||
```bash
|
||||
mkdir -p docs/env.d
|
||||
cd docs
|
||||
curl -o compose.yaml https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/docs/examples/compose/compose.yaml
|
||||
curl -o env.d/common https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/env.d/production.dist/common
|
||||
curl -o env.d/backend https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/env.d/production.dist/backend
|
||||
curl -o env.d/yprovider https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/env.d/production.dist/yprovider
|
||||
curl -o env.d/common https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/env.d/production.dist/postgresql
|
||||
curl -o env.d/postgresql https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/env.d/production.dist/postgresql
|
||||
```
|
||||
|
||||
If you are using the sample nginx-proxy configuration:
|
||||
```bash
|
||||
curl -o default.conf.template https://raw.githubusercontent.com/suitenumerique/docs/refs/heads/main/docker/files/production/etc/nginx/conf.d/default.conf.template
|
||||
```
|
||||
|
||||
## Step 2: Configuration
|
||||
|
||||
@@ -168,9 +168,6 @@ DB_NAME: impress
|
||||
DB_USER: dinum
|
||||
DB_PASSWORD: pass
|
||||
DB_PORT: 5432
|
||||
POSTGRES_DB: impress
|
||||
POSTGRES_USER: dinum
|
||||
POSTGRES_PASSWORD: pass
|
||||
```
|
||||
|
||||
### Find s3 bucket connection values
|
||||
|
||||
@@ -83,55 +83,6 @@ If you already have CRLF line endings in your local repository, the **best appro
|
||||
git commit -m "✏️(project) Fix line endings to LF"
|
||||
```
|
||||
|
||||
## Minio Permission Issues on Windows
|
||||
|
||||
### Problem Description
|
||||
|
||||
On Windows, you may encounter permission-related errors when running Minio in development mode with Docker Compose. This typically happens because:
|
||||
|
||||
- **Windows file permissions** don't map well to Unix-style user IDs used in Docker containers
|
||||
- **Docker Desktop** may have issues with user mapping when using the `DOCKER_USER` environment variable
|
||||
- **Minio container** fails to start or access volumes due to permission conflicts
|
||||
|
||||
### Common Symptoms
|
||||
|
||||
- Minio container fails to start with permission denied errors
|
||||
- Error messages related to file system permissions in Minio logs
|
||||
- Unable to create or access buckets in the development environment
|
||||
- Docker Compose showing Minio service as unhealthy or exited
|
||||
|
||||
### Solution for Windows Users
|
||||
|
||||
If you encounter Minio permission issues on Windows, you can temporarily disable user mapping for the Minio service:
|
||||
|
||||
1. **Open the `compose.yml` file**
|
||||
|
||||
2. **Comment out the user directive** in the `minio` service section:
|
||||
```yaml
|
||||
minio:
|
||||
# user: ${DOCKER_USER:-1000} # Comment this line on Windows if permission issues occur
|
||||
image: minio/minio
|
||||
environment:
|
||||
- MINIO_ROOT_USER=impress
|
||||
- MINIO_ROOT_PASSWORD=password
|
||||
# ... rest of the configuration
|
||||
```
|
||||
|
||||
3. **Restart the services**:
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
|
||||
### Why This Works
|
||||
|
||||
- Commenting out the `user` directive allows the Minio container to run with its default user
|
||||
- This bypasses Windows-specific permission mapping issues
|
||||
- The container will have the necessary permissions to access and manage the mounted volumes
|
||||
|
||||
### Note
|
||||
|
||||
This is a **development-only workaround**. In production environments, proper user mapping and security considerations should be maintained according to your deployment requirements.
|
||||
|
||||
## Frontend File Watching Issues on Windows
|
||||
|
||||
### Problem Description
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Y_PROVIDER_API_BASE_URL=http://${YPROVIDER_HOST}:4444/api
|
||||
Y_PROVIDER_API_BASE_URL=http://${YPROVIDER_HOST}:4444/api/
|
||||
Y_PROVIDER_API_KEY=<generate a random key>
|
||||
COLLABORATION_SERVER_SECRET=<generate a random key>
|
||||
COLLABORATION_SERVER_ORIGIN=https://${DOCS_HOST}
|
||||
|
||||
@@ -60,6 +60,9 @@ class ListDocumentFilter(DocumentFilter):
|
||||
is_creator_me = django_filters.BooleanFilter(
|
||||
method="filter_is_creator_me", label=_("Creator is me")
|
||||
)
|
||||
is_masked = django_filters.BooleanFilter(
|
||||
method="filter_is_masked", label=_("Masked")
|
||||
)
|
||||
is_favorite = django_filters.BooleanFilter(
|
||||
method="filter_is_favorite", label=_("Favorite")
|
||||
)
|
||||
@@ -106,3 +109,22 @@ class ListDocumentFilter(DocumentFilter):
|
||||
return queryset
|
||||
|
||||
return queryset.filter(is_favorite=bool(value))
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def filter_is_masked(self, queryset, name, value):
|
||||
"""
|
||||
Filter documents based on whether they are masked by the current user.
|
||||
|
||||
Example:
|
||||
- /api/v1.0/documents/?is_masked=true
|
||||
→ Filters documents marked as masked by the logged-in user
|
||||
- /api/v1.0/documents/?is_masked=false
|
||||
→ Filters documents not marked as masked by the logged-in user
|
||||
"""
|
||||
user = self.request.user
|
||||
|
||||
if not user.is_authenticated:
|
||||
return queryset
|
||||
|
||||
queryset_method = queryset.filter if bool(value) else queryset.exclude
|
||||
return queryset_method(link_traces__user=user, link_traces__is_masked=True)
|
||||
|
||||
@@ -455,9 +455,8 @@ class DocumentViewSet(
|
||||
|
||||
# Annotate favorite status and filter if applicable as late as possible
|
||||
queryset = queryset.annotate_is_favorite(user)
|
||||
queryset = filterset.filters["is_favorite"].filter(
|
||||
queryset, filter_data["is_favorite"]
|
||||
)
|
||||
for field in ["is_favorite", "is_masked"]:
|
||||
queryset = filterset.filters[field].filter(queryset, filter_data[field])
|
||||
|
||||
# Apply ordering only now that everything is filtered and annotated
|
||||
queryset = filters.OrderingFilter().filter_queryset(
|
||||
@@ -1109,15 +1108,50 @@ class DocumentViewSet(
|
||||
document=document, user=user
|
||||
).delete()
|
||||
if deleted:
|
||||
return drf.response.Response(
|
||||
{"detail": "Document unmarked as favorite"},
|
||||
status=drf.status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
return drf.response.Response(status=drf.status.HTTP_204_NO_CONTENT)
|
||||
return drf.response.Response(
|
||||
{"detail": "Document was already not marked as favorite"},
|
||||
status=drf.status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@drf.decorators.action(detail=True, methods=["post", "delete"], url_path="mask")
|
||||
def mask(self, request, *args, **kwargs):
|
||||
"""Mask or unmask the document for the logged-in user based on the HTTP method."""
|
||||
# Check permissions first
|
||||
document = self.get_object()
|
||||
user = request.user
|
||||
|
||||
try:
|
||||
link_trace = models.LinkTrace.objects.get(document=document, user=user)
|
||||
except models.LinkTrace.DoesNotExist:
|
||||
return drf.response.Response(
|
||||
{"detail": "User never accessed this document before."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if request.method == "POST":
|
||||
if link_trace.is_masked:
|
||||
return drf.response.Response(
|
||||
{"detail": "Document was already masked"},
|
||||
status=drf.status.HTTP_200_OK,
|
||||
)
|
||||
link_trace.is_masked = True
|
||||
link_trace.save(update_fields=["is_masked"])
|
||||
return drf.response.Response(
|
||||
{"detail": "Document was masked"},
|
||||
status=drf.status.HTTP_201_CREATED,
|
||||
)
|
||||
|
||||
# Handle DELETE method to unmask document
|
||||
if not link_trace.is_masked:
|
||||
return drf.response.Response(
|
||||
{"detail": "Document was already not masked"},
|
||||
status=drf.status.HTTP_200_OK,
|
||||
)
|
||||
link_trace.is_masked = False
|
||||
link_trace.save(update_fields=["is_masked"])
|
||||
return drf.response.Response(status=drf.status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@drf.decorators.action(detail=True, methods=["post"], url_path="attachment-upload")
|
||||
def attachment_upload(self, request, *args, **kwargs):
|
||||
"""Upload a file related to a given document"""
|
||||
|
||||
@@ -150,7 +150,7 @@ class DocumentFactory(factory.django.DjangoModelFactory):
|
||||
"""Add link traces to document from a given list of users."""
|
||||
if create and extracted:
|
||||
for item in extracted:
|
||||
models.LinkTrace.objects.create(document=self, user=item)
|
||||
models.LinkTrace.objects.update_or_create(document=self, user=item)
|
||||
|
||||
@factory.post_generation
|
||||
def favorited_by(self, create, extracted, **kwargs):
|
||||
@@ -159,6 +159,15 @@ class DocumentFactory(factory.django.DjangoModelFactory):
|
||||
for item in extracted:
|
||||
models.DocumentFavorite.objects.create(document=self, user=item)
|
||||
|
||||
@factory.post_generation
|
||||
def masked_by(self, create, extracted, **kwargs):
|
||||
"""Mark document as masked by a list of users."""
|
||||
if create and extracted:
|
||||
for item in extracted:
|
||||
models.LinkTrace.objects.update_or_create(
|
||||
document=self, user=item, defaults={"is_masked": True}
|
||||
)
|
||||
|
||||
|
||||
class UserDocumentAccessFactory(factory.django.DjangoModelFactory):
|
||||
"""Create fake document user accesses for testing."""
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Generated by Django 5.2.3 on 2025-07-13 08:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("core", "0023_remove_document_is_public_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="linktrace",
|
||||
name="is_masked",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="language",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("en-us", "English"),
|
||||
("fr-fr", "Français"),
|
||||
("de-de", "Deutsch"),
|
||||
("nl-nl", "Nederlands"),
|
||||
("es-es", "Español"),
|
||||
],
|
||||
default=None,
|
||||
help_text="The language in which the user wants to see the interface.",
|
||||
max_length=10,
|
||||
null=True,
|
||||
verbose_name="language",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -793,6 +793,7 @@ class Document(MP_Node, BaseModel):
|
||||
"favorite": can_get and user.is_authenticated,
|
||||
"link_configuration": is_owner_or_admin,
|
||||
"invite_owner": is_owner,
|
||||
"mask": can_get and user.is_authenticated,
|
||||
"move": is_owner_or_admin and not self.ancestors_deleted_at,
|
||||
"partial_update": can_update,
|
||||
"restore": is_owner,
|
||||
@@ -958,6 +959,7 @@ class LinkTrace(BaseModel):
|
||||
related_name="link_traces",
|
||||
)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="link_traces")
|
||||
is_masked = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
db_table = "impress_link_trace"
|
||||
|
||||
@@ -9,7 +9,8 @@ from core import enums
|
||||
|
||||
AI_ACTIONS = {
|
||||
"prompt": (
|
||||
"Answer the prompt in markdown format. "
|
||||
"Answer the prompt using markdown formatting for structure and emphasis. "
|
||||
"Return the content directly without wrapping it in code blocks or markdown delimiters. "
|
||||
"Preserve the language and markdown formatting. "
|
||||
"Do not provide any other information. "
|
||||
"Preserve the language."
|
||||
|
||||
@@ -175,8 +175,11 @@ def test_api_documents_ai_transform_authenticated_success(mock_create, reach, ro
|
||||
{
|
||||
"role": "system",
|
||||
"content": (
|
||||
"Answer the prompt in markdown format. Preserve the language and markdown "
|
||||
"formatting. Do not provide any other information. Preserve the language."
|
||||
"Answer the prompt using markdown formatting for structure and emphasis. "
|
||||
"Return the content directly without wrapping it in code blocks or markdown delimiters. "
|
||||
"Preserve the language and markdown formatting. "
|
||||
"Do not provide any other information. "
|
||||
"Preserve the language."
|
||||
),
|
||||
},
|
||||
{"role": "user", "content": "Hello"},
|
||||
@@ -249,8 +252,11 @@ def test_api_documents_ai_transform_success(mock_create, via, role, mock_user_te
|
||||
{
|
||||
"role": "system",
|
||||
"content": (
|
||||
"Answer the prompt in markdown format. Preserve the language and markdown "
|
||||
"formatting. Do not provide any other information. Preserve the language."
|
||||
"Answer the prompt using markdown formatting for structure and emphasis. "
|
||||
"Return the content directly without wrapping it in code blocks or markdown delimiters. "
|
||||
"Preserve the language and markdown formatting. "
|
||||
"Do not provide any other information. "
|
||||
"Preserve the language."
|
||||
),
|
||||
},
|
||||
{"role": "user", "content": "Hello"},
|
||||
|
||||
@@ -45,7 +45,10 @@ def test_api_document_favorite_anonymous_user(method, reach):
|
||||
],
|
||||
)
|
||||
def test_api_document_favorite_authenticated_post_allowed(reach, has_role):
|
||||
"""Authenticated users should be able to mark a document as favorite using POST."""
|
||||
"""
|
||||
Authenticated users should be able to mark a document to which they have access
|
||||
as favorite using POST.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
document = factories.DocumentFactory(link_reach=reach)
|
||||
client = APIClient()
|
||||
@@ -69,7 +72,10 @@ def test_api_document_favorite_authenticated_post_allowed(reach, has_role):
|
||||
|
||||
|
||||
def test_api_document_favorite_authenticated_post_forbidden():
|
||||
"""Authenticated users should be able to mark a document as favorite using POST."""
|
||||
"""
|
||||
Authenticated users should not be allowed to mark a document to which they don't
|
||||
have access as favorite using POST.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
document = factories.DocumentFactory(link_reach="restricted")
|
||||
client = APIClient()
|
||||
|
||||
@@ -41,8 +41,8 @@ def test_api_document_favorite_list_authenticated_with_favorite():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
# User don't have access to this document, let say it had access and this access has been
|
||||
# removed. It should not be in the favorite list anymore.
|
||||
# If the user doesn't have access to this document (e.g the user had access
|
||||
# and this access was removed), it should not be in the favorite list anymore.
|
||||
factories.DocumentFactory(favorited_by=[user])
|
||||
|
||||
document = factories.UserDocumentAccessFactory(
|
||||
|
||||
@@ -312,6 +312,84 @@ def test_api_documents_list_filter_is_favorite_invalid():
|
||||
assert len(results) == 5
|
||||
|
||||
|
||||
# Filters: is_masked
|
||||
|
||||
|
||||
def test_api_documents_list_filter_is_masked_true():
|
||||
"""
|
||||
Authenticated users should be able to filter documents they marked as masked.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
factories.DocumentFactory.create_batch(2, users=[user])
|
||||
masked_documents = factories.DocumentFactory.create_batch(
|
||||
3, users=[user], masked_by=[user]
|
||||
)
|
||||
unmasked_documents = factories.DocumentFactory.create_batch(2, users=[user])
|
||||
for document in unmasked_documents:
|
||||
models.LinkTrace.objects.create(document=document, user=user, is_masked=False)
|
||||
|
||||
response = client.get("/api/v1.0/documents/?is_masked=true")
|
||||
|
||||
assert response.status_code == 200
|
||||
results = response.json()["results"]
|
||||
assert len(results) == 3
|
||||
|
||||
# Ensure all results are marked as masked by the current user
|
||||
masked_documents_ids = [str(doc.id) for doc in masked_documents]
|
||||
for result in results:
|
||||
assert result["id"] in masked_documents_ids
|
||||
|
||||
|
||||
def test_api_documents_list_filter_is_masked_false():
|
||||
"""
|
||||
Authenticated users should be able to filter documents they didn't mark as masked.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
factories.DocumentFactory.create_batch(2, users=[user])
|
||||
masked_documents = factories.DocumentFactory.create_batch(
|
||||
3, users=[user], masked_by=[user]
|
||||
)
|
||||
unmasked_documents = factories.DocumentFactory.create_batch(2, users=[user])
|
||||
for document in unmasked_documents:
|
||||
models.LinkTrace.objects.create(document=document, user=user, is_masked=False)
|
||||
|
||||
response = client.get("/api/v1.0/documents/?is_masked=false")
|
||||
|
||||
assert response.status_code == 200
|
||||
results = response.json()["results"]
|
||||
assert len(results) == 4
|
||||
|
||||
# Ensure all results are not marked as masked by the current user
|
||||
masked_documents_ids = [str(doc.id) for doc in masked_documents]
|
||||
for result in results:
|
||||
assert result["id"] not in masked_documents_ids
|
||||
|
||||
|
||||
def test_api_documents_list_filter_is_masked_invalid():
|
||||
"""Filtering with an invalid `is_masked` value should do nothing."""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
factories.DocumentFactory.create_batch(2, users=[user])
|
||||
factories.DocumentFactory.create_batch(3, users=[user], masked_by=[user])
|
||||
unmasked_documents = factories.DocumentFactory.create_batch(2, users=[user])
|
||||
for document in unmasked_documents:
|
||||
models.LinkTrace.objects.create(document=document, user=user, is_masked=False)
|
||||
|
||||
response = client.get("/api/v1.0/documents/?is_masked=invalid")
|
||||
|
||||
assert response.status_code == 200
|
||||
results = response.json()["results"]
|
||||
assert len(results) == 7
|
||||
|
||||
|
||||
# Filters: title
|
||||
|
||||
|
||||
|
||||
353
src/backend/core/tests/documents/test_api_documents_mask.py
Normal file
353
src/backend/core/tests/documents/test_api_documents_mask.py
Normal file
@@ -0,0 +1,353 @@
|
||||
"""Test mask document API endpoint for users in impress's core app."""
|
||||
|
||||
import pytest
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core import factories, models
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reach",
|
||||
[
|
||||
"restricted",
|
||||
"authenticated",
|
||||
"public",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("method", ["post", "delete"])
|
||||
def test_api_document_mask_anonymous_user(method, reach):
|
||||
"""Anonymous users should not be able to mask/unmask documents."""
|
||||
document = factories.DocumentFactory(link_reach=reach)
|
||||
|
||||
response = getattr(APIClient(), method)(
|
||||
f"/api/v1.0/documents/{document.id!s}/mask/"
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
assert response.json() == {
|
||||
"detail": "Authentication credentials were not provided."
|
||||
}
|
||||
|
||||
# Verify in database
|
||||
assert models.LinkTrace.objects.exists() is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reach, has_role",
|
||||
[
|
||||
["restricted", True],
|
||||
["authenticated", False],
|
||||
["authenticated", True],
|
||||
["public", False],
|
||||
["public", True],
|
||||
],
|
||||
)
|
||||
def test_api_document_mask_authenticated_post_allowed(reach, has_role):
|
||||
"""Authenticated users should be able to mask a document to which they have access."""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach=reach)
|
||||
if has_role:
|
||||
models.DocumentAccess.objects.create(document=document, user=user)
|
||||
|
||||
# Try masking the document without a link trace
|
||||
response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/")
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {"detail": "User never accessed this document before."}
|
||||
assert not models.LinkTrace.objects.filter(document=document, user=user).exists()
|
||||
|
||||
models.LinkTrace.objects.create(document=document, user=user)
|
||||
# Mask document
|
||||
response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/")
|
||||
|
||||
assert response.status_code == 201
|
||||
assert response.json() == {"detail": "Document was masked"}
|
||||
assert models.LinkTrace.objects.filter(
|
||||
document=document, user=user, is_masked=True
|
||||
).exists()
|
||||
|
||||
|
||||
def test_api_document_mask_authenticated_post_forbidden():
|
||||
"""
|
||||
Authenticated users should no be allowed to mask a document
|
||||
to which they don't have access.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach="restricted")
|
||||
|
||||
# Try masking
|
||||
response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/")
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {
|
||||
"detail": "You do not have permission to perform this action."
|
||||
}
|
||||
|
||||
# Verify in database
|
||||
assert (
|
||||
models.LinkTrace.objects.filter(document=document, user=user).exists() is False
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reach, has_role",
|
||||
[
|
||||
["restricted", True],
|
||||
["authenticated", False],
|
||||
["authenticated", True],
|
||||
["public", False],
|
||||
["public", True],
|
||||
],
|
||||
)
|
||||
def test_api_document_mask_authenticated_post_already_masked_allowed(reach, has_role):
|
||||
"""POST should not create duplicate link trace if already marked."""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach=reach, masked_by=[user])
|
||||
if has_role:
|
||||
models.DocumentAccess.objects.create(document=document, user=user)
|
||||
|
||||
# Try masking again
|
||||
response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"detail": "Document was already masked"}
|
||||
assert models.LinkTrace.objects.filter(
|
||||
document=document, user=user, is_masked=True
|
||||
).exists()
|
||||
|
||||
|
||||
def test_api_document_mask_authenticated_post_already_masked_forbidden():
|
||||
"""POST should not create duplicate masks if already marked."""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach="restricted", masked_by=[user])
|
||||
# Try masking again
|
||||
response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/")
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {
|
||||
"detail": "You do not have permission to perform this action."
|
||||
}
|
||||
assert models.LinkTrace.objects.filter(document=document, user=user).exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reach, has_role",
|
||||
[
|
||||
["restricted", True],
|
||||
["authenticated", False],
|
||||
["authenticated", True],
|
||||
["public", False],
|
||||
["public", True],
|
||||
],
|
||||
)
|
||||
def test_api_document_mask_authenticated_post_unmasked_allowed(reach, has_role):
|
||||
"""POST should not create duplicate link trace if unmasked."""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach=reach)
|
||||
models.LinkTrace.objects.create(document=document, user=user, is_masked=False)
|
||||
if has_role:
|
||||
models.DocumentAccess.objects.create(document=document, user=user)
|
||||
|
||||
# Try masking again
|
||||
response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/")
|
||||
|
||||
assert response.status_code == 201
|
||||
assert response.json() == {"detail": "Document was masked"}
|
||||
assert models.LinkTrace.objects.filter(
|
||||
document=document, user=user, is_masked=True
|
||||
).exists()
|
||||
|
||||
|
||||
def test_api_document_mask_authenticated_post_unmasked_forbidden():
|
||||
"""POST should not create duplicate masks if unmasked."""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach="restricted")
|
||||
models.LinkTrace.objects.create(document=document, user=user, is_masked=False)
|
||||
# Try masking again
|
||||
response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/")
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {
|
||||
"detail": "You do not have permission to perform this action."
|
||||
}
|
||||
assert models.LinkTrace.objects.filter(
|
||||
document=document, user=user, is_masked=False
|
||||
).exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reach, has_role",
|
||||
[
|
||||
["restricted", True],
|
||||
["authenticated", False],
|
||||
["authenticated", True],
|
||||
["public", False],
|
||||
["public", True],
|
||||
],
|
||||
)
|
||||
def test_api_document_mask_authenticated_delete_allowed(reach, has_role):
|
||||
"""Authenticated users should be able to unmask a document using DELETE."""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach=reach, masked_by=[user])
|
||||
if has_role:
|
||||
models.DocumentAccess.objects.create(document=document, user=user)
|
||||
|
||||
# Unmask document
|
||||
response = client.delete(f"/api/v1.0/documents/{document.id!s}/mask/")
|
||||
|
||||
assert response.status_code == 204
|
||||
assert response.content == b"" # No body
|
||||
assert response.text == "" # Empty decoded text
|
||||
assert "Content-Type" not in response.headers # No Content-Type for 204
|
||||
|
||||
assert models.LinkTrace.objects.filter(
|
||||
document=document, user=user, is_masked=False
|
||||
).exists()
|
||||
|
||||
|
||||
def test_api_document_mask_authenticated_delete_forbidden():
|
||||
"""
|
||||
Authenticated users should not be allowed to unmask a document if
|
||||
they don't have access to it.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach="restricted", masked_by=[user])
|
||||
|
||||
# Unmask document
|
||||
response = client.delete(f"/api/v1.0/documents/{document.id!s}/mask/")
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {
|
||||
"detail": "You do not have permission to perform this action."
|
||||
}
|
||||
assert models.LinkTrace.objects.filter(
|
||||
document=document, user=user, is_masked=True
|
||||
).exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reach, has_role",
|
||||
[
|
||||
["restricted", True],
|
||||
["authenticated", False],
|
||||
["authenticated", True],
|
||||
["public", False],
|
||||
["public", True],
|
||||
],
|
||||
)
|
||||
def test_api_document_mask_authenticated_delete_not_masked_allowed(reach, has_role):
|
||||
"""DELETE should be idempotent if the document is not masked."""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach=reach)
|
||||
if has_role:
|
||||
models.DocumentAccess.objects.create(document=document, user=user)
|
||||
|
||||
# Try unmasking the document without a link trace
|
||||
response = client.delete(f"/api/v1.0/documents/{document.id!s}/mask/")
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {"detail": "User never accessed this document before."}
|
||||
assert not models.LinkTrace.objects.filter(document=document, user=user).exists()
|
||||
|
||||
models.LinkTrace.objects.create(document=document, user=user, is_masked=False)
|
||||
# Unmask document
|
||||
response = client.delete(f"/api/v1.0/documents/{document.id!s}/mask/")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"detail": "Document was already not masked"}
|
||||
assert models.LinkTrace.objects.filter(
|
||||
document=document, user=user, is_masked=False
|
||||
).exists()
|
||||
|
||||
|
||||
def test_api_document_mask_authenticated_delete_not_masked_forbidden():
|
||||
"""DELETE should be idempotent if the document is not masked."""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach="restricted")
|
||||
|
||||
# Try to unmask when no entry exists
|
||||
response = client.delete(f"/api/v1.0/documents/{document.id!s}/mask/")
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {
|
||||
"detail": "You do not have permission to perform this action."
|
||||
}
|
||||
assert (
|
||||
models.LinkTrace.objects.filter(document=document, user=user).exists() is False
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reach, has_role",
|
||||
[
|
||||
["restricted", True],
|
||||
["authenticated", False],
|
||||
["authenticated", True],
|
||||
["public", False],
|
||||
["public", True],
|
||||
],
|
||||
)
|
||||
def test_api_document_mask_authenticated_post_unmark_then_mark_again_allowed(
|
||||
reach, has_role
|
||||
):
|
||||
"""A user should be able to mask, unmask, and mask a document again."""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach=reach)
|
||||
if has_role:
|
||||
models.DocumentAccess.objects.create(document=document, user=user)
|
||||
models.LinkTrace.objects.create(document=document, user=user, is_masked=False)
|
||||
|
||||
url = f"/api/v1.0/documents/{document.id!s}/mask/"
|
||||
|
||||
# Mask document
|
||||
response = client.post(url)
|
||||
assert response.status_code == 201
|
||||
|
||||
# Unmask document
|
||||
response = client.delete(url)
|
||||
assert response.status_code == 204
|
||||
assert response.content == b"" # No body
|
||||
assert response.text == "" # Empty decoded text
|
||||
assert "Content-Type" not in response.headers # No Content-Type for 204
|
||||
|
||||
# Mask document again
|
||||
response = client.post(url)
|
||||
assert response.status_code == 201
|
||||
assert response.json() == {"detail": "Document was masked"}
|
||||
|
||||
assert models.LinkTrace.objects.filter(
|
||||
document=document, user=user, is_masked=True
|
||||
).exists()
|
||||
@@ -49,6 +49,7 @@ def test_api_documents_retrieve_anonymous_public_standalone():
|
||||
"public": ["reader", "editor"],
|
||||
"restricted": None,
|
||||
},
|
||||
"mask": False,
|
||||
"media_auth": True,
|
||||
"media_check": True,
|
||||
"move": False,
|
||||
@@ -121,6 +122,7 @@ def test_api_documents_retrieve_anonymous_public_parent():
|
||||
"link_select_options": models.LinkReachChoices.get_select_options(
|
||||
**links_definition
|
||||
),
|
||||
"mask": False,
|
||||
"media_auth": True,
|
||||
"media_check": True,
|
||||
"move": False,
|
||||
@@ -226,6 +228,7 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated(
|
||||
"public": ["reader", "editor"],
|
||||
"restricted": None,
|
||||
},
|
||||
"mask": True,
|
||||
"media_auth": True,
|
||||
"media_check": True,
|
||||
"move": False,
|
||||
@@ -305,6 +308,7 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
|
||||
"link_select_options": models.LinkReachChoices.get_select_options(
|
||||
**links_definition
|
||||
),
|
||||
"mask": True,
|
||||
"move": False,
|
||||
"media_auth": True,
|
||||
"media_check": True,
|
||||
@@ -498,6 +502,7 @@ def test_api_documents_retrieve_authenticated_related_parent():
|
||||
"link_select_options": models.LinkReachChoices.get_select_options(
|
||||
**link_definition
|
||||
),
|
||||
"mask": True,
|
||||
"media_auth": True,
|
||||
"media_check": True,
|
||||
"move": access.role in ["administrator", "owner"],
|
||||
|
||||
@@ -91,6 +91,7 @@ def test_api_documents_trashbin_format():
|
||||
"public": ["reader", "editor"],
|
||||
"restricted": None,
|
||||
},
|
||||
"mask": True,
|
||||
"media_auth": True,
|
||||
"media_check": True,
|
||||
"move": False, # Can't move a deleted document
|
||||
|
||||
@@ -165,6 +165,7 @@ def test_models_documents_get_abilities_forbidden(
|
||||
"duplicate": False,
|
||||
"favorite": False,
|
||||
"invite_owner": False,
|
||||
"mask": False,
|
||||
"media_auth": False,
|
||||
"media_check": False,
|
||||
"move": False,
|
||||
@@ -233,6 +234,7 @@ def test_models_documents_get_abilities_reader(
|
||||
"public": ["reader", "editor"],
|
||||
"restricted": None,
|
||||
},
|
||||
"mask": is_authenticated,
|
||||
"media_auth": True,
|
||||
"media_check": True,
|
||||
"move": False,
|
||||
@@ -297,6 +299,7 @@ def test_models_documents_get_abilities_editor(
|
||||
"public": ["reader", "editor"],
|
||||
"restricted": None,
|
||||
},
|
||||
"mask": is_authenticated,
|
||||
"media_auth": True,
|
||||
"media_check": True,
|
||||
"move": False,
|
||||
@@ -350,6 +353,7 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
|
||||
"public": ["reader", "editor"],
|
||||
"restricted": None,
|
||||
},
|
||||
"mask": True,
|
||||
"media_auth": True,
|
||||
"media_check": True,
|
||||
"move": True,
|
||||
@@ -400,6 +404,7 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries)
|
||||
"public": ["reader", "editor"],
|
||||
"restricted": None,
|
||||
},
|
||||
"mask": True,
|
||||
"media_auth": True,
|
||||
"media_check": True,
|
||||
"move": True,
|
||||
@@ -453,6 +458,7 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries):
|
||||
"public": ["reader", "editor"],
|
||||
"restricted": None,
|
||||
},
|
||||
"mask": True,
|
||||
"media_auth": True,
|
||||
"media_check": True,
|
||||
"move": False,
|
||||
@@ -513,6 +519,7 @@ def test_models_documents_get_abilities_reader_user(
|
||||
"public": ["reader", "editor"],
|
||||
"restricted": None,
|
||||
},
|
||||
"mask": True,
|
||||
"media_auth": True,
|
||||
"media_check": True,
|
||||
"move": False,
|
||||
@@ -571,6 +578,7 @@ def test_models_documents_get_abilities_preset_role(django_assert_num_queries):
|
||||
"public": ["reader", "editor"],
|
||||
"restricted": None,
|
||||
},
|
||||
"mask": True,
|
||||
"media_auth": True,
|
||||
"media_check": True,
|
||||
"move": False,
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-08 15:21+0000\n"
|
||||
"PO-Revision-Date: 2025-07-09 10:42\n"
|
||||
"POT-Creation-Date: 2025-07-24 20:42+0000\n"
|
||||
"PO-Revision-Date: 2025-07-31 12:38\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Breton\n"
|
||||
"Language: br_FR\n"
|
||||
@@ -43,6 +43,10 @@ msgid "Creator is me"
|
||||
msgstr "Me eo an aozer"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
msgid "Favorite"
|
||||
msgstr "Sinedoù"
|
||||
|
||||
@@ -66,7 +70,7 @@ msgstr "Doare korf"
|
||||
msgid "Format"
|
||||
msgstr "Stumm"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:942 core/api/viewsets.py:942
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "eilenn {title}"
|
||||
@@ -79,7 +83,7 @@ msgstr "Lenner"
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:43 core/choices.py:36
|
||||
#: core/choices.py:43
|
||||
msgid "Editor"
|
||||
msgstr ""
|
||||
msgstr "Embanner"
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
@@ -91,11 +95,11 @@ msgstr "Perc'henn"
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
msgstr "Strishaet"
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
msgstr "Anavezet"
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
@@ -111,11 +115,11 @@ msgstr "Bugel diwezhañ"
|
||||
|
||||
#: build/lib/core/enums.py:38 core/enums.py:38
|
||||
msgid "First sibling"
|
||||
msgstr ""
|
||||
msgstr "Breur pe c'hoar kentañ"
|
||||
|
||||
#: build/lib/core/enums.py:39 core/enums.py:39
|
||||
msgid "Last sibling"
|
||||
msgstr ""
|
||||
msgstr "Liamm diwezhañ"
|
||||
|
||||
#: build/lib/core/enums.py:40 core/enums.py:40
|
||||
msgid "Left"
|
||||
@@ -131,7 +135,7 @@ msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
msgstr "alc'hwez kentañ evit an enrollañ evel UIID"
|
||||
|
||||
#: build/lib/core/models.py:86 core/models.py:86
|
||||
msgid "created on"
|
||||
@@ -139,7 +143,7 @@ msgstr "krouet d'ar/al"
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
msgstr "deiziad hag eurvezh krouidigezh an enrolladenn"
|
||||
|
||||
#: build/lib/core/models.py:92 core/models.py:92
|
||||
msgid "updated on"
|
||||
@@ -147,23 +151,23 @@ msgstr "hizivaet d'ar/al"
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
msgstr "deiziad hag eurvezh m'eo bet hizivaet an enrolladenn"
|
||||
|
||||
#: build/lib/core/models.py:129 core/models.py:129
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr ""
|
||||
msgstr "N'hon eus kavet implijer ebet gant an isstrollad-mañ met ar postel a zo liammet ouzh un implijer enrollet."
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr ""
|
||||
msgstr "Ebarzhit un isstrollad mat. An talvoud-mañ a c'hall enderc'hel lizhiri, sifroù hag arouezioù @/./+/-/_/: hepken."
|
||||
|
||||
#: build/lib/core/models.py:148 core/models.py:148
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
msgstr "isstrollad"
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr ""
|
||||
msgstr "Rekis. 255 arouezenn pe nebeutoc'h. Lizhiri, sifroù hag arouezioù @/./+/-/_/: hepken."
|
||||
|
||||
#: build/lib/core/models.py:159 core/models.py:159
|
||||
msgid "full name"
|
||||
@@ -175,11 +179,11 @@ msgstr "anv berr"
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
msgstr "postel identelezh"
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
msgstr "postel ar merour"
|
||||
|
||||
#: build/lib/core/models.py:174 core/models.py:174
|
||||
msgid "language"
|
||||
@@ -187,11 +191,11 @@ msgstr "yezh"
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
msgstr "Ar yezh en deus c'hoant da welet an implijer an etrefas enni."
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
msgstr "Ar gwerzhid-eur en deus c'hoant da welet an implijer an eur drezañ."
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
msgid "device"
|
||||
@@ -199,23 +203,23 @@ msgstr "trevnad"
|
||||
|
||||
#: build/lib/core/models.py:188 core/models.py:188
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
msgstr "Pe vefe an implijer un aparailh pe un implijer gwirion."
|
||||
|
||||
#: build/lib/core/models.py:191 core/models.py:191
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
msgstr "statud ar skipailh"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
msgstr "Ma c'hall an implijer kevreañ ouzh al lec'hienn verañ-mañ."
|
||||
|
||||
#: build/lib/core/models.py:196 core/models.py:196
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
msgstr "oberiant"
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
msgstr "Ma rank bezañ tretet an implijer-mañ evel oberiant. Diziuzit an dra-mañ e-plas dilemel kontoù."
|
||||
|
||||
#: build/lib/core/models.py:211 core/models.py:211
|
||||
msgid "user"
|
||||
@@ -225,179 +229,179 @@ msgstr "implijer"
|
||||
msgid "users"
|
||||
msgstr "implijerien"
|
||||
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1281
|
||||
#: core/models.py:368 core/models.py:1281
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1283
|
||||
#: core/models.py:368 core/models.py:1283
|
||||
msgid "title"
|
||||
msgstr "titl"
|
||||
|
||||
#: build/lib/core/models.py:369 core/models.py:369
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
msgstr "bomm"
|
||||
|
||||
#: build/lib/core/models.py:418 core/models.py:418
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
msgstr "Restr"
|
||||
|
||||
#: build/lib/core/models.py:419 core/models.py:419
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
msgstr "Restroù"
|
||||
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:820 core/models.py:431
|
||||
#: core/models.py:820
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:821 core/models.py:431
|
||||
#: core/models.py:821
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
msgstr "Restr hep titl"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:856 core/models.py:856
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
msgstr "{name} en deus rannet ur restr ganeoc'h!"
|
||||
|
||||
#: build/lib/core/models.py:859 core/models.py:859
|
||||
#: build/lib/core/models.py:860 core/models.py:860
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
msgstr "{name} en deus pedet ac'hanoc'h gant ar rol \"{role}\" war ar restr da-heul:"
|
||||
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#: build/lib/core/models.py:866 core/models.py:866
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
msgstr "{name} en deus rannet ur restr ganeoc'h: {title}"
|
||||
|
||||
#: build/lib/core/models.py:964 core/models.py:964
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
msgstr "Roud liamm ar restr/an implijer"
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
msgstr "Roudoù liamm ar restr/an implijer"
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:973 core/models.py:973
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
msgstr "Ur roud liamm a zo dija evit an restr/an implijer."
|
||||
|
||||
#: build/lib/core/models.py:994 core/models.py:994
|
||||
#: build/lib/core/models.py:996 core/models.py:996
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
msgstr "Restr muiañ-karet"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:997 core/models.py:997
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
msgstr "Restroù muiañ-karet"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1003 core/models.py:1003
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
msgstr "Ar restr-mañ a zo ur restr muiañ karet gant an implijer-mañ."
|
||||
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
#: build/lib/core/models.py:1025 core/models.py:1025
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
msgstr "Liamm restr/implijer"
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1026 core/models.py:1026
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
msgstr "Liammoù restr/implijer"
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1032 core/models.py:1032
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
msgstr "An implijer-mañ a zo dija er restr-mañ."
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1038 core/models.py:1038
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
msgstr "Ar skipailh-mañ a zo dija en restr-mañ."
|
||||
|
||||
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
|
||||
#: core/models.py:1042 core/models.py:1367
|
||||
#: build/lib/core/models.py:1044 build/lib/core/models.py:1369
|
||||
#: core/models.py:1044 core/models.py:1369
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
msgstr "An implijer pe ar skipailh a rank bezañ termenet, ket an daou avat."
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
#: build/lib/core/models.py:1190 core/models.py:1190
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
msgstr "Goulenn tizhout ar restr"
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
#: build/lib/core/models.py:1191 core/models.py:1191
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
msgstr "Goulennoù tizhout ar restr"
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
msgstr "An implijer en deus goulennet tizhout ar restr-mañ."
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#: build/lib/core/models.py:1262 core/models.py:1262
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
msgstr "{name} en defe c'hoant da dizhout ar restr-mañ!"
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#: build/lib/core/models.py:1266 core/models.py:1266
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
msgstr "{name} en defe c'hoant da dizhout ar restr da-heul:"
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1282 core/models.py:1282
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
msgstr "{name} en defe c'hoant da dizhout ar restr: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
msgid "description"
|
||||
msgstr "deskrivadur"
|
||||
|
||||
#: build/lib/core/models.py:1285 core/models.py:1285
|
||||
msgid "code"
|
||||
msgstr "kod"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
msgid "css"
|
||||
msgstr "css"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
msgid "public"
|
||||
msgstr "publik"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1290 core/models.py:1290
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr ""
|
||||
msgstr "M'eo foran ar patrom-mañ hag implijus gant n'eus forzh piv."
|
||||
|
||||
#: build/lib/core/models.py:1294 core/models.py:1294
|
||||
#: build/lib/core/models.py:1296 core/models.py:1296
|
||||
msgid "Template"
|
||||
msgstr "Patrom"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1297 core/models.py:1297
|
||||
msgid "Templates"
|
||||
msgstr "Patromoù"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1350 core/models.py:1350
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
msgstr "Liamm patrom/implijer"
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1351 core/models.py:1351
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
msgstr "Liammoù patrom/implijer"
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1357 core/models.py:1357
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
msgstr "An implijer-mañ a zo dija er patrom-mañ."
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1363 core/models.py:1363
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
msgstr "Ar skipailh-mañ a zo dija er patrom-mañ."
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1440 core/models.py:1440
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
msgstr "postel"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1459 core/models.py:1459
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
msgstr "Pedadenn d'ur restr"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1460 core/models.py:1460
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
msgstr "Pedadennoù d'ur restr"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1480 core/models.py:1480
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
msgstr "Ar postel-mañ a zo liammet ouzh un implijer enskrivet."
|
||||
|
||||
#: core/templates/mail/html/template.html:162
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr ""
|
||||
msgstr "Logo ar postel"
|
||||
|
||||
#: core/templates/mail/html/template.html:209
|
||||
#: core/templates/mail/text/template.txt:10
|
||||
@@ -407,11 +411,11 @@ msgstr "Digeriñ"
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr ""
|
||||
msgstr " Docs, hoc'h ostilh nevez ret-holl evit aozañ, rannañ ha kenlabourat war ar restr e skipailh. "
|
||||
|
||||
#: core/templates/mail/html/template.html:233
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
msgstr ""
|
||||
msgstr " Kinniget gant %(brandname)s "
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-08 15:21+0000\n"
|
||||
"PO-Revision-Date: 2025-07-09 10:42\n"
|
||||
"POT-Creation-Date: 2025-07-24 20:42+0000\n"
|
||||
"PO-Revision-Date: 2025-07-31 12:38\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Language: de_DE\n"
|
||||
@@ -43,6 +43,10 @@ msgid "Creator is me"
|
||||
msgstr "Ersteller bin ich"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
msgid "Favorite"
|
||||
msgstr "Favorit"
|
||||
|
||||
@@ -66,7 +70,7 @@ msgstr "Typ"
|
||||
msgid "Format"
|
||||
msgstr "Format"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:942 core/api/viewsets.py:942
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "Kopie von {title}"
|
||||
@@ -225,8 +229,8 @@ msgstr "Benutzer"
|
||||
msgid "users"
|
||||
msgstr "Benutzer"
|
||||
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1281
|
||||
#: core/models.py:368 core/models.py:1281
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1283
|
||||
#: core/models.py:368 core/models.py:1283
|
||||
msgid "title"
|
||||
msgstr "Titel"
|
||||
|
||||
@@ -242,155 +246,155 @@ msgstr "Dokument"
|
||||
msgid "Documents"
|
||||
msgstr "Dokumente"
|
||||
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:820 core/models.py:431
|
||||
#: core/models.py:820
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:821 core/models.py:431
|
||||
#: core/models.py:821
|
||||
msgid "Untitled Document"
|
||||
msgstr "Unbenanntes Dokument"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:856 core/models.py:856
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} hat ein Dokument mit Ihnen geteilt!"
|
||||
|
||||
#: build/lib/core/models.py:859 core/models.py:859
|
||||
#: build/lib/core/models.py:860 core/models.py:860
|
||||
#, 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:"
|
||||
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#: build/lib/core/models.py:866 core/models.py:866
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} hat ein Dokument mit Ihnen geteilt: {title}"
|
||||
|
||||
#: build/lib/core/models.py:964 core/models.py:964
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Dokument/Benutzer Linkverfolgung"
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Dokument/Benutzer Linkverfolgung"
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:973 core/models.py:973
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Für dieses Dokument/ diesen Benutzer ist bereits eine Linkverfolgung vorhanden."
|
||||
|
||||
#: build/lib/core/models.py:994 core/models.py:994
|
||||
#: build/lib/core/models.py:996 core/models.py:996
|
||||
msgid "Document favorite"
|
||||
msgstr "Dokumentenfavorit"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:997 core/models.py:997
|
||||
msgid "Document favorites"
|
||||
msgstr "Dokumentfavoriten"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1003 core/models.py:1003
|
||||
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."
|
||||
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
#: build/lib/core/models.py:1025 core/models.py:1025
|
||||
msgid "Document/user relation"
|
||||
msgstr "Dokument/Benutzerbeziehung"
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1026 core/models.py:1026
|
||||
msgid "Document/user relations"
|
||||
msgstr "Dokument/Benutzerbeziehungen"
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1032 core/models.py:1032
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Dieser Benutzer befindet sich bereits in diesem Dokument."
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1038 core/models.py:1038
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Dieses Team befindet sich bereits in diesem Dokument."
|
||||
|
||||
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
|
||||
#: core/models.py:1042 core/models.py:1367
|
||||
#: build/lib/core/models.py:1044 build/lib/core/models.py:1369
|
||||
#: core/models.py:1044 core/models.py:1369
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "Benutzer oder Team müssen gesetzt werden, nicht beides."
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
#: build/lib/core/models.py:1190 core/models.py:1190
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
#: build/lib/core/models.py:1191 core/models.py:1191
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#: build/lib/core/models.py:1262 core/models.py:1262
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#: build/lib/core/models.py:1266 core/models.py:1266
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1282 core/models.py:1282
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
msgid "description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1285 core/models.py:1285
|
||||
msgid "code"
|
||||
msgstr "Code"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
msgid "css"
|
||||
msgstr "CSS"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
msgid "public"
|
||||
msgstr "öffentlich"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1290 core/models.py:1290
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr "Ob diese Vorlage für jedermann öffentlich ist."
|
||||
|
||||
#: build/lib/core/models.py:1294 core/models.py:1294
|
||||
#: build/lib/core/models.py:1296 core/models.py:1296
|
||||
msgid "Template"
|
||||
msgstr "Vorlage"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1297 core/models.py:1297
|
||||
msgid "Templates"
|
||||
msgstr "Vorlagen"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1350 core/models.py:1350
|
||||
msgid "Template/user relation"
|
||||
msgstr "Vorlage/Benutzer-Beziehung"
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1351 core/models.py:1351
|
||||
msgid "Template/user relations"
|
||||
msgstr "Vorlage/Benutzerbeziehungen"
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1357 core/models.py:1357
|
||||
msgid "This user is already in this template."
|
||||
msgstr "Dieser Benutzer ist bereits in dieser Vorlage."
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1363 core/models.py:1363
|
||||
msgid "This team is already in this template."
|
||||
msgstr "Dieses Team ist bereits in diesem Template."
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1440 core/models.py:1440
|
||||
msgid "email address"
|
||||
msgstr "E-Mail-Adresse"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1459 core/models.py:1459
|
||||
msgid "Document invitation"
|
||||
msgstr "Einladung zum Dokument"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1460 core/models.py:1460
|
||||
msgid "Document invitations"
|
||||
msgstr "Dokumenteinladungen"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1480 core/models.py:1480
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Diese E-Mail ist bereits einem registrierten Benutzer zugeordnet."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-08 15:21+0000\n"
|
||||
"PO-Revision-Date: 2025-07-09 10:42\n"
|
||||
"POT-Creation-Date: 2025-07-24 20:42+0000\n"
|
||||
"PO-Revision-Date: 2025-07-31 12:38\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: English\n"
|
||||
"Language: en_US\n"
|
||||
@@ -43,6 +43,10 @@ msgid "Creator is me"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
@@ -66,7 +70,7 @@ msgstr ""
|
||||
msgid "Format"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:942 core/api/viewsets.py:942
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
@@ -225,8 +229,8 @@ msgstr ""
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1281
|
||||
#: core/models.py:368 core/models.py:1281
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1283
|
||||
#: core/models.py:368 core/models.py:1283
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
@@ -242,155 +246,155 @@ msgstr ""
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:820 core/models.py:431
|
||||
#: core/models.py:820
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:821 core/models.py:431
|
||||
#: core/models.py:821
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:856 core/models.py:856
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:859 core/models.py:859
|
||||
#: build/lib/core/models.py:860 core/models.py:860
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#: build/lib/core/models.py:866 core/models.py:866
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:964 core/models.py:964
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:973 core/models.py:973
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:994 core/models.py:994
|
||||
#: build/lib/core/models.py:996 core/models.py:996
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:997 core/models.py:997
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1003 core/models.py:1003
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
#: build/lib/core/models.py:1025 core/models.py:1025
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1026 core/models.py:1026
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1032 core/models.py:1032
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1038 core/models.py:1038
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
|
||||
#: core/models.py:1042 core/models.py:1367
|
||||
#: build/lib/core/models.py:1044 build/lib/core/models.py:1369
|
||||
#: core/models.py:1044 core/models.py:1369
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
#: build/lib/core/models.py:1190 core/models.py:1190
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
#: build/lib/core/models.py:1191 core/models.py:1191
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#: build/lib/core/models.py:1262 core/models.py:1262
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#: build/lib/core/models.py:1266 core/models.py:1266
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1282 core/models.py:1282
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1285 core/models.py:1285
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
msgid "css"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
msgid "public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1290 core/models.py:1290
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1294 core/models.py:1294
|
||||
#: build/lib/core/models.py:1296 core/models.py:1296
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1297 core/models.py:1297
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1350 core/models.py:1350
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1351 core/models.py:1351
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1357 core/models.py:1357
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1363 core/models.py:1363
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1440 core/models.py:1440
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1459 core/models.py:1459
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1460 core/models.py:1460
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1480 core/models.py:1480
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-08 15:21+0000\n"
|
||||
"PO-Revision-Date: 2025-07-09 10:42\n"
|
||||
"POT-Creation-Date: 2025-07-24 20:42+0000\n"
|
||||
"PO-Revision-Date: 2025-07-31 12:38\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Language: es_ES\n"
|
||||
@@ -43,6 +43,10 @@ msgid "Creator is me"
|
||||
msgstr "Yo soy el creador"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
msgid "Favorite"
|
||||
msgstr "Favorito"
|
||||
|
||||
@@ -66,7 +70,7 @@ msgstr "Tipo de Cuerpo"
|
||||
msgid "Format"
|
||||
msgstr "Formato"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:942 core/api/viewsets.py:942
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "copia de {title}"
|
||||
@@ -225,8 +229,8 @@ msgstr "usuario"
|
||||
msgid "users"
|
||||
msgstr "usuarios"
|
||||
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1281
|
||||
#: core/models.py:368 core/models.py:1281
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1283
|
||||
#: core/models.py:368 core/models.py:1283
|
||||
msgid "title"
|
||||
msgstr "título"
|
||||
|
||||
@@ -242,155 +246,155 @@ msgstr "Documento"
|
||||
msgid "Documents"
|
||||
msgstr "Documentos"
|
||||
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:820 core/models.py:431
|
||||
#: core/models.py:820
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:821 core/models.py:431
|
||||
#: core/models.py:821
|
||||
msgid "Untitled Document"
|
||||
msgstr "Documento sin título"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:856 core/models.py:856
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "¡{name} ha compartido un documento contigo!"
|
||||
|
||||
#: build/lib/core/models.py:859 core/models.py:859
|
||||
#: build/lib/core/models.py:860 core/models.py:860
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "Te ha invitado {name} al siguiente documento con el rol \"{role}\" :"
|
||||
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#: build/lib/core/models.py:866 core/models.py:866
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} ha compartido un documento contigo: {title}"
|
||||
|
||||
#: build/lib/core/models.py:964 core/models.py:964
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Traza del enlace de documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Trazas del enlace de documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:973 core/models.py:973
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Ya existe una traza de enlace para este documento/usuario."
|
||||
|
||||
#: build/lib/core/models.py:994 core/models.py:994
|
||||
#: build/lib/core/models.py:996 core/models.py:996
|
||||
msgid "Document favorite"
|
||||
msgstr "Documento favorito"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:997 core/models.py:997
|
||||
msgid "Document favorites"
|
||||
msgstr "Documentos favoritos"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1003 core/models.py:1003
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Este documento ya ha sido marcado como favorito por el usuario."
|
||||
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
#: build/lib/core/models.py:1025 core/models.py:1025
|
||||
msgid "Document/user relation"
|
||||
msgstr "Relación documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1026 core/models.py:1026
|
||||
msgid "Document/user relations"
|
||||
msgstr "Relaciones documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1032 core/models.py:1032
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Este usuario ya forma parte del documento."
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1038 core/models.py:1038
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Este equipo ya forma parte del documento."
|
||||
|
||||
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
|
||||
#: core/models.py:1042 core/models.py:1367
|
||||
#: build/lib/core/models.py:1044 build/lib/core/models.py:1369
|
||||
#: core/models.py:1044 core/models.py:1369
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "Debe establecerse un usuario o un equipo, no ambos."
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
#: build/lib/core/models.py:1190 core/models.py:1190
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
#: build/lib/core/models.py:1191 core/models.py:1191
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "Este usuario ya ha solicitado acceso a este documento."
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#: build/lib/core/models.py:1262 core/models.py:1262
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "¡{name} desea acceder a un documento!"
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#: build/lib/core/models.py:1266 core/models.py:1266
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1282 core/models.py:1282
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
msgid "description"
|
||||
msgstr "descripción"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1285 core/models.py:1285
|
||||
msgid "code"
|
||||
msgstr "código"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
msgid "css"
|
||||
msgstr "css"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
msgid "public"
|
||||
msgstr "público"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1290 core/models.py:1290
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr "Si esta plantilla es pública para que cualquiera la utilice."
|
||||
|
||||
#: build/lib/core/models.py:1294 core/models.py:1294
|
||||
#: build/lib/core/models.py:1296 core/models.py:1296
|
||||
msgid "Template"
|
||||
msgstr "Plantilla"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1297 core/models.py:1297
|
||||
msgid "Templates"
|
||||
msgstr "Plantillas"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1350 core/models.py:1350
|
||||
msgid "Template/user relation"
|
||||
msgstr "Relación plantilla/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1351 core/models.py:1351
|
||||
msgid "Template/user relations"
|
||||
msgstr "Relaciones plantilla/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1357 core/models.py:1357
|
||||
msgid "This user is already in this template."
|
||||
msgstr "Este usuario ya forma parte de la plantilla."
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1363 core/models.py:1363
|
||||
msgid "This team is already in this template."
|
||||
msgstr "Este equipo ya se encuentra en esta plantilla."
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1440 core/models.py:1440
|
||||
msgid "email address"
|
||||
msgstr "dirección de correo electrónico"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1459 core/models.py:1459
|
||||
msgid "Document invitation"
|
||||
msgstr "Invitación al documento"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1460 core/models.py:1460
|
||||
msgid "Document invitations"
|
||||
msgstr "Invitaciones a documentos"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1480 core/models.py:1480
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Este correo electrónico está asociado a un usuario registrado."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-08 15:21+0000\n"
|
||||
"PO-Revision-Date: 2025-07-09 11:52\n"
|
||||
"POT-Creation-Date: 2025-07-24 20:42+0000\n"
|
||||
"PO-Revision-Date: 2025-07-31 12:38\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr_FR\n"
|
||||
@@ -43,6 +43,10 @@ msgid "Creator is me"
|
||||
msgstr "Je suis l'auteur"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
msgid "Favorite"
|
||||
msgstr "Favoris"
|
||||
|
||||
@@ -66,7 +70,7 @@ msgstr "Type de corps"
|
||||
msgid "Format"
|
||||
msgstr "Format"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:942 core/api/viewsets.py:942
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "copie de {title}"
|
||||
@@ -225,8 +229,8 @@ msgstr "utilisateur"
|
||||
msgid "users"
|
||||
msgstr "utilisateurs"
|
||||
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1281
|
||||
#: core/models.py:368 core/models.py:1281
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1283
|
||||
#: core/models.py:368 core/models.py:1283
|
||||
msgid "title"
|
||||
msgstr "titre"
|
||||
|
||||
@@ -242,155 +246,155 @@ msgstr "Document"
|
||||
msgid "Documents"
|
||||
msgstr "Documents"
|
||||
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:820 core/models.py:431
|
||||
#: core/models.py:820
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:821 core/models.py:431
|
||||
#: core/models.py:821
|
||||
msgid "Untitled Document"
|
||||
msgstr "Document sans titre"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:856 core/models.py:856
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} a partagé un document avec vous!"
|
||||
|
||||
#: build/lib/core/models.py:859 core/models.py:859
|
||||
#: build/lib/core/models.py:860 core/models.py:860
|
||||
#, 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 :"
|
||||
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#: build/lib/core/models.py:866 core/models.py:866
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} a partagé un document avec vous : {title}"
|
||||
|
||||
#: build/lib/core/models.py:964 core/models.py:964
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Trace du lien document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Traces du lien document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:973 core/models.py:973
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Une trace de lien existe déjà pour ce document/utilisateur."
|
||||
|
||||
#: build/lib/core/models.py:994 core/models.py:994
|
||||
#: build/lib/core/models.py:996 core/models.py:996
|
||||
msgid "Document favorite"
|
||||
msgstr "Document favori"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:997 core/models.py:997
|
||||
msgid "Document favorites"
|
||||
msgstr "Documents favoris"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1003 core/models.py:1003
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Ce document est déjà un favori de cet utilisateur."
|
||||
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
#: build/lib/core/models.py:1025 core/models.py:1025
|
||||
msgid "Document/user relation"
|
||||
msgstr "Relation document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1026 core/models.py:1026
|
||||
msgid "Document/user relations"
|
||||
msgstr "Relations document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1032 core/models.py:1032
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Cet utilisateur est déjà dans ce document."
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1038 core/models.py:1038
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Cette équipe est déjà dans ce document."
|
||||
|
||||
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
|
||||
#: core/models.py:1042 core/models.py:1367
|
||||
#: build/lib/core/models.py:1044 build/lib/core/models.py:1369
|
||||
#: core/models.py:1044 core/models.py:1369
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "L'utilisateur ou l'équipe doivent être définis, pas les deux."
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
#: build/lib/core/models.py:1190 core/models.py:1190
|
||||
msgid "Document ask for access"
|
||||
msgstr "Demande d'accès au document"
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
#: build/lib/core/models.py:1191 core/models.py:1191
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Demande d'accès au document"
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "Cet utilisateur a déjà demandé l'accès à ce document."
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#: build/lib/core/models.py:1262 core/models.py:1262
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "{name} souhaiterait accéder au document suivant !"
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#: build/lib/core/models.py:1266 core/models.py:1266
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "{name} souhaiterait accéder au document suivant :"
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "{name} demande l'accès au document : {title}"
|
||||
|
||||
#: build/lib/core/models.py:1282 core/models.py:1282
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
msgid "description"
|
||||
msgstr "description"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1285 core/models.py:1285
|
||||
msgid "code"
|
||||
msgstr "code"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
msgid "css"
|
||||
msgstr "CSS"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
msgid "public"
|
||||
msgstr "public"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1290 core/models.py:1290
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr "Si ce modèle est public, utilisable par n'importe qui."
|
||||
|
||||
#: build/lib/core/models.py:1294 core/models.py:1294
|
||||
#: build/lib/core/models.py:1296 core/models.py:1296
|
||||
msgid "Template"
|
||||
msgstr "Modèle"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1297 core/models.py:1297
|
||||
msgid "Templates"
|
||||
msgstr "Modèles"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1350 core/models.py:1350
|
||||
msgid "Template/user relation"
|
||||
msgstr "Relation modèle/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1351 core/models.py:1351
|
||||
msgid "Template/user relations"
|
||||
msgstr "Relations modèle/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1357 core/models.py:1357
|
||||
msgid "This user is already in this template."
|
||||
msgstr "Cet utilisateur est déjà dans ce modèle."
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1363 core/models.py:1363
|
||||
msgid "This team is already in this template."
|
||||
msgstr "Cette équipe est déjà modèle."
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1440 core/models.py:1440
|
||||
msgid "email address"
|
||||
msgstr "adresse e-mail"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1459 core/models.py:1459
|
||||
msgid "Document invitation"
|
||||
msgstr "Invitation à un document"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1460 core/models.py:1460
|
||||
msgid "Document invitations"
|
||||
msgstr "Invitations à un document"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1480 core/models.py:1480
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Cette adresse email est déjà associée à un utilisateur inscrit."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-08 15:21+0000\n"
|
||||
"PO-Revision-Date: 2025-07-09 10:42\n"
|
||||
"POT-Creation-Date: 2025-07-24 20:42+0000\n"
|
||||
"PO-Revision-Date: 2025-07-31 12:38\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Italian\n"
|
||||
"Language: it_IT\n"
|
||||
@@ -43,6 +43,10 @@ msgid "Creator is me"
|
||||
msgstr "Il creatore sono io"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
msgid "Favorite"
|
||||
msgstr "Preferiti"
|
||||
|
||||
@@ -66,7 +70,7 @@ msgstr ""
|
||||
msgid "Format"
|
||||
msgstr "Formato"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:942 core/api/viewsets.py:942
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "copia di {title}"
|
||||
@@ -225,8 +229,8 @@ msgstr "utente"
|
||||
msgid "users"
|
||||
msgstr "utenti"
|
||||
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1281
|
||||
#: core/models.py:368 core/models.py:1281
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1283
|
||||
#: core/models.py:368 core/models.py:1283
|
||||
msgid "title"
|
||||
msgstr "titolo"
|
||||
|
||||
@@ -242,155 +246,155 @@ msgstr "Documento"
|
||||
msgid "Documents"
|
||||
msgstr "Documenti"
|
||||
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:820 core/models.py:431
|
||||
#: core/models.py:820
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:821 core/models.py:431
|
||||
#: core/models.py:821
|
||||
msgid "Untitled Document"
|
||||
msgstr "Documento senza titolo"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:856 core/models.py:856
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} ha condiviso un documento con te!"
|
||||
|
||||
#: build/lib/core/models.py:859 core/models.py:859
|
||||
#: build/lib/core/models.py:860 core/models.py:860
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "{name} ti ha invitato con il ruolo \"{role}\" nel seguente documento:"
|
||||
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#: build/lib/core/models.py:866 core/models.py:866
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} ha condiviso un documento con te: {title}"
|
||||
|
||||
#: build/lib/core/models.py:964 core/models.py:964
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:973 core/models.py:973
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:994 core/models.py:994
|
||||
#: build/lib/core/models.py:996 core/models.py:996
|
||||
msgid "Document favorite"
|
||||
msgstr "Documento preferito"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:997 core/models.py:997
|
||||
msgid "Document favorites"
|
||||
msgstr "Documenti preferiti"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1003 core/models.py:1003
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
#: build/lib/core/models.py:1025 core/models.py:1025
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1026 core/models.py:1026
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1032 core/models.py:1032
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Questo utente è già presente in questo documento."
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1038 core/models.py:1038
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Questo team è già presente in questo documento."
|
||||
|
||||
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
|
||||
#: core/models.py:1042 core/models.py:1367
|
||||
#: build/lib/core/models.py:1044 build/lib/core/models.py:1369
|
||||
#: core/models.py:1044 core/models.py:1369
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
#: build/lib/core/models.py:1190 core/models.py:1190
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
#: build/lib/core/models.py:1191 core/models.py:1191
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#: build/lib/core/models.py:1262 core/models.py:1262
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#: build/lib/core/models.py:1266 core/models.py:1266
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1282 core/models.py:1282
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
msgid "description"
|
||||
msgstr "descrizione"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1285 core/models.py:1285
|
||||
msgid "code"
|
||||
msgstr "code"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
msgid "css"
|
||||
msgstr "css"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
msgid "public"
|
||||
msgstr "pubblico"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1290 core/models.py:1290
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr "Indica se questo modello è pubblico per chiunque."
|
||||
|
||||
#: build/lib/core/models.py:1294 core/models.py:1294
|
||||
#: build/lib/core/models.py:1296 core/models.py:1296
|
||||
msgid "Template"
|
||||
msgstr "Modello"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1297 core/models.py:1297
|
||||
msgid "Templates"
|
||||
msgstr "Modelli"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1350 core/models.py:1350
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1351 core/models.py:1351
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1357 core/models.py:1357
|
||||
msgid "This user is already in this template."
|
||||
msgstr "Questo utente è già in questo modello."
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1363 core/models.py:1363
|
||||
msgid "This team is already in this template."
|
||||
msgstr "Questo team è già in questo modello."
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1440 core/models.py:1440
|
||||
msgid "email address"
|
||||
msgstr "indirizzo e-mail"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1459 core/models.py:1459
|
||||
msgid "Document invitation"
|
||||
msgstr "Invito al documento"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1460 core/models.py:1460
|
||||
msgid "Document invitations"
|
||||
msgstr "Inviti al documento"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1480 core/models.py:1480
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Questa email è già associata a un utente registrato."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-08 15:21+0000\n"
|
||||
"PO-Revision-Date: 2025-07-09 10:42\n"
|
||||
"POT-Creation-Date: 2025-07-24 20:42+0000\n"
|
||||
"PO-Revision-Date: 2025-07-31 12:38\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"Language: nl_NL\n"
|
||||
@@ -43,6 +43,10 @@ msgid "Creator is me"
|
||||
msgstr "Ik ben Eigenaar"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
msgid "Favorite"
|
||||
msgstr "Favoriete"
|
||||
|
||||
@@ -66,7 +70,7 @@ msgstr "Text type"
|
||||
msgid "Format"
|
||||
msgstr "Formaat"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:942 core/api/viewsets.py:942
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "kopie van {title}"
|
||||
@@ -225,8 +229,8 @@ msgstr "gebruiker"
|
||||
msgid "users"
|
||||
msgstr "gebruikers"
|
||||
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1281
|
||||
#: core/models.py:368 core/models.py:1281
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1283
|
||||
#: core/models.py:368 core/models.py:1283
|
||||
msgid "title"
|
||||
msgstr "titel"
|
||||
|
||||
@@ -242,155 +246,155 @@ msgstr "Document"
|
||||
msgid "Documents"
|
||||
msgstr "Documenten"
|
||||
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:820 core/models.py:431
|
||||
#: core/models.py:820
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:821 core/models.py:431
|
||||
#: core/models.py:821
|
||||
msgid "Untitled Document"
|
||||
msgstr "Naamloos Document"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:856 core/models.py:856
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} heeft een document met gedeeld!"
|
||||
|
||||
#: build/lib/core/models.py:859 core/models.py:859
|
||||
#: build/lib/core/models.py:860 core/models.py:860
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "{name} heeft u uitgenodigd met de rol \"{role}\" op het volgende document:"
|
||||
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#: build/lib/core/models.py:866 core/models.py:866
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} heeft een document met u gedeeld: {title}"
|
||||
|
||||
#: build/lib/core/models.py:964 core/models.py:964
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Document/gebruiker url"
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Document/gebruiker url"
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:973 core/models.py:973
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Een url bestaat al voor dit document/deze gebruiker."
|
||||
|
||||
#: build/lib/core/models.py:994 core/models.py:994
|
||||
#: build/lib/core/models.py:996 core/models.py:996
|
||||
msgid "Document favorite"
|
||||
msgstr "Document favoriet"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:997 core/models.py:997
|
||||
msgid "Document favorites"
|
||||
msgstr "Document favorieten"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1003 core/models.py:1003
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Dit document is al in gebruik als favoriete door dezelfde gebruiker."
|
||||
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
#: build/lib/core/models.py:1025 core/models.py:1025
|
||||
msgid "Document/user relation"
|
||||
msgstr "Document/gebruiker relatie"
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1026 core/models.py:1026
|
||||
msgid "Document/user relations"
|
||||
msgstr "Document/gebruiker relaties"
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1032 core/models.py:1032
|
||||
msgid "This user is already in this document."
|
||||
msgstr "De gebruiker is al in dit document."
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1038 core/models.py:1038
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Het team is al in dit document."
|
||||
|
||||
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
|
||||
#: core/models.py:1042 core/models.py:1367
|
||||
#: build/lib/core/models.py:1044 build/lib/core/models.py:1369
|
||||
#: core/models.py:1044 core/models.py:1369
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "Een gebruiker of team moet gekozen worden, maar niet beide."
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
#: build/lib/core/models.py:1190 core/models.py:1190
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
#: build/lib/core/models.py:1191 core/models.py:1191
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#: build/lib/core/models.py:1262 core/models.py:1262
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#: build/lib/core/models.py:1266 core/models.py:1266
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1282 core/models.py:1282
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
msgid "description"
|
||||
msgstr "omschrijving"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1285 core/models.py:1285
|
||||
msgid "code"
|
||||
msgstr "code"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
msgid "css"
|
||||
msgstr "css"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
msgid "public"
|
||||
msgstr "publiek"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1290 core/models.py:1290
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr "Of dit template als publiek is en door iedereen te gebruiken is."
|
||||
|
||||
#: build/lib/core/models.py:1294 core/models.py:1294
|
||||
#: build/lib/core/models.py:1296 core/models.py:1296
|
||||
msgid "Template"
|
||||
msgstr "Template"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1297 core/models.py:1297
|
||||
msgid "Templates"
|
||||
msgstr "Templates"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1350 core/models.py:1350
|
||||
msgid "Template/user relation"
|
||||
msgstr "Template/gebruiker relatie"
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1351 core/models.py:1351
|
||||
msgid "Template/user relations"
|
||||
msgstr "Template/gebruiker relaties"
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1357 core/models.py:1357
|
||||
msgid "This user is already in this template."
|
||||
msgstr "De gebruiker bestaat al in dit template."
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1363 core/models.py:1363
|
||||
msgid "This team is already in this template."
|
||||
msgstr "Het team bestaat al in dit template."
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1440 core/models.py:1440
|
||||
msgid "email address"
|
||||
msgstr "email adres"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1459 core/models.py:1459
|
||||
msgid "Document invitation"
|
||||
msgstr "Document uitnodiging"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1460 core/models.py:1460
|
||||
msgid "Document invitations"
|
||||
msgstr "Document uitnodigingen"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1480 core/models.py:1480
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Deze email is al geassocieerd met een geregistreerde gebruiker."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-08 15:21+0000\n"
|
||||
"PO-Revision-Date: 2025-07-09 10:42\n"
|
||||
"POT-Creation-Date: 2025-07-24 20:42+0000\n"
|
||||
"PO-Revision-Date: 2025-07-31 12:38\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese\n"
|
||||
"Language: pt_PT\n"
|
||||
@@ -19,87 +19,91 @@ msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:37 core/admin.py:37
|
||||
msgid "Personal info"
|
||||
msgstr ""
|
||||
msgstr "Informações Pessoais"
|
||||
|
||||
#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50
|
||||
#: core/admin.py:138
|
||||
msgid "Permissions"
|
||||
msgstr ""
|
||||
msgstr "Permissões"
|
||||
|
||||
#: build/lib/core/admin.py:62 core/admin.py:62
|
||||
msgid "Important dates"
|
||||
msgstr ""
|
||||
msgstr "Datas importantes"
|
||||
|
||||
#: build/lib/core/admin.py:148 core/admin.py:148
|
||||
msgid "Tree structure"
|
||||
msgstr ""
|
||||
msgstr "Estrutura de árvore"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
msgstr "Título"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
msgid "Creator is me"
|
||||
msgstr ""
|
||||
msgstr "Eu sou o criador"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
msgid "Favorite"
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
msgid "Favorite"
|
||||
msgstr "Favorito"
|
||||
|
||||
#: build/lib/core/api/serializers.py:467 core/api/serializers.py:467
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr ""
|
||||
msgstr "Um novo documento foi criado em seu nome!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:471 core/api/serializers.py:471
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr ""
|
||||
msgstr "A propriedade de um novo documento foi concedida a você:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:608 core/api/serializers.py:608
|
||||
msgid "Body"
|
||||
msgstr ""
|
||||
msgstr "Corpo"
|
||||
|
||||
#: build/lib/core/api/serializers.py:611 core/api/serializers.py:611
|
||||
msgid "Body type"
|
||||
msgstr ""
|
||||
msgstr "Tipo de corpo"
|
||||
|
||||
#: build/lib/core/api/serializers.py:617 core/api/serializers.py:617
|
||||
msgid "Format"
|
||||
msgstr ""
|
||||
msgstr "Formato"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:942 core/api/viewsets.py:942
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
msgstr "cópia de {title}"
|
||||
|
||||
#: build/lib/core/choices.py:35 build/lib/core/choices.py:42 core/choices.py:35
|
||||
#: core/choices.py:42
|
||||
msgid "Reader"
|
||||
msgstr ""
|
||||
msgstr "Leitor"
|
||||
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:43 core/choices.py:36
|
||||
#: core/choices.py:43
|
||||
msgid "Editor"
|
||||
msgstr ""
|
||||
msgstr "Editor"
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
msgstr ""
|
||||
msgstr "Administrador"
|
||||
|
||||
#: build/lib/core/choices.py:45 core/choices.py:45
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
msgstr "Dono"
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
msgstr "Restrito"
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
msgstr "Autenticado"
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
msgstr "Público"
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
@@ -225,8 +229,8 @@ msgstr ""
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1281
|
||||
#: core/models.py:368 core/models.py:1281
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1283
|
||||
#: core/models.py:368 core/models.py:1283
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
@@ -242,155 +246,155 @@ msgstr ""
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:820 core/models.py:431
|
||||
#: core/models.py:820
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:821 core/models.py:431
|
||||
#: core/models.py:821
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:856 core/models.py:856
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:859 core/models.py:859
|
||||
#: build/lib/core/models.py:860 core/models.py:860
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#: build/lib/core/models.py:866 core/models.py:866
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:964 core/models.py:964
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:973 core/models.py:973
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:994 core/models.py:994
|
||||
#: build/lib/core/models.py:996 core/models.py:996
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:997 core/models.py:997
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1003 core/models.py:1003
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
#: build/lib/core/models.py:1025 core/models.py:1025
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1026 core/models.py:1026
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1032 core/models.py:1032
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1038 core/models.py:1038
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
|
||||
#: core/models.py:1042 core/models.py:1367
|
||||
#: build/lib/core/models.py:1044 build/lib/core/models.py:1369
|
||||
#: core/models.py:1044 core/models.py:1369
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
#: build/lib/core/models.py:1190 core/models.py:1190
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
#: build/lib/core/models.py:1191 core/models.py:1191
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#: build/lib/core/models.py:1262 core/models.py:1262
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#: build/lib/core/models.py:1266 core/models.py:1266
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1282 core/models.py:1282
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1285 core/models.py:1285
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
msgid "css"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
msgid "public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1290 core/models.py:1290
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1294 core/models.py:1294
|
||||
#: build/lib/core/models.py:1296 core/models.py:1296
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1297 core/models.py:1297
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1350 core/models.py:1350
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1351 core/models.py:1351
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1357 core/models.py:1357
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1363 core/models.py:1363
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1440 core/models.py:1440
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1459 core/models.py:1459
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1460 core/models.py:1460
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1480 core/models.py:1480
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-08 15:21+0000\n"
|
||||
"PO-Revision-Date: 2025-07-09 10:42\n"
|
||||
"POT-Creation-Date: 2025-07-24 20:42+0000\n"
|
||||
"PO-Revision-Date: 2025-07-31 12:38\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Slovenian\n"
|
||||
"Language: sl_SI\n"
|
||||
@@ -43,6 +43,10 @@ msgid "Creator is me"
|
||||
msgstr "Ustvaril sem jaz"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
msgid "Favorite"
|
||||
msgstr "Priljubljena"
|
||||
|
||||
@@ -66,7 +70,7 @@ msgstr "Vrsta telesa"
|
||||
msgid "Format"
|
||||
msgstr "Oblika"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:942 core/api/viewsets.py:942
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
@@ -225,8 +229,8 @@ msgstr "uporabnik"
|
||||
msgid "users"
|
||||
msgstr "uporabniki"
|
||||
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1281
|
||||
#: core/models.py:368 core/models.py:1281
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1283
|
||||
#: core/models.py:368 core/models.py:1283
|
||||
msgid "title"
|
||||
msgstr "naslov"
|
||||
|
||||
@@ -242,155 +246,155 @@ msgstr "Dokument"
|
||||
msgid "Documents"
|
||||
msgstr "Dokumenti"
|
||||
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:820 core/models.py:431
|
||||
#: core/models.py:820
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:821 core/models.py:431
|
||||
#: core/models.py:821
|
||||
msgid "Untitled Document"
|
||||
msgstr "Dokument brez naslova"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:856 core/models.py:856
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} je delil dokument z vami!"
|
||||
|
||||
#: build/lib/core/models.py:859 core/models.py:859
|
||||
#: build/lib/core/models.py:860 core/models.py:860
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "{name} vas je povabil z vlogo \"{role}\" na naslednjem dokumentu:"
|
||||
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#: build/lib/core/models.py:866 core/models.py:866
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} je delil dokument z vami: {title}"
|
||||
|
||||
#: build/lib/core/models.py:964 core/models.py:964
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Dokument/sled povezave uporabnika"
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Sledi povezav dokumenta/uporabnika"
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:973 core/models.py:973
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Za ta dokument/uporabnika že obstaja sled povezave."
|
||||
|
||||
#: build/lib/core/models.py:994 core/models.py:994
|
||||
#: build/lib/core/models.py:996 core/models.py:996
|
||||
msgid "Document favorite"
|
||||
msgstr "Priljubljeni dokument"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:997 core/models.py:997
|
||||
msgid "Document favorites"
|
||||
msgstr "Priljubljeni dokumenti"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1003 core/models.py:1003
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Ta dokument je že ciljno usmerjen s priljubljenim primerkom relacije za istega uporabnika."
|
||||
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
#: build/lib/core/models.py:1025 core/models.py:1025
|
||||
msgid "Document/user relation"
|
||||
msgstr "Odnos dokument/uporabnik"
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1026 core/models.py:1026
|
||||
msgid "Document/user relations"
|
||||
msgstr "Odnosi dokument/uporabnik"
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1032 core/models.py:1032
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Ta uporabnik je že v tem dokumentu."
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1038 core/models.py:1038
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Ta ekipa je že v tem dokumentu."
|
||||
|
||||
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
|
||||
#: core/models.py:1042 core/models.py:1367
|
||||
#: build/lib/core/models.py:1044 build/lib/core/models.py:1369
|
||||
#: core/models.py:1044 core/models.py:1369
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "Nastaviti je treba bodisi uporabnika ali ekipo, a ne obojega."
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
#: build/lib/core/models.py:1190 core/models.py:1190
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
#: build/lib/core/models.py:1191 core/models.py:1191
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#: build/lib/core/models.py:1262 core/models.py:1262
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#: build/lib/core/models.py:1266 core/models.py:1266
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1282 core/models.py:1282
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
msgid "description"
|
||||
msgstr "opis"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1285 core/models.py:1285
|
||||
msgid "code"
|
||||
msgstr "koda"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
msgid "css"
|
||||
msgstr "css"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
msgid "public"
|
||||
msgstr "javno"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1290 core/models.py:1290
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr "Ali je ta predloga javna za uporabo."
|
||||
|
||||
#: build/lib/core/models.py:1294 core/models.py:1294
|
||||
#: build/lib/core/models.py:1296 core/models.py:1296
|
||||
msgid "Template"
|
||||
msgstr "Predloga"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1297 core/models.py:1297
|
||||
msgid "Templates"
|
||||
msgstr "Predloge"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1350 core/models.py:1350
|
||||
msgid "Template/user relation"
|
||||
msgstr "Odnos predloga/uporabnik"
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1351 core/models.py:1351
|
||||
msgid "Template/user relations"
|
||||
msgstr "Odnosi med predlogo in uporabnikom"
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1357 core/models.py:1357
|
||||
msgid "This user is already in this template."
|
||||
msgstr "Ta uporabnik je že v tej predlogi."
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1363 core/models.py:1363
|
||||
msgid "This team is already in this template."
|
||||
msgstr "Ta ekipa je že v tej predlogi."
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1440 core/models.py:1440
|
||||
msgid "email address"
|
||||
msgstr "elektronski naslov"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1459 core/models.py:1459
|
||||
msgid "Document invitation"
|
||||
msgstr "Vabilo na dokument"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1460 core/models.py:1460
|
||||
msgid "Document invitations"
|
||||
msgstr "Vabila na dokument"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1480 core/models.py:1480
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Ta e-poštni naslov je že povezan z registriranim uporabnikom."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-08 15:21+0000\n"
|
||||
"PO-Revision-Date: 2025-07-09 10:42\n"
|
||||
"POT-Creation-Date: 2025-07-24 20:42+0000\n"
|
||||
"PO-Revision-Date: 2025-07-31 12:38\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Swedish\n"
|
||||
"Language: sv_SE\n"
|
||||
@@ -43,6 +43,10 @@ msgid "Creator is me"
|
||||
msgstr "Skaparen är jag"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
msgid "Favorite"
|
||||
msgstr "Favoriter"
|
||||
|
||||
@@ -66,7 +70,7 @@ msgstr ""
|
||||
msgid "Format"
|
||||
msgstr "Format"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:942 core/api/viewsets.py:942
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
@@ -225,8 +229,8 @@ msgstr ""
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1281
|
||||
#: core/models.py:368 core/models.py:1281
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1283
|
||||
#: core/models.py:368 core/models.py:1283
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
@@ -242,155 +246,155 @@ msgstr ""
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:820 core/models.py:431
|
||||
#: core/models.py:820
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:821 core/models.py:431
|
||||
#: core/models.py:821
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:856 core/models.py:856
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:859 core/models.py:859
|
||||
#: build/lib/core/models.py:860 core/models.py:860
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#: build/lib/core/models.py:866 core/models.py:866
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:964 core/models.py:964
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:973 core/models.py:973
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:994 core/models.py:994
|
||||
#: build/lib/core/models.py:996 core/models.py:996
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:997 core/models.py:997
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1003 core/models.py:1003
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
#: build/lib/core/models.py:1025 core/models.py:1025
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1026 core/models.py:1026
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1032 core/models.py:1032
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1038 core/models.py:1038
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
|
||||
#: core/models.py:1042 core/models.py:1367
|
||||
#: build/lib/core/models.py:1044 build/lib/core/models.py:1369
|
||||
#: core/models.py:1044 core/models.py:1369
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
#: build/lib/core/models.py:1190 core/models.py:1190
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
#: build/lib/core/models.py:1191 core/models.py:1191
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#: build/lib/core/models.py:1262 core/models.py:1262
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#: build/lib/core/models.py:1266 core/models.py:1266
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1282 core/models.py:1282
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1285 core/models.py:1285
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
msgid "css"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
msgid "public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1290 core/models.py:1290
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1294 core/models.py:1294
|
||||
#: build/lib/core/models.py:1296 core/models.py:1296
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1297 core/models.py:1297
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1350 core/models.py:1350
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1351 core/models.py:1351
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1357 core/models.py:1357
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1363 core/models.py:1363
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1440 core/models.py:1440
|
||||
msgid "email address"
|
||||
msgstr "e-postadress"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1459 core/models.py:1459
|
||||
msgid "Document invitation"
|
||||
msgstr "Bjud in dokument"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1460 core/models.py:1460
|
||||
msgid "Document invitations"
|
||||
msgstr "Inbjudningar dokument"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1480 core/models.py:1480
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Denna e-postadress är redan associerad med en registrerad användare."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-08 15:21+0000\n"
|
||||
"PO-Revision-Date: 2025-07-09 10:42\n"
|
||||
"POT-Creation-Date: 2025-07-24 20:42+0000\n"
|
||||
"PO-Revision-Date: 2025-07-31 12:38\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Turkish\n"
|
||||
"Language: tr_TR\n"
|
||||
@@ -43,6 +43,10 @@ msgid "Creator is me"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
@@ -66,7 +70,7 @@ msgstr ""
|
||||
msgid "Format"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:942 core/api/viewsets.py:942
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
@@ -225,8 +229,8 @@ msgstr ""
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1281
|
||||
#: core/models.py:368 core/models.py:1281
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1283
|
||||
#: core/models.py:368 core/models.py:1283
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
@@ -242,155 +246,155 @@ msgstr ""
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:820 core/models.py:431
|
||||
#: core/models.py:820
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:821 core/models.py:431
|
||||
#: core/models.py:821
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:856 core/models.py:856
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:859 core/models.py:859
|
||||
#: build/lib/core/models.py:860 core/models.py:860
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#: build/lib/core/models.py:866 core/models.py:866
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:964 core/models.py:964
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:973 core/models.py:973
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:994 core/models.py:994
|
||||
#: build/lib/core/models.py:996 core/models.py:996
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:997 core/models.py:997
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1003 core/models.py:1003
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
#: build/lib/core/models.py:1025 core/models.py:1025
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1026 core/models.py:1026
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1032 core/models.py:1032
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1038 core/models.py:1038
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
|
||||
#: core/models.py:1042 core/models.py:1367
|
||||
#: build/lib/core/models.py:1044 build/lib/core/models.py:1369
|
||||
#: core/models.py:1044 core/models.py:1369
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
#: build/lib/core/models.py:1190 core/models.py:1190
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
#: build/lib/core/models.py:1191 core/models.py:1191
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#: build/lib/core/models.py:1262 core/models.py:1262
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#: build/lib/core/models.py:1266 core/models.py:1266
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1282 core/models.py:1282
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1285 core/models.py:1285
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
msgid "css"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
msgid "public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1290 core/models.py:1290
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1294 core/models.py:1294
|
||||
#: build/lib/core/models.py:1296 core/models.py:1296
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1297 core/models.py:1297
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1350 core/models.py:1350
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1351 core/models.py:1351
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1357 core/models.py:1357
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1363 core/models.py:1363
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1440 core/models.py:1440
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1459 core/models.py:1459
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1460 core/models.py:1460
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1480 core/models.py:1480
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-08 15:21+0000\n"
|
||||
"PO-Revision-Date: 2025-07-09 10:42\n"
|
||||
"POT-Creation-Date: 2025-07-24 20:42+0000\n"
|
||||
"PO-Revision-Date: 2025-07-31 12:38\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"Language: zh_CN\n"
|
||||
@@ -43,6 +43,10 @@ msgid "Creator is me"
|
||||
msgstr "创建者是我"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
msgid "Favorite"
|
||||
msgstr "收藏"
|
||||
|
||||
@@ -66,7 +70,7 @@ msgstr "正文类型"
|
||||
msgid "Format"
|
||||
msgstr "格式"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:942 core/api/viewsets.py:942
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "{title} 的副本"
|
||||
@@ -225,8 +229,8 @@ msgstr "用户"
|
||||
msgid "users"
|
||||
msgstr "个用户"
|
||||
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1281
|
||||
#: core/models.py:368 core/models.py:1281
|
||||
#: build/lib/core/models.py:368 build/lib/core/models.py:1283
|
||||
#: core/models.py:368 core/models.py:1283
|
||||
msgid "title"
|
||||
msgstr "标题"
|
||||
|
||||
@@ -242,155 +246,155 @@ msgstr "文档"
|
||||
msgid "Documents"
|
||||
msgstr "个文档"
|
||||
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:820 core/models.py:431
|
||||
#: core/models.py:820
|
||||
#: build/lib/core/models.py:431 build/lib/core/models.py:821 core/models.py:431
|
||||
#: core/models.py:821
|
||||
msgid "Untitled Document"
|
||||
msgstr "未命名文档"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:856 core/models.py:856
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} 与您共享了一个文档!"
|
||||
|
||||
#: build/lib/core/models.py:859 core/models.py:859
|
||||
#: build/lib/core/models.py:860 core/models.py:860
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "{name} 邀请您以“{role}”角色访问以下文档:"
|
||||
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#: build/lib/core/models.py:866 core/models.py:866
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} 与您共享了一个文档:{title}"
|
||||
|
||||
#: build/lib/core/models.py:964 core/models.py:964
|
||||
#: build/lib/core/models.py:966 core/models.py:966
|
||||
msgid "Document/user link trace"
|
||||
msgstr "文档/用户链接跟踪"
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:967 core/models.py:967
|
||||
msgid "Document/user link traces"
|
||||
msgstr "个文档/用户链接跟踪"
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:973 core/models.py:973
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "此文档/用户的链接跟踪已存在。"
|
||||
|
||||
#: build/lib/core/models.py:994 core/models.py:994
|
||||
#: build/lib/core/models.py:996 core/models.py:996
|
||||
msgid "Document favorite"
|
||||
msgstr "文档收藏"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:997 core/models.py:997
|
||||
msgid "Document favorites"
|
||||
msgstr "文档收藏夹"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1003 core/models.py:1003
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "该文档已被同一用户的收藏关系实例关联。"
|
||||
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
#: build/lib/core/models.py:1025 core/models.py:1025
|
||||
msgid "Document/user relation"
|
||||
msgstr "文档/用户关系"
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1026 core/models.py:1026
|
||||
msgid "Document/user relations"
|
||||
msgstr "文档/用户关系集"
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1032 core/models.py:1032
|
||||
msgid "This user is already in this document."
|
||||
msgstr "该用户已在此文档中。"
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1038 core/models.py:1038
|
||||
msgid "This team is already in this document."
|
||||
msgstr "该团队已在此文档中。"
|
||||
|
||||
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
|
||||
#: core/models.py:1042 core/models.py:1367
|
||||
#: build/lib/core/models.py:1044 build/lib/core/models.py:1369
|
||||
#: core/models.py:1044 core/models.py:1369
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "必须设置用户或团队之一,不能同时设置两者。"
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
#: build/lib/core/models.py:1190 core/models.py:1190
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
#: build/lib/core/models.py:1191 core/models.py:1191
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#: build/lib/core/models.py:1262 core/models.py:1262
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#: build/lib/core/models.py:1266 core/models.py:1266
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1282 core/models.py:1282
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
msgid "description"
|
||||
msgstr "说明"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1285 core/models.py:1285
|
||||
msgid "code"
|
||||
msgstr "代码"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
msgid "css"
|
||||
msgstr "css"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
msgid "public"
|
||||
msgstr "公开"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1290 core/models.py:1290
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr "该模板是否公开供任何人使用。"
|
||||
|
||||
#: build/lib/core/models.py:1294 core/models.py:1294
|
||||
#: build/lib/core/models.py:1296 core/models.py:1296
|
||||
msgid "Template"
|
||||
msgstr "模板"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1297 core/models.py:1297
|
||||
msgid "Templates"
|
||||
msgstr "模板"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1350 core/models.py:1350
|
||||
msgid "Template/user relation"
|
||||
msgstr "模板/用户关系"
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1351 core/models.py:1351
|
||||
msgid "Template/user relations"
|
||||
msgstr "模板/用户关系集"
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1357 core/models.py:1357
|
||||
msgid "This user is already in this template."
|
||||
msgstr "该用户已在此模板中。"
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1363 core/models.py:1363
|
||||
msgid "This team is already in this template."
|
||||
msgstr "该团队已在此模板中。"
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1440 core/models.py:1440
|
||||
msgid "email address"
|
||||
msgstr "电子邮件地址"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1459 core/models.py:1459
|
||||
msgid "Document invitation"
|
||||
msgstr "文档邀请"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1460 core/models.py:1460
|
||||
msgid "Document invitations"
|
||||
msgstr "文档邀请"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1480 core/models.py:1480
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "此电子邮件已经与现有注册用户关联。"
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "impress"
|
||||
version = "3.4.1"
|
||||
version = "3.5.0"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -64,10 +64,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
"Bug Tracker" = "https://github.com/numerique-gouv/impress/issues/new"
|
||||
"Changelog" = "https://github.com/numerique-gouv/impress/blob/main/CHANGELOG.md"
|
||||
"Homepage" = "https://github.com/numerique-gouv/impress"
|
||||
"Repository" = "https://github.com/numerique-gouv/impress"
|
||||
"Bug Tracker" = "https://github.com/suitenumerique/docs/issues/new"
|
||||
"Changelog" = "https://github.com/suitenumerique/docs/blob/main/CHANGELOG.md"
|
||||
"Homepage" = "https://github.com/suitenumerique/docs"
|
||||
"Repository" = "https://github.com/suitenumerique/docs"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
|
||||
@@ -11,12 +11,14 @@ COPY ./src/frontend/package.json ./package.json
|
||||
COPY ./src/frontend/yarn.lock ./yarn.lock
|
||||
COPY ./src/frontend/apps/impress/package.json ./apps/impress/package.json
|
||||
COPY ./src/frontend/packages/eslint-config-impress/package.json ./packages/eslint-config-impress/package.json
|
||||
COPY ./src/frontend/packages/common/package.json ./packages/common/package.json
|
||||
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
COPY .dockerignore ./.dockerignore
|
||||
COPY ./src/frontend/.prettierrc.js ./.prettierrc.js
|
||||
COPY ./src/frontend/packages/eslint-config-impress ./packages/eslint-config-impress
|
||||
COPY ./src/frontend/packages/common ./packages/common
|
||||
COPY ./src/frontend/apps/impress ./apps/impress
|
||||
|
||||
### ---- Front-end builder image ----
|
||||
|
||||
@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(
|
||||
page.locator('header').first().locator('h2').getByText('Docs'),
|
||||
page.locator('header').first().locator('h1').getByText('Docs'),
|
||||
).toBeVisible();
|
||||
await page.goto('unknown-page404');
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FullConfig, FullProject, chromium, expect } from '@playwright/test';
|
||||
|
||||
import { keyCloakSignIn } from './common';
|
||||
import { keyCloakSignIn } from './utils-common';
|
||||
|
||||
const saveStorageState = async (
|
||||
browserConfig: FullProject<unknown, unknown>,
|
||||
@@ -23,7 +23,7 @@ const saveStorageState = async (
|
||||
page.locator('header').first().getByRole('button', {
|
||||
name: 'Logout',
|
||||
}),
|
||||
).toBeVisible();
|
||||
).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await page.context().storageState({
|
||||
path: storageState as string,
|
||||
|
||||
@@ -2,7 +2,7 @@ import path from 'path';
|
||||
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { CONFIG, createDoc, overrideConfig } from './common';
|
||||
import { CONFIG, createDoc, overrideConfig } from './utils-common';
|
||||
|
||||
test.describe('Config', () => {
|
||||
test('it checks that sentry is trying to init from config endpoint', async ({
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
keyCloakSignIn,
|
||||
randomName,
|
||||
verifyDocName,
|
||||
} from './common';
|
||||
} from './utils-common';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
@@ -22,13 +22,57 @@ test.describe('Doc Create', () => {
|
||||
);
|
||||
|
||||
const header = page.locator('header').first();
|
||||
await header.locator('h2').getByText('Docs').click();
|
||||
await header.locator('h1').getByText('Docs').click();
|
||||
|
||||
const docsGrid = page.getByTestId('docs-grid');
|
||||
await expect(docsGrid).toBeVisible();
|
||||
await expect(page.getByTestId('grid-loader')).toBeHidden();
|
||||
await expect(docsGrid.getByText(docTitle)).toBeVisible();
|
||||
});
|
||||
|
||||
test('it creates a sub doc from slash menu editor', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
const [title] = await createDoc(page, 'my-new-slash-doc', browserName, 1);
|
||||
|
||||
await verifyDocName(page, title);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('/');
|
||||
await page
|
||||
.getByText('New sub-doc', {
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
|
||||
const input = page.getByRole('textbox', { name: 'doc title input' });
|
||||
await expect(input).toHaveText('');
|
||||
await expect(
|
||||
page.locator('.c__tree-view--row-content').getByText('Untitled document'),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('it creates a sub doc from interlinking dropdown', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
const [title] = await createDoc(page, 'my-new-slash-doc', browserName, 1);
|
||||
|
||||
await verifyDocName(page, title);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('/');
|
||||
await page.getByText('Link a doc').first().click();
|
||||
await page
|
||||
.locator('.quick-search-container')
|
||||
.getByText('New sub-doc')
|
||||
.click();
|
||||
|
||||
const input = page.getByRole('textbox', { name: 'doc title input' });
|
||||
await expect(input).toHaveText('');
|
||||
await expect(
|
||||
page.locator('.c__tree-view--row-content').getByText('Untitled document'),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Doc Create: Not logged', () => {
|
||||
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
mockedDocument,
|
||||
overrideConfig,
|
||||
verifyDocName,
|
||||
} from './common';
|
||||
import { createRootSubPage } from './sub-pages-utils';
|
||||
} from './utils-common';
|
||||
import { createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
@@ -706,4 +706,75 @@ test.describe('Doc Editor', () => {
|
||||
'pink',
|
||||
);
|
||||
});
|
||||
|
||||
test('it checks interlink feature', async ({ page, browserName }) => {
|
||||
const [randomDoc] = await createDoc(page, 'doc-interlink', browserName, 1);
|
||||
|
||||
await verifyDocName(page, randomDoc);
|
||||
|
||||
const { name: docChild1 } = await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'doc-interlink-child-1',
|
||||
);
|
||||
|
||||
await verifyDocName(page, docChild1);
|
||||
|
||||
const { name: docChild2 } = await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'doc-interlink-child-2',
|
||||
);
|
||||
|
||||
await verifyDocName(page, docChild2);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('/');
|
||||
await page.getByText('Link a doc').first().click();
|
||||
|
||||
const input = page.locator(
|
||||
"span[data-inline-content-type='interlinkingSearchInline'] input",
|
||||
);
|
||||
const searchContainer = page.locator('.quick-search-container');
|
||||
|
||||
await input.fill('doc-interlink');
|
||||
|
||||
await expect(searchContainer.getByText(randomDoc)).toBeVisible();
|
||||
await expect(searchContainer.getByText(docChild1)).toBeVisible();
|
||||
await expect(searchContainer.getByText(docChild2)).toBeVisible();
|
||||
|
||||
await input.pressSequentially('-child');
|
||||
|
||||
await expect(searchContainer.getByText(docChild1)).toBeVisible();
|
||||
await expect(searchContainer.getByText(docChild2)).toBeVisible();
|
||||
await expect(searchContainer.getByText(randomDoc)).toBeHidden();
|
||||
|
||||
// use keydown to select the second result
|
||||
await page.keyboard.press('ArrowDown');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
const interlink = page.getByRole('link', {
|
||||
name: 'child-2',
|
||||
});
|
||||
|
||||
await expect(interlink).toBeVisible();
|
||||
await interlink.click();
|
||||
|
||||
await verifyDocName(page, docChild2);
|
||||
});
|
||||
|
||||
test('it checks interlink shortcut @', async ({ page, browserName }) => {
|
||||
const [randomDoc] = await createDoc(page, 'doc-interlink', browserName, 1);
|
||||
|
||||
await verifyDocName(page, randomDoc);
|
||||
|
||||
const editor = page.locator('.bn-block-outer').last();
|
||||
await editor.click();
|
||||
await page.keyboard.press('@');
|
||||
|
||||
await expect(
|
||||
page.locator(
|
||||
"span[data-inline-content-type='interlinkingSearchInline'] input",
|
||||
),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,14 @@ import { expect, test } from '@playwright/test';
|
||||
import cs from 'convert-stream';
|
||||
import pdf from 'pdf-parse';
|
||||
|
||||
import { createDoc, verifyDocName } from './common';
|
||||
import {
|
||||
TestLanguage,
|
||||
createDoc,
|
||||
randomName,
|
||||
verifyDocName,
|
||||
waitForLanguageSwitch,
|
||||
} from './utils-common';
|
||||
import { createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
@@ -346,4 +353,204 @@ test.describe('Doc Export', () => {
|
||||
const pdfData = await pdf(pdfBuffer);
|
||||
expect(pdfData.text).toContain('Hello World');
|
||||
});
|
||||
|
||||
test('it exports the doc with multi columns', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
const [randomDoc] = 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');
|
||||
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'download',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', {
|
||||
name: 'Download',
|
||||
exact: true,
|
||||
}),
|
||||
).toBeVisible();
|
||||
|
||||
const downloadPromise = page.waitForEvent('download', (download) => {
|
||||
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
|
||||
});
|
||||
|
||||
void page
|
||||
.getByRole('button', {
|
||||
name: 'Download',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
|
||||
|
||||
const pdfBuffer = await cs.toBuffer(await download.createReadStream());
|
||||
const pdfData = await pdf(pdfBuffer);
|
||||
expect(pdfData.text).toContain('Column 1');
|
||||
expect(pdfData.text).toContain('Column 2');
|
||||
expect(pdfData.text).toContain('Column 3');
|
||||
});
|
||||
|
||||
test('it injects the correct language attribute into PDF export', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await waitForLanguageSwitch(page, TestLanguage.French);
|
||||
|
||||
// Wait for the page to be ready after language switch
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
const header = page.locator('header').first();
|
||||
await header.locator('h1').getByText('Docs').click();
|
||||
|
||||
const randomDocFrench = randomName(
|
||||
'doc-language-export-french',
|
||||
browserName,
|
||||
1,
|
||||
)[0];
|
||||
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Nouveau doc',
|
||||
})
|
||||
.click();
|
||||
|
||||
await page.waitForURL('**/docs/**', {
|
||||
timeout: 10000,
|
||||
waitUntil: 'domcontentloaded',
|
||||
});
|
||||
|
||||
const input = page.getByLabel('doc title input');
|
||||
await expect(input).toBeVisible();
|
||||
await expect(input).toHaveText('');
|
||||
await input.click();
|
||||
await input.fill(randomDocFrench);
|
||||
await input.blur();
|
||||
|
||||
const editor = page.locator('.ProseMirror.bn-editor');
|
||||
await editor.click();
|
||||
await editor.fill('Contenu de test pour export en français');
|
||||
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'download',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
|
||||
const downloadPromise = page.waitForEvent('download', (download) => {
|
||||
return download.suggestedFilename().includes(`${randomDocFrench}.pdf`);
|
||||
});
|
||||
|
||||
void page
|
||||
.getByRole('button', {
|
||||
name: 'Télécharger',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toBe(`${randomDocFrench}.pdf`);
|
||||
|
||||
const pdfBuffer = await cs.toBuffer(await download.createReadStream());
|
||||
const pdfString = pdfBuffer.toString('latin1');
|
||||
|
||||
expect(pdfString).toContain('/Lang (fr)');
|
||||
});
|
||||
|
||||
test('it exports the doc with interlinking', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
const [randomDoc] = await createDoc(
|
||||
page,
|
||||
'export-interlinking',
|
||||
browserName,
|
||||
1,
|
||||
);
|
||||
|
||||
await verifyDocName(page, randomDoc);
|
||||
|
||||
const { name: docChild } = await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'export-interlink-child',
|
||||
);
|
||||
|
||||
await verifyDocName(page, docChild);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('/');
|
||||
await page.getByText('Link a doc').first().click();
|
||||
|
||||
await page
|
||||
.locator(
|
||||
"span[data-inline-content-type='interlinkingSearchInline'] input",
|
||||
)
|
||||
.fill('interlink-child');
|
||||
|
||||
await page
|
||||
.locator('.quick-search-container')
|
||||
.getByText('interlink-child')
|
||||
.click();
|
||||
|
||||
const interlink = page.getByRole('link', {
|
||||
name: 'interlink-child',
|
||||
});
|
||||
|
||||
await expect(interlink).toBeVisible();
|
||||
|
||||
const downloadPromise = page.waitForEvent('download', (download) => {
|
||||
return download.suggestedFilename().includes(`${docChild}.pdf`);
|
||||
});
|
||||
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'download',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
|
||||
void page
|
||||
.getByRole('button', {
|
||||
name: 'Download',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toBe(`${docChild}.pdf`);
|
||||
|
||||
const pdfBuffer = await cs.toBuffer(await download.createReadStream());
|
||||
const pdfData = await pdf(pdfBuffer);
|
||||
|
||||
expect(pdfData.text).toContain('interlink-child'); // This is the pdf text
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, mockedListDocs } from './common';
|
||||
import { createDoc, mockedListDocs } from './utils-common';
|
||||
import { createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.describe('Doc grid dnd', () => {
|
||||
test('it creates a doc', async ({ page, browserName }) => {
|
||||
await page.goto('/');
|
||||
const header = page.locator('header').first();
|
||||
await createDoc(page, 'Draggable doc', browserName, 1);
|
||||
await header.locator('h2').getByText('Docs').click();
|
||||
await header.locator('h1').getByText('Docs').click();
|
||||
await createDoc(page, 'Droppable doc', browserName, 1);
|
||||
await header.locator('h2').getByText('Docs').click();
|
||||
await header.locator('h1').getByText('Docs').click();
|
||||
|
||||
const response = await page.waitForResponse(
|
||||
(response) =>
|
||||
@@ -165,6 +166,40 @@ test.describe('Doc grid dnd', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Doc grid dnd mobile', () => {
|
||||
test.use({ viewport: { width: 500, height: 1200 } });
|
||||
|
||||
test('DND is deactivated on mobile', async ({ page, browserName }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const docsGrid = page.getByTestId('docs-grid');
|
||||
await expect(page.getByTestId('docs-grid')).toBeVisible();
|
||||
await expect(page.getByTestId('grid-loader')).toBeHidden();
|
||||
|
||||
await expect(docsGrid.getByRole('row').first()).toBeVisible();
|
||||
await expect(docsGrid.locator('.--docs--grid-droppable')).toHaveCount(0);
|
||||
|
||||
await createDoc(page, 'Draggable doc mobile', browserName, 1, true);
|
||||
|
||||
await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'Draggable doc mobile child',
|
||||
true,
|
||||
);
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'Open the header menu' })
|
||||
.getByText('menu')
|
||||
.click();
|
||||
|
||||
await expect(page.locator('.--docs-sub-page-item').first()).toHaveAttribute(
|
||||
'draggable',
|
||||
'false',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 'can-drop-and-drag',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, getGridRow } from './common';
|
||||
import { createDoc, getGridRow } from './utils-common';
|
||||
|
||||
type SmallDoc = {
|
||||
id: string;
|
||||
@@ -119,7 +119,7 @@ test.describe('Document grid item options', () => {
|
||||
await page.getByText('push_pin').click();
|
||||
|
||||
// Check is pinned
|
||||
await expect(row.getByLabel('Pin document icon')).toBeVisible();
|
||||
await expect(row.locator('[data-testid^="doc-pinned-"]')).toBeVisible();
|
||||
const leftPanelFavorites = page.getByTestId('left-panel-favorites');
|
||||
await expect(leftPanelFavorites.getByText(docTitle)).toBeVisible();
|
||||
|
||||
@@ -128,7 +128,7 @@ test.describe('Document grid item options', () => {
|
||||
await page.getByText('Unpin').click();
|
||||
|
||||
// Check is unpinned
|
||||
await expect(row.getByLabel('Pin document icon')).toBeHidden();
|
||||
await expect(row.locator('[data-testid^="doc-pinned-"]')).toBeHidden();
|
||||
await expect(leftPanelFavorites.getByText(docTitle)).toBeHidden();
|
||||
});
|
||||
|
||||
@@ -227,18 +227,18 @@ test.describe('Documents filters', () => {
|
||||
|
||||
// Initial state
|
||||
await expect(allDocs).toBeVisible();
|
||||
await expect(allDocs).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(allDocs).toHaveAttribute('aria-current', 'page');
|
||||
|
||||
await expect(myDocs).toBeVisible();
|
||||
await expect(myDocs).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
|
||||
await expect(myDocs).toHaveAttribute('aria-selected', 'false');
|
||||
await expect(myDocs).not.toHaveAttribute('aria-current');
|
||||
|
||||
await expect(sharedWithMe).toBeVisible();
|
||||
await expect(sharedWithMe).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(0, 0, 0, 0)',
|
||||
);
|
||||
await expect(sharedWithMe).toHaveAttribute('aria-selected', 'false');
|
||||
await expect(sharedWithMe).not.toHaveAttribute('aria-current');
|
||||
|
||||
await allDocs.click();
|
||||
|
||||
|
||||
@@ -4,12 +4,11 @@ import {
|
||||
createDoc,
|
||||
getGridRow,
|
||||
goToGridDoc,
|
||||
mockedAccesses,
|
||||
mockedDocument,
|
||||
mockedInvitations,
|
||||
verifyDocName,
|
||||
} from './common';
|
||||
import { createRootSubPage } from './sub-pages-utils';
|
||||
} from './utils-common';
|
||||
import { mockedAccesses, mockedInvitations } from './utils-share';
|
||||
import { createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
@@ -410,7 +409,7 @@ test.describe('Doc Header', () => {
|
||||
const row = await getGridRow(page, docTitle);
|
||||
|
||||
// Check is pinned
|
||||
await expect(row.getByLabel('Pin document icon')).toBeVisible();
|
||||
await expect(row.locator('[data-testid^="doc-pinned-"]')).toBeVisible();
|
||||
const leftPanelFavorites = page.getByTestId('left-panel-favorites');
|
||||
await expect(leftPanelFavorites.getByText(docTitle)).toBeVisible();
|
||||
|
||||
@@ -425,7 +424,7 @@ test.describe('Doc Header', () => {
|
||||
await page.goto('/');
|
||||
|
||||
// Check is unpinned
|
||||
await expect(row.getByLabel('Pin document icon')).toBeHidden();
|
||||
await expect(row.locator('[data-testid^="doc-pinned-"]')).toBeHidden();
|
||||
await expect(leftPanelFavorites.getByText(docTitle)).toBeHidden();
|
||||
});
|
||||
|
||||
@@ -443,9 +442,10 @@ test.describe('Doc Header', () => {
|
||||
page.getByText('Document duplicated successfully!'),
|
||||
).toBeVisible();
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const duplicateTitle = 'Copy of ' + docTitle;
|
||||
await verifyDocName(page, duplicateTitle);
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const row = await getGridRow(page, duplicateTitle);
|
||||
|
||||
@@ -471,16 +471,24 @@ test.describe('Doc Header', () => {
|
||||
await editor.click();
|
||||
await editor.fill('Hello Duplicated World');
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
const duplicateTitle = 'Copy of ' + childTitle;
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
|
||||
const child = docTree
|
||||
.getByRole('treeitem')
|
||||
.locator('.--docs-sub-page-item')
|
||||
.filter({
|
||||
hasText: childTitle,
|
||||
});
|
||||
await child.hover();
|
||||
await child.getByText(`more_horiz`).click();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
||||
await expect(
|
||||
page.getByText('Document duplicated successfully!'),
|
||||
).toBeVisible();
|
||||
|
||||
const duplicateDuplicateTitle = 'Copy of ' + childTitle;
|
||||
await verifyDocName(page, duplicateTitle);
|
||||
|
||||
await expect(
|
||||
page.getByTestId('doc-tree').getByText(duplicateDuplicateTitle),
|
||||
page.getByTestId('doc-tree').getByText(duplicateTitle),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, verifyDocName } from './common';
|
||||
import { updateShareLink } from './share-utils';
|
||||
import { createRootSubPage } from './sub-pages-utils';
|
||||
import { createDoc, verifyDocName } from './utils-common';
|
||||
import { updateShareLink } from './utils-share';
|
||||
import { createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.describe('Inherited share accesses', () => {
|
||||
test('it checks inherited accesses', async ({ page, browserName }) => {
|
||||
@@ -31,9 +31,7 @@ test.describe('Inherited share accesses', () => {
|
||||
|
||||
await verifyDocName(page, parentTitle);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Inherited share link', () => {
|
||||
test('it checks if the link is inherited', async ({ page, browserName }) => {
|
||||
await page.goto('/');
|
||||
// Create root doc
|
||||
@@ -47,12 +45,50 @@ test.describe('Inherited share link', () => {
|
||||
// Create sub page
|
||||
await createRootSubPage(page, browserName, 'sub-page');
|
||||
|
||||
// // verify share link is restricted and reader
|
||||
// Verify share link is like the parent document
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
// await expect(page.getByText('Inherited share')).toBeVisible();
|
||||
const docVisibilityCard = page.getByLabel('Doc visibility card');
|
||||
await expect(docVisibilityCard).toBeVisible();
|
||||
|
||||
await expect(docVisibilityCard.getByText('Connected')).toBeVisible();
|
||||
await expect(docVisibilityCard.getByText('Reading')).toBeVisible();
|
||||
|
||||
// Verify inherited link
|
||||
await docVisibilityCard.getByText('Connected').click();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Private' }),
|
||||
).toBeDisabled();
|
||||
|
||||
// Update child link
|
||||
await page.getByRole('menuitem', { name: 'Public' }).click();
|
||||
|
||||
await docVisibilityCard.getByText('Reading').click();
|
||||
await page.getByRole('menuitem', { name: 'Editing' }).click();
|
||||
|
||||
await expect(docVisibilityCard.getByText('Connected')).toBeHidden();
|
||||
await expect(docVisibilityCard.getByText('Reading')).toBeHidden();
|
||||
await expect(
|
||||
docVisibilityCard.getByText('Public', {
|
||||
exact: true,
|
||||
}),
|
||||
).toBeVisible();
|
||||
await expect(docVisibilityCard.getByText('Editing')).toBeVisible();
|
||||
await expect(
|
||||
docVisibilityCard.getByText(
|
||||
'The link sharing rules differ from the parent document',
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
// Restore inherited link
|
||||
await page.getByRole('button', { name: 'Restore' }).click();
|
||||
|
||||
await expect(docVisibilityCard.getByText('Connected')).toBeVisible();
|
||||
await expect(docVisibilityCard.getByText('Reading')).toBeVisible();
|
||||
await expect(docVisibilityCard.getByText('Public')).toBeHidden();
|
||||
await expect(docVisibilityCard.getByText('Editing')).toBeHidden();
|
||||
await expect(
|
||||
docVisibilityCard.getByText(
|
||||
'The link sharing rules differ from the parent document',
|
||||
),
|
||||
).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
keyCloakSignIn,
|
||||
randomName,
|
||||
verifyDocName,
|
||||
} from './common';
|
||||
import { createRootSubPage } from './sub-pages-utils';
|
||||
} from './utils-common';
|
||||
import { createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.describe('Document create member', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -244,9 +244,7 @@ test.describe('Document create member: Multiple login', () => {
|
||||
|
||||
await keyCloakSignIn(page, otherBrowser!);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs Logo Docs' }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByTestId('header-logo-link')).toBeVisible();
|
||||
|
||||
await page.goto(urlDoc);
|
||||
|
||||
@@ -271,9 +269,7 @@ test.describe('Document create member: Multiple login', () => {
|
||||
await page.goto('/');
|
||||
await keyCloakSignIn(page, browserName);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs Logo Docs' }),
|
||||
).toBeVisible({
|
||||
await expect(page.getByTestId('header-logo-link')).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
@@ -334,9 +330,7 @@ test.describe('Document create member: Multiple login', () => {
|
||||
|
||||
await keyCloakSignIn(page, otherBrowser!);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs Logo Docs' }),
|
||||
).toBeVisible({
|
||||
await expect(page.getByTestId('header-logo-link')).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { addNewMember, createDoc, goToGridDoc, verifyDocName } from './common';
|
||||
import { createDoc, goToGridDoc, verifyDocName } from './utils-common';
|
||||
import { addNewMember } from './utils-share';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
@@ -148,7 +149,11 @@ test.describe('Document list members', () => {
|
||||
`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.`,
|
||||
);
|
||||
await expect(soloOwner).toBeVisible();
|
||||
await list.click();
|
||||
|
||||
await list.click({
|
||||
// eslint-disable-next-line playwright/no-force-option
|
||||
force: true, // Force click to close the dropdown
|
||||
});
|
||||
const newUserEmail = await addNewMember(page, 0, 'Owner');
|
||||
const newUser = list.getByTestId(`doc-share-member-row-${newUserEmail}`);
|
||||
const newUserRoles = newUser.getByLabel('doc-role-dropdown');
|
||||
@@ -157,10 +162,16 @@ test.describe('Document list members', () => {
|
||||
|
||||
await currentUserRole.click();
|
||||
await expect(soloOwner).toBeHidden();
|
||||
await list.click();
|
||||
await list.click({
|
||||
// eslint-disable-next-line playwright/no-force-option
|
||||
force: true, // Force click to close the dropdown
|
||||
});
|
||||
|
||||
await newUserRoles.click();
|
||||
await list.click();
|
||||
await list.click({
|
||||
// eslint-disable-next-line playwright/no-force-option
|
||||
force: true, // Force click to close the dropdown
|
||||
});
|
||||
|
||||
await currentUserRole.click();
|
||||
await page.getByLabel('Administrator').click();
|
||||
|
||||
@@ -8,25 +8,19 @@ import {
|
||||
keyCloakSignIn,
|
||||
mockedDocument,
|
||||
verifyDocName,
|
||||
} from './common';
|
||||
} from './utils-common';
|
||||
import { createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.describe('Doc Routing', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('Check the presence of the meta tag noindex', async ({ page }) => {
|
||||
const buttonCreateHomepage = page.getByRole('button', {
|
||||
name: 'New doc',
|
||||
});
|
||||
|
||||
await expect(buttonCreateHomepage).toBeVisible();
|
||||
await buttonCreateHomepage.click();
|
||||
await expect(
|
||||
page.getByRole('button', {
|
||||
name: 'Share',
|
||||
}),
|
||||
).toBeVisible();
|
||||
test('Check the presence of the meta tag noindex', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await createDoc(page, 'doc-routing-test', browserName, 1);
|
||||
const metaDescription = page.locator('meta[name="robots"]');
|
||||
await expect(metaDescription).toHaveAttribute('content', 'noindex');
|
||||
});
|
||||
@@ -60,16 +54,20 @@ test.describe('Doc Routing', () => {
|
||||
});
|
||||
|
||||
test('checks 401 on docs/[id] page', async ({ page, browserName }) => {
|
||||
const [docTitle] = await createDoc(page, '401-doc', browserName, 1);
|
||||
const [docTitle] = await createDoc(page, '401-doc-parent', browserName, 1);
|
||||
await verifyDocName(page, docTitle);
|
||||
|
||||
await createRootSubPage(page, browserName, '401-doc-child');
|
||||
|
||||
await page.locator('.ProseMirror.bn-editor').fill('Hello World');
|
||||
|
||||
const responsePromise = page.route(
|
||||
/.*\/link-configuration\/$|users\/me\/$/,
|
||||
/.*\/documents\/.*\/$|users\/me\/$/,
|
||||
async (route) => {
|
||||
const request = route.request();
|
||||
|
||||
if (
|
||||
request.method().includes('PUT') ||
|
||||
request.method().includes('PATCH') ||
|
||||
request.method().includes('GET')
|
||||
) {
|
||||
await route.fulfill({
|
||||
@@ -84,11 +82,7 @@ test.describe('Doc Routing', () => {
|
||||
},
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const selectVisibility = page.getByLabel('Visibility', { exact: true });
|
||||
await selectVisibility.click();
|
||||
await page.getByLabel('Connected').click();
|
||||
await page.getByRole('link', { name: '401-doc-parent' }).click();
|
||||
|
||||
await responsePromise;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, randomName, verifyDocName } from './common';
|
||||
import { createDoc, verifyDocName } from './utils-common';
|
||||
import { createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
@@ -25,10 +26,7 @@ test.describe('Document search', () => {
|
||||
);
|
||||
await verifyDocName(page, doc2Title);
|
||||
await page.goto('/');
|
||||
await page
|
||||
.getByTestId('left-panel-desktop')
|
||||
.getByRole('button', { name: 'search' })
|
||||
.click();
|
||||
await page.getByTestId('search-docs-button').click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('img', { name: 'No active search' }),
|
||||
@@ -98,39 +96,44 @@ test.describe('Document search', () => {
|
||||
).toBeHidden();
|
||||
});
|
||||
|
||||
test("it checks we don't see filters in search modal", async ({ page }) => {
|
||||
const searchButton = page
|
||||
.getByTestId('left-panel-desktop')
|
||||
.getByRole('button', { name: 'search' });
|
||||
|
||||
await expect(searchButton).toBeVisible();
|
||||
await page.getByRole('button', { name: 'search', exact: true }).click();
|
||||
await expect(
|
||||
page.getByRole('combobox', { name: 'Quick search input' }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByTestId('doc-search-filters')).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Sub page search', () => {
|
||||
test('it check the presence of filters in search modal', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await page.goto('/');
|
||||
const [doc1Title] = await createDoc(
|
||||
page,
|
||||
'My sub page search',
|
||||
browserName,
|
||||
1,
|
||||
);
|
||||
await verifyDocName(page, doc1Title);
|
||||
const searchButton = page
|
||||
.getByTestId('left-panel-desktop')
|
||||
.getByRole('button', { name: 'search' });
|
||||
await searchButton.click();
|
||||
// Doc grid filters are not visible
|
||||
const searchButton = page.getByTestId('search-docs-button');
|
||||
|
||||
const filters = page.getByTestId('doc-search-filters');
|
||||
|
||||
await searchButton.click();
|
||||
await expect(
|
||||
page.getByRole('combobox', { name: 'Quick search input' }),
|
||||
).toBeVisible();
|
||||
await expect(filters).toBeHidden();
|
||||
|
||||
await page.getByRole('button', { name: 'close' }).click();
|
||||
|
||||
// Create a doc without children for the moment
|
||||
// and check that filters are not visible
|
||||
const [doc1Title] = await createDoc(page, 'My page search', browserName, 1);
|
||||
await verifyDocName(page, doc1Title);
|
||||
|
||||
await searchButton.click();
|
||||
await expect(
|
||||
page.getByRole('combobox', { name: 'Quick search input' }),
|
||||
).toBeVisible();
|
||||
await expect(filters).toBeHidden();
|
||||
|
||||
await page.getByRole('button', { name: 'close' }).click();
|
||||
|
||||
// Create a sub page
|
||||
// and check that filters are visible
|
||||
await createRootSubPage(page, browserName, 'My sub page search');
|
||||
|
||||
await searchButton.click();
|
||||
|
||||
await expect(filters).toBeVisible();
|
||||
|
||||
await filters.click();
|
||||
await filters.getByRole('button', { name: 'Current doc' }).click();
|
||||
await expect(
|
||||
@@ -139,43 +142,67 @@ test.describe('Sub page search', () => {
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Current doc' }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('menuitem', { name: 'Current doc' }).click();
|
||||
await page.getByRole('menuitem', { name: 'All docs' }).click();
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Reset' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('it searches sub pages', async ({ page, browserName }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const [doc1Title] = await createDoc(
|
||||
// First doc
|
||||
const [firstDocTitle] = await createDoc(
|
||||
page,
|
||||
'My sub page search',
|
||||
'My first sub page search',
|
||||
browserName,
|
||||
1,
|
||||
);
|
||||
await verifyDocName(page, doc1Title);
|
||||
await page.getByRole('button', { name: 'New doc' }).click();
|
||||
await verifyDocName(page, '');
|
||||
await page.getByRole('textbox', { name: 'doc title input' }).click();
|
||||
await page
|
||||
.getByRole('textbox', { name: 'doc title input' })
|
||||
.press('ControlOrMeta+a');
|
||||
const [randomDocName] = randomName('doc-sub-page', browserName, 1);
|
||||
await page
|
||||
.getByRole('textbox', { name: 'doc title input' })
|
||||
.fill(randomDocName);
|
||||
const searchButton = page
|
||||
.getByTestId('left-panel-desktop')
|
||||
.getByRole('button', { name: 'search' });
|
||||
await verifyDocName(page, firstDocTitle);
|
||||
|
||||
// Create a new doc - for the moment without children
|
||||
const [secondDocTitle] = await createDoc(
|
||||
page,
|
||||
'My second sub page search',
|
||||
browserName,
|
||||
1,
|
||||
);
|
||||
|
||||
const searchButton = page.getByTestId('search-docs-button');
|
||||
|
||||
await searchButton.click();
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Current doc' }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('combobox', { name: 'Quick search input' }).click();
|
||||
await page
|
||||
.getByRole('combobox', { name: 'Quick search input' })
|
||||
.fill('sub');
|
||||
await expect(page.getByLabel(randomDocName)).toBeVisible();
|
||||
.fill('sub page search');
|
||||
|
||||
// Expect to find the first doc
|
||||
await expect(
|
||||
page.getByRole('presentation').getByLabel(firstDocTitle),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('presentation').getByLabel(secondDocTitle),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'close' }).click();
|
||||
|
||||
// Create a sub page
|
||||
const { name: secondChildDocTitle } = await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'second - Child doc',
|
||||
);
|
||||
await searchButton.click();
|
||||
await page
|
||||
.getByRole('combobox', { name: 'Quick search input' })
|
||||
.fill('second');
|
||||
|
||||
// Now there is a sub page - expect to have the focus on the current doc
|
||||
await expect(
|
||||
page.getByRole('presentation').getByLabel(secondDocTitle),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('presentation').getByLabel(secondChildDocTitle),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('presentation').getByLabel(firstDocTitle),
|
||||
).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, verifyDocName } from './common';
|
||||
import { createDoc, verifyDocName } from './utils-common';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addNewMember,
|
||||
createDoc,
|
||||
expectLoginPage,
|
||||
keyCloakSignIn,
|
||||
randomName,
|
||||
updateDocTitle,
|
||||
verifyDocName,
|
||||
} from './common';
|
||||
import { clickOnAddRootSubPage, createRootSubPage } from './sub-pages-utils';
|
||||
} from './utils-common';
|
||||
import { addNewMember } from './utils-share';
|
||||
import { clickOnAddRootSubPage, createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.describe('Doc Tree', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -25,7 +25,7 @@ test.describe('Doc Tree', () => {
|
||||
1,
|
||||
);
|
||||
await verifyDocName(page, titleParent);
|
||||
const addButton = page.getByRole('button', { name: 'New doc' });
|
||||
const addButton = page.getByTestId('new-doc-button');
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
|
||||
await expect(addButton).toBeVisible();
|
||||
@@ -63,7 +63,7 @@ test.describe('Doc Tree', () => {
|
||||
|
||||
test('check the reorder of sub pages', async ({ page, browserName }) => {
|
||||
await createDoc(page, 'doc-tree-content', browserName, 1);
|
||||
const addButton = page.getByRole('button', { name: 'New doc' });
|
||||
const addButton = page.getByTestId('new-doc-button');
|
||||
await expect(addButton).toBeVisible();
|
||||
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
@@ -201,7 +201,7 @@ test.describe('Doc Tree', () => {
|
||||
).not.toHaveText(docChild);
|
||||
|
||||
const header = page.locator('header').first();
|
||||
await header.locator('h2').getByText('Docs').click();
|
||||
await header.locator('h1').getByText('Docs').click();
|
||||
await expect(page.getByText(docChild)).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
goToGridDoc,
|
||||
mockedDocument,
|
||||
verifyDocName,
|
||||
} from './common';
|
||||
} from './utils-common';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
expectLoginPage,
|
||||
keyCloakSignIn,
|
||||
verifyDocName,
|
||||
} from './common';
|
||||
import { createRootSubPage } from './sub-pages-utils';
|
||||
} from './utils-common';
|
||||
import { createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.describe('Doc Visibility', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -122,9 +122,7 @@ test.describe('Doc Visibility: Restricted', () => {
|
||||
|
||||
await keyCloakSignIn(page, otherBrowser!);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs Logo Docs' }),
|
||||
).toBeVisible({
|
||||
await expect(page.getByTestId('header-logo-link')).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
@@ -178,9 +176,7 @@ test.describe('Doc Visibility: Restricted', () => {
|
||||
|
||||
await keyCloakSignIn(page, otherBrowser!);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs Logo Docs' }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByTestId('header-logo-link')).toBeVisible();
|
||||
|
||||
await page.goto(urlDoc);
|
||||
|
||||
@@ -246,8 +242,8 @@ test.describe('Doc Visibility: Public', () => {
|
||||
cardContainer.getByText('Public document', { exact: true }),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(page.getByRole('button', { name: 'search' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'New doc' })).toBeVisible();
|
||||
await expect(page.getByTestId('search-docs-button')).toBeVisible();
|
||||
await expect(page.getByTestId('new-doc-button')).toBeVisible();
|
||||
|
||||
const urlDoc = page.url();
|
||||
|
||||
@@ -262,8 +258,8 @@ test.describe('Doc Visibility: Public', () => {
|
||||
await page.goto(urlDoc);
|
||||
|
||||
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.getByTestId('search-docs-button')).toBeHidden();
|
||||
await expect(page.getByTestId('new-doc-button')).toBeHidden();
|
||||
await expect(page.getByRole('button', { name: 'Share' })).toBeVisible();
|
||||
const card = page.getByLabel('It is the card information');
|
||||
await expect(card).toBeVisible();
|
||||
@@ -455,9 +451,7 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
const otherBrowser = BROWSERS.find((b) => b !== browserName);
|
||||
await keyCloakSignIn(page, otherBrowser!);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs Logo Docs' }),
|
||||
).toBeVisible({
|
||||
await expect(page.getByTestId('header-logo-link')).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
@@ -545,9 +539,7 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
const otherBrowser = BROWSERS.find((b) => b !== browserName);
|
||||
await keyCloakSignIn(page, otherBrowser!);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs Logo Docs' }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByTestId('header-logo-link')).toBeVisible();
|
||||
|
||||
await page.goto(urlDoc);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { overrideConfig } from './common';
|
||||
import { overrideConfig } from './utils-common';
|
||||
|
||||
test.describe('Footer', () => {
|
||||
test.use({ storageState: { cookies: [], origins: [] } });
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { expectLoginPage, keyCloakSignIn, overrideConfig } from './common';
|
||||
import {
|
||||
expectLoginPage,
|
||||
keyCloakSignIn,
|
||||
overrideConfig,
|
||||
} from './utils-common';
|
||||
|
||||
test.describe('Header', () => {
|
||||
test('checks all the elements are visible', async ({ page }) => {
|
||||
@@ -8,8 +12,8 @@ test.describe('Header', () => {
|
||||
|
||||
const header = page.locator('header').first();
|
||||
|
||||
await expect(header.getByLabel('Docs Logo')).toBeVisible();
|
||||
await expect(header.locator('h2').getByText('Docs')).toHaveCSS(
|
||||
await expect(header.getByTestId('header-logo-link')).toBeVisible();
|
||||
await expect(header.locator('h1').getByText('Docs')).toHaveCSS(
|
||||
'font-family',
|
||||
/Roboto/i,
|
||||
);
|
||||
@@ -33,8 +37,8 @@ test.describe('Header', () => {
|
||||
|
||||
const header = page.locator('header').first();
|
||||
|
||||
await expect(header.getByLabel('Docs Logo')).toBeVisible();
|
||||
await expect(header.locator('h2').getByText('Docs')).toHaveCSS(
|
||||
await expect(header.getByTestId('header-icon-docs')).toBeVisible();
|
||||
await expect(header.locator('h1').getByText('Docs')).toHaveCSS(
|
||||
'font-family',
|
||||
/Marianne/i,
|
||||
);
|
||||
@@ -102,7 +106,7 @@ test.describe('Header mobile', () => {
|
||||
const header = page.locator('header').first();
|
||||
|
||||
await expect(header.getByLabel('Open the header menu')).toBeVisible();
|
||||
await expect(header.getByRole('link', { name: 'Docs Logo' })).toBeVisible();
|
||||
await expect(header.getByTestId('header-icon-docs')).toBeVisible();
|
||||
await expect(
|
||||
header.getByRole('button', {
|
||||
name: 'Les services de La Suite numérique',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { overrideConfig } from './common';
|
||||
import { overrideConfig } from './utils-common';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/docs/');
|
||||
@@ -15,10 +15,13 @@ test.describe('Home page', () => {
|
||||
const header = page.locator('header').first();
|
||||
const footer = page.locator('footer').first();
|
||||
await expect(header).toBeVisible();
|
||||
await expect(
|
||||
header.getByRole('button', { name: /Language/ }),
|
||||
).toBeVisible();
|
||||
await expect(header.getByRole('img', { name: 'Docs logo' })).toBeVisible();
|
||||
|
||||
const languageButton = page.getByRole('button', {
|
||||
name: /Language|Select language/,
|
||||
});
|
||||
await expect(languageButton).toBeVisible();
|
||||
|
||||
await expect(header.getByTestId('header-icon-docs')).toBeVisible();
|
||||
await expect(header.getByRole('heading', { name: 'Docs' })).toBeVisible();
|
||||
|
||||
// Check the titles
|
||||
@@ -65,20 +68,31 @@ test.describe('Home page', () => {
|
||||
|
||||
await page.goto('/docs/');
|
||||
|
||||
// Wait for the page to be fully loaded and responsive store to be initialized
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
// Wait a bit more for the responsive store to be initialized
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check header content
|
||||
const header = page.locator('header').first();
|
||||
const footer = page.locator('footer').first();
|
||||
await expect(header).toBeVisible();
|
||||
await expect(
|
||||
header.getByRole('button', { name: /Language/ }),
|
||||
).toBeVisible();
|
||||
|
||||
// Check for language picker - it should be visible on desktop
|
||||
// Use a more flexible selector that works with both Header and HomeHeader
|
||||
const languageButton = page.getByRole('button', {
|
||||
name: /Language|Select language/,
|
||||
});
|
||||
await expect(languageButton).toBeVisible();
|
||||
|
||||
await expect(
|
||||
header.getByRole('button', { name: 'Les services de La Suite numé' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
header.getByRole('img', { name: 'Gouvernement Logo' }),
|
||||
).toBeVisible();
|
||||
await expect(header.getByRole('img', { name: 'Docs logo' })).toBeVisible();
|
||||
await expect(header.getByTestId('header-icon-docs')).toBeVisible();
|
||||
await expect(header.getByRole('heading', { name: 'Docs' })).toBeVisible();
|
||||
|
||||
// Check the titles
|
||||
|
||||
@@ -1,30 +1,17 @@
|
||||
import { Page, expect, test } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc } from './common';
|
||||
|
||||
test.describe.serial('Language', () => {
|
||||
let page: Page;
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close();
|
||||
});
|
||||
import { TestLanguage, createDoc, waitForLanguageSwitch } from './utils-common';
|
||||
|
||||
test.describe('Language', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForLanguageSwitch(page, TestLanguage.English);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
// Switch back to English - important for other tests to run as expected
|
||||
await waitForLanguageSwitch(page, TestLanguage.English);
|
||||
});
|
||||
|
||||
test('checks language switching', async ({ page }) => {
|
||||
const header = page.locator('header').first();
|
||||
const languagePicker = header.locator('.--docs--language-picker-text');
|
||||
|
||||
await expect(page.locator('html')).toHaveAttribute('lang', 'en-us');
|
||||
|
||||
// initial language should be english
|
||||
await expect(
|
||||
@@ -36,17 +23,57 @@ test.describe.serial('Language', () => {
|
||||
// switch to french
|
||||
await waitForLanguageSwitch(page, TestLanguage.French);
|
||||
|
||||
await expect(page.locator('html')).toHaveAttribute('lang', 'fr');
|
||||
|
||||
await expect(
|
||||
header.getByRole('button').getByText('Français'),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(page.getByLabel('Se déconnecter')).toBeVisible();
|
||||
|
||||
await header.getByRole('button').getByText('Français').click();
|
||||
await page.getByLabel('Deutsch').click();
|
||||
// Switch to German using the utility function for consistency
|
||||
await waitForLanguageSwitch(page, TestLanguage.German);
|
||||
await expect(header.getByRole('button').getByText('Deutsch')).toBeVisible();
|
||||
|
||||
await expect(page.getByLabel('Abmelden')).toBeVisible();
|
||||
|
||||
await expect(page.locator('html')).toHaveAttribute('lang', 'de');
|
||||
|
||||
await languagePicker.click();
|
||||
|
||||
await expect(page.locator('[role="menu"]')).toBeVisible();
|
||||
|
||||
const menuItems = page.getByRole('menuitem');
|
||||
await expect(menuItems.first()).toBeVisible();
|
||||
|
||||
await menuItems.first().click();
|
||||
|
||||
await expect(page.locator('html')).toHaveAttribute('lang', 'en');
|
||||
await expect(languagePicker).toContainText('English');
|
||||
});
|
||||
test('can switch language using only keyboard', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await waitForLanguageSwitch(page, TestLanguage.English);
|
||||
|
||||
const languagePicker = page.getByRole('button', {
|
||||
name: /select language/i,
|
||||
});
|
||||
|
||||
await expect(languagePicker).toBeVisible();
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
await page.keyboard.press('Tab');
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
const menu = page.getByRole('menu');
|
||||
await expect(menu).toBeVisible();
|
||||
|
||||
await page.keyboard.press('ArrowDown');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
await expect(page.locator('html')).not.toHaveAttribute('lang', 'en-us');
|
||||
});
|
||||
|
||||
test('checks that backend uses the same language as the frontend', async ({
|
||||
@@ -94,48 +121,3 @@ test.describe.serial('Language', () => {
|
||||
await expect(page.getByText('Titres', { exact: true })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
// language helper
|
||||
export const TestLanguage = {
|
||||
English: {
|
||||
label: 'English',
|
||||
expectedLocale: ['en-us'],
|
||||
},
|
||||
French: {
|
||||
label: 'Français',
|
||||
expectedLocale: ['fr-fr'],
|
||||
},
|
||||
German: {
|
||||
label: 'Deutsch',
|
||||
expectedLocale: ['de-de'],
|
||||
},
|
||||
} as const;
|
||||
|
||||
type TestLanguageKey = keyof typeof TestLanguage;
|
||||
type TestLanguageValue = (typeof TestLanguage)[TestLanguageKey];
|
||||
|
||||
export async function waitForLanguageSwitch(
|
||||
page: Page,
|
||||
lang: TestLanguageValue,
|
||||
) {
|
||||
const header = page.locator('header').first();
|
||||
const languagePicker = header.locator('.--docs--language-picker-text');
|
||||
const isAlreadyTargetLanguage = await languagePicker
|
||||
.innerText()
|
||||
.then((text) => text.toLowerCase().includes(lang.label.toLowerCase()));
|
||||
|
||||
if (isAlreadyTargetLanguage) {
|
||||
return;
|
||||
}
|
||||
|
||||
await languagePicker.click();
|
||||
const responsePromise = page.waitForResponse(
|
||||
(resp) =>
|
||||
resp.url().includes('/user') && resp.request().method() === 'PATCH',
|
||||
);
|
||||
await page.getByLabel(lang.label).click();
|
||||
const resolvedResponsePromise = await responsePromise;
|
||||
const responseData = await resolvedResponsePromise.json();
|
||||
|
||||
expect(lang.expectedLocale).toContain(responseData.language);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ test.describe('Left panel desktop', () => {
|
||||
test('checks all the elements are visible', async ({ page }) => {
|
||||
await expect(page.getByTestId('left-panel-desktop')).toBeVisible();
|
||||
await expect(page.getByTestId('left-panel-mobile')).toBeHidden();
|
||||
await expect(page.getByRole('button', { name: 'house' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'New doc' })).toBeVisible();
|
||||
await expect(page.getByTestId('home-button')).toBeVisible();
|
||||
await expect(page.getByTestId('new-doc-button')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,9 +27,11 @@ test.describe('Left panel mobile', () => {
|
||||
await expect(page.getByTestId('left-panel-mobile')).not.toBeInViewport();
|
||||
|
||||
const header = page.locator('header').first();
|
||||
const homeButton = page.getByRole('button', { name: 'house' });
|
||||
const newDocButton = page.getByRole('button', { name: 'New doc' });
|
||||
const languageButton = page.getByRole('button', { name: /Language/ });
|
||||
const homeButton = page.getByTestId('home-button');
|
||||
const newDocButton = page.getByTestId('new-doc-button');
|
||||
const languageButton = page.getByRole('button', {
|
||||
name: 'Select language',
|
||||
});
|
||||
const logoutButton = page.getByRole('button', { name: 'Logout' });
|
||||
|
||||
await expect(homeButton).not.toBeInViewport();
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
import { Locator, Page, expect } from '@playwright/test';
|
||||
|
||||
export type UserSearchResult = {
|
||||
email: string;
|
||||
full_name?: string | null;
|
||||
};
|
||||
|
||||
export type Role = 'Administrator' | 'Owner' | 'Member' | 'Editor' | 'Reader';
|
||||
export type LinkReach = 'Private' | 'Connected' | 'Public';
|
||||
export type LinkRole = 'Reading' | 'Edition';
|
||||
|
||||
export const searchUserToInviteToDoc = async (
|
||||
page: Page,
|
||||
inputFill?: string,
|
||||
): Promise<UserSearchResult[]> => {
|
||||
const inputFillValue = inputFill ?? 'user ';
|
||||
|
||||
const responsePromise = page.waitForResponse(
|
||||
(response) =>
|
||||
response
|
||||
.url()
|
||||
.includes(`/users/?q=${encodeURIComponent(inputFillValue)}`) &&
|
||||
response.status() === 200,
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
const inputSearch = page.getByRole('combobox', {
|
||||
name: 'Quick search input',
|
||||
});
|
||||
await expect(inputSearch).toBeVisible();
|
||||
await inputSearch.fill(inputFillValue);
|
||||
const response = await responsePromise;
|
||||
const users = (await response.json()) as UserSearchResult[];
|
||||
return users;
|
||||
};
|
||||
|
||||
export const addMemberToDoc = async (
|
||||
page: Page,
|
||||
role: Role,
|
||||
users: UserSearchResult[],
|
||||
) => {
|
||||
const list = page.getByTestId('doc-share-add-member-list');
|
||||
await expect(list).toBeHidden();
|
||||
const quickSearchContent = page.getByTestId('doc-share-quick-search');
|
||||
for (const user of users) {
|
||||
await quickSearchContent
|
||||
.getByTestId(`search-user-row-${user.email}`)
|
||||
.click();
|
||||
}
|
||||
|
||||
await list.getByLabel('doc-role-dropdown').click();
|
||||
await expect(page.getByLabel(role)).toBeVisible();
|
||||
await page.getByLabel(role).click();
|
||||
await page.getByRole('button', { name: 'Invite' }).click();
|
||||
};
|
||||
|
||||
export const verifyMemberAddedToDoc = async (
|
||||
page: Page,
|
||||
user: UserSearchResult,
|
||||
role: Role,
|
||||
): Promise<Locator> => {
|
||||
const container = page.getByLabel('List members card');
|
||||
await expect(container).toBeVisible();
|
||||
const userRow = container.getByTestId(`doc-share-member-row-${user.email}`);
|
||||
await expect(userRow).toBeVisible();
|
||||
await expect(userRow.getByText(role)).toBeVisible();
|
||||
await expect(userRow.getByText(user.full_name || user.email)).toBeVisible();
|
||||
return userRow;
|
||||
};
|
||||
|
||||
export const updateShareLink = async (
|
||||
page: Page,
|
||||
linkReach: LinkReach,
|
||||
linkRole?: LinkRole | null,
|
||||
) => {
|
||||
await page.getByRole('button', { name: 'Visibility', exact: true }).click();
|
||||
await page.getByRole('menuitem', { name: linkReach }).click();
|
||||
|
||||
const visibilityUpdatedText = page
|
||||
.getByText('The document visibility has been updated')
|
||||
.first();
|
||||
|
||||
await expect(visibilityUpdatedText).toBeVisible();
|
||||
|
||||
if (linkRole) {
|
||||
await page
|
||||
.getByRole('button', { name: 'Visibility mode', exact: true })
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: linkRole }).click();
|
||||
await expect(visibilityUpdatedText).toBeVisible();
|
||||
}
|
||||
};
|
||||
|
||||
export const verifyLinkReachIsDisabled = async (
|
||||
page: Page,
|
||||
linkReach: LinkReach,
|
||||
) => {
|
||||
await page.getByRole('button', { name: 'Visibility', exact: true }).click();
|
||||
const item = page.getByRole('menuitem', { name: linkReach });
|
||||
await expect(item).toBeDisabled();
|
||||
await page.click('body');
|
||||
};
|
||||
|
||||
export const verifyLinkReachIsEnabled = async (
|
||||
page: Page,
|
||||
linkReach: LinkReach,
|
||||
) => {
|
||||
await page.getByRole('button', { name: 'Visibility', exact: true }).click();
|
||||
const item = page.getByRole('menuitem', { name: linkReach });
|
||||
await expect(item).toBeEnabled();
|
||||
await page.click('body');
|
||||
};
|
||||
|
||||
export const verifyLinkRoleIsDisabled = async (
|
||||
page: Page,
|
||||
linkRole: LinkRole,
|
||||
) => {
|
||||
await page
|
||||
.getByRole('button', { name: 'Visibility mode', exact: true })
|
||||
.click();
|
||||
const item = page.getByRole('menuitem', { name: linkRole });
|
||||
await expect(item).toBeDisabled();
|
||||
await page.click('body');
|
||||
};
|
||||
|
||||
export const verifyLinkRoleIsEnabled = async (
|
||||
page: Page,
|
||||
linkRole: LinkRole,
|
||||
) => {
|
||||
await page
|
||||
.getByRole('button', { name: 'Visibility mode', exact: true })
|
||||
.click();
|
||||
const item = page.getByRole('menuitem', { name: linkRole });
|
||||
await expect(item).toBeEnabled();
|
||||
await page.click('body');
|
||||
};
|
||||
|
||||
export const verifyShareLink = async (
|
||||
page: Page,
|
||||
linkReach: LinkReach,
|
||||
linkRole?: LinkRole | null,
|
||||
) => {
|
||||
const visibilityDropdownButton = page.getByRole('button', {
|
||||
name: 'Visibility',
|
||||
exact: true,
|
||||
});
|
||||
await expect(visibilityDropdownButton).toBeVisible();
|
||||
await expect(visibilityDropdownButton.getByText(linkReach)).toBeVisible();
|
||||
|
||||
if (linkRole) {
|
||||
const visibilityModeButton = page.getByRole('button', {
|
||||
name: 'Visibility mode',
|
||||
exact: true,
|
||||
});
|
||||
await expect(visibilityModeButton).toBeVisible();
|
||||
await expect(page.getByText(linkRole)).toBeVisible();
|
||||
}
|
||||
};
|
||||
@@ -1,80 +0,0 @@
|
||||
import { Page, expect } from '@playwright/test';
|
||||
|
||||
import { randomName, updateDocTitle, waitForResponseCreateDoc } from './common';
|
||||
|
||||
export const createRootSubPage = async (
|
||||
page: Page,
|
||||
browserName: string,
|
||||
docName: string,
|
||||
) => {
|
||||
// Get response
|
||||
const responsePromise = waitForResponseCreateDoc(page);
|
||||
await clickOnAddRootSubPage(page);
|
||||
const response = await responsePromise;
|
||||
expect(response.ok()).toBeTruthy();
|
||||
const subPageJson = (await response.json()) as { id: string };
|
||||
|
||||
// Get doc tree
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
await expect(docTree).toBeVisible();
|
||||
|
||||
// Get sub page item
|
||||
const subPageItem = docTree
|
||||
.getByTestId(`doc-sub-page-item-${subPageJson.id}`)
|
||||
.first();
|
||||
await expect(subPageItem).toBeVisible();
|
||||
await subPageItem.click();
|
||||
|
||||
// Update sub page name
|
||||
const randomDocs = randomName(docName, browserName, 1);
|
||||
await updateDocTitle(page, randomDocs[0]);
|
||||
|
||||
// Return sub page data
|
||||
return { name: randomDocs[0], docTreeItem: subPageItem, item: subPageJson };
|
||||
};
|
||||
|
||||
export const clickOnAddRootSubPage = async (page: Page) => {
|
||||
const rootItem = page.getByTestId('doc-tree-root-item');
|
||||
await expect(rootItem).toBeVisible();
|
||||
await rootItem.hover();
|
||||
await rootItem.getByRole('button', { name: 'add_box' }).click();
|
||||
};
|
||||
|
||||
export const createSubPageFromParent = async (
|
||||
page: Page,
|
||||
browserName: string,
|
||||
parentId: string,
|
||||
subPageName: string,
|
||||
) => {
|
||||
// Get parent doc tree item
|
||||
const parentDocTreeItem = page.getByTestId(`doc-sub-page-item-${parentId}`);
|
||||
await expect(parentDocTreeItem).toBeVisible();
|
||||
await parentDocTreeItem.hover();
|
||||
|
||||
// Create sub page
|
||||
const responsePromise = waitForResponseCreateDoc(page);
|
||||
await parentDocTreeItem.getByRole('button', { name: 'add_box' }).click();
|
||||
|
||||
// Get response
|
||||
const response = await responsePromise;
|
||||
expect(response.ok()).toBeTruthy();
|
||||
const subPageJson = (await response.json()) as { id: string };
|
||||
|
||||
// Get doc tree
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
await expect(docTree).toBeVisible();
|
||||
|
||||
// Get sub page item
|
||||
const subPageItem = docTree
|
||||
.getByTestId(`doc-sub-page-item-${subPageJson.id}`)
|
||||
.first();
|
||||
await expect(subPageItem).toBeVisible();
|
||||
await subPageItem.click();
|
||||
|
||||
// Update sub page name
|
||||
const subPageTitle = randomName(subPageName, browserName, 1)[0];
|
||||
await updateDocTitle(page, subPageTitle);
|
||||
|
||||
// Return sub page data
|
||||
return { name: subPageTitle, docTreeItem: subPageItem, item: subPageJson };
|
||||
};
|
||||
@@ -78,14 +78,16 @@ export const createDoc = async (
|
||||
docName: string,
|
||||
browserName: string,
|
||||
length: number = 1,
|
||||
isChild: boolean = false,
|
||||
isMobile: boolean = false,
|
||||
) => {
|
||||
const randomDocs = randomName(docName, browserName, length);
|
||||
|
||||
for (let i = 0; i < randomDocs.length; i++) {
|
||||
if (!isChild) {
|
||||
const header = page.locator('header').first();
|
||||
await header.locator('h2').getByText('Docs').click();
|
||||
if (isMobile) {
|
||||
await page
|
||||
.getByRole('button', { name: 'Open the header menu' })
|
||||
.getByText('menu')
|
||||
.click();
|
||||
}
|
||||
|
||||
await page
|
||||
@@ -127,42 +129,6 @@ export const verifyDocName = async (page: Page, docName: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const addNewMember = async (
|
||||
page: Page,
|
||||
index: number,
|
||||
role: 'Administrator' | 'Owner' | 'Editor' | 'Reader',
|
||||
fillText: string = 'user ',
|
||||
) => {
|
||||
const responsePromiseSearchUser = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes(`/users/?q=${encodeURIComponent(fillText)}`) &&
|
||||
response.status() === 200,
|
||||
);
|
||||
|
||||
const inputSearch = page.getByRole('combobox', {
|
||||
name: 'Quick search input',
|
||||
});
|
||||
|
||||
// Select a new user
|
||||
await inputSearch.fill(fillText);
|
||||
|
||||
// Intercept response
|
||||
const responseSearchUser = await responsePromiseSearchUser;
|
||||
const users = (await responseSearchUser.json()) as {
|
||||
email: string;
|
||||
}[];
|
||||
|
||||
// Choose user
|
||||
await page.getByRole('option', { name: users[index].email }).click();
|
||||
|
||||
// Choose a role
|
||||
await page.getByLabel('doc-role-dropdown').click();
|
||||
await page.getByLabel(role).click();
|
||||
await page.getByRole('button', { name: 'Invite' }).click();
|
||||
|
||||
return users[index].email;
|
||||
};
|
||||
|
||||
export const getGridRow = async (page: Page, title: string) => {
|
||||
const docsGrid = page.getByRole('grid');
|
||||
await expect(docsGrid).toBeVisible();
|
||||
@@ -188,7 +154,7 @@ export const goToGridDoc = async (
|
||||
{ nthRow = 1, title }: GoToGridDocOptions = {},
|
||||
) => {
|
||||
const header = page.locator('header').first();
|
||||
await header.locator('h2').getByText('Docs').click();
|
||||
await header.locator('h1').getByText('Docs').click();
|
||||
|
||||
const docsGrid = page.getByTestId('docs-grid');
|
||||
await expect(docsGrid).toBeVisible();
|
||||
@@ -301,38 +267,40 @@ export const mockedListDocs = async (page: Page, data: object[] = []) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const mockedInvitations = async (page: Page, json?: object) => {
|
||||
let result = [
|
||||
{
|
||||
id: '120ec765-43af-4602-83eb-7f4e1224548a',
|
||||
abilities: {
|
||||
destroy: true,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
},
|
||||
created_at: '2024-10-03T12:19:26.107687Z',
|
||||
email: 'test@invitation.test',
|
||||
document: '4888c328-8406-4412-9b0b-c0ba5b9e5fb6',
|
||||
role: 'editor',
|
||||
issuer: '7380f42f-02eb-4ad5-b8f0-037a0e66066d',
|
||||
is_expired: false,
|
||||
...json,
|
||||
},
|
||||
];
|
||||
await page.route('**/invitations/**/', async (route) => {
|
||||
const request = route.request();
|
||||
if (
|
||||
request.method().includes('GET') &&
|
||||
request.url().includes('invitations') &&
|
||||
request.url().includes('page=')
|
||||
) {
|
||||
export const expectLoginPage = async (page: Page) =>
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Collaborative writing' }),
|
||||
).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
// language helper
|
||||
export const TestLanguage = {
|
||||
English: {
|
||||
label: 'English',
|
||||
expectedLocale: ['en-us'],
|
||||
},
|
||||
French: {
|
||||
label: 'Français',
|
||||
expectedLocale: ['fr-fr'],
|
||||
},
|
||||
German: {
|
||||
label: 'Deutsch',
|
||||
expectedLocale: ['de-de'],
|
||||
},
|
||||
} as const;
|
||||
|
||||
type TestLanguageKey = keyof typeof TestLanguage;
|
||||
type TestLanguageValue = (typeof TestLanguage)[TestLanguageKey];
|
||||
|
||||
export async function waitForLanguageSwitch(
|
||||
page: Page,
|
||||
lang: TestLanguageValue,
|
||||
) {
|
||||
await page.route('**/api/v1.0/users/**', async (route, request) => {
|
||||
if (request.method().includes('PATCH')) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
count: 1,
|
||||
next: null,
|
||||
previous: null,
|
||||
results: result,
|
||||
language: lang.expectedLocale[0],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@@ -340,71 +308,17 @@ export const mockedInvitations = async (page: Page, json?: object) => {
|
||||
}
|
||||
});
|
||||
|
||||
await page.route(
|
||||
'**/invitations/120ec765-43af-4602-83eb-7f4e1224548a/**/',
|
||||
async (route) => {
|
||||
const request = route.request();
|
||||
if (request.method().includes('DELETE')) {
|
||||
result = [];
|
||||
const header = page.locator('header').first();
|
||||
const languagePicker = header.locator('.--docs--language-picker-text');
|
||||
const isAlreadyTargetLanguage = await languagePicker
|
||||
.innerText()
|
||||
.then((text) => text.toLowerCase().includes(lang.label.toLowerCase()));
|
||||
|
||||
await route.fulfill({
|
||||
json: {},
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
if (isAlreadyTargetLanguage) {
|
||||
return;
|
||||
}
|
||||
|
||||
export const mockedAccesses = async (page: Page, json?: object) => {
|
||||
await page.route('**/accesses/**/', async (route) => {
|
||||
const request = route.request();
|
||||
await languagePicker.click();
|
||||
|
||||
if (
|
||||
request.method().includes('GET') &&
|
||||
request.url().includes('accesses')
|
||||
) {
|
||||
await route.fulfill({
|
||||
json: [
|
||||
{
|
||||
id: 'bc8bbbc5-a635-4f65-9817-fd1e9ec8ef87',
|
||||
user: {
|
||||
id: 'b4a21bb3-722e-426c-9f78-9d190eda641c',
|
||||
email: 'test@accesses.test',
|
||||
},
|
||||
team: '',
|
||||
max_ancestors_role: null,
|
||||
max_role: 'reader',
|
||||
role: 'reader',
|
||||
document: {
|
||||
id: 'mocked-document-id',
|
||||
path: '000000',
|
||||
depth: 1,
|
||||
},
|
||||
abilities: {
|
||||
destroy: true,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
link_select_options: {
|
||||
public: ['reader', 'editor'],
|
||||
authenticated: ['reader', 'editor'],
|
||||
restricted: null,
|
||||
},
|
||||
set_role_to: ['administrator', 'editor'],
|
||||
},
|
||||
...json,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const expectLoginPage = async (page: Page) =>
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Collaborative writing' }),
|
||||
).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
await page.getByLabel(lang.label).click();
|
||||
}
|
||||
165
src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts
Normal file
165
src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { Page, expect } from '@playwright/test';
|
||||
|
||||
export type Role = 'Administrator' | 'Owner' | 'Member' | 'Editor' | 'Reader';
|
||||
export type LinkReach = 'Private' | 'Connected' | 'Public';
|
||||
export type LinkRole = 'Reading' | 'Edition';
|
||||
|
||||
export const addNewMember = async (
|
||||
page: Page,
|
||||
index: number,
|
||||
role: 'Administrator' | 'Owner' | 'Editor' | 'Reader',
|
||||
fillText: string = 'user ',
|
||||
) => {
|
||||
const responsePromiseSearchUser = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes(`/users/?q=${encodeURIComponent(fillText)}`) &&
|
||||
response.status() === 200,
|
||||
);
|
||||
|
||||
const inputSearch = page.getByRole('combobox', {
|
||||
name: 'Quick search input',
|
||||
});
|
||||
|
||||
// Select a new user
|
||||
await inputSearch.fill(fillText);
|
||||
|
||||
// Intercept response
|
||||
const responseSearchUser = await responsePromiseSearchUser;
|
||||
const users = (await responseSearchUser.json()) as {
|
||||
email: string;
|
||||
}[];
|
||||
|
||||
// Choose user
|
||||
await page.getByRole('option', { name: users[index].email }).click();
|
||||
|
||||
// Choose a role
|
||||
await page.getByLabel('doc-role-dropdown').click();
|
||||
await page.getByLabel(role).click();
|
||||
await page.getByRole('button', { name: 'Invite' }).click();
|
||||
|
||||
return users[index].email;
|
||||
};
|
||||
|
||||
export const updateShareLink = async (
|
||||
page: Page,
|
||||
linkReach: LinkReach,
|
||||
linkRole?: LinkRole | null,
|
||||
) => {
|
||||
await page.getByRole('button', { name: 'Visibility', exact: true }).click();
|
||||
await page.getByRole('menuitem', { name: linkReach }).click();
|
||||
|
||||
const visibilityUpdatedText = page
|
||||
.getByText('The document visibility has been updated')
|
||||
.first();
|
||||
|
||||
await expect(visibilityUpdatedText).toBeVisible();
|
||||
|
||||
if (linkRole) {
|
||||
await page
|
||||
.getByRole('button', { name: 'Visibility mode', exact: true })
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: linkRole }).click();
|
||||
await expect(visibilityUpdatedText).toBeVisible();
|
||||
}
|
||||
};
|
||||
|
||||
export const mockedInvitations = async (page: Page, json?: object) => {
|
||||
let result = [
|
||||
{
|
||||
id: '120ec765-43af-4602-83eb-7f4e1224548a',
|
||||
abilities: {
|
||||
destroy: true,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
},
|
||||
created_at: '2024-10-03T12:19:26.107687Z',
|
||||
email: 'test@invitation.test',
|
||||
document: '4888c328-8406-4412-9b0b-c0ba5b9e5fb6',
|
||||
role: 'editor',
|
||||
issuer: '7380f42f-02eb-4ad5-b8f0-037a0e66066d',
|
||||
is_expired: false,
|
||||
...json,
|
||||
},
|
||||
];
|
||||
await page.route('**/invitations/**/', async (route) => {
|
||||
const request = route.request();
|
||||
if (
|
||||
request.method().includes('GET') &&
|
||||
request.url().includes('invitations') &&
|
||||
request.url().includes('page=')
|
||||
) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
count: 1,
|
||||
next: null,
|
||||
previous: null,
|
||||
results: result,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
await page.route(
|
||||
'**/invitations/120ec765-43af-4602-83eb-7f4e1224548a/**/',
|
||||
async (route) => {
|
||||
const request = route.request();
|
||||
if (request.method().includes('DELETE')) {
|
||||
result = [];
|
||||
|
||||
await route.fulfill({
|
||||
json: {},
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const mockedAccesses = async (page: Page, json?: object) => {
|
||||
await page.route('**/accesses/**/', async (route) => {
|
||||
const request = route.request();
|
||||
|
||||
if (
|
||||
request.method().includes('GET') &&
|
||||
request.url().includes('accesses')
|
||||
) {
|
||||
await route.fulfill({
|
||||
json: [
|
||||
{
|
||||
id: 'bc8bbbc5-a635-4f65-9817-fd1e9ec8ef87',
|
||||
user: {
|
||||
id: 'b4a21bb3-722e-426c-9f78-9d190eda641c',
|
||||
email: 'test@accesses.test',
|
||||
},
|
||||
team: '',
|
||||
max_ancestors_role: null,
|
||||
max_role: 'reader',
|
||||
role: 'reader',
|
||||
document: {
|
||||
id: 'mocked-document-id',
|
||||
path: '000000',
|
||||
depth: 1,
|
||||
},
|
||||
abilities: {
|
||||
destroy: true,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
link_select_options: {
|
||||
public: ['reader', 'editor'],
|
||||
authenticated: ['reader', 'editor'],
|
||||
restricted: null,
|
||||
},
|
||||
set_role_to: ['administrator', 'editor'],
|
||||
},
|
||||
...json,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Page, expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
randomName,
|
||||
updateDocTitle,
|
||||
waitForResponseCreateDoc,
|
||||
} from './utils-common';
|
||||
|
||||
export const createRootSubPage = async (
|
||||
page: Page,
|
||||
browserName: string,
|
||||
docName: string,
|
||||
isMobile: boolean = false,
|
||||
) => {
|
||||
if (isMobile) {
|
||||
await page
|
||||
.getByRole('button', { name: 'Open the header menu' })
|
||||
.getByText('menu')
|
||||
.click();
|
||||
}
|
||||
|
||||
// Get response
|
||||
const responsePromise = waitForResponseCreateDoc(page);
|
||||
await clickOnAddRootSubPage(page);
|
||||
const response = await responsePromise;
|
||||
expect(response.ok()).toBeTruthy();
|
||||
const subPageJson = (await response.json()) as { id: string };
|
||||
|
||||
if (isMobile) {
|
||||
await page
|
||||
.getByRole('button', { name: 'Open the header menu' })
|
||||
.getByText('menu')
|
||||
.click();
|
||||
}
|
||||
|
||||
// Get doc tree
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
await expect(docTree).toBeVisible();
|
||||
|
||||
// Get sub page item
|
||||
const subPageItem = docTree
|
||||
.getByTestId(`doc-sub-page-item-${subPageJson.id}`)
|
||||
.first();
|
||||
await expect(subPageItem).toBeVisible();
|
||||
await subPageItem.click();
|
||||
|
||||
if (isMobile) {
|
||||
await page
|
||||
.getByRole('button', { name: 'Open the header menu' })
|
||||
.getByText('close')
|
||||
.click();
|
||||
}
|
||||
|
||||
// Update sub page name
|
||||
const randomDocs = randomName(docName, browserName, 1);
|
||||
await updateDocTitle(page, randomDocs[0]);
|
||||
|
||||
// Return sub page data
|
||||
return { name: randomDocs[0], docTreeItem: subPageItem, item: subPageJson };
|
||||
};
|
||||
|
||||
export const clickOnAddRootSubPage = async (page: Page) => {
|
||||
const rootItem = page.getByTestId('doc-tree-root-item');
|
||||
await expect(rootItem).toBeVisible();
|
||||
await rootItem.hover();
|
||||
await rootItem.getByRole('button', { name: 'add_box' }).click();
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-e2e",
|
||||
"version": "3.4.1",
|
||||
"version": "3.5.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext .ts",
|
||||
@@ -12,7 +12,7 @@
|
||||
"test:ui::chromium": "yarn test:ui --project=chromium"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.54.1",
|
||||
"@playwright/test": "1.54.2",
|
||||
"@types/node": "*",
|
||||
"@types/pdf-parse": "1.1.5",
|
||||
"eslint-config-impress": "*",
|
||||
|
||||
@@ -8,6 +8,7 @@ const buildId = crypto.randomBytes(256).toString('hex').slice(0, 8);
|
||||
const nextConfig = {
|
||||
output: 'export',
|
||||
trailingSlash: true,
|
||||
transpilePackages: ['package-docs'],
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-impress",
|
||||
"version": "3.4.1",
|
||||
"version": "3.5.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -16,24 +16,25 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ag-media/react-pdf-table": "2.0.3",
|
||||
"@blocknote/code-block": "0.33.0",
|
||||
"@blocknote/core": "0.33.0",
|
||||
"@blocknote/mantine": "0.33.0",
|
||||
"@blocknote/react": "0.33.0",
|
||||
"@blocknote/xl-docx-exporter": "0.33.0",
|
||||
"@blocknote/xl-pdf-exporter": "0.33.0",
|
||||
"@blocknote/code-block": "0.35.0",
|
||||
"@blocknote/core": "0.35.0",
|
||||
"@blocknote/mantine": "0.35.0",
|
||||
"@blocknote/react": "0.35.0",
|
||||
"@blocknote/xl-docx-exporter": "0.35.0",
|
||||
"@blocknote/xl-multi-column": "0.35.0",
|
||||
"@blocknote/xl-pdf-exporter": "0.35.0",
|
||||
"@dnd-kit/core": "6.3.1",
|
||||
"@dnd-kit/modifiers": "9.0.0",
|
||||
"@emoji-mart/data": "1.2.1",
|
||||
"@emoji-mart/react": "1.1.1",
|
||||
"@fontsource/material-icons": "5.2.5",
|
||||
"@gouvfr-lasuite/integration": "1.0.3",
|
||||
"@gouvfr-lasuite/ui-kit": "0.8.2",
|
||||
"@gouvfr-lasuite/ui-kit": "0.11.0",
|
||||
"@hocuspocus/provider": "2.15.2",
|
||||
"@openfun/cunningham-react": "3.1.0",
|
||||
"@openfun/cunningham-react": "3.2.1",
|
||||
"@react-pdf/renderer": "4.3.0",
|
||||
"@sentry/nextjs": "9.38.0",
|
||||
"@tanstack/react-query": "5.83.0",
|
||||
"@sentry/nextjs": "10.2.0",
|
||||
"@tanstack/react-query": "5.84.1",
|
||||
"canvg": "4.0.3",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.1.1",
|
||||
@@ -45,46 +46,47 @@
|
||||
"idb": "8.0.3",
|
||||
"lodash": "4.17.21",
|
||||
"luxon": "3.7.1",
|
||||
"next": "15.4.1",
|
||||
"posthog-js": "1.257.0",
|
||||
"next": "15.4.6",
|
||||
"package-docs": "*",
|
||||
"posthog-js": "1.258.6",
|
||||
"react": "*",
|
||||
"react-aria-components": "1.10.1",
|
||||
"react-aria-components": "1.11.0",
|
||||
"react-dom": "*",
|
||||
"react-i18next": "15.6.0",
|
||||
"react-i18next": "15.6.1",
|
||||
"react-intersection-observer": "9.16.0",
|
||||
"react-select": "5.10.2",
|
||||
"styled-components": "6.1.19",
|
||||
"use-debounce": "10.0.5",
|
||||
"y-protocols": "1.0.6",
|
||||
"yjs": "*",
|
||||
"zustand": "5.0.6"
|
||||
"zustand": "5.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svgr/webpack": "8.1.0",
|
||||
"@tanstack/react-query-devtools": "5.83.0",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@tanstack/react-query-devtools": "5.84.1",
|
||||
"@testing-library/dom": "10.4.1",
|
||||
"@testing-library/jest-dom": "6.6.4",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/jest": "30.0.0",
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/luxon": "3.6.2",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "17.2.0",
|
||||
"cross-env": "10.0.0",
|
||||
"dotenv": "17.2.1",
|
||||
"eslint-config-impress": "*",
|
||||
"fetch-mock": "9.11.0",
|
||||
"jest": "30.0.4",
|
||||
"jest-environment-jsdom": "30.0.4",
|
||||
"jest": "30.0.5",
|
||||
"jest-environment-jsdom": "30.0.5",
|
||||
"node-fetch": "2.7.0",
|
||||
"prettier": "3.6.2",
|
||||
"stylelint": "16.21.1",
|
||||
"stylelint-config-standard": "38.0.0",
|
||||
"stylelint": "16.23.0",
|
||||
"stylelint-config-standard": "39.0.0",
|
||||
"stylelint-prettier": "5.0.3",
|
||||
"typescript": "*",
|
||||
"webpack": "5.100.1",
|
||||
"webpack": "5.101.0",
|
||||
"workbox-webpack-plugin": "7.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface BoxProps {
|
||||
$background?: CSSProperties['background'];
|
||||
$color?: CSSProperties['color'];
|
||||
$css?: string | RuleSet<object>;
|
||||
$cursor?: CSSProperties['cursor'];
|
||||
$direction?: CSSProperties['flexDirection'];
|
||||
$display?: CSSProperties['display'];
|
||||
$effect?: 'show' | 'hide';
|
||||
@@ -44,13 +45,13 @@ export interface BoxProps {
|
||||
export type BoxType = ComponentPropsWithRef<typeof Box>;
|
||||
|
||||
export const Box = styled('div')<BoxProps>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
${({ $align }) => $align && `align-items: ${$align};`}
|
||||
${({ $background }) => $background && `background: ${$background};`}
|
||||
${({ $color }) => $color && `color: ${$color};`}
|
||||
${({ $direction }) => $direction && `flex-direction: ${$direction};`}
|
||||
${({ $display }) => $display && `display: ${$display};`}
|
||||
${({ $cursor }) => $cursor && `cursor: ${$cursor};`}
|
||||
${({ $direction }) => `flex-direction: ${$direction || 'column'};`}
|
||||
${({ $display, as }) =>
|
||||
`display: ${$display || as?.match('span|input') ? 'inline-flex' : 'flex'};`}
|
||||
${({ $flex }) => $flex && `flex: ${$flex};`}
|
||||
${({ $gap }) => $gap && `gap: ${$gap};`}
|
||||
${({ $height }) => $height && `height: ${$height};`}
|
||||
|
||||
@@ -31,11 +31,11 @@ const BoxButton = forwardRef<HTMLDivElement, BoxButtonType>(
|
||||
$background="none"
|
||||
$margin="none"
|
||||
$padding="none"
|
||||
$hasTransition
|
||||
$css={css`
|
||||
cursor: ${props.disabled ? 'not-allowed' : 'pointer'};
|
||||
border: none;
|
||||
outline: none;
|
||||
transition: all 0.2s ease-in-out;
|
||||
font-family: inherit;
|
||||
|
||||
color: ${props.disabled
|
||||
|
||||
@@ -33,6 +33,13 @@ const StyledButton = styled(Button)<StyledButtonProps>`
|
||||
font-size: 0.938rem;
|
||||
padding: 0;
|
||||
${({ $css }) => $css};
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--c--theme--colors--primary-500);
|
||||
outline-offset: 2px;
|
||||
border-radius: 4px;
|
||||
transition: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface DropButtonProps {
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import { HorizontalSeparator } from '@gouvfr-lasuite/ui-kit';
|
||||
import { Fragment, PropsWithChildren, useRef, useState } from 'react';
|
||||
import {
|
||||
Fragment,
|
||||
PropsWithChildren,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, BoxButton, BoxProps, DropButton, Icon, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
import { useDropdownKeyboardNav } from './hook/useDropdownKeyboardNav';
|
||||
|
||||
export type DropdownMenuOption = {
|
||||
icon?: string;
|
||||
label: string;
|
||||
@@ -25,6 +34,7 @@ export type DropdownMenuProps = {
|
||||
arrowCss?: BoxProps['$css'];
|
||||
buttonCss?: BoxProps['$css'];
|
||||
disabled?: boolean;
|
||||
opened?: boolean;
|
||||
topMessage?: string;
|
||||
selectedValues?: string[];
|
||||
afterOpenChange?: (isOpen: boolean) => void;
|
||||
@@ -38,18 +48,47 @@ export const DropdownMenu = ({
|
||||
arrowCss,
|
||||
buttonCss,
|
||||
label,
|
||||
opened,
|
||||
topMessage,
|
||||
afterOpenChange,
|
||||
selectedValues,
|
||||
}: PropsWithChildren<DropdownMenuProps>) => {
|
||||
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(opened ?? false);
|
||||
const [focusedIndex, setFocusedIndex] = useState(-1);
|
||||
const blockButtonRef = useRef<HTMLDivElement>(null);
|
||||
const menuItemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
|
||||
const onOpenChange = (isOpen: boolean) => {
|
||||
setIsOpen(isOpen);
|
||||
afterOpenChange?.(isOpen);
|
||||
};
|
||||
const onOpenChange = useCallback(
|
||||
(isOpen: boolean) => {
|
||||
setIsOpen(isOpen);
|
||||
setFocusedIndex(-1);
|
||||
afterOpenChange?.(isOpen);
|
||||
},
|
||||
[afterOpenChange],
|
||||
);
|
||||
|
||||
useDropdownKeyboardNav({
|
||||
isOpen,
|
||||
focusedIndex,
|
||||
options,
|
||||
menuItemRefs,
|
||||
setFocusedIndex,
|
||||
onOpenChange,
|
||||
});
|
||||
|
||||
// Focus selected menu item when menu opens
|
||||
useEffect(() => {
|
||||
if (isOpen && menuItemRefs.current.length > 0) {
|
||||
const selectedIndex = options.findIndex((option) => option.isSelected);
|
||||
if (selectedIndex !== -1) {
|
||||
setFocusedIndex(selectedIndex);
|
||||
setTimeout(() => {
|
||||
menuItemRefs.current[selectedIndex]?.focus();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}, [isOpen, options]);
|
||||
|
||||
if (disabled) {
|
||||
return children;
|
||||
@@ -93,6 +132,7 @@ export const DropdownMenu = ({
|
||||
$maxWidth="320px"
|
||||
$minWidth={`${blockButtonRef.current?.clientWidth}px`}
|
||||
role="menu"
|
||||
aria-label={label}
|
||||
>
|
||||
{topMessage && (
|
||||
<Text
|
||||
@@ -113,14 +153,20 @@ export const DropdownMenu = ({
|
||||
return;
|
||||
}
|
||||
const isDisabled = option.disabled !== undefined && option.disabled;
|
||||
const isFocused = index === focusedIndex;
|
||||
|
||||
return (
|
||||
<Fragment key={option.label}>
|
||||
<BoxButton
|
||||
ref={(el) => {
|
||||
menuItemRefs.current[index] = el;
|
||||
}}
|
||||
role="menuitem"
|
||||
aria-label={option.label}
|
||||
data-testid={option.testId}
|
||||
$direction="row"
|
||||
disabled={isDisabled}
|
||||
$hasTransition={false}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -156,6 +202,19 @@ export const DropdownMenu = ({
|
||||
&:hover {
|
||||
background-color: var(--c--theme--colors--greyscale-050);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--c--theme--colors--primary-500);
|
||||
outline-offset: -2px;
|
||||
background-color: var(--c--theme--colors--greyscale-050);
|
||||
}
|
||||
|
||||
${isFocused &&
|
||||
css`
|
||||
outline: 2px solid var(--c--theme--colors--primary-500);
|
||||
outline-offset: -2px;
|
||||
background-color: var(--c--theme--colors--greyscale-050);
|
||||
`}
|
||||
`}
|
||||
>
|
||||
<Box
|
||||
@@ -0,0 +1,88 @@
|
||||
import { RefObject, useEffect } from 'react';
|
||||
|
||||
import { DropdownMenuOption } from '../DropdownMenu';
|
||||
|
||||
type UseDropdownKeyboardNavProps = {
|
||||
isOpen: boolean;
|
||||
focusedIndex: number;
|
||||
options: DropdownMenuOption[];
|
||||
menuItemRefs: RefObject<(HTMLDivElement | null)[]>;
|
||||
setFocusedIndex: (index: number) => void;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
};
|
||||
|
||||
export const useDropdownKeyboardNav = ({
|
||||
isOpen,
|
||||
focusedIndex,
|
||||
options,
|
||||
menuItemRefs,
|
||||
setFocusedIndex,
|
||||
onOpenChange,
|
||||
}: UseDropdownKeyboardNavProps) => {
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const enabledIndices = options
|
||||
.map((option, index) =>
|
||||
option.show !== false && !option.disabled ? index : -1,
|
||||
)
|
||||
.filter((index) => index !== -1);
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
event.preventDefault();
|
||||
const nextIndex =
|
||||
focusedIndex < enabledIndices.length - 1 ? focusedIndex + 1 : 0;
|
||||
const nextEnabledIndex = enabledIndices[nextIndex];
|
||||
setFocusedIndex(nextIndex);
|
||||
menuItemRefs.current[nextEnabledIndex]?.focus();
|
||||
break;
|
||||
|
||||
case 'ArrowUp':
|
||||
event.preventDefault();
|
||||
const prevIndex =
|
||||
focusedIndex > 0 ? focusedIndex - 1 : enabledIndices.length - 1;
|
||||
const prevEnabledIndex = enabledIndices[prevIndex];
|
||||
setFocusedIndex(prevIndex);
|
||||
menuItemRefs.current[prevEnabledIndex]?.focus();
|
||||
break;
|
||||
|
||||
case 'Enter':
|
||||
case ' ':
|
||||
event.preventDefault();
|
||||
if (focusedIndex >= 0 && focusedIndex < enabledIndices.length) {
|
||||
const selectedOptionIndex = enabledIndices[focusedIndex];
|
||||
const selectedOption = options[selectedOptionIndex];
|
||||
if (selectedOption && selectedOption.callback) {
|
||||
onOpenChange(false);
|
||||
void selectedOption.callback();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Escape':
|
||||
event.preventDefault();
|
||||
onOpenChange(false);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [
|
||||
isOpen,
|
||||
focusedIndex,
|
||||
options,
|
||||
menuItemRefs,
|
||||
setFocusedIndex,
|
||||
onOpenChange,
|
||||
]);
|
||||
};
|
||||
@@ -1,9 +1,12 @@
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box } from '../Box';
|
||||
import { DropdownMenu, DropdownMenuOption } from '../DropdownMenu';
|
||||
import { Icon } from '../Icon';
|
||||
import { Text } from '../Text';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuOption,
|
||||
} from '../dropdown-menu/DropdownMenu';
|
||||
|
||||
export type FilterDropdownProps = {
|
||||
options: DropdownMenuOption[];
|
||||
|
||||
@@ -3,7 +3,8 @@ export * from './Box';
|
||||
export * from './BoxButton';
|
||||
export * from './Card';
|
||||
export * from './DropButton';
|
||||
export * from './DropdownMenu';
|
||||
export * from './dropdown-menu/DropdownMenu';
|
||||
export * from './quick-search';
|
||||
export * from './Icon';
|
||||
export * from './InfiniteScroll';
|
||||
export * from './Link';
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { Command } from 'cmdk';
|
||||
import { ReactNode, useRef } from 'react';
|
||||
import {
|
||||
PropsWithChildren,
|
||||
ReactNode,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { hasChildrens } from '@/utils/children';
|
||||
|
||||
@@ -30,7 +36,6 @@ export type QuickSearchProps = {
|
||||
loading?: boolean;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export const QuickSearch = ({
|
||||
@@ -42,14 +47,47 @@ export const QuickSearch = ({
|
||||
label,
|
||||
placeholder,
|
||||
children,
|
||||
}: QuickSearchProps) => {
|
||||
}: PropsWithChildren<QuickSearchProps>) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const [selectedValue, setSelectedValue] = useState<string>('');
|
||||
|
||||
// Auto-select first item when children change
|
||||
useEffect(() => {
|
||||
if (!children) {
|
||||
setSelectedValue('');
|
||||
return;
|
||||
}
|
||||
|
||||
// Small delay for DOM to update
|
||||
const timeoutId = setTimeout(() => {
|
||||
const firstItem = ref.current?.querySelector('[cmdk-item]');
|
||||
if (firstItem) {
|
||||
const value =
|
||||
firstItem.getAttribute('data-value') ||
|
||||
firstItem.getAttribute('value') ||
|
||||
firstItem.textContent?.trim() ||
|
||||
'';
|
||||
if (value) {
|
||||
setSelectedValue(value);
|
||||
}
|
||||
}
|
||||
}, 50);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [children]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<QuickSearchStyle />
|
||||
<div className="quick-search-container">
|
||||
<Command label={label} shouldFilter={false} ref={ref}>
|
||||
<Command
|
||||
label={label}
|
||||
shouldFilter={false}
|
||||
ref={ref}
|
||||
value={selectedValue}
|
||||
onValueChange={setSelectedValue}
|
||||
tabIndex={0}
|
||||
>
|
||||
{showInput && (
|
||||
<QuickSearchInput
|
||||
loading={loading}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Command } from 'cmdk';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { Box } from '../Box';
|
||||
import { Box, Text } from '@/components';
|
||||
|
||||
import { QuickSearchData } from './QuickSearch';
|
||||
import { QuickSearchItem } from './QuickSearchItem';
|
||||
@@ -23,6 +23,7 @@ export const QuickSearchGroup = <T,>({
|
||||
key={group.groupName}
|
||||
heading={group.groupName}
|
||||
forceMount={false}
|
||||
contentEditable={false}
|
||||
>
|
||||
{group.startActions?.map((action, index) => {
|
||||
return (
|
||||
@@ -58,7 +59,13 @@ export const QuickSearchGroup = <T,>({
|
||||
);
|
||||
})}
|
||||
{group.emptyString && group.elements.length === 0 && (
|
||||
<span className="ml-b clr-greyscale-500">{group.emptyString}</span>
|
||||
<Text
|
||||
$variation="500"
|
||||
$margin={{ left: '2xs', bottom: '3xs' }}
|
||||
$size="sm"
|
||||
>
|
||||
{group.emptyString}
|
||||
</Text>
|
||||
)}
|
||||
</Command.Group>
|
||||
</Box>
|
||||
|
||||
@@ -1,133 +1,136 @@
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
export const QuickSearchStyle = createGlobalStyle`
|
||||
& *:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.quick-search-container {
|
||||
[cmdk-root] {
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
transition: transform 100ms ease;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
[cmdk-input] {
|
||||
border: none;
|
||||
width: 100%;
|
||||
font-size: 17px;
|
||||
padding: 8px;
|
||||
background: white;
|
||||
outline: none;
|
||||
color: var(--c--theme--colors--greyscale-1000);
|
||||
border-radius: 0;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--c--theme--colors--greyscale-500);
|
||||
}
|
||||
}
|
||||
|
||||
[cmdk-item] {
|
||||
content-visibility: auto;
|
||||
cursor: pointer;
|
||||
border-radius: var(--c--theme--spacings--xs);
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
user-select: none;
|
||||
will-change: background, color;
|
||||
transition: all 150ms ease;
|
||||
transition-property: none;
|
||||
|
||||
.show-right-on-focus {
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
transition: transform 100ms ease;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&[data-selected='true'] {
|
||||
background: var(--c--theme--colors--greyscale-100);
|
||||
.show-right-on-focus {
|
||||
opacity: 1;
|
||||
[cmdk-input] {
|
||||
border: none;
|
||||
width: 100%;
|
||||
font-size: 17px;
|
||||
padding: 8px;
|
||||
background: white;
|
||||
outline: none;
|
||||
color: var(--c--theme--colors--greyscale-1000);
|
||||
border-radius: 0;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--c--theme--colors--greyscale-500);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-disabled='true'] {
|
||||
color: var(--c--theme--colors--greyscale-500);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
& + [cmdk-item] {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
[cmdk-list] {
|
||||
flex:1;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
[cmdk-vercel-shortcuts] {
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
gap: 8px;
|
||||
|
||||
kbd {
|
||||
font-size: 12px;
|
||||
min-width: 20px;
|
||||
padding: 4px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
background: var(--c--theme--colors--greyscale-500);
|
||||
display: inline-flex;
|
||||
[cmdk-item] {
|
||||
content-visibility: auto;
|
||||
cursor: pointer;
|
||||
border-radius: var(--c--theme--spacings--xs);
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-transform: uppercase;
|
||||
gap: 8px;
|
||||
user-select: none;
|
||||
will-change: background, color;
|
||||
transition: all 150ms ease;
|
||||
transition-property: none;
|
||||
|
||||
.show-right-on-focus {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&[data-selected='true'] {
|
||||
background: var(--c--theme--colors--greyscale-100);
|
||||
.show-right-on-focus {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-disabled='true'] {
|
||||
color: var(--c--theme--colors--greyscale-500);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
& + [cmdk-item] {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
[cmdk-list] {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
[cmdk-vercel-shortcuts] {
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
gap: 8px;
|
||||
|
||||
kbd {
|
||||
font-size: 12px;
|
||||
min-width: 20px;
|
||||
padding: 4px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
background: var(--c--theme--colors--greyscale-500);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
[cmdk-separator] {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: var(--c--theme--colors--greyscale-500);
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
*:not([hidden]) + [cmdk-group] {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
[cmdk-group-heading] {
|
||||
user-select: none;
|
||||
font-size: var(--c--theme--font--sizes--sm);
|
||||
color: var(--c--theme--colors--greyscale-700);
|
||||
font-weight: bold;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: var(--c--theme--spacings--xs);
|
||||
}
|
||||
|
||||
[cmdk-empty] {
|
||||
}
|
||||
}
|
||||
|
||||
[cmdk-separator] {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: var(--c--theme--colors--greyscale-500);
|
||||
margin: 4px 0;
|
||||
.c__modal__scroller:has(.quick-search-container),
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
*:not([hidden]) + [cmdk-group] {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
[cmdk-group-heading] {
|
||||
user-select: none;
|
||||
font-size: var(--c--theme--font--sizes--sm);
|
||||
color: var(--c--theme--colors--greyscale-700);
|
||||
font-weight: bold;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: var(--c--theme--spacings--xs);
|
||||
}
|
||||
|
||||
[cmdk-empty] {
|
||||
}
|
||||
}
|
||||
|
||||
.c__modal__scroller:has(.quick-search-container),
|
||||
.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;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { CunninghamProvider } from '@openfun/cunningham-react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import {
|
||||
MutationCache,
|
||||
QueryClient,
|
||||
QueryClientProvider,
|
||||
} from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
@@ -24,8 +28,24 @@ const defaultOptions = {
|
||||
retry: DEFAULT_QUERY_RETRY,
|
||||
},
|
||||
};
|
||||
|
||||
let globalRouterReplace: ((url: string) => void) | null = null;
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions,
|
||||
mutationCache: new MutationCache({
|
||||
onError: (error) => {
|
||||
if (error instanceof Error && 'status' in error && error.status === 401) {
|
||||
void queryClient.resetQueries({
|
||||
queryKey: [KEY_AUTH],
|
||||
});
|
||||
setAuthUrl();
|
||||
if (globalRouterReplace) {
|
||||
void globalRouterReplace('/401');
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
export function AppProvider({ children }: { children: React.ReactNode }) {
|
||||
@@ -40,25 +60,14 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
|
||||
return initializeResizeListener();
|
||||
}, [initializeResizeListener]);
|
||||
|
||||
/**
|
||||
* Update the global router replace function
|
||||
* This allows us to use the router replace function globally
|
||||
*/
|
||||
useEffect(() => {
|
||||
queryClient.setDefaultOptions({
|
||||
...defaultOptions,
|
||||
mutations: {
|
||||
onError: (error) => {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
'status' in error &&
|
||||
error.status === 401
|
||||
) {
|
||||
void queryClient.resetQueries({
|
||||
queryKey: [KEY_AUTH],
|
||||
});
|
||||
setAuthUrl();
|
||||
void replace(`/401`);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
globalRouterReplace = (url: string) => {
|
||||
void replace(url);
|
||||
};
|
||||
}, [replace]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -51,6 +51,17 @@
|
||||
filter: var(--c--components--image-system-filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toast
|
||||
*/
|
||||
.c__toast__container {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.c__toast__container:has(.c__toast) {
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Inter;
|
||||
font-style: italic;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="23" viewBox="0 0 18 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.57954 5.93282C4.44486 5.79021 4.37793 5.61484 4.37793 5.41299C4.37793 5.21112 4.44495 5.03795 4.58154 4.90136C4.72456 4.75833 4.90437 4.6875 5.11367 4.6875H12.8964C13.0991 4.6875 13.2722 4.75857 13.408 4.90133C13.5508 5.03718 13.6219 5.21027 13.6219 5.41299C13.6219 5.61613 13.5506 5.7918 13.409 5.93387C13.273 6.07732 13.0996 6.14873 12.8964 6.14873H5.11367C4.90437 6.14873 4.72456 6.0779 4.58154 5.93487L4.57954 5.93282ZM4.57954 9.51144C4.44486 9.36882 4.37793 9.19346 4.37793 8.9916C4.37793 8.78973 4.44495 8.61656 4.58154 8.47997C4.72456 8.33695 4.90437 8.26611 5.11367 8.26611H12.8964C13.0991 8.26611 13.2722 8.33719 13.408 8.47995C13.5508 8.61579 13.6219 8.78888 13.6219 8.9916C13.6219 9.19475 13.5506 9.37042 13.409 9.51249C13.273 9.65593 13.0996 9.72734 12.8964 9.72734H5.11367C4.90437 9.72734 4.72456 9.65651 4.58154 9.51348L4.57954 9.51144ZM4.57954 13.1003C4.44561 12.9585 4.37793 12.7869 4.37793 12.5907C4.37793 12.3831 4.44414 12.204 4.57954 12.0606L4.58151 12.0586C4.72453 11.9155 4.90437 11.8447 5.11367 11.8447H8.79482C9.00363 11.8447 9.18092 11.9153 9.3177 12.0596C9.46006 12.2024 9.53057 12.3819 9.53057 12.5907C9.53057 12.7887 9.45812 12.9609 9.31671 13.1024C9.17936 13.2397 9.00235 13.306 8.79482 13.306H5.11367C4.90609 13.306 4.72695 13.2397 4.58358 13.1043L4.57954 13.1003ZM1.09476 0.851519C1.65317 0.285946 2.47955 0.0117188 3.55508 0.0117188H14.4447C15.52 0.0117188 16.3433 0.28583 16.895 0.851748C17.4529 1.41698 17.7234 2.24966 17.7234 3.33145V18.8866C17.7234 19.975 17.4531 20.8082 16.8945 21.3668C16.3427 21.9256 15.5196 22.1961 14.4447 22.1961H3.55508C2.47988 22.1961 1.65367 21.9255 1.09521 21.367C0.543652 20.8083 0.276367 19.9747 0.276367 18.8866V3.33145C0.276367 2.24984 0.543796 1.41679 1.09476 0.851519ZM15.5624 20.0351C15.2958 20.3085 14.8959 20.4452 14.3627 20.4452H3.63711C3.10391 20.4452 2.70059 20.3085 2.42715 20.0351L2.49875 19.9652L2.49786 19.9643L2.42715 20.0351C2.16055 19.7616 2.02725 19.3686 2.02725 18.8559V3.36221C2.02725 2.84951 2.16055 2.45645 2.42715 2.18301C2.70059 1.90273 3.10391 1.7626 3.63711 1.7626H14.3627C14.8959 1.7626 15.2958 1.90273 15.5624 2.18301C15.8358 2.45645 15.9726 2.84951 15.9726 3.36221V18.8559C15.9726 19.3686 15.8358 19.7616 15.5624 20.0351Z" fill="#3A3A3A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
width="24"
|
||||
height="25"
|
||||
viewBox="0 0 24 25"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M20.709 15.8262C21.202 15.8262 21.5875 15.9577 21.8574 16.2275L21.9521 16.333C22.1583 16.592 22.2588 16.939 22.2588 17.3682V22.5098C22.2588 22.9389 22.1583 23.286 21.9521 23.5449L21.8574 23.6504C21.5875 23.9202 21.202 24.0517 20.709 24.0518H15.584C15.1523 24.0518 14.8024 23.9513 14.541 23.7451L14.4346 23.6504C14.1647 23.3833 14.0332 23 14.0332 22.5098V17.3682C14.0332 16.8779 14.1646 16.4947 14.4346 16.2275L14.541 16.1328C14.8024 15.9267 15.1523 15.8262 15.584 15.8262H20.709ZM17.4443 0.961914C18.5273 0.961914 19.3662 1.23768 19.9307 1.81641L20.0342 1.92773C20.5341 2.50088 20.7734 3.30971 20.7734 4.33105V13.6318C20.7734 14.1429 20.3587 14.5576 19.8477 14.5576C19.3367 14.5574 18.9229 14.1428 18.9229 13.6318V4.3623C18.9229 3.85862 18.7884 3.48005 18.5273 3.21875L18.5264 3.21777C18.2723 2.95072 17.8871 2.8125 17.3623 2.8125H6.63672C6.17751 2.81256 5.82275 2.91826 5.56641 3.12402L5.46289 3.21777C5.20816 3.47904 5.07715 3.85807 5.07715 4.3623V19.8555C5.07715 20.336 5.19802 20.7014 5.42969 20.9609L5.46289 20.9287L5.49805 20.8936L5.5332 20.9297H5.53418L5.56934 20.9658L5.5332 21.001L5.5 21.0332C5.76013 21.2713 6.13568 21.3954 6.63672 21.3955H11.625C12.1358 21.3957 12.5496 21.8095 12.5498 22.3203C12.5498 22.8313 12.1359 23.2459 11.625 23.2461H6.55469C5.53947 23.246 4.7368 23.0064 4.16992 22.5059L4.05957 22.4023C3.49544 21.8309 3.22658 20.9822 3.22656 19.8867V4.33105C3.22663 3.24207 3.49545 2.39422 4.05859 1.81641H4.05957L4.16895 1.71191C4.73581 1.20479 5.53881 0.961976 6.55469 0.961914H17.4443ZM17.3682 18.1484C17.2625 18.1485 17.1758 18.1703 17.1055 18.21L17.04 18.2559C16.9619 18.3244 16.9209 18.4175 16.9209 18.541C16.9209 18.6617 16.9604 18.7537 17.0361 18.8223L17.1006 18.8682C17.171 18.908 17.2583 18.9287 17.3643 18.9287H18.0039L18.5303 18.8613L18.6934 18.8398L18.5703 18.9482L17.9668 19.4795L16.5449 20.9004V20.9014C16.4462 20.9975 16.3985 21.1065 16.3984 21.2305C16.3984 21.3719 16.4428 21.4799 16.5273 21.5596H16.5264C16.6159 21.6414 16.723 21.6825 16.8496 21.6826C16.9174 21.6826 16.981 21.6706 17.04 21.6475L17.125 21.6025C17.1525 21.5839 17.1799 21.5613 17.2061 21.5352L18.6162 20.125L19.1416 19.5273L19.2451 19.4092L19.2285 19.5664L19.1689 20.1182V20.7168C19.169 20.8574 19.2064 20.9646 19.2754 21.0439L19.3311 21.0947C19.392 21.1378 19.4683 21.1592 19.5615 21.1592C19.6851 21.1591 19.7763 21.1186 19.8418 21.041H19.8428L19.8887 20.9756C19.9283 20.9057 19.9492 20.8201 19.9492 20.7168V18.6855C19.9492 18.5477 19.9209 18.4395 19.8682 18.3574L19.8076 18.2842C19.7144 18.196 19.5832 18.1484 19.4082 18.1484H17.3682ZM11.7949 12.7949C12.0155 12.7949 12.2056 12.87 12.3525 13.0244H12.3535C12.5057 13.1772 12.5811 13.3698 12.5811 13.5908C12.581 13.7495 12.5367 13.8931 12.4512 14.0186L12.3525 14.1377C12.2046 14.2856 12.0139 14.3564 11.7949 14.3564H8.11328C7.89437 14.3564 7.70227 14.2855 7.54883 14.1406L7.54297 14.1348C7.40022 13.9836 7.32815 13.7995 7.32812 13.5908C7.32812 13.3718 7.39806 13.1799 7.54297 13.0264L7.5459 13.0234L7.60547 12.9697C7.74785 12.8528 7.91945 12.795 8.11328 12.7949H11.7949ZM15.8965 9.21582C16.1111 9.21584 16.2969 9.29151 16.4424 9.44336L16.4961 9.5C16.6134 9.63628 16.6718 9.80257 16.6719 9.99121C16.6719 10.2068 16.5949 10.3944 16.4443 10.5459L16.4453 10.5469C16.2994 10.7008 16.1126 10.7773 15.8965 10.7773H8.11328C7.89178 10.7773 7.69883 10.7018 7.5459 10.5488V10.5479L7.54395 10.5459H7.54297C7.39907 10.3935 7.32812 10.2051 7.32812 9.99121C7.32821 9.77745 7.39921 9.59103 7.5459 9.44434L7.60547 9.39062C7.74779 9.27383 7.91955 9.2159 8.11328 9.21582H15.8965ZM15.8965 5.6377C16.1112 5.63772 16.2968 5.71321 16.4424 5.86523L16.4961 5.92188C16.6133 6.0582 16.6719 6.22441 16.6719 6.41309C16.6719 6.62878 16.595 6.81624 16.4443 6.96777L16.4453 6.96875C16.2994 7.12253 16.1125 7.19822 15.8965 7.19824H8.11328C7.89183 7.19815 7.69881 7.12357 7.5459 6.9707V6.96973L7.54297 6.9668C7.39925 6.81448 7.32815 6.62682 7.32812 6.41309C7.32812 6.19928 7.39928 6.01295 7.5459 5.86621C7.69883 5.71328 7.89178 5.63778 8.11328 5.6377H15.8965Z"
|
||||
fill="#3A3A3A"
|
||||
stroke="#3A3A3A"
|
||||
stroke-width="0.1"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -0,0 +1,30 @@
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M18.4661 8.93913C17.8524 8.93913 17.2752 8.82368 16.7344 8.59277C16.1997 8.35579 15.7257 8.03375 15.3125 7.62663C14.9054 7.21343 14.5833 6.73644 14.3464 6.19564C14.1155 5.65484 14 5.08062 14 4.47298C14 3.65267 14.2005 2.90527 14.6016 2.23079C15.0087 1.55632 15.5495 1.01855 16.224 0.617513C16.8984 0.210395 17.6458 0.00683594 18.4661 0.00683594C19.2804 0.00683594 20.0247 0.210395 20.6992 0.617513C21.3798 1.01855 21.9206 1.55632 22.3216 2.23079C22.7227 2.90527 22.9232 3.65267 22.9232 4.47298C22.9232 5.08062 22.8047 5.65484 22.5677 6.19564C22.3368 6.73036 22.0148 7.20432 21.6016 7.61751C21.1944 8.03071 20.7205 8.35579 20.1797 8.59277C19.6389 8.82368 19.0677 8.93913 18.4661 8.93913ZM18.4753 7.31673C18.6576 7.31673 18.8034 7.26204 18.9128 7.15267C19.0282 7.03722 19.0859 6.88835 19.0859 6.70606V5.08366H20.6992C20.8815 5.08366 21.0273 5.02897 21.1367 4.9196C21.2522 4.80415 21.3099 4.65527 21.3099 4.47298C21.3099 4.28461 21.2522 4.13574 21.1367 4.02637C21.0273 3.91699 20.8815 3.8623 20.6992 3.8623H19.0859V2.24902C19.0859 2.06673 19.0282 1.9209 18.9128 1.81152C18.8034 1.69607 18.6576 1.63835 18.4753 1.63835C18.2869 1.63835 18.135 1.69607 18.0195 1.81152C17.9102 1.9209 17.8555 2.06673 17.8555 2.24902V3.8623H16.2422C16.0599 3.8623 15.911 3.91699 15.7956 4.02637C15.6862 4.13574 15.6315 4.28461 15.6315 4.47298C15.6315 4.65527 15.6862 4.80415 15.7956 4.9196C15.911 5.02897 16.0599 5.08366 16.2422 5.08366H17.8555V6.70606C17.8555 6.88835 17.9102 7.03722 18.0195 7.15267C18.135 7.26204 18.2869 7.31673 18.4753 7.31673Z"
|
||||
fill="#3A3A3A"
|
||||
/>
|
||||
<path
|
||||
d="M20.7234 19.8862V9.53888C20.1803 9.77921 19.591 9.93419 18.9726 9.98682V19.8555C18.9726 20.3682 18.8358 20.7612 18.5624 21.0347C18.2958 21.3081 17.8959 21.4448 17.3627 21.4448H6.63711C6.10391 21.4448 5.70059 21.3081 5.42715 21.0347L5.49875 20.9649L5.49786 20.964L5.42715 21.0347C5.16055 20.7612 5.02725 20.3682 5.02725 19.8555V4.36181C5.02725 3.84911 5.16055 3.45605 5.42715 3.18261C5.70059 2.90234 6.10391 2.7622 6.63711 2.7622H13.2825C13.4981 2.11701 13.8301 1.52509 14.2535 1.01132H6.55508C5.47955 1.01132 4.65317 1.28555 4.09476 1.85112C3.5438 2.41639 3.27637 3.24944 3.27637 4.33105V19.8862C3.27637 20.9743 3.54365 21.8079 4.09521 22.3666C4.65367 22.9251 5.47988 23.1957 6.55508 23.1957H17.4447C18.5196 23.1957 19.3427 22.9252 19.8945 22.3664C20.4531 21.8078 20.7234 20.9746 20.7234 19.8862Z"
|
||||
fill="#3A3A3A"
|
||||
/>
|
||||
<path
|
||||
d="M13.6747 7.14833C13.4266 6.69614 13.2402 6.20528 13.1269 5.6871H8.11367C7.90437 5.6871 7.72456 5.75794 7.58154 5.90096C7.44495 6.03755 7.37793 6.21072 7.37793 6.41259C7.37793 6.61445 7.44486 6.78981 7.57954 6.93243L7.58154 6.93447C7.72456 7.0775 7.90437 7.14833 8.11367 7.14833H13.6747Z"
|
||||
fill="#3A3A3A"
|
||||
/>
|
||||
<path
|
||||
d="M16.5378 9.64649C16.2607 9.54063 15.9943 9.41301 15.7408 9.26572H8.11367C7.90437 9.26572 7.72456 9.33655 7.58154 9.47958C7.44495 9.61616 7.37793 9.78933 7.37793 9.99121C7.37793 10.1931 7.44486 10.3684 7.57954 10.511L7.58154 10.5131C7.72456 10.6561 7.90437 10.7269 8.11367 10.7269H15.8964C16.0996 10.7269 16.273 10.6555 16.409 10.5121C16.5506 10.37 16.6219 10.1944 16.6219 9.99121C16.6219 9.86401 16.5939 9.74847 16.5378 9.64649Z"
|
||||
fill="#3A3A3A"
|
||||
/>
|
||||
<path
|
||||
d="M7.57954 14.0999C7.44561 13.9581 7.37793 13.7865 7.37793 13.5903C7.37793 13.3827 7.44414 13.2036 7.57954 13.0602L7.58151 13.0582C7.72453 12.9151 7.90437 12.8443 8.11367 12.8443H11.7948C12.0036 12.8443 12.1809 12.9149 12.3177 13.0592C12.4601 13.2021 12.5306 13.3815 12.5306 13.5903C12.5306 13.7883 12.4581 13.9605 12.3167 14.102C12.1794 14.2393 12.0024 14.3056 11.7948 14.3056H8.11367C7.90609 14.3056 7.72695 14.2393 7.58358 14.1039L7.57954 14.0999Z"
|
||||
fill="#3A3A3A"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg viewBox="0 0 12 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M3.40918 4.70117C3.28613 4.70117 3.18359 4.66016 3.10156 4.57812C3.02409 4.49609 2.98535 4.39583 2.98535 4.27734C2.98535 4.15885 3.02409 4.06087 3.10156 3.9834C3.18359 3.90137 3.28613 3.86035 3.40918 3.86035H8.59766C8.71615 3.86035 8.81413 3.90137 8.8916 3.9834C8.97363 4.06087 9.01465 4.15885 9.01465 4.27734C9.01465 4.39583 8.97363 4.49609 8.8916 4.57812C8.81413 4.66016 8.71615 4.70117 8.59766 4.70117H3.40918ZM3.40918 7.08691C3.28613 7.08691 3.18359 7.0459 3.10156 6.96387C3.02409 6.88184 2.98535 6.78158 2.98535 6.66309C2.98535 6.5446 3.02409 6.44661 3.10156 6.36914C3.18359 6.28711 3.28613 6.24609 3.40918 6.24609H8.59766C8.71615 6.24609 8.81413 6.28711 8.8916 6.36914C8.97363 6.44661 9.01465 6.5446 9.01465 6.66309C9.01465 6.78158 8.97363 6.88184 8.8916 6.96387C8.81413 7.0459 8.71615 7.08691 8.59766 7.08691H3.40918ZM3.40918 9.47266C3.28613 9.47266 3.18359 9.43392 3.10156 9.35645C3.02409 9.27441 2.98535 9.17643 2.98535 9.0625C2.98535 8.93945 3.02409 8.83691 3.10156 8.75488C3.18359 8.67285 3.28613 8.63184 3.40918 8.63184H5.86328C5.98633 8.63184 6.08659 8.67285 6.16406 8.75488C6.24609 8.83691 6.28711 8.93945 6.28711 9.0625C6.28711 9.17643 6.24609 9.27441 6.16406 9.35645C6.08659 9.43392 5.98633 9.47266 5.86328 9.47266H3.40918ZM0.250977 13.2598V2.88965C0.250977 2.17871 0.426432 1.64323 0.777344 1.2832C1.13281 0.923177 1.66374 0.743164 2.37012 0.743164H9.62988C10.3363 0.743164 10.8649 0.923177 11.2158 1.2832C11.5713 1.64323 11.749 2.17871 11.749 2.88965V13.2598C11.749 13.9753 11.5713 14.5107 11.2158 14.8662C10.8649 15.2217 10.3363 15.3994 9.62988 15.3994H2.37012C1.66374 15.3994 1.13281 15.2217 0.777344 14.8662C0.426432 14.5107 0.250977 13.9753 0.250977 13.2598ZM1.35156 13.2393C1.35156 13.5811 1.44043 13.8431 1.61816 14.0254C1.80046 14.2077 2.06934 14.2988 2.4248 14.2988H9.5752C9.93066 14.2988 10.1973 14.2077 10.375 14.0254C10.5573 13.8431 10.6484 13.5811 10.6484 13.2393V2.91016C10.6484 2.56836 10.5573 2.30632 10.375 2.12402C10.1973 1.93717 9.93066 1.84375 9.5752 1.84375H2.4248C2.06934 1.84375 1.80046 1.93717 1.61816 2.12402C1.44043 2.30632 1.35156 2.56836 1.35156 2.91016V13.2393Z"
|
||||
fill="#8585F6"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -2,6 +2,7 @@ import { codeBlock } from '@blocknote/code-block';
|
||||
import {
|
||||
BlockNoteSchema,
|
||||
defaultBlockSpecs,
|
||||
defaultInlineContentSpecs,
|
||||
withPageBreak,
|
||||
} from '@blocknote/core';
|
||||
import '@blocknote/core/fonts/inter.css';
|
||||
@@ -10,6 +11,7 @@ import { BlockNoteView } from '@blocknote/mantine';
|
||||
import '@blocknote/mantine/style.css';
|
||||
import { useCreateBlockNote } from '@blocknote/react';
|
||||
import { HocuspocusProvider } from '@hocuspocus/provider';
|
||||
import { createDividerBlock } from 'package-docs';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import * as Y from 'yjs';
|
||||
@@ -18,8 +20,13 @@ import { Box, TextErrors } from '@/components';
|
||||
import { Doc, useIsCollaborativeEditable } from '@/docs/doc-management';
|
||||
import { useAuth } from '@/features/auth';
|
||||
|
||||
import { useHeadings, useUploadFile, useUploadStatus } from '../hook/';
|
||||
import useSaveDoc from '../hook/useSaveDoc';
|
||||
import {
|
||||
useHeadings,
|
||||
useSaveDoc,
|
||||
useShortcuts,
|
||||
useUploadFile,
|
||||
useUploadStatus,
|
||||
} from '../hook';
|
||||
import { useEditorStore } from '../stores';
|
||||
import { cssEditor } from '../styles';
|
||||
import { DocsBlockNoteEditor } from '../types';
|
||||
@@ -27,18 +34,37 @@ import { randomColor } from '../utils';
|
||||
|
||||
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
|
||||
import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar';
|
||||
import { CalloutBlock, DividerBlock } from './custom-blocks';
|
||||
import { CalloutBlock } from './custom-blocks';
|
||||
import {
|
||||
InterlinkingLinkInlineContent,
|
||||
InterlinkingSearchInlineContent,
|
||||
} from './custom-inline-content';
|
||||
import XLMultiColumn from './xl-multi-column';
|
||||
|
||||
export const blockNoteSchema = withPageBreak(
|
||||
const multiColumnDropCursor = XLMultiColumn?.multiColumnDropCursor;
|
||||
const multiColumnLocales = XLMultiColumn?.locales;
|
||||
const withMultiColumn = XLMultiColumn?.withMultiColumn;
|
||||
|
||||
const baseBlockNoteSchema = withPageBreak(
|
||||
BlockNoteSchema.create({
|
||||
blockSpecs: {
|
||||
...defaultBlockSpecs,
|
||||
callout: CalloutBlock,
|
||||
divider: DividerBlock,
|
||||
divider: createDividerBlock({
|
||||
color: 'var(--c--theme--colors--greyscale-300)',
|
||||
}),
|
||||
},
|
||||
inlineContentSpecs: {
|
||||
...defaultInlineContentSpecs,
|
||||
interlinkingSearchInline: InterlinkingSearchInlineContent,
|
||||
interlinkingLinkInline: InterlinkingLinkInlineContent,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const blockNoteSchema = (withMultiColumn?.(baseBlockNoteSchema) ||
|
||||
baseBlockNoteSchema) as typeof baseBlockNoteSchema;
|
||||
|
||||
interface BlockNoteEditorProps {
|
||||
doc: Doc;
|
||||
provider: HocuspocusProvider;
|
||||
@@ -116,7 +142,11 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
||||
},
|
||||
showCursorLabels: showCursorLabels as 'always' | 'activity',
|
||||
},
|
||||
dictionary: locales[lang as keyof typeof locales],
|
||||
dictionary: {
|
||||
...locales[lang as keyof typeof locales],
|
||||
multi_column:
|
||||
multiColumnLocales?.[lang as keyof typeof multiColumnLocales],
|
||||
},
|
||||
tables: {
|
||||
splitCells: true,
|
||||
cellBackgroundColor: true,
|
||||
@@ -125,11 +155,13 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
||||
},
|
||||
uploadFile,
|
||||
schema: blockNoteSchema,
|
||||
dropCursor: multiColumnDropCursor,
|
||||
},
|
||||
[collabName, lang, provider, uploadFile],
|
||||
);
|
||||
|
||||
useHeadings(editor);
|
||||
useShortcuts(editor);
|
||||
useUploadStatus(editor);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user