Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eb9487d5d7 | |||
| da40a84a51 | |||
| 48cacf4c99 | |||
| cfa11c5b35 | |||
| 2e26ad2b09 | |||
| dd742d9e6d | |||
| a0c56816f2 | |||
| 93def378a9 | |||
| 6224e98774 | |||
| c438d4f081 | |||
| 6188427b58 | |||
| 1d087726fc | |||
| b852d19f43 | |||
| a7a00a3087 | |||
| 8ae112b43b | |||
| e72317fbaa | |||
| 507c9a583a | |||
| d7ff740213 | |||
| 4a44b44aec | |||
| 94c372ea6e | |||
| 45ab75d638 | |||
| c807aded5c | |||
| ad7a5235e1 | |||
| 62ff585539 | |||
| 8c6bf045cc | |||
| 8e7651d5cd | |||
| 12a229e0ab | |||
| a3e3ede1f8 | |||
| 46ac2e11ed | |||
| e0d6271677 | |||
| 9e9efa197a | |||
| 9b29a0b693 | |||
| b85f1d1737 | |||
| 4474fc8ddb | |||
| 2f980e880a | |||
| 6edb92ee62 | |||
| ee8f61a858 | |||
| 40785f381a | |||
| 149f711f60 | |||
| b5dad9eb1a | |||
| 8c714bfdc6 | |||
| 55ea7f8375 | |||
| abddb4d585 |
@@ -6,6 +6,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'feature/doc-dnd'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
|
||||
+17
-30
@@ -8,40 +8,18 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.4.1] - 2025-07-15
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🌐(frontend) keep simple tag during export #1154
|
||||
- 🐛(back) manage can-edit endpoint without created room
|
||||
in the ws #1152
|
||||
- 🐛(frontend) fix action buttons not clickable #1162
|
||||
- 🐛(frontend) fix crash share modal on grid options #1174
|
||||
|
||||
## [3.4.0] - 2025-07-09
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(frontend) multi-pages #701
|
||||
- ✨(frontend) Duplicate a doc #1078
|
||||
- ✨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) 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
|
||||
attachment upload #1099
|
||||
- ✨(doc) add documentation to install with compose #855
|
||||
- ✨ Give priority to users connected to collaboration server
|
||||
(aka no websocket feature) #1093
|
||||
- (doc) add documentation to install with compose #855
|
||||
- ✨(backend) allow to disable checking unsafe mimetype on attachment upload
|
||||
- ✨Ask for access #1081
|
||||
|
||||
### Changed
|
||||
|
||||
- ♻️(backend) stop requiring owner for non-root documents #846
|
||||
- ♻️(backend) simplify roles by ranking them and return only the max role #846
|
||||
- 📌(yjs) stop pinning node to minor version on yjs docker image #1005
|
||||
- 🧑💻(docker) add .next to .dockerignore #1055
|
||||
- 🧑💻(docker) handle frontend development images with docker compose #1033
|
||||
@@ -50,13 +28,13 @@ and this project adheres to
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(backend) fix link definition select options linked to ancestors #846
|
||||
- 🐛(frontend) table of content disappearing #982
|
||||
- 🐛(frontend) fix multiple EmojiPicker #1012
|
||||
- 🐛(frontend) fix meta title #1017
|
||||
- 🔧(git) set LF line endings for all text files #1032
|
||||
- 📝(docs) minor fixes to docs/env.md
|
||||
- ✨support `_FILE` environment variables for secrets #912
|
||||
- ✨(backend) support `_FILE` environment variables for secrets #912
|
||||
- ✨(frontend) support `_FILE` environment variables for secrets #912
|
||||
|
||||
### Removed
|
||||
|
||||
@@ -107,6 +85,9 @@ and this project adheres to
|
||||
|
||||
## Added
|
||||
|
||||
- ✨(frontend) multi-pages #701
|
||||
- ✨(backend) include ancestors accesses on document accesses list view #846
|
||||
- ✨(backend) add ancestors links reach and role to document API #846
|
||||
- 🚸(backend) make document search on title accent-insensitive #874
|
||||
- 🚩 add homepage feature flag #861
|
||||
- 📝(doc) update contributing policy (commit signatures are now mandatory) #895
|
||||
@@ -118,23 +99,30 @@ and this project adheres to
|
||||
|
||||
## Changed
|
||||
|
||||
- ♻️(backend) stop requiring owner for non-root documents #846
|
||||
- ♻️(backend) simplify roles by ranking them and return only the max role #846
|
||||
- ⚡️(frontend) reduce unblocking time for config #867
|
||||
- ♻️(frontend) bind UI with ability access #900
|
||||
- ♻️(frontend) use built-in Quote block #908
|
||||
|
||||
## Fixed
|
||||
|
||||
- 🐛(backend) fix link definition select options linked to ancestors #846
|
||||
- 🐛(nginx) fix 404 when accessing a doc #866
|
||||
- 🔒️(drf) disable browsable HTML API renderer #919
|
||||
- 🔒(frontend) enhance file download security #889
|
||||
- 🐛(backend) race condition create doc #633
|
||||
- 🐛(frontend) fix breaklines in custom blocks #908
|
||||
|
||||
## Fixed
|
||||
|
||||
- 🐛(backend) fix link definition select options linked to ancestors #846
|
||||
|
||||
## [3.1.0] - 2025-04-07
|
||||
|
||||
## Added
|
||||
|
||||
- ✨(backend) add ancestors links definitions to document abilities #846
|
||||
- 🚩(backend) add feature flag for the footer #841
|
||||
- 🔧(backend) add view to manage footer json #841
|
||||
- ✨(frontend) add custom css style #771
|
||||
@@ -146,6 +134,7 @@ and this project adheres to
|
||||
|
||||
## Fixed
|
||||
|
||||
- 🐛(backend) fix link definition select options linked to ancestors #846
|
||||
- 🐛(back) validate document content in serializer #822
|
||||
- 🐛(frontend) fix selection click past end of content #840
|
||||
|
||||
@@ -646,9 +635,7 @@ 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
|
||||
[unreleased]: https://github.com/numerique-gouv/impress/compare/v3.3.0...main
|
||||
[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
|
||||
|
||||
@@ -34,6 +34,8 @@ Docs, where your notes can become knowledge through live collaboration.
|
||||
## Why use Docs ❓
|
||||
Docs is a collaborative text editor designed to address common challenges in knowledge building and sharing.
|
||||
|
||||
It offers a scalable and secure alternative to tools such as Google Docs, Notion (without the dbs), Outline, or Confluence.
|
||||
|
||||
### Write
|
||||
* 😌 Get simple, accessible online editing for your team.
|
||||
* 💅 Create clean documents with beautiful formatting options.
|
||||
|
||||
+98
-98
@@ -6,104 +6,104 @@ Here we describe all environment variables that can be set for the docs applicat
|
||||
|
||||
These are the environment variables you can set for the `impress-backend` container.
|
||||
|
||||
| Option | Description | default |
|
||||
|-------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
|
||||
| AI_ALLOW_REACH_FROM | Users that can use AI must be this level. options are "public", "authenticated", "restricted" | authenticated |
|
||||
| AI_API_KEY | AI key to be used for AI Base url | |
|
||||
| AI_BASE_URL | OpenAI compatible AI base url | |
|
||||
| AI_FEATURE_ENABLED | Enable AI options | false |
|
||||
| AI_MODEL | AI Model to use | |
|
||||
| ALLOW_LOGOUT_GET_METHOD | Allow get logout method | true |
|
||||
| API_USERS_LIST_LIMIT | Limit on API users | 5 |
|
||||
| API_USERS_LIST_THROTTLE_RATE_BURST | Throttle rate for api on burst | 30/minute |
|
||||
| API_USERS_LIST_THROTTLE_RATE_SUSTAINED | Throttle rate for api | 180/hour |
|
||||
| AWS_S3_ACCESS_KEY_ID | Access id for s3 endpoint | |
|
||||
| AWS_S3_ENDPOINT_URL | S3 endpoint | |
|
||||
| AWS_S3_REGION_NAME | Region name for s3 endpoint | |
|
||||
| AWS_S3_SECRET_ACCESS_KEY | Access key for s3 endpoint | |
|
||||
| AWS_STORAGE_BUCKET_NAME | Bucket name for s3 endpoint | impress-media-storage |
|
||||
| CACHES_DEFAULT_TIMEOUT | Cache default timeout | 30 |
|
||||
| CACHES_KEY_PREFIX | The prefix used to every cache keys. | docs |
|
||||
| COLLABORATION_API_URL | Collaboration api host | |
|
||||
| COLLABORATION_SERVER_SECRET | Collaboration api secret | |
|
||||
| COLLABORATION_WS_NOT_CONNECTED_READY_ONLY | Users not connected to the collaboration server cannot edit | false |
|
||||
| COLLABORATION_WS_URL | Collaboration websocket url | |
|
||||
| CONVERSION_API_CONTENT_FIELD | Conversion api content field | content |
|
||||
| CONVERSION_API_ENDPOINT | Conversion API endpoint | convert |
|
||||
| CONVERSION_API_SECURE | Require secure conversion api | false |
|
||||
| CONVERSION_API_TIMEOUT | Conversion api timeout | 30 |
|
||||
| CRISP_WEBSITE_ID | Crisp website id for support | |
|
||||
| DB_ENGINE | Engine to use for database connections | django.db.backends.postgresql_psycopg2 |
|
||||
| DB_HOST | Host of the database | localhost |
|
||||
| DB_NAME | Name of the database | impress |
|
||||
| DB_PASSWORD | Password to authenticate with | pass |
|
||||
| DB_PORT | Port of the database | 5432 |
|
||||
| DB_USER | User to authenticate with | dinum |
|
||||
| DJANGO_ALLOWED_HOSTS | Allowed hosts | [] |
|
||||
| DJANGO_CELERY_BROKER_TRANSPORT_OPTIONS | Celery broker transport options | {} |
|
||||
| DJANGO_CELERY_BROKER_URL | Celery broker url | redis://redis:6379/0 |
|
||||
| DJANGO_CORS_ALLOW_ALL_ORIGINS | Allow all CORS origins | false |
|
||||
| DJANGO_CORS_ALLOWED_ORIGIN_REGEXES | List of origins allowed for CORS using regulair expressions | [] |
|
||||
| DJANGO_CORS_ALLOWED_ORIGINS | List of origins allowed for CORS | [] |
|
||||
| DJANGO_CSRF_TRUSTED_ORIGINS | CSRF trusted origins | [] |
|
||||
| DJANGO_EMAIL_BACKEND | Email backend library | django.core.mail.backends.smtp.EmailBackend |
|
||||
| DJANGO_EMAIL_BRAND_NAME | Brand name for email | |
|
||||
| DJANGO_EMAIL_FROM | Email address used as sender | from@example.com |
|
||||
| DJANGO_EMAIL_HOST | Hostname of email | |
|
||||
| DJANGO_EMAIL_HOST_PASSWORD | Password to authenticate with on the email host | |
|
||||
| DJANGO_EMAIL_HOST_USER | User to authenticate with on the email host | |
|
||||
| DJANGO_EMAIL_LOGO_IMG | Logo for the email | |
|
||||
| DJANGO_EMAIL_PORT | Port used to connect to email host | |
|
||||
| DJANGO_EMAIL_USE_SSL | Use ssl for email host connection | false |
|
||||
| DJANGO_EMAIL_USE_TLS | Use tls for email host connection | false |
|
||||
| DJANGO_SECRET_KEY | Secret key | |
|
||||
| DJANGO_SERVER_TO_SERVER_API_TOKENS | | [] |
|
||||
| DOCUMENT_IMAGE_MAX_SIZE | Maximum size of document in bytes | 10485760 |
|
||||
| FRONTEND_CSS_URL | To add a external css file to the app | |
|
||||
| FRONTEND_HOMEPAGE_FEATURE_ENABLED | Frontend feature flag to display the homepage | false |
|
||||
| FRONTEND_THEME | Frontend theme to use | |
|
||||
| LANGUAGE_CODE | Default language | en-us |
|
||||
| LOGGING_LEVEL_LOGGERS_APP | Application logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO |
|
||||
| LOGGING_LEVEL_LOGGERS_ROOT | Default logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO |
|
||||
| LOGIN_REDIRECT_URL | Login redirect url | |
|
||||
| LOGIN_REDIRECT_URL_FAILURE | Login redirect url on failure | |
|
||||
| LOGOUT_REDIRECT_URL | Logout redirect url | |
|
||||
| MALWARE_DETECTION_BACKEND | The malware detection backend use from the django-lasuite package | lasuite.malware_detection.backends.dummy.DummyBackend |
|
||||
| MALWARE_DETECTION_PARAMETERS | A dict containing all the parameters to initiate the malware detection backend | {"callback_path": "core.malware_detection.malware_detection_callback",} |
|
||||
| MEDIA_BASE_URL | | |
|
||||
| NO_WEBSOCKET_CACHE_TIMEOUT | Cache used to store current editor session key when only users without websocket are editing a document | 120 |
|
||||
| OIDC_ALLOW_DUPLICATE_EMAILS | Allow duplicate emails | false |
|
||||
| OIDC_AUTH_REQUEST_EXTRA_PARAMS | OIDC extra auth parameters | {} |
|
||||
| OIDC_CREATE_USER | Create used on OIDC | false |
|
||||
| OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION | Fallback to email for identification | true |
|
||||
| OIDC_OP_AUTHORIZATION_ENDPOINT | Authorization endpoint for OIDC | |
|
||||
| OIDC_OP_JWKS_ENDPOINT | JWKS endpoint for OIDC | |
|
||||
| OIDC_OP_LOGOUT_ENDPOINT | Logout endpoint for OIDC | |
|
||||
| OIDC_OP_TOKEN_ENDPOINT | Token endpoint for OIDC | |
|
||||
| OIDC_OP_USER_ENDPOINT | User endpoint for OIDC | |
|
||||
| OIDC_REDIRECT_ALLOWED_HOSTS | Allowed hosts for OIDC redirect url | [] |
|
||||
| OIDC_REDIRECT_REQUIRE_HTTPS | Require https for OIDC redirect url | false |
|
||||
| OIDC_RP_CLIENT_ID | Client id used for OIDC | impress |
|
||||
| OIDC_RP_CLIENT_SECRET | Client secret used for OIDC | |
|
||||
| OIDC_RP_SCOPES | Scopes requested for OIDC | openid email |
|
||||
| OIDC_RP_SIGN_ALGO | verification algorithm used OIDC tokens | RS256 |
|
||||
| OIDC_STORE_ID_TOKEN | Store OIDC token | true |
|
||||
| OIDC_USE_NONCE | Use nonce for OIDC | true |
|
||||
| OIDC_USERINFO_FULLNAME_FIELDS | OIDC token claims to create full name | ["first_name", "last_name"] |
|
||||
| OIDC_USERINFO_SHORTNAME_FIELD | OIDC token claims to create shortname | first_name |
|
||||
| POSTHOG_KEY | Posthog key for analytics | |
|
||||
| REDIS_URL | Cache url | redis://redis:6379/1 |
|
||||
| SENTRY_DSN | Sentry host | |
|
||||
| SESSION_COOKIE_AGE | duration of the cookie session | 60*60*12 |
|
||||
| SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK | | false |
|
||||
| STORAGES_STATICFILES_BACKEND | | whitenoise.storage.CompressedManifestStaticFilesStorage |
|
||||
| THEME_CUSTOMIZATION_CACHE_TIMEOUT | Cache duration for the customization settings | 86400 |
|
||||
| THEME_CUSTOMIZATION_FILE_PATH | Full path to the file customizing the theme. An example is provided in src/backend/impress/configuration/theme/default.json | BASE_DIR/impress/configuration/theme/default.json |
|
||||
| TRASHBIN_CUTOFF_DAYS | Trashbin cutoff | 30 |
|
||||
| USER_OIDC_ESSENTIAL_CLAIMS | Essential claims in OIDC token | [] |
|
||||
| Y_PROVIDER_API_BASE_URL | Y Provider url | |
|
||||
| Y_PROVIDER_API_KEY | Y provider API key | |
|
||||
|
||||
| Option | Description | default |
|
||||
| ----------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| AI_ALLOW_REACH_FROM | Users that can use AI must be this level. options are "public", "authenticated", "restricted" | authenticated |
|
||||
| AI_API_KEY | AI key to be used for AI Base url | |
|
||||
| AI_BASE_URL | OpenAI compatible AI base url | |
|
||||
| AI_FEATURE_ENABLED | Enable AI options | false |
|
||||
| AI_MODEL | AI Model to use | |
|
||||
| ALLOW_LOGOUT_GET_METHOD | Allow get logout method | true |
|
||||
| API_USERS_LIST_LIMIT | Limit on API users | 5 |
|
||||
| API_USERS_LIST_THROTTLE_RATE_BURST | throttle rate for api on burst | 30/minute |
|
||||
| API_USERS_LIST_THROTTLE_RATE_SUSTAINED | throttle rate for api | 180/hour |
|
||||
| AWS_S3_ACCESS_KEY_ID | access id for s3 endpoint | |
|
||||
| AWS_S3_ENDPOINT_URL | S3 endpoint | |
|
||||
| AWS_S3_REGION_NAME | region name for s3 endpoint | |
|
||||
| AWS_S3_SECRET_ACCESS_KEY | access key for s3 endpoint | |
|
||||
| AWS_STORAGE_BUCKET_NAME | bucket name for s3 endpoint | impress-media-storage |
|
||||
| CACHES_DEFAULT_TIMEOUT | cache default timeout | 30 |
|
||||
| CACHES_KEY_PREFIX | The prefix used to every cache keys. | docs |
|
||||
| COLLABORATION_API_URL | collaboration api host | |
|
||||
| COLLABORATION_SERVER_SECRET | collaboration api secret | |
|
||||
| COLLABORATION_WS_NOT_CONNECTED_READY_ONLY | Users not connected to the collaboration server cannot edit | false |
|
||||
| COLLABORATION_WS_URL | collaboration websocket url | |
|
||||
| CONVERSION_API_CONTENT_FIELD | Conversion api content field | content |
|
||||
| CONVERSION_API_ENDPOINT | Conversion API endpoint | convert-markdown |
|
||||
| CONVERSION_API_SECURE | Require secure conversion api | false |
|
||||
| CONVERSION_API_TIMEOUT | Conversion api timeout | 30 |
|
||||
| CONTENT_SECURITY_POLICY_DIRECTIVES | A dict of directives set in the Content-Security-Policy header | All directives are set to 'none' |
|
||||
| CONTENT_SECURITY_POLICY_EXCLUDE_URL_PREFIXES | Url with this prefix will not have the header Content-Security-Policy included | |
|
||||
| CRISP_WEBSITE_ID | crisp website id for support | |
|
||||
| DB_ENGINE | engine to use for database connections | django.db.backends.postgresql_psycopg2 |
|
||||
| DB_HOST | host of the database | localhost |
|
||||
| DB_NAME | name of the database | impress |
|
||||
| DB_PASSWORD | password to authenticate with | pass |
|
||||
| DB_PORT | port of the database | 5432 |
|
||||
| DB_USER | user to authenticate with | dinum |
|
||||
| DJANGO_ALLOWED_HOSTS | allowed hosts | [] |
|
||||
| DJANGO_CELERY_BROKER_TRANSPORT_OPTIONS | celery broker transport options | {} |
|
||||
| DJANGO_CELERY_BROKER_URL | celery broker url | redis://redis:6379/0 |
|
||||
| DJANGO_CORS_ALLOW_ALL_ORIGINS | allow all CORS origins | false |
|
||||
| DJANGO_CORS_ALLOWED_ORIGIN_REGEXES | list of origins allowed for CORS using regulair expressions | [] |
|
||||
| DJANGO_CORS_ALLOWED_ORIGINS | list of origins allowed for CORS | [] |
|
||||
| DJANGO_CSRF_TRUSTED_ORIGINS | CSRF trusted origins | [] |
|
||||
| DJANGO_EMAIL_BACKEND | email backend library | django.core.mail.backends.smtp.EmailBackend |
|
||||
| DJANGO_EMAIL_BRAND_NAME | brand name for email | |
|
||||
| DJANGO_EMAIL_FROM | email address used as sender | from@example.com |
|
||||
| DJANGO_EMAIL_HOST | host name of email | |
|
||||
| DJANGO_EMAIL_HOST_PASSWORD | password to authenticate with on the email host | |
|
||||
| DJANGO_EMAIL_HOST_USER | user to authenticate with on the email host | |
|
||||
| DJANGO_EMAIL_LOGO_IMG | logo for the email | |
|
||||
| DJANGO_EMAIL_PORT | port used to connect to email host | |
|
||||
| DJANGO_EMAIL_USE_SSL | use sstl for email host connection | false |
|
||||
| DJANGO_EMAIL_USE_TLS | use tls for email host connection | false |
|
||||
| DJANGO_SECRET_KEY | secret key | |
|
||||
| DJANGO_SERVER_TO_SERVER_API_TOKENS | | [] |
|
||||
| DOCUMENT_IMAGE_MAX_SIZE | maximum size of document in bytes | 10485760 |
|
||||
| FRONTEND_CSS_URL | To add a external css file to the app | |
|
||||
| FRONTEND_HOMEPAGE_FEATURE_ENABLED | frontend feature flag to display the homepage | false |
|
||||
| FRONTEND_THEME | frontend theme to use | |
|
||||
| LANGUAGE_CODE | default language | en-us |
|
||||
| LOGGING_LEVEL_LOGGERS_APP | application logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO |
|
||||
| LOGGING_LEVEL_LOGGERS_ROOT | default logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO |
|
||||
| LOGIN_REDIRECT_URL | login redirect url | |
|
||||
| LOGIN_REDIRECT_URL_FAILURE | login redirect url on failure | |
|
||||
| LOGOUT_REDIRECT_URL | logout redirect url | |
|
||||
| MALWARE_DETECTION_BACKEND | The malware detection backend use from the django-lasuite package | lasuite.malware_detection.backends.dummy.DummyBackend |
|
||||
| MALWARE_DETECTION_PARAMETERS | A dict containing all the parameters to initiate the malware detection backend | {"callback_path": "core.malware_detection.malware_detection_callback",} |
|
||||
| MEDIA_BASE_URL | | |
|
||||
| OIDC_ALLOW_DUPLICATE_EMAILS | Allow duplicate emails | false |
|
||||
| OIDC_AUTH_REQUEST_EXTRA_PARAMS | OIDC extra auth parameters | {} |
|
||||
| OIDC_CREATE_USER | create used on OIDC | false |
|
||||
| OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION | faillback to email for identification | true |
|
||||
| OIDC_OP_AUTHORIZATION_ENDPOINT | Authorization endpoint for OIDC | |
|
||||
| OIDC_OP_JWKS_ENDPOINT | JWKS endpoint for OIDC | |
|
||||
| OIDC_OP_LOGOUT_ENDPOINT | Logout endpoint for OIDC | |
|
||||
| OIDC_OP_TOKEN_ENDPOINT | Token endpoint for OIDC | |
|
||||
| OIDC_OP_USER_ENDPOINT | User endpoint for OIDC | |
|
||||
| OIDC_REDIRECT_ALLOWED_HOSTS | Allowed hosts for OIDC redirect url | [] |
|
||||
| OIDC_REDIRECT_REQUIRE_HTTPS | Require https for OIDC redirect url | false |
|
||||
| OIDC_RP_CLIENT_ID | client id used for OIDC | impress |
|
||||
| OIDC_RP_CLIENT_SECRET | client secret used for OIDC | |
|
||||
| OIDC_RP_SCOPES | scopes requested for OIDC | openid email |
|
||||
| OIDC_RP_SIGN_ALGO | verification algorithm used OIDC tokens | RS256 |
|
||||
| OIDC_STORE_ID_TOKEN | Store OIDC token | true |
|
||||
| OIDC_USE_NONCE | use nonce for OIDC | true |
|
||||
| OIDC_USERINFO_FULLNAME_FIELDS | OIDC token claims to create full name | ["first_name", "last_name"] |
|
||||
| OIDC_USERINFO_SHORTNAME_FIELD | OIDC token claims to create shortname | first_name |
|
||||
| POSTHOG_KEY | posthog key for analytics | |
|
||||
| REDIS_URL | cache url | redis://redis:6379/1 |
|
||||
| SENTRY_DSN | sentry host | |
|
||||
| SESSION_COOKIE_AGE | duration of the cookie session | 60*60*12 |
|
||||
| SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK | | false |
|
||||
| STORAGES_STATICFILES_BACKEND | | whitenoise.storage.CompressedManifestStaticFilesStorage |
|
||||
| THEME_CUSTOMIZATION_CACHE_TIMEOUT | Cache duration for the customization settings | 86400 |
|
||||
| THEME_CUSTOMIZATION_FILE_PATH | full path to the file customizing the theme. An example is provided in src/backend/impress/configuration/theme/default.json | BASE_DIR/impress/configuration/theme/default.json |
|
||||
| TRASHBIN_CUTOFF_DAYS | trashbin cutoff | 30 |
|
||||
| USER_OIDC_ESSENTIAL_CLAIMS | essential claims in OIDC token | [] |
|
||||
| Y_PROVIDER_API_BASE_URL | Y Provider url | |
|
||||
| Y_PROVIDER_API_KEY | Y provider API key | |
|
||||
|
||||
## impress-frontend image
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ COLLABORATION_API_URL=http://y-provider-development:4444/collaboration/api/
|
||||
COLLABORATION_BACKEND_BASE_URL=http://app-dev:8000
|
||||
COLLABORATION_SERVER_ORIGIN=http://localhost:3000
|
||||
COLLABORATION_SERVER_SECRET=my-secret
|
||||
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY=true
|
||||
COLLABORATION_WS_URL=ws://localhost:4444/collaboration/ws/
|
||||
|
||||
DJANGO_SERVER_TO_SERVER_API_TOKENS=server-api-token
|
||||
|
||||
@@ -3,3 +3,7 @@ BURST_THROTTLE_RATES="200/minute"
|
||||
COLLABORATION_API_URL=http://y-provider:4444/collaboration/api/
|
||||
SUSTAINED_THROTTLE_RATES="200/hour"
|
||||
Y_PROVIDER_API_BASE_URL=http://y-provider:4444/api/
|
||||
THEME_CUSTOMIZATION_FILE_PATH="" #force theme_customization to be empty
|
||||
#COLLABORATION_API_URL=http://y-provider:4444/collaboration/api/
|
||||
#Y_PROVIDER_API_BASE_URL=http://y-provider:4444/api/
|
||||
|
||||
|
||||
@@ -159,7 +159,6 @@ class DocumentSerializer(ListDocumentSerializer):
|
||||
"""Serialize documents with all fields for display in detail views."""
|
||||
|
||||
content = serializers.CharField(required=False)
|
||||
websocket = serializers.BooleanField(required=False, write_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.Document
|
||||
@@ -185,7 +184,6 @@ class DocumentSerializer(ListDocumentSerializer):
|
||||
"title",
|
||||
"updated_at",
|
||||
"user_role",
|
||||
"websocket",
|
||||
]
|
||||
read_only_fields = [
|
||||
"id",
|
||||
@@ -431,7 +429,9 @@ class ServerCreateDocumentSerializer(serializers.Serializer):
|
||||
language = user.language or language
|
||||
|
||||
try:
|
||||
document_content = YdocConverter().convert(validated_data["content"])
|
||||
document_content = YdocConverter().convert_markdown(
|
||||
validated_data["content"]
|
||||
)
|
||||
except ConversionError as err:
|
||||
raise serializers.ValidationError(
|
||||
{"content": ["Could not convert content"]}
|
||||
|
||||
@@ -513,83 +513,6 @@ class DocumentViewSet(
|
||||
"""Override to implement a soft delete instead of dumping the record in database."""
|
||||
instance.soft_delete()
|
||||
|
||||
def _can_user_edit_document(self, document_id, set_cache=False):
|
||||
"""Check if the user can edit the document."""
|
||||
try:
|
||||
count, exists = CollaborationService().get_document_connection_info(
|
||||
document_id,
|
||||
self.request.session.session_key,
|
||||
)
|
||||
except requests.HTTPError as e:
|
||||
logger.exception("Failed to call collaboration server: %s", e)
|
||||
count = 0
|
||||
exists = False
|
||||
|
||||
if count == 0:
|
||||
# Nobody is connected to the websocket server
|
||||
logger.debug("update without connection found in the websocket server")
|
||||
cache_key = f"docs:no-websocket:{document_id}"
|
||||
current_editor = cache.get(cache_key)
|
||||
|
||||
if not current_editor:
|
||||
if set_cache:
|
||||
cache.set(
|
||||
cache_key,
|
||||
self.request.session.session_key,
|
||||
settings.NO_WEBSOCKET_CACHE_TIMEOUT,
|
||||
)
|
||||
return True
|
||||
|
||||
if current_editor != self.request.session.session_key:
|
||||
return False
|
||||
|
||||
if set_cache:
|
||||
cache.touch(cache_key, settings.NO_WEBSOCKET_CACHE_TIMEOUT)
|
||||
return True
|
||||
|
||||
if exists:
|
||||
# Current user is connected to the websocket server
|
||||
logger.debug("session key found in the websocket server")
|
||||
return True
|
||||
|
||||
logger.debug(
|
||||
"Users connected to the websocket but current editor not connected to it. Can not edit."
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
def perform_update(self, serializer):
|
||||
"""Check rules about collaboration."""
|
||||
if (
|
||||
serializer.validated_data.get("websocket", False)
|
||||
or not settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY
|
||||
):
|
||||
return super().perform_update(serializer)
|
||||
|
||||
if self._can_user_edit_document(serializer.instance.id, set_cache=True):
|
||||
return super().perform_update(serializer)
|
||||
|
||||
raise drf.exceptions.PermissionDenied(
|
||||
"You are not allowed to edit this document."
|
||||
)
|
||||
|
||||
@drf.decorators.action(
|
||||
detail=True,
|
||||
methods=["get"],
|
||||
url_path="can-edit",
|
||||
)
|
||||
def can_edit(self, request, *args, **kwargs):
|
||||
"""Check if the current user can edit the document."""
|
||||
document = self.get_object()
|
||||
|
||||
can_edit = (
|
||||
True
|
||||
if not settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY
|
||||
else self._can_user_edit_document(document.id)
|
||||
)
|
||||
|
||||
return drf.response.Response({"can_edit": can_edit})
|
||||
|
||||
@drf.decorators.action(
|
||||
detail=False,
|
||||
methods=["get"],
|
||||
@@ -926,7 +849,6 @@ class DocumentViewSet(
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
with_accesses = serializer.validated_data.get("with_accesses", False)
|
||||
is_owner_or_admin = document.get_role(request.user) in models.PRIVILEGED_ROLES
|
||||
|
||||
base64_yjs_content = document.content
|
||||
|
||||
@@ -948,34 +870,33 @@ class DocumentViewSet(
|
||||
**link_kwargs,
|
||||
)
|
||||
|
||||
# Always add the logged-in user as OWNER for root documents
|
||||
if document.is_root():
|
||||
accesses_to_create = [
|
||||
# Always add the logged-in user as OWNER
|
||||
accesses_to_create = [
|
||||
models.DocumentAccess(
|
||||
document=duplicated_document,
|
||||
user=request.user,
|
||||
role=models.RoleChoices.OWNER,
|
||||
)
|
||||
]
|
||||
|
||||
# If accesses should be duplicated, add other users' accesses as per original document
|
||||
if with_accesses:
|
||||
original_accesses = models.DocumentAccess.objects.filter(
|
||||
document=document
|
||||
).exclude(user=request.user)
|
||||
|
||||
accesses_to_create.extend(
|
||||
models.DocumentAccess(
|
||||
document=duplicated_document,
|
||||
user=request.user,
|
||||
role=models.RoleChoices.OWNER,
|
||||
user_id=access.user_id,
|
||||
team=access.team,
|
||||
role=access.role,
|
||||
)
|
||||
]
|
||||
for access in original_accesses
|
||||
)
|
||||
|
||||
# If accesses should be duplicated, add other users' accesses as per original document
|
||||
if with_accesses and is_owner_or_admin:
|
||||
original_accesses = models.DocumentAccess.objects.filter(
|
||||
document=document
|
||||
).exclude(user=request.user)
|
||||
|
||||
accesses_to_create.extend(
|
||||
models.DocumentAccess(
|
||||
document=duplicated_document,
|
||||
user_id=access.user_id,
|
||||
team=access.team,
|
||||
role=access.role,
|
||||
)
|
||||
for access in original_accesses
|
||||
)
|
||||
|
||||
# Bulk create all the duplicated accesses
|
||||
models.DocumentAccess.objects.bulk_create(accesses_to_create)
|
||||
# Bulk create all the duplicated accesses
|
||||
models.DocumentAccess.objects.bulk_create(accesses_to_create)
|
||||
|
||||
return drf_response.Response(
|
||||
{"id": str(duplicated_document.id)}, status=status.HTTP_201_CREATED
|
||||
@@ -1911,8 +1832,7 @@ class DocumentAskForAccessViewSet(
|
||||
if self._document is None:
|
||||
try:
|
||||
self._document = models.Document.objects.get(
|
||||
pk=self.kwargs["resource_id"],
|
||||
depth=1,
|
||||
pk=self.kwargs["resource_id"]
|
||||
)
|
||||
except models.Document.DoesNotExist as e:
|
||||
raise drf.exceptions.NotFound("Document not found.") from e
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
"""Force session creation for all requests."""
|
||||
|
||||
|
||||
class ForceSessionMiddleware:
|
||||
"""
|
||||
Force session creation for unauthenticated users.
|
||||
Must be used after Authentication middleware.
|
||||
"""
|
||||
|
||||
def __init__(self, get_response):
|
||||
"""Initialize the middleware."""
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
"""Force session creation for unauthenticated users."""
|
||||
|
||||
if not request.user.is_authenticated and request.session.session_key is None:
|
||||
request.session.create()
|
||||
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
@@ -671,7 +671,7 @@ class Document(MP_Node, BaseModel):
|
||||
|
||||
@property
|
||||
def ancestors_link_definition(self):
|
||||
"""Link definition equivalent to all document's ancestors."""
|
||||
"""Link defintion equivalent to all document's ancestors."""
|
||||
if getattr(self, "_ancestors_link_definition", None) is None:
|
||||
if self.depth <= 1:
|
||||
ancestors_links = []
|
||||
@@ -782,14 +782,13 @@ class Document(MP_Node, BaseModel):
|
||||
"ai_translate": ai_access,
|
||||
"attachment_upload": can_update,
|
||||
"media_check": can_get,
|
||||
"can_edit": can_update,
|
||||
"children_list": can_get,
|
||||
"children_create": can_update and user.is_authenticated,
|
||||
"collaboration_auth": can_get,
|
||||
"cors_proxy": can_get,
|
||||
"descendants": can_get,
|
||||
"destroy": is_owner,
|
||||
"duplicate": can_get and user.is_authenticated,
|
||||
"duplicate": can_get,
|
||||
"favorite": can_get and user.is_authenticated,
|
||||
"link_configuration": is_owner_or_admin,
|
||||
"invite_owner": is_owner,
|
||||
|
||||
@@ -41,35 +41,3 @@ class CollaborationService:
|
||||
f"Failed to notify WebSocket server. Status code: {response.status_code}, "
|
||||
f"Response: {response.text}"
|
||||
)
|
||||
|
||||
def get_document_connection_info(self, room, session_key):
|
||||
"""
|
||||
Get the connection info for a document.
|
||||
"""
|
||||
endpoint = "get-connections"
|
||||
querystring = {
|
||||
"room": room,
|
||||
"sessionKey": session_key,
|
||||
}
|
||||
endpoint_url = f"{settings.COLLABORATION_API_URL}{endpoint}/"
|
||||
|
||||
headers = {"Authorization": settings.COLLABORATION_SERVER_SECRET}
|
||||
|
||||
try:
|
||||
response = requests.get(
|
||||
endpoint_url, headers=headers, params=querystring, timeout=10
|
||||
)
|
||||
except requests.RequestException as e:
|
||||
raise requests.HTTPError("Failed to get document connection info.") from e
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
return result.get("count", 0), result.get("exists", False)
|
||||
|
||||
if response.status_code == 404:
|
||||
return 0, False
|
||||
|
||||
raise requests.HTTPError(
|
||||
f"Failed to get document connection info. Status code: {response.status_code}, "
|
||||
f"Response: {response.text}"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Converter services."""
|
||||
|
||||
from base64 import b64encode
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
import requests
|
||||
@@ -19,6 +17,14 @@ class ServiceUnavailableError(ConversionError):
|
||||
"""Raised when the conversion service is unavailable."""
|
||||
|
||||
|
||||
class InvalidResponseError(ConversionError):
|
||||
"""Raised when the conversion service returns an invalid response."""
|
||||
|
||||
|
||||
class MissingContentError(ConversionError):
|
||||
"""Raised when the response is missing required content."""
|
||||
|
||||
|
||||
class YdocConverter:
|
||||
"""Service class for conversion-related operations."""
|
||||
|
||||
@@ -26,9 +32,9 @@ class YdocConverter:
|
||||
def auth_header(self):
|
||||
"""Build microservice authentication header."""
|
||||
# Note: Yprovider microservice accepts only raw token, which is not recommended
|
||||
return f"Bearer {settings.Y_PROVIDER_API_KEY}"
|
||||
return settings.Y_PROVIDER_API_KEY
|
||||
|
||||
def convert(self, text):
|
||||
def convert_markdown(self, text):
|
||||
"""Convert a Markdown text into our internal format using an external microservice."""
|
||||
|
||||
if not text:
|
||||
@@ -37,17 +43,36 @@ class YdocConverter:
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{settings.Y_PROVIDER_API_BASE_URL}{settings.CONVERSION_API_ENDPOINT}/",
|
||||
data=text,
|
||||
json={
|
||||
"content": text,
|
||||
},
|
||||
headers={
|
||||
"Authorization": self.auth_header,
|
||||
"Content-Type": "text/markdown",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout=settings.CONVERSION_API_TIMEOUT,
|
||||
verify=settings.CONVERSION_API_SECURE,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return b64encode(response.content).decode("utf-8")
|
||||
conversion_response = response.json()
|
||||
|
||||
except requests.RequestException as err:
|
||||
raise ServiceUnavailableError(
|
||||
"Failed to connect to conversion service",
|
||||
) from err
|
||||
|
||||
except ValueError as err:
|
||||
raise InvalidResponseError(
|
||||
"Could not parse conversion service response"
|
||||
) from err
|
||||
|
||||
try:
|
||||
document_content = conversion_response[
|
||||
settings.CONVERSION_API_CONTENT_FIELD
|
||||
]
|
||||
except KeyError as err:
|
||||
raise MissingContentError(
|
||||
f"Response missing required field: {settings.CONVERSION_API_CONTENT_FIELD}"
|
||||
) from err
|
||||
|
||||
return document_content
|
||||
|
||||
@@ -48,7 +48,7 @@ def test_api_documents_ask_for_access_create_authenticated():
|
||||
An email should be sent to document owners and admins to notify them.
|
||||
"""
|
||||
owner_user = UserFactory(language="en-us")
|
||||
admin_user = UserFactory(language="en-us")
|
||||
admin_user = UserFactory(language="fr-fr")
|
||||
document = DocumentFactory(
|
||||
users=[
|
||||
(owner_user, RoleChoices.OWNER),
|
||||
@@ -97,23 +97,6 @@ def test_api_documents_ask_for_access_create_authenticated():
|
||||
assert document.title.lower() in email_subject.lower()
|
||||
|
||||
|
||||
def test_api_documents_ask_for_access_create_authenticated_non_root_document():
|
||||
"""
|
||||
Authenticated users should not be able to create a document ask for access on a non-root
|
||||
document.
|
||||
"""
|
||||
parent = DocumentFactory()
|
||||
child = DocumentFactory(parent=parent)
|
||||
|
||||
user = UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.post(f"/api/v1.0/documents/{child.id}/ask-for-access/")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_api_documents_ask_for_access_create_authenticated_specific_role():
|
||||
"""
|
||||
Authenticated users should be able to create a document ask for access with a specific role.
|
||||
@@ -213,20 +196,6 @@ def test_api_documents_ask_for_access_list_authenticated():
|
||||
}
|
||||
|
||||
|
||||
def test_api_documents_ask_for_access_list_authenticated_non_root_document():
|
||||
"""
|
||||
Authenticated users should not be able to list document ask for access on a non-root document.
|
||||
"""
|
||||
parent = DocumentFactory()
|
||||
child = DocumentFactory(parent=parent)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(UserFactory())
|
||||
|
||||
response = client.get(f"/api/v1.0/documents/{child.id}/ask-for-access/")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_api_documents_ask_for_access_list_authenticated_own_request():
|
||||
"""Authenticated users should be able to list their own document ask for access."""
|
||||
document = DocumentFactory()
|
||||
@@ -320,7 +289,7 @@ def test_api_documents_ask_for_access_list_non_owner_or_admin(role):
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("role", [RoleChoices.OWNER, RoleChoices.ADMIN])
|
||||
@pytest.mark.parametrize("role", [RoleChoices.OWNER])
|
||||
def test_api_documents_ask_for_access_list_owner_or_admin(role):
|
||||
"""Owner or admin users should be able to list document ask for access."""
|
||||
user = UserFactory()
|
||||
@@ -360,23 +329,6 @@ def test_api_documents_ask_for_access_list_owner_or_admin(role):
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("role", [RoleChoices.OWNER, RoleChoices.ADMIN])
|
||||
def test_api_documents_ask_for_access_list_admin_non_root_document(role):
|
||||
"""
|
||||
Authenticated users should not be able to list document ask for access on a non-root document.
|
||||
"""
|
||||
user = UserFactory()
|
||||
parent = DocumentFactory(users=[(user, role)])
|
||||
child = DocumentFactory(parent=parent, users=[(user, role)])
|
||||
DocumentAskForAccessFactory.create_batch(3, document=child, role=RoleChoices.READER)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.get(f"/api/v1.0/documents/{child.id}/ask-for-access/")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
## Retrieve
|
||||
|
||||
|
||||
@@ -463,28 +415,6 @@ def test_api_documents_ask_for_access_retrieve_owner_or_admin(role):
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("role", [RoleChoices.OWNER, RoleChoices.ADMIN])
|
||||
def test_api_documents_ask_for_access_retrieve_authenticated_non_root_document(role):
|
||||
"""
|
||||
Authenticated users should not be able to retrieve document ask for access on a non-root
|
||||
document.
|
||||
"""
|
||||
user = UserFactory()
|
||||
parent = DocumentFactory(users=[(user, role)])
|
||||
child = DocumentFactory(parent=parent, users=[(user, role)])
|
||||
document_ask_for_access = DocumentAskForAccessFactory(
|
||||
document=child, role=RoleChoices.READER
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/documents/{child.id}/ask-for-access/{document_ask_for_access.id}/"
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
## Delete
|
||||
|
||||
|
||||
@@ -557,28 +487,6 @@ def test_api_documents_ask_for_access_delete_owner_or_admin(role):
|
||||
).exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("role", [RoleChoices.OWNER, RoleChoices.ADMIN])
|
||||
def test_api_documents_ask_for_access_delete_authenticated_non_root_document(role):
|
||||
"""
|
||||
Authenticated users should not be able to delete document ask for access on a non-root
|
||||
document.
|
||||
"""
|
||||
user = UserFactory()
|
||||
parent = DocumentFactory(users=[(user, role)])
|
||||
child = DocumentFactory(parent=parent, users=[(user, role)])
|
||||
document_ask_for_access = DocumentAskForAccessFactory(
|
||||
document=child, role=RoleChoices.READER
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.delete(
|
||||
f"/api/v1.0/documents/{child.id}/ask-for-access/{document_ask_for_access.id}/"
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
## Accept
|
||||
|
||||
|
||||
@@ -746,25 +654,3 @@ def test_api_documents_ask_for_access_accept_authenticated_owner_or_admin_update
|
||||
).exists()
|
||||
document_access.refresh_from_db()
|
||||
assert document_access.role == RoleChoices.ADMIN
|
||||
|
||||
|
||||
@pytest.mark.parametrize("role", [RoleChoices.OWNER, RoleChoices.ADMIN])
|
||||
def test_api_documents_ask_for_access_accept_authenticated_non_root_document(role):
|
||||
"""
|
||||
Authenticated users should not be able to accept document ask for access on a non-root
|
||||
document.
|
||||
"""
|
||||
user = UserFactory()
|
||||
parent = DocumentFactory(users=[(user, role)])
|
||||
child = DocumentFactory(parent=parent, users=[(user, role)])
|
||||
document_ask_for_access = DocumentAskForAccessFactory(
|
||||
document=child, role=RoleChoices.READER
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{child.id}/ask-for-access/{document_ask_for_access.id}/accept/"
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
@@ -1,318 +0,0 @@
|
||||
"""Test the can_edit endpoint in the viewset DocumentViewSet."""
|
||||
|
||||
from django.core.cache import cache
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core import factories
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@responses.activate
|
||||
@pytest.mark.parametrize("ws_not_connected_ready_only", [True, False])
|
||||
@pytest.mark.parametrize("role", ["editor", "reader"])
|
||||
def test_api_documents_can_edit_anonymous(settings, ws_not_connected_ready_only, role):
|
||||
"""Anonymous users can edit documents when link_role is editor."""
|
||||
document = factories.DocumentFactory(link_reach="public", link_role=role)
|
||||
client = APIClient()
|
||||
session_key = client.session.session_key
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = ws_not_connected_ready_only
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 0, "exists": False})
|
||||
|
||||
response = client.get(f"/api/v1.0/documents/{document.id!s}/can-edit/")
|
||||
|
||||
if role == "reader":
|
||||
assert response.status_code == 401
|
||||
else:
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"can_edit": True}
|
||||
assert ws_resp.call_count == (1 if ws_not_connected_ready_only else 0)
|
||||
|
||||
|
||||
@responses.activate
|
||||
@pytest.mark.parametrize("ws_not_connected_ready_only", [True, False])
|
||||
def test_api_documents_can_edit_authenticated_no_websocket(
|
||||
settings, ws_not_connected_ready_only
|
||||
):
|
||||
"""
|
||||
A user not connected to the websocket and no other user have already updated the document,
|
||||
the document can be updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = ws_not_connected_ready_only
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 0, "exists": False})
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/documents/{document.id!s}/can-edit/",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
assert response.json() == {"can_edit": True}
|
||||
assert ws_resp.call_count == (1 if ws_not_connected_ready_only else 0)
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_can_edit_authenticated_no_websocket_user_already_editing(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
A user not connected to the websocket and another user have already updated the document,
|
||||
the document can not be updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 0, "exists": False})
|
||||
|
||||
cache.set(f"docs:no-websocket:{document.id}", "other_session_key")
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/documents/{document.id!s}/can-edit/",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"can_edit": False}
|
||||
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_can_edit_no_websocket_other_user_connected_to_websocket(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
A user not connected to the websocket and another user is connected to the websocket,
|
||||
the document can not be updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 3, "exists": False})
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/documents/{document.id!s}/can-edit/",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"can_edit": False}
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_can_edit_user_connected_to_websocket(settings):
|
||||
"""
|
||||
A user connected to the websocket, the document can be updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 3, "exists": True})
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/documents/{document.id!s}/can-edit/",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"can_edit": True}
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_can_edit_websocket_server_unreachable_fallback_to_no_websocket(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
When the websocket server is unreachable, the document can be updated like if the user was
|
||||
not connected to the websocket.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=500)
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/documents/{document.id!s}/can-edit/",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"can_edit": True}
|
||||
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_can_edit_websocket_server_unreachable_fallback_to_no_websocket_other_users(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
When the websocket server is unreachable, the behavior fallback to the no websocket one.
|
||||
If an other user is already editing, the document can not be updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=500)
|
||||
|
||||
cache.set(f"docs:no-websocket:{document.id}", "other_session_key")
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/documents/{document.id!s}/can-edit/",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"can_edit": False}
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") == "other_session_key"
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_can_edit_websocket_server_room_not_found(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
When the websocket server returns a 404, the document can be updated like if the user was
|
||||
not connected to the websocket.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=404)
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/documents/{document.id!s}/can-edit/",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"can_edit": True}
|
||||
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_can_edit_websocket_server_room_not_found_other_already_editing(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
When the websocket server returns a 404 and another user is editing the document,
|
||||
the response should be can-edit=False.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=404)
|
||||
|
||||
cache.set(f"docs:no-websocket:{document.id}", "other_session_key")
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/documents/{document.id!s}/can-edit/",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"can_edit": False}
|
||||
|
||||
assert ws_resp.call_count == 1
|
||||
@@ -23,10 +23,10 @@ pytestmark = pytest.mark.django_db
|
||||
|
||||
@pytest.fixture
|
||||
def mock_convert_md():
|
||||
"""Mock YdocConverter.convert to return a converted content."""
|
||||
"""Mock YdocConverter.convert_markdown to return a converted content."""
|
||||
with patch.object(
|
||||
YdocConverter,
|
||||
"convert",
|
||||
"convert_markdown",
|
||||
return_value="Converted document content",
|
||||
) as mock:
|
||||
yield mock
|
||||
|
||||
@@ -61,7 +61,7 @@ def test_api_documents_duplicate_forbidden():
|
||||
def test_api_documents_duplicate_anonymous():
|
||||
"""Anonymous users should not be able to duplicate documents even with read access."""
|
||||
|
||||
document = factories.DocumentFactory(link_reach="public", link_role="reader")
|
||||
document = factories.DocumentFactory(link_reach="public")
|
||||
|
||||
response = APIClient().post(f"/api/v1.0/documents/{document.id!s}/duplicate/")
|
||||
|
||||
@@ -171,17 +171,14 @@ def test_api_documents_duplicate_success(index):
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.parametrize("role", ["owner", "administrator"])
|
||||
def test_api_documents_duplicate_with_accesses_admin(role):
|
||||
"""
|
||||
Accesses should be duplicated if the user requests it specifically and is owner or admin.
|
||||
"""
|
||||
def test_api_documents_duplicate_with_accesses():
|
||||
"""Accesses should be duplicated if the user requests it specifically."""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(
|
||||
users=[(user, role)],
|
||||
users=[user],
|
||||
title="document with accesses",
|
||||
)
|
||||
user_access = factories.UserDocumentAccessFactory(document=document)
|
||||
@@ -211,85 +208,3 @@ def test_api_documents_duplicate_with_accesses_admin(role):
|
||||
assert duplicated_accesses.get(user=user).role == "owner"
|
||||
assert duplicated_accesses.get(user=user_access.user).role == user_access.role
|
||||
assert duplicated_accesses.get(team=team_access.team).role == team_access.role
|
||||
|
||||
|
||||
@pytest.mark.parametrize("role", ["editor", "reader"])
|
||||
def test_api_documents_duplicate_with_accesses_non_admin(role):
|
||||
"""
|
||||
Accesses should not be duplicated if the user requests it specifically and is not owner
|
||||
or admin.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(
|
||||
users=[(user, role)],
|
||||
title="document with accesses",
|
||||
)
|
||||
factories.UserDocumentAccessFactory(document=document)
|
||||
factories.TeamDocumentAccessFactory(document=document)
|
||||
|
||||
# Duplicate the document via the API endpoint requesting to duplicate accesses
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{document.id!s}/duplicate/",
|
||||
{"with_accesses": True},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
duplicated_document = models.Document.objects.get(id=response.json()["id"])
|
||||
assert duplicated_document.title == "Copy of document with accesses"
|
||||
assert duplicated_document.content == document.content
|
||||
assert duplicated_document.link_reach == document.link_reach
|
||||
assert duplicated_document.link_role == document.link_role
|
||||
assert duplicated_document.creator == user
|
||||
assert duplicated_document.duplicated_from == document
|
||||
assert duplicated_document.attachments == []
|
||||
|
||||
# Check that accesses were duplicated and the user who did the duplicate is forced as owner
|
||||
duplicated_accesses = duplicated_document.accesses
|
||||
assert duplicated_accesses.count() == 1
|
||||
assert duplicated_accesses.get(user=user).role == "owner"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("role", ["editor", "reader"])
|
||||
def test_api_documents_duplicate_non_root_document(role):
|
||||
"""
|
||||
Non-root documents can be duplicated but without accesses.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "owner")])
|
||||
child = factories.DocumentFactory(
|
||||
parent=document, users=[(user, role)], title="document with accesses"
|
||||
)
|
||||
|
||||
assert child.accesses.count() == 1
|
||||
|
||||
# Duplicate the document via the API endpoint requesting to duplicate accesses
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{child.id!s}/duplicate/",
|
||||
{"with_accesses": True},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
duplicated_document = models.Document.objects.get(id=response.json()["id"])
|
||||
assert duplicated_document.title == "Copy of document with accesses"
|
||||
assert duplicated_document.content == child.content
|
||||
assert duplicated_document.link_reach == child.link_reach
|
||||
assert duplicated_document.link_role == child.link_role
|
||||
assert duplicated_document.creator == user
|
||||
assert duplicated_document.duplicated_from == child
|
||||
assert duplicated_document.attachments == []
|
||||
|
||||
# No access should be created for non root documents
|
||||
duplicated_accesses = duplicated_document.accesses
|
||||
assert duplicated_accesses.count() == 0
|
||||
assert duplicated_document.is_sibling_of(child)
|
||||
assert duplicated_document.is_child_of(document)
|
||||
|
||||
@@ -32,14 +32,13 @@ def test_api_documents_retrieve_anonymous_public_standalone():
|
||||
"ai_transform": False,
|
||||
"ai_translate": False,
|
||||
"attachment_upload": document.link_role == "editor",
|
||||
"can_edit": document.link_role == "editor",
|
||||
"children_create": False,
|
||||
"children_list": True,
|
||||
"collaboration_auth": True,
|
||||
"cors_proxy": True,
|
||||
"descendants": True,
|
||||
"destroy": False,
|
||||
"duplicate": False,
|
||||
"duplicate": True,
|
||||
# Anonymous user can't favorite a document even with read access
|
||||
"favorite": False,
|
||||
"invite_owner": False,
|
||||
@@ -106,14 +105,13 @@ def test_api_documents_retrieve_anonymous_public_parent():
|
||||
"ai_transform": False,
|
||||
"ai_translate": False,
|
||||
"attachment_upload": grand_parent.link_role == "editor",
|
||||
"can_edit": grand_parent.link_role == "editor",
|
||||
"children_create": False,
|
||||
"children_list": True,
|
||||
"collaboration_auth": True,
|
||||
"descendants": True,
|
||||
"cors_proxy": True,
|
||||
"destroy": False,
|
||||
"duplicate": False,
|
||||
"duplicate": True,
|
||||
# Anonymous user can't favorite a document even with read access
|
||||
"favorite": False,
|
||||
"invite_owner": False,
|
||||
@@ -210,7 +208,6 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated(
|
||||
"ai_transform": document.link_role == "editor",
|
||||
"ai_translate": document.link_role == "editor",
|
||||
"attachment_upload": document.link_role == "editor",
|
||||
"can_edit": document.link_role == "editor",
|
||||
"children_create": document.link_role == "editor",
|
||||
"children_list": True,
|
||||
"collaboration_auth": True,
|
||||
@@ -291,7 +288,6 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
|
||||
"ai_transform": grand_parent.link_role == "editor",
|
||||
"ai_translate": grand_parent.link_role == "editor",
|
||||
"attachment_upload": grand_parent.link_role == "editor",
|
||||
"can_edit": grand_parent.link_role == "editor",
|
||||
"children_create": grand_parent.link_role == "editor",
|
||||
"children_list": True,
|
||||
"collaboration_auth": True,
|
||||
@@ -484,7 +480,6 @@ def test_api_documents_retrieve_authenticated_related_parent():
|
||||
"ai_transform": access.role != "reader",
|
||||
"ai_translate": access.role != "reader",
|
||||
"attachment_upload": access.role != "reader",
|
||||
"can_edit": access.role != "reader",
|
||||
"children_create": access.role != "reader",
|
||||
"children_list": True,
|
||||
"collaboration_auth": True,
|
||||
|
||||
@@ -75,7 +75,6 @@ def test_api_documents_trashbin_format():
|
||||
"ai_transform": True,
|
||||
"ai_translate": True,
|
||||
"attachment_upload": True,
|
||||
"can_edit": True,
|
||||
"children_create": True,
|
||||
"children_list": True,
|
||||
"collaboration_auth": True,
|
||||
|
||||
@@ -5,10 +5,8 @@ Tests for Documents API endpoint in impress's core app: update
|
||||
import random
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.core.cache import cache
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core import factories, models
|
||||
@@ -46,7 +44,6 @@ def test_api_documents_update_anonymous_forbidden(reach, role, via_parent):
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = True
|
||||
response = APIClient().put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
@@ -93,9 +90,8 @@ def test_api_documents_update_authenticated_unrelated_forbidden(
|
||||
|
||||
old_document_values = serializers.DocumentSerializer(instance=document).data
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory(),
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = True
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
@@ -145,9 +141,8 @@ def test_api_documents_update_anonymous_or_authenticated_unrelated(
|
||||
|
||||
old_document_values = serializers.DocumentSerializer(instance=document).data
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory(),
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = True
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
@@ -215,7 +210,6 @@ def test_api_documents_update_authenticated_reader(via, via_parent, mock_user_te
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = True
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
@@ -268,7 +262,6 @@ def test_api_documents_update_authenticated_editor_administrator_or_owner(
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = True
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
@@ -302,359 +295,6 @@ def test_api_documents_update_authenticated_editor_administrator_or_owner(
|
||||
assert value == new_document_values[key]
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_update_authenticated_no_websocket(settings):
|
||||
"""
|
||||
When a user updates the document, not connected to the websocket and is the first to update,
|
||||
the document should be updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = False
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 0, "exists": False})
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") == session_key
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_update_authenticated_no_websocket_user_already_editing(settings):
|
||||
"""
|
||||
When a user updates the document, not connected to the websocket and is not the first to update,
|
||||
the document should not be updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = False
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 0, "exists": False})
|
||||
|
||||
cache.set(f"docs:no-websocket:{document.id}", "other_session_key")
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {"detail": "You are not allowed to edit this document."}
|
||||
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_update_no_websocket_other_user_connected_to_websocket(settings):
|
||||
"""
|
||||
When a user updates the document, not connected to the websocket and another user is connected
|
||||
to the websocket, the document should not be updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = False
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 3, "exists": False})
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {"detail": "You are not allowed to edit this document."}
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_update_user_connected_to_websocket(settings):
|
||||
"""
|
||||
When a user updates the document, connected to the websocket, the document should be updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = False
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 3, "exists": True})
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_update_websocket_server_unreachable_fallback_to_no_websocket(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
When the websocket server is unreachable, the document should be updated like if the user was
|
||||
not connected to the websocket.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = False
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=500)
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") == session_key
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_update_websocket_server_unreachable_fallback_to_no_websocket_other_users(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
When the websocket server is unreachable, the behavior fallback to the no websocket one.
|
||||
If an other user is already editing, the document should not be updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = False
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=500)
|
||||
|
||||
cache.set(f"docs:no-websocket:{document.id}", "other_session_key")
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") == "other_session_key"
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_update_websocket_server_room_not_found_fallback_to_no_websocket_other_users(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
When the WebSocket server does not have the room created, the logic should fallback to
|
||||
no-WebSocket. If another user is already editing, the update must be denied.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = False
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=404)
|
||||
|
||||
cache.set(f"docs:no-websocket:{document.id}", "other_session_key")
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") == "other_session_key"
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_update_force_websocket_param_to_true(settings):
|
||||
"""
|
||||
When the websocket parameter is set to true, the document should be updated without any check.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = True
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=500)
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 0
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_update_feature_flag_disabled(settings):
|
||||
"""
|
||||
When the feature flag is disabled, the document should be updated without any check.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = False
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = False
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=500)
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
new_document_values,
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_documents_update_administrator_or_owner_of_another(via, mock_user_teams):
|
||||
"""
|
||||
@@ -685,7 +325,6 @@ def test_api_documents_update_administrator_or_owner_of_another(via, mock_user_t
|
||||
new_document_values = serializers.DocumentSerializer(
|
||||
instance=factories.DocumentFactory()
|
||||
).data
|
||||
new_document_values["websocket"] = True
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{other_document.id!s}/",
|
||||
new_document_values,
|
||||
|
||||
@@ -50,7 +50,7 @@ def test_api_documents_update_new_attachment_keys_anonymous(django_assert_num_qu
|
||||
with django_assert_num_queries(11):
|
||||
response = APIClient().put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": get_ydoc_with_mages(image_keys), "websocket": True},
|
||||
{"content": get_ydoc_with_mages(image_keys)},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
@@ -63,7 +63,7 @@ def test_api_documents_update_new_attachment_keys_anonymous(django_assert_num_qu
|
||||
with django_assert_num_queries(7):
|
||||
response = APIClient().put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": get_ydoc_with_mages(image_keys[:2]), "websocket": True},
|
||||
{"content": get_ydoc_with_mages(image_keys[:2])},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
@@ -155,7 +155,6 @@ def test_models_documents_get_abilities_forbidden(
|
||||
"ai_transform": False,
|
||||
"ai_translate": False,
|
||||
"attachment_upload": False,
|
||||
"can_edit": False,
|
||||
"children_create": False,
|
||||
"children_list": False,
|
||||
"collaboration_auth": False,
|
||||
@@ -217,14 +216,13 @@ def test_models_documents_get_abilities_reader(
|
||||
"ai_transform": False,
|
||||
"ai_translate": False,
|
||||
"attachment_upload": False,
|
||||
"can_edit": False,
|
||||
"children_create": False,
|
||||
"children_list": True,
|
||||
"collaboration_auth": True,
|
||||
"descendants": True,
|
||||
"cors_proxy": True,
|
||||
"destroy": False,
|
||||
"duplicate": is_authenticated,
|
||||
"duplicate": True,
|
||||
"favorite": is_authenticated,
|
||||
"invite_owner": False,
|
||||
"link_configuration": False,
|
||||
@@ -281,14 +279,13 @@ def test_models_documents_get_abilities_editor(
|
||||
"ai_transform": is_authenticated,
|
||||
"ai_translate": is_authenticated,
|
||||
"attachment_upload": True,
|
||||
"can_edit": True,
|
||||
"children_create": is_authenticated,
|
||||
"children_list": True,
|
||||
"collaboration_auth": True,
|
||||
"descendants": True,
|
||||
"cors_proxy": True,
|
||||
"destroy": False,
|
||||
"duplicate": is_authenticated,
|
||||
"duplicate": True,
|
||||
"favorite": is_authenticated,
|
||||
"invite_owner": False,
|
||||
"link_configuration": False,
|
||||
@@ -334,7 +331,6 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
|
||||
"ai_transform": True,
|
||||
"ai_translate": True,
|
||||
"attachment_upload": True,
|
||||
"can_edit": True,
|
||||
"children_create": True,
|
||||
"children_list": True,
|
||||
"collaboration_auth": True,
|
||||
@@ -384,7 +380,6 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries)
|
||||
"ai_transform": True,
|
||||
"ai_translate": True,
|
||||
"attachment_upload": True,
|
||||
"can_edit": True,
|
||||
"children_create": True,
|
||||
"children_list": True,
|
||||
"collaboration_auth": True,
|
||||
@@ -437,7 +432,6 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries):
|
||||
"ai_transform": True,
|
||||
"ai_translate": True,
|
||||
"attachment_upload": True,
|
||||
"can_edit": True,
|
||||
"children_create": True,
|
||||
"children_list": True,
|
||||
"collaboration_auth": True,
|
||||
@@ -497,7 +491,6 @@ def test_models_documents_get_abilities_reader_user(
|
||||
"ai_transform": access_from_link and ai_access_setting != "restricted",
|
||||
"ai_translate": access_from_link and ai_access_setting != "restricted",
|
||||
"attachment_upload": access_from_link,
|
||||
"can_edit": access_from_link,
|
||||
"children_create": access_from_link,
|
||||
"children_list": True,
|
||||
"collaboration_auth": True,
|
||||
@@ -555,7 +548,6 @@ def test_models_documents_get_abilities_preset_role(django_assert_num_queries):
|
||||
"ai_transform": False,
|
||||
"ai_translate": False,
|
||||
"attachment_upload": False,
|
||||
"can_edit": False,
|
||||
"children_create": False,
|
||||
"children_list": True,
|
||||
"collaboration_auth": True,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
"""Test converter services."""
|
||||
|
||||
from base64 import b64decode
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from core.services.converter_services import (
|
||||
InvalidResponseError,
|
||||
MissingContentError,
|
||||
ServiceUnavailableError,
|
||||
ValidationError,
|
||||
YdocConverter,
|
||||
@@ -17,18 +18,18 @@ def test_auth_header(settings):
|
||||
"""Test authentication header generation."""
|
||||
settings.Y_PROVIDER_API_KEY = "test-key"
|
||||
converter = YdocConverter()
|
||||
assert converter.auth_header == "Bearer test-key"
|
||||
assert converter.auth_header == "test-key"
|
||||
|
||||
|
||||
def test_convert_empty_text():
|
||||
def test_convert_markdown_empty_text():
|
||||
"""Should raise ValidationError when text is empty."""
|
||||
converter = YdocConverter()
|
||||
with pytest.raises(ValidationError, match="Input text cannot be empty"):
|
||||
converter.convert("")
|
||||
converter.convert_markdown("")
|
||||
|
||||
|
||||
@patch("requests.post")
|
||||
def test_convert_service_unavailable(mock_post):
|
||||
def test_convert_markdown_service_unavailable(mock_post):
|
||||
"""Should raise ServiceUnavailableError when service is unavailable."""
|
||||
converter = YdocConverter()
|
||||
|
||||
@@ -38,11 +39,11 @@ def test_convert_service_unavailable(mock_post):
|
||||
ServiceUnavailableError,
|
||||
match="Failed to connect to conversion service",
|
||||
):
|
||||
converter.convert("test text")
|
||||
converter.convert_markdown("test text")
|
||||
|
||||
|
||||
@patch("requests.post")
|
||||
def test_convert_http_error(mock_post):
|
||||
def test_convert_markdown_http_error(mock_post):
|
||||
"""Should raise ServiceUnavailableError when HTTP error occurs."""
|
||||
converter = YdocConverter()
|
||||
|
||||
@@ -54,11 +55,46 @@ def test_convert_http_error(mock_post):
|
||||
ServiceUnavailableError,
|
||||
match="Failed to connect to conversion service",
|
||||
):
|
||||
converter.convert("test text")
|
||||
converter.convert_markdown("test text")
|
||||
|
||||
|
||||
@patch("requests.post")
|
||||
def test_convert_full_integration(mock_post, settings):
|
||||
def test_convert_markdown_invalid_json_response(mock_post):
|
||||
"""Should raise InvalidResponseError when response is not valid JSON."""
|
||||
converter = YdocConverter()
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.side_effect = ValueError("Invalid JSON")
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
with pytest.raises(
|
||||
InvalidResponseError,
|
||||
match="Could not parse conversion service response",
|
||||
):
|
||||
converter.convert_markdown("test text")
|
||||
|
||||
|
||||
@patch("requests.post")
|
||||
def test_convert_markdown_missing_content_field(mock_post, settings):
|
||||
"""Should raise MissingContentError when response is missing required field."""
|
||||
|
||||
settings.CONVERSION_API_CONTENT_FIELD = "expected_field"
|
||||
|
||||
converter = YdocConverter()
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {"wrong_field": "content"}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
with pytest.raises(
|
||||
MissingContentError,
|
||||
match="Response missing required field: expected_field",
|
||||
):
|
||||
converter.convert_markdown("test text")
|
||||
|
||||
|
||||
@patch("requests.post")
|
||||
def test_convert_markdown_full_integration(mock_post, settings):
|
||||
"""Test full integration with all settings."""
|
||||
|
||||
settings.Y_PROVIDER_API_BASE_URL = "http://test.com/"
|
||||
@@ -69,21 +105,20 @@ def test_convert_full_integration(mock_post, settings):
|
||||
|
||||
converter = YdocConverter()
|
||||
|
||||
expected_content = b"converted content"
|
||||
expected_content = {"converted": "content"}
|
||||
mock_response = MagicMock()
|
||||
mock_response.content = expected_content
|
||||
mock_response.json.return_value = {"content": expected_content}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
result = converter.convert("test markdown")
|
||||
|
||||
assert b64decode(result) == expected_content
|
||||
result = converter.convert_markdown("test markdown")
|
||||
|
||||
assert result == expected_content
|
||||
mock_post.assert_called_once_with(
|
||||
"http://test.com/conversion-endpoint/",
|
||||
data="test markdown",
|
||||
json={"content": "test markdown"},
|
||||
headers={
|
||||
"Authorization": "Bearer test-key",
|
||||
"Content-Type": "text/markdown",
|
||||
"Authorization": "test-key",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout=5,
|
||||
verify=False,
|
||||
@@ -91,7 +126,7 @@ def test_convert_full_integration(mock_post, settings):
|
||||
|
||||
|
||||
@patch("requests.post")
|
||||
def test_convert_timeout(mock_post):
|
||||
def test_convert_markdown_timeout(mock_post):
|
||||
"""Should raise ServiceUnavailableError when request times out."""
|
||||
converter = YdocConverter()
|
||||
|
||||
@@ -101,12 +136,12 @@ def test_convert_timeout(mock_post):
|
||||
ServiceUnavailableError,
|
||||
match="Failed to connect to conversion service",
|
||||
):
|
||||
converter.convert("test text")
|
||||
converter.convert_markdown("test text")
|
||||
|
||||
|
||||
def test_convert_none_input():
|
||||
def test_convert_markdown_none_input():
|
||||
"""Should raise ValidationError when input is None."""
|
||||
converter = YdocConverter()
|
||||
|
||||
with pytest.raises(ValidationError, match="Input text cannot be empty"):
|
||||
converter.convert(None)
|
||||
converter.convert_markdown(None)
|
||||
|
||||
@@ -291,7 +291,6 @@ class Base(Configuration):
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"core.middleware.ForceSessionMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"dockerflow.django.middleware.DockerflowMiddleware",
|
||||
"csp.middleware.CSPMiddleware",
|
||||
@@ -481,7 +480,6 @@ class Base(Configuration):
|
||||
SESSION_COOKIE_AGE = values.PositiveIntegerValue(
|
||||
default=60 * 60 * 12, environ_name="SESSION_COOKIE_AGE", environ_prefix=None
|
||||
)
|
||||
SESSION_COOKIE_NAME = "docs_sessionid"
|
||||
|
||||
# OIDC - Authorization Code Flow
|
||||
OIDC_CREATE_USER = values.BooleanValue(
|
||||
@@ -641,7 +639,7 @@ class Base(Configuration):
|
||||
|
||||
# Conversion endpoint
|
||||
CONVERSION_API_ENDPOINT = values.Value(
|
||||
default="convert",
|
||||
default="convert-markdown",
|
||||
environ_name="CONVERSION_API_ENDPOINT",
|
||||
environ_prefix=None,
|
||||
)
|
||||
@@ -661,12 +659,6 @@ class Base(Configuration):
|
||||
environ_prefix=None,
|
||||
)
|
||||
|
||||
NO_WEBSOCKET_CACHE_TIMEOUT = values.Value(
|
||||
default=120,
|
||||
environ_name="NO_WEBSOCKET_CACHE_TIMEOUT",
|
||||
environ_prefix=None,
|
||||
)
|
||||
|
||||
# Logging
|
||||
# We want to make it easy to log to console but by default we log production
|
||||
# to Sentry and don't want to log to console.
|
||||
@@ -861,9 +853,15 @@ class Development(Base):
|
||||
CSRF_TRUSTED_ORIGINS = ["http://localhost:8072", "http://localhost:3000"]
|
||||
DEBUG = True
|
||||
|
||||
SESSION_COOKIE_NAME = "impress_sessionid"
|
||||
|
||||
USE_SWAGGER = True
|
||||
SESSION_CACHE_ALIAS = "session"
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.dummy.DummyCache",
|
||||
},
|
||||
"session": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": values.Value(
|
||||
"redis://redis:6379/2",
|
||||
|
||||
@@ -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-05-22 12:09+0000\n"
|
||||
"PO-Revision-Date: 2025-05-22 14:16\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Breton\n"
|
||||
"Language: br_FR\n"
|
||||
@@ -46,61 +46,31 @@ msgstr "Me eo an aozer"
|
||||
msgid "Favorite"
|
||||
msgstr "Sinedoù"
|
||||
|
||||
#: build/lib/core/api/serializers.py:467 core/api/serializers.py:467
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Ur restr nevez a zo bet krouet ganeoc'h!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:471 core/api/serializers.py:471
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "C'hwi zo bet disklaeriet perc'henn ur restr nevez:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:608 core/api/serializers.py:608
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
msgid "Body"
|
||||
msgstr "Korf"
|
||||
|
||||
#: build/lib/core/api/serializers.py:611 core/api/serializers.py:611
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
msgid "Body type"
|
||||
msgstr "Doare korf"
|
||||
|
||||
#: build/lib/core/api/serializers.py:617 core/api/serializers.py:617
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
msgid "Format"
|
||||
msgstr "Stumm"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "eilenn {title}"
|
||||
|
||||
#: build/lib/core/choices.py:35 build/lib/core/choices.py:42 core/choices.py:35
|
||||
#: core/choices.py:42
|
||||
msgid "Reader"
|
||||
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 ""
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
msgstr "Merour"
|
||||
|
||||
#: build/lib/core/choices.py:45 core/choices.py:45
|
||||
msgid "Owner"
|
||||
msgstr "Perc'henn"
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
msgstr "Publik"
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
msgstr "Bugel kentañ"
|
||||
@@ -125,292 +95,295 @@ msgstr "Kleiz"
|
||||
msgid "Right"
|
||||
msgstr "Dehoù"
|
||||
|
||||
#: build/lib/core/models.py:79 core/models.py:79
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Reader"
|
||||
msgstr "Lenner"
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
msgid "Editor"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Administrator"
|
||||
msgstr "Merour"
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
msgid "Owner"
|
||||
msgstr "Perc'henn"
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
msgid "Public"
|
||||
msgstr "Publik"
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:86 core/models.py:86
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "created on"
|
||||
msgstr "krouet d'ar/al"
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:92 core/models.py:92
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "updated on"
|
||||
msgstr "hizivaet d'ar/al"
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:129 core/models.py:129
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:148 core/models.py:148
|
||||
#: build/lib/core/models.py:223 core/models.py:223
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:159 core/models.py:159
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "full name"
|
||||
msgstr "anv klok"
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
msgid "short name"
|
||||
msgstr "anv berr"
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:174 core/models.py:174
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
msgid "language"
|
||||
msgstr "yezh"
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
msgid "device"
|
||||
msgstr "trevnad"
|
||||
|
||||
#: build/lib/core/models.py:188 core/models.py:188
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:191 core/models.py:191
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:196 core/models.py:196
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:211 core/models.py:211
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "user"
|
||||
msgstr "implijer"
|
||||
|
||||
#: build/lib/core/models.py:212 core/models.py:212
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
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:470 build/lib/core/models.py:1155
|
||||
#: core/models.py:470 core/models.py:1155
|
||||
msgid "title"
|
||||
msgstr "titl"
|
||||
|
||||
#: build/lib/core/models.py:369 core/models.py:369
|
||||
#: build/lib/core/models.py:471 core/models.py:471
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:418 core/models.py:418
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:419 core/models.py:419
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
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:532 build/lib/core/models.py:873 core/models.py:532
|
||||
#: core/models.py:873
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:908 core/models.py:908
|
||||
#, 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:912 core/models.py:912
|
||||
#, 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:918 core/models.py:918
|
||||
#, 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:1016 core/models.py:1016
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:1017 core/models.py:1017
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
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:1046 core/models.py:1046
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
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:1075 core/models.py:1075
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1082 core/models.py:1082
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1088 core/models.py:1088
|
||||
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:1094 build/lib/core/models.py:1242
|
||||
#: core/models.py:1094 core/models.py:1242
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#, 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:1156 core/models.py:1156
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1158 core/models.py:1158
|
||||
msgid "css"
|
||||
msgstr "css"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1160 core/models.py:1160
|
||||
msgid "public"
|
||||
msgstr "publik"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1162 core/models.py:1162
|
||||
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:1168 core/models.py:1168
|
||||
msgid "Template"
|
||||
msgstr "Patrom"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1169 core/models.py:1169
|
||||
msgid "Templates"
|
||||
msgstr "Patromoù"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1224 core/models.py:1224
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1230 core/models.py:1230
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1236 core/models.py:1236
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1259 core/models.py:1259
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1279 core/models.py:1279
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1299 core/models.py:1299
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:162
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:209
|
||||
#: core/templates/mail/text/template.txt:10
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr "Digeriñ"
|
||||
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:233
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
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-05-22 12:09+0000\n"
|
||||
"PO-Revision-Date: 2025-05-22 14:16\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Language: de_DE\n"
|
||||
@@ -46,61 +46,31 @@ msgstr "Ersteller bin ich"
|
||||
msgid "Favorite"
|
||||
msgstr "Favorit"
|
||||
|
||||
#: build/lib/core/api/serializers.py:467 core/api/serializers.py:467
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Ein neues Dokument wurde in Ihrem Namen erstellt!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:471 core/api/serializers.py:471
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Sie sind Besitzer eines neuen Dokuments:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:608 core/api/serializers.py:608
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
msgid "Body"
|
||||
msgstr "Inhalt"
|
||||
|
||||
#: build/lib/core/api/serializers.py:611 core/api/serializers.py:611
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
msgid "Body type"
|
||||
msgstr "Typ"
|
||||
|
||||
#: build/lib/core/api/serializers.py:617 core/api/serializers.py:617
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
msgid "Format"
|
||||
msgstr "Format"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "Kopie von {title}"
|
||||
|
||||
#: build/lib/core/choices.py:35 build/lib/core/choices.py:42 core/choices.py:35
|
||||
#: core/choices.py:42
|
||||
msgid "Reader"
|
||||
msgstr "Lesen"
|
||||
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:43 core/choices.py:36
|
||||
#: core/choices.py:43
|
||||
msgid "Editor"
|
||||
msgstr "Bearbeiten"
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: build/lib/core/choices.py:45 core/choices.py:45
|
||||
msgid "Owner"
|
||||
msgstr "Besitzer"
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr "Beschränkt"
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr "Authentifiziert"
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
msgstr "Öffentlich"
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
msgstr "Erstes Unterelement"
|
||||
@@ -125,292 +95,295 @@ msgstr "Links"
|
||||
msgid "Right"
|
||||
msgstr "Rechts"
|
||||
|
||||
#: build/lib/core/models.py:79 core/models.py:79
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Reader"
|
||||
msgstr "Lesen"
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
msgid "Editor"
|
||||
msgstr "Bearbeiten"
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Administrator"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
msgid "Owner"
|
||||
msgstr "Besitzer"
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
msgid "Restricted"
|
||||
msgstr "Beschränkt"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "Authenticated"
|
||||
msgstr "Authentifiziert"
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
msgid "Public"
|
||||
msgstr "Öffentlich"
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "primärer Schlüssel für den Datensatz als UUID"
|
||||
|
||||
#: build/lib/core/models.py:86 core/models.py:86
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "created on"
|
||||
msgstr "Erstellt"
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "Datum und Uhrzeit, an dem ein Datensatz erstellt wurde"
|
||||
|
||||
#: build/lib/core/models.py:92 core/models.py:92
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "updated on"
|
||||
msgstr "Aktualisiert"
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "Datum und Uhrzeit, an dem zuletzt aktualisiert wurde"
|
||||
|
||||
#: build/lib/core/models.py:129 core/models.py:129
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr "Wir konnten keinen Benutzer mit diesem Abo finden, aber die E-Mail-Adresse ist bereits einem registrierten Benutzer zugeordnet."
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr "Geben Sie eine gültige Unterseite ein. Dieser Wert darf nur Buchstaben, Zahlen und die @/./+/-/_/: Zeichen enthalten."
|
||||
|
||||
#: build/lib/core/models.py:148 core/models.py:148
|
||||
#: build/lib/core/models.py:223 core/models.py:223
|
||||
msgid "sub"
|
||||
msgstr "unter"
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr "Erforderlich. 255 Zeichen oder weniger. Buchstaben, Zahlen und die Zeichen @/./+/-/_/:"
|
||||
|
||||
#: build/lib/core/models.py:159 core/models.py:159
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "full name"
|
||||
msgstr "Name"
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
msgid "short name"
|
||||
msgstr "Kurzbezeichnung"
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
msgid "identity email address"
|
||||
msgstr "Identitäts-E-Mail-Adresse"
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "admin email address"
|
||||
msgstr "Admin E-Mail-Adresse"
|
||||
|
||||
#: build/lib/core/models.py:174 core/models.py:174
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
msgid "language"
|
||||
msgstr "Sprache"
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "Die Sprache, in der der Benutzer die Benutzeroberfläche sehen möchte."
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "Die Zeitzone, in der der Nutzer Zeiten sehen möchte."
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
msgid "device"
|
||||
msgstr "Gerät"
|
||||
|
||||
#: build/lib/core/models.py:188 core/models.py:188
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Ob der Benutzer ein Gerät oder ein echter Benutzer ist."
|
||||
|
||||
#: build/lib/core/models.py:191 core/models.py:191
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
msgid "staff status"
|
||||
msgstr "Status des Teammitgliedes"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Gibt an, ob der Benutzer sich in diese Admin-Seite einloggen kann."
|
||||
|
||||
#: build/lib/core/models.py:196 core/models.py:196
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
msgid "active"
|
||||
msgstr "aktiviert"
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "Ob dieser Benutzer als aktiviert behandelt werden soll. Deaktivieren Sie diese Option, anstatt Konten zu löschen."
|
||||
|
||||
#: build/lib/core/models.py:211 core/models.py:211
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "user"
|
||||
msgstr "Benutzer"
|
||||
|
||||
#: build/lib/core/models.py:212 core/models.py:212
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
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:470 build/lib/core/models.py:1155
|
||||
#: core/models.py:470 core/models.py:1155
|
||||
msgid "title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: build/lib/core/models.py:369 core/models.py:369
|
||||
#: build/lib/core/models.py:471 core/models.py:471
|
||||
msgid "excerpt"
|
||||
msgstr "Auszug"
|
||||
|
||||
#: build/lib/core/models.py:418 core/models.py:418
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
msgid "Document"
|
||||
msgstr "Dokument"
|
||||
|
||||
#: build/lib/core/models.py:419 core/models.py:419
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
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:532 build/lib/core/models.py:873 core/models.py:532
|
||||
#: core/models.py:873
|
||||
msgid "Untitled Document"
|
||||
msgstr "Unbenanntes Dokument"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:908 core/models.py:908
|
||||
#, 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:912 core/models.py:912
|
||||
#, 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:918 core/models.py:918
|
||||
#, 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:1016 core/models.py:1016
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Dokument/Benutzer Linkverfolgung"
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:1017 core/models.py:1017
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Dokument/Benutzer Linkverfolgung"
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
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:1046 core/models.py:1046
|
||||
msgid "Document favorite"
|
||||
msgstr "Dokumentenfavorit"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
msgid "Document favorites"
|
||||
msgstr "Dokumentfavoriten"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
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:1075 core/models.py:1075
|
||||
msgid "Document/user relation"
|
||||
msgstr "Dokument/Benutzerbeziehung"
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "Document/user relations"
|
||||
msgstr "Dokument/Benutzerbeziehungen"
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1082 core/models.py:1082
|
||||
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:1088 core/models.py:1088
|
||||
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:1094 build/lib/core/models.py:1242
|
||||
#: core/models.py:1094 core/models.py:1242
|
||||
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
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#, 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:1156 core/models.py:1156
|
||||
msgid "description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
msgid "code"
|
||||
msgstr "Code"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1158 core/models.py:1158
|
||||
msgid "css"
|
||||
msgstr "CSS"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1160 core/models.py:1160
|
||||
msgid "public"
|
||||
msgstr "öffentlich"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1162 core/models.py:1162
|
||||
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:1168 core/models.py:1168
|
||||
msgid "Template"
|
||||
msgstr "Vorlage"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1169 core/models.py:1169
|
||||
msgid "Templates"
|
||||
msgstr "Vorlagen"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
msgid "Template/user relation"
|
||||
msgstr "Vorlage/Benutzer-Beziehung"
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1224 core/models.py:1224
|
||||
msgid "Template/user relations"
|
||||
msgstr "Vorlage/Benutzerbeziehungen"
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1230 core/models.py:1230
|
||||
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:1236 core/models.py:1236
|
||||
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:1259 core/models.py:1259
|
||||
msgid "email address"
|
||||
msgstr "E-Mail-Adresse"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
msgid "Document invitation"
|
||||
msgstr "Einladung zum Dokument"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1279 core/models.py:1279
|
||||
msgid "Document invitations"
|
||||
msgstr "Dokumenteinladungen"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1299 core/models.py:1299
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Diese E-Mail ist bereits einem registrierten Benutzer zugeordnet."
|
||||
|
||||
#: core/templates/mail/html/template.html:162
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr "Logo-E-Mail"
|
||||
|
||||
#: core/templates/mail/html/template.html:209
|
||||
#: core/templates/mail/text/template.txt:10
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr "Öffnen"
|
||||
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr " Docs, Ihr neues unentbehrliches Werkzeug für die Organisation, den Austausch und die Zusammenarbeit in Ihren Dokumenten als Team. "
|
||||
|
||||
#: core/templates/mail/html/template.html:233
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
msgstr " Erstellt von %(brandname)s "
|
||||
|
||||
@@ -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-05-22 12:09+0000\n"
|
||||
"PO-Revision-Date: 2025-05-22 14:16\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: English\n"
|
||||
"Language: en_US\n"
|
||||
@@ -46,61 +46,31 @@ msgstr ""
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:467 core/api/serializers.py:467
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:471 core/api/serializers.py:471
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:608 core/api/serializers.py:608
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
msgid "Body"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:611 core/api/serializers.py:611
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
msgid "Body type"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:617 core/api/serializers.py:617
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
msgid "Format"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:35 build/lib/core/choices.py:42 core/choices.py:35
|
||||
#: core/choices.py:42
|
||||
msgid "Reader"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:43 core/choices.py:36
|
||||
#: core/choices.py:43
|
||||
msgid "Editor"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:45 core/choices.py:45
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
msgstr ""
|
||||
@@ -125,292 +95,295 @@ msgstr ""
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:79 core/models.py:79
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Reader"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
msgid "Editor"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Administrator"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:86 core/models.py:86
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:92 core/models.py:92
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:129 core/models.py:129
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:148 core/models.py:148
|
||||
#: build/lib/core/models.py:223 core/models.py:223
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:159 core/models.py:159
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
msgid "short name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:174 core/models.py:174
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
msgid "device"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:188 core/models.py:188
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:191 core/models.py:191
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:196 core/models.py:196
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:211 core/models.py:211
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:212 core/models.py:212
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
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:470 build/lib/core/models.py:1155
|
||||
#: core/models.py:470 core/models.py:1155
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:369 core/models.py:369
|
||||
#: build/lib/core/models.py:471 core/models.py:471
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:418 core/models.py:418
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:419 core/models.py:419
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
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:532 build/lib/core/models.py:873 core/models.py:532
|
||||
#: core/models.py:873
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:908 core/models.py:908
|
||||
#, 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:912 core/models.py:912
|
||||
#, 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:918 core/models.py:918
|
||||
#, 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:1016 core/models.py:1016
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:1017 core/models.py:1017
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
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:1046 core/models.py:1046
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
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:1075 core/models.py:1075
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1082 core/models.py:1082
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1088 core/models.py:1088
|
||||
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:1094 build/lib/core/models.py:1242
|
||||
#: core/models.py:1094 core/models.py:1242
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#, 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:1156 core/models.py:1156
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1158 core/models.py:1158
|
||||
msgid "css"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1160 core/models.py:1160
|
||||
msgid "public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1162 core/models.py:1162
|
||||
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:1168 core/models.py:1168
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1169 core/models.py:1169
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1224 core/models.py:1224
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1230 core/models.py:1230
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1236 core/models.py:1236
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1259 core/models.py:1259
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1279 core/models.py:1279
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1299 core/models.py:1299
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:162
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:209
|
||||
#: core/templates/mail/text/template.txt:10
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:233
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
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-05-22 12:09+0000\n"
|
||||
"PO-Revision-Date: 2025-05-22 14:16\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Language: es_ES\n"
|
||||
@@ -46,61 +46,31 @@ msgstr "Yo soy el creador"
|
||||
msgid "Favorite"
|
||||
msgstr "Favorito"
|
||||
|
||||
#: build/lib/core/api/serializers.py:467 core/api/serializers.py:467
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "¡Un nuevo documento se ha creado por ti!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:471 core/api/serializers.py:471
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Se le ha concedido la propiedad de un nuevo documento :"
|
||||
|
||||
#: build/lib/core/api/serializers.py:608 core/api/serializers.py:608
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
msgid "Body"
|
||||
msgstr "Cuerpo"
|
||||
|
||||
#: build/lib/core/api/serializers.py:611 core/api/serializers.py:611
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
msgid "Body type"
|
||||
msgstr "Tipo de Cuerpo"
|
||||
|
||||
#: build/lib/core/api/serializers.py:617 core/api/serializers.py:617
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
msgid "Format"
|
||||
msgstr "Formato"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "copia 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 "Lector"
|
||||
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:43 core/choices.py:36
|
||||
#: core/choices.py:43
|
||||
msgid "Editor"
|
||||
msgstr "Editor"
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
msgstr "Administrador"
|
||||
|
||||
#: build/lib/core/choices.py:45 core/choices.py:45
|
||||
msgid "Owner"
|
||||
msgstr "Propietario"
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr "Restringido"
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr "Autentificado"
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
msgstr "Público"
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
msgstr "Primer nodo"
|
||||
@@ -125,292 +95,295 @@ msgstr "Izquierda"
|
||||
msgid "Right"
|
||||
msgstr "Derecha"
|
||||
|
||||
#: build/lib/core/models.py:79 core/models.py:79
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Reader"
|
||||
msgstr "Lector"
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
msgid "Editor"
|
||||
msgstr "Editor"
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Administrator"
|
||||
msgstr "Administrador"
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
msgid "Owner"
|
||||
msgstr "Propietario"
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
msgid "Restricted"
|
||||
msgstr "Restringido"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "Authenticated"
|
||||
msgstr "Autentificado"
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
msgid "Public"
|
||||
msgstr "Público"
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "clave primaria para el registro como UUID"
|
||||
|
||||
#: build/lib/core/models.py:86 core/models.py:86
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "created on"
|
||||
msgstr "creado el"
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "fecha y hora en la que se creó un registro"
|
||||
|
||||
#: build/lib/core/models.py:92 core/models.py:92
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "updated on"
|
||||
msgstr "actualizado el"
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "fecha y hora en la que un registro fue actualizado por última vez"
|
||||
|
||||
#: build/lib/core/models.py:129 core/models.py:129
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr "No se ha podido encontrar un usuario con este sub (UUID), pero el correo electrónico ya está asociado con un usuario."
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr "Introduzca un sub (UUID) válido. Este valor solo puede contener letras, números y los siguientes caracteres @/./+/-/_/:"
|
||||
|
||||
#: build/lib/core/models.py:148 core/models.py:148
|
||||
#: build/lib/core/models.py:223 core/models.py:223
|
||||
msgid "sub"
|
||||
msgstr "sub (UUID)"
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr "Requerido. 255 caracteres o menos. Letras, números y los siguientes caracteres @/./+/-/_/: solamente."
|
||||
|
||||
#: build/lib/core/models.py:159 core/models.py:159
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "full name"
|
||||
msgstr "nombre completo"
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
msgid "short name"
|
||||
msgstr "nombre abreviado"
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
msgid "identity email address"
|
||||
msgstr "correo electrónico de identidad"
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "admin email address"
|
||||
msgstr "correo electrónico del administrador"
|
||||
|
||||
#: build/lib/core/models.py:174 core/models.py:174
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
msgid "language"
|
||||
msgstr "idioma"
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "El idioma en el que el usuario desea ver la interfaz."
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "La zona horaria en la que el usuario quiere ver los tiempos."
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
msgid "device"
|
||||
msgstr "dispositivo"
|
||||
|
||||
#: build/lib/core/models.py:188 core/models.py:188
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Si el usuario es un dispositivo o un usuario real."
|
||||
|
||||
#: build/lib/core/models.py:191 core/models.py:191
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
msgid "staff status"
|
||||
msgstr "rol en el equipo"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Si el usuario puede iniciar sesión en esta página web de administración."
|
||||
|
||||
#: build/lib/core/models.py:196 core/models.py:196
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
msgid "active"
|
||||
msgstr "activo"
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "Si este usuario debe ser considerado como activo. Deseleccionar en lugar de eliminar cuentas."
|
||||
|
||||
#: build/lib/core/models.py:211 core/models.py:211
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "user"
|
||||
msgstr "usuario"
|
||||
|
||||
#: build/lib/core/models.py:212 core/models.py:212
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
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:470 build/lib/core/models.py:1155
|
||||
#: core/models.py:470 core/models.py:1155
|
||||
msgid "title"
|
||||
msgstr "título"
|
||||
|
||||
#: build/lib/core/models.py:369 core/models.py:369
|
||||
#: build/lib/core/models.py:471 core/models.py:471
|
||||
msgid "excerpt"
|
||||
msgstr "resumen"
|
||||
|
||||
#: build/lib/core/models.py:418 core/models.py:418
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
msgid "Document"
|
||||
msgstr "Documento"
|
||||
|
||||
#: build/lib/core/models.py:419 core/models.py:419
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
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:532 build/lib/core/models.py:873 core/models.py:532
|
||||
#: core/models.py:873
|
||||
msgid "Untitled Document"
|
||||
msgstr "Documento sin título"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:908 core/models.py:908
|
||||
#, 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:912 core/models.py:912
|
||||
#, 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:918 core/models.py:918
|
||||
#, 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:1016 core/models.py:1016
|
||||
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:1017 core/models.py:1017
|
||||
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:1023 core/models.py:1023
|
||||
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:1046 core/models.py:1046
|
||||
msgid "Document favorite"
|
||||
msgstr "Documento favorito"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
msgid "Document favorites"
|
||||
msgstr "Documentos favoritos"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
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:1075 core/models.py:1075
|
||||
msgid "Document/user relation"
|
||||
msgstr "Relación documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "Document/user relations"
|
||||
msgstr "Relaciones documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1082 core/models.py:1082
|
||||
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:1088 core/models.py:1088
|
||||
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:1094 build/lib/core/models.py:1242
|
||||
#: core/models.py:1094 core/models.py:1242
|
||||
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
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
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
|
||||
#, 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
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#, 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:1156 core/models.py:1156
|
||||
msgid "description"
|
||||
msgstr "descripción"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
msgid "code"
|
||||
msgstr "código"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1158 core/models.py:1158
|
||||
msgid "css"
|
||||
msgstr "css"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1160 core/models.py:1160
|
||||
msgid "public"
|
||||
msgstr "público"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1162 core/models.py:1162
|
||||
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:1168 core/models.py:1168
|
||||
msgid "Template"
|
||||
msgstr "Plantilla"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1169 core/models.py:1169
|
||||
msgid "Templates"
|
||||
msgstr "Plantillas"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
msgid "Template/user relation"
|
||||
msgstr "Relación plantilla/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1224 core/models.py:1224
|
||||
msgid "Template/user relations"
|
||||
msgstr "Relaciones plantilla/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1230 core/models.py:1230
|
||||
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:1236 core/models.py:1236
|
||||
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:1259 core/models.py:1259
|
||||
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:1278 core/models.py:1278
|
||||
msgid "Document invitation"
|
||||
msgstr "Invitación al documento"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1279 core/models.py:1279
|
||||
msgid "Document invitations"
|
||||
msgstr "Invitaciones a documentos"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1299 core/models.py:1299
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Este correo electrónico está asociado a un usuario registrado."
|
||||
|
||||
#: core/templates/mail/html/template.html:162
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr "Logo de correo electrónico"
|
||||
|
||||
#: core/templates/mail/html/template.html:209
|
||||
#: core/templates/mail/text/template.txt:10
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr "Abrir"
|
||||
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr "Docs, su nueva herramienta esencial para organizar, compartir y colaborar en sus documentos como equipo."
|
||||
|
||||
#: core/templates/mail/html/template.html:233
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
msgstr " Presentado por %(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 11:52\n"
|
||||
"POT-Creation-Date: 2025-05-22 12:09+0000\n"
|
||||
"PO-Revision-Date: 2025-05-22 14:16\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr_FR\n"
|
||||
@@ -46,61 +46,31 @@ msgstr "Je suis l'auteur"
|
||||
msgid "Favorite"
|
||||
msgstr "Favoris"
|
||||
|
||||
#: build/lib/core/api/serializers.py:467 core/api/serializers.py:467
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Un nouveau document a été créé pour vous !"
|
||||
|
||||
#: build/lib/core/api/serializers.py:471 core/api/serializers.py:471
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Vous avez été déclaré propriétaire d'un nouveau document :"
|
||||
|
||||
#: build/lib/core/api/serializers.py:608 core/api/serializers.py:608
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
msgid "Body"
|
||||
msgstr "Corps"
|
||||
|
||||
#: build/lib/core/api/serializers.py:611 core/api/serializers.py:611
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
msgid "Body type"
|
||||
msgstr "Type de corps"
|
||||
|
||||
#: build/lib/core/api/serializers.py:617 core/api/serializers.py:617
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
msgid "Format"
|
||||
msgstr "Format"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "copie 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 "Lecteur"
|
||||
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:43 core/choices.py:36
|
||||
#: core/choices.py:43
|
||||
msgid "Editor"
|
||||
msgstr "Éditeur"
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
msgstr "Administrateur"
|
||||
|
||||
#: build/lib/core/choices.py:45 core/choices.py:45
|
||||
msgid "Owner"
|
||||
msgstr "Propriétaire"
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr "Restreint"
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr "Authentifié"
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
msgstr "Public"
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
msgstr "Premier enfant"
|
||||
@@ -125,292 +95,295 @@ msgstr "Gauche"
|
||||
msgid "Right"
|
||||
msgstr "Droite"
|
||||
|
||||
#: build/lib/core/models.py:79 core/models.py:79
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Reader"
|
||||
msgstr "Lecteur"
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
msgid "Editor"
|
||||
msgstr "Éditeur"
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Administrator"
|
||||
msgstr "Administrateur"
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
msgid "Owner"
|
||||
msgstr "Propriétaire"
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
msgid "Restricted"
|
||||
msgstr "Restreint"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "Authenticated"
|
||||
msgstr "Authentifié"
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
msgid "Public"
|
||||
msgstr "Public"
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "id"
|
||||
msgstr "identifiant/id"
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "clé primaire pour l'enregistrement en tant que UUID"
|
||||
|
||||
#: build/lib/core/models.py:86 core/models.py:86
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "created on"
|
||||
msgstr "créé le"
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "date et heure de création de l'enregistrement"
|
||||
|
||||
#: build/lib/core/models.py:92 core/models.py:92
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "updated on"
|
||||
msgstr "mis à jour le"
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "date et heure de la dernière mise à jour de l'enregistrement"
|
||||
|
||||
#: build/lib/core/models.py:129 core/models.py:129
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr "Nous n'avons pas pu trouver un utilisateur avec ce sous-groupe mais l'e-mail est déjà associé à un utilisateur enregistré."
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr "Saisissez un sous-groupe valide. Cette valeur ne peut contenir que des lettres, des chiffres et les caractères @/./+/-/_/: uniquement."
|
||||
|
||||
#: build/lib/core/models.py:148 core/models.py:148
|
||||
#: build/lib/core/models.py:223 core/models.py:223
|
||||
msgid "sub"
|
||||
msgstr "sous-groupe"
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr "Obligatoire. 255 caractères ou moins. Lettres, chiffres et caractères @/./+/-/_/: uniquement."
|
||||
|
||||
#: build/lib/core/models.py:159 core/models.py:159
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "full name"
|
||||
msgstr "nom complet"
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
msgid "short name"
|
||||
msgstr "nom court"
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
msgid "identity email address"
|
||||
msgstr "adresse e-mail d'identité"
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "admin email address"
|
||||
msgstr "adresse e-mail de l'administrateur"
|
||||
|
||||
#: build/lib/core/models.py:174 core/models.py:174
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
msgid "language"
|
||||
msgstr "langue"
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "La langue dans laquelle l'utilisateur veut voir l'interface."
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "Le fuseau horaire dans lequel l'utilisateur souhaite voir les heures."
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
msgid "device"
|
||||
msgstr "appareil"
|
||||
|
||||
#: build/lib/core/models.py:188 core/models.py:188
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Si l'utilisateur est un appareil ou un utilisateur réel."
|
||||
|
||||
#: build/lib/core/models.py:191 core/models.py:191
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
msgid "staff status"
|
||||
msgstr "statut d'équipe"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Si l'utilisateur peut se connecter à ce site d'administration."
|
||||
|
||||
#: build/lib/core/models.py:196 core/models.py:196
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
msgid "active"
|
||||
msgstr "actif"
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "Si cet utilisateur doit être traité comme actif. Désélectionnez ceci au lieu de supprimer des comptes."
|
||||
|
||||
#: build/lib/core/models.py:211 core/models.py:211
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "user"
|
||||
msgstr "utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:212 core/models.py:212
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
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:470 build/lib/core/models.py:1155
|
||||
#: core/models.py:470 core/models.py:1155
|
||||
msgid "title"
|
||||
msgstr "titre"
|
||||
|
||||
#: build/lib/core/models.py:369 core/models.py:369
|
||||
#: build/lib/core/models.py:471 core/models.py:471
|
||||
msgid "excerpt"
|
||||
msgstr "extrait"
|
||||
|
||||
#: build/lib/core/models.py:418 core/models.py:418
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
msgid "Document"
|
||||
msgstr "Document"
|
||||
|
||||
#: build/lib/core/models.py:419 core/models.py:419
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
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:532 build/lib/core/models.py:873 core/models.py:532
|
||||
#: core/models.py:873
|
||||
msgid "Untitled Document"
|
||||
msgstr "Document sans titre"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:908 core/models.py:908
|
||||
#, 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:912 core/models.py:912
|
||||
#, 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:918 core/models.py:918
|
||||
#, 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:1016 core/models.py:1016
|
||||
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:1017 core/models.py:1017
|
||||
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:1023 core/models.py:1023
|
||||
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:1046 core/models.py:1046
|
||||
msgid "Document favorite"
|
||||
msgstr "Document favori"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
msgid "Document favorites"
|
||||
msgstr "Documents favoris"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
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:1075 core/models.py:1075
|
||||
msgid "Document/user relation"
|
||||
msgstr "Relation document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "Document/user relations"
|
||||
msgstr "Relations document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1082 core/models.py:1082
|
||||
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:1088 core/models.py:1088
|
||||
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:1094 build/lib/core/models.py:1242
|
||||
#: core/models.py:1094 core/models.py:1242
|
||||
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
|
||||
msgid "Document ask for access"
|
||||
msgstr "Demande d'accès au document"
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Demande d'accès au document"
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
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
|
||||
#, 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
|
||||
#, 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
|
||||
#, 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:1156 core/models.py:1156
|
||||
msgid "description"
|
||||
msgstr "description"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
msgid "code"
|
||||
msgstr "code"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1158 core/models.py:1158
|
||||
msgid "css"
|
||||
msgstr "CSS"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1160 core/models.py:1160
|
||||
msgid "public"
|
||||
msgstr "public"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1162 core/models.py:1162
|
||||
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:1168 core/models.py:1168
|
||||
msgid "Template"
|
||||
msgstr "Modèle"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1169 core/models.py:1169
|
||||
msgid "Templates"
|
||||
msgstr "Modèles"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
msgid "Template/user relation"
|
||||
msgstr "Relation modèle/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1224 core/models.py:1224
|
||||
msgid "Template/user relations"
|
||||
msgstr "Relations modèle/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1230 core/models.py:1230
|
||||
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:1236 core/models.py:1236
|
||||
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:1259 core/models.py:1259
|
||||
msgid "email address"
|
||||
msgstr "adresse e-mail"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
msgid "Document invitation"
|
||||
msgstr "Invitation à un document"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1279 core/models.py:1279
|
||||
msgid "Document invitations"
|
||||
msgstr "Invitations à un document"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1299 core/models.py:1299
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Cette adresse email est déjà associée à un utilisateur inscrit."
|
||||
|
||||
#: core/templates/mail/html/template.html:162
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr "Logo de l'e-mail"
|
||||
|
||||
#: core/templates/mail/html/template.html:209
|
||||
#: core/templates/mail/text/template.txt:10
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr "Ouvrir"
|
||||
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr " Docs, votre nouvel outil incontournable pour organiser, partager et collaborer sur vos documents en équipe. "
|
||||
|
||||
#: core/templates/mail/html/template.html:233
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
msgstr " Proposé par %(brandname)s "
|
||||
|
||||
@@ -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-05-22 12:09+0000\n"
|
||||
"PO-Revision-Date: 2025-05-22 14:16\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Italian\n"
|
||||
"Language: it_IT\n"
|
||||
@@ -46,61 +46,31 @@ msgstr "Il creatore sono io"
|
||||
msgid "Favorite"
|
||||
msgstr "Preferiti"
|
||||
|
||||
#: build/lib/core/api/serializers.py:467 core/api/serializers.py:467
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Un nuovo documento è stato creato a tuo nome!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:471 core/api/serializers.py:471
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Sei ora proprietario di un nuovo documento:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:608 core/api/serializers.py:608
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
msgid "Body"
|
||||
msgstr "Corpo"
|
||||
|
||||
#: build/lib/core/api/serializers.py:611 core/api/serializers.py:611
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
msgid "Body type"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:617 core/api/serializers.py:617
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
msgid "Format"
|
||||
msgstr "Formato"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "copia di {title}"
|
||||
|
||||
#: build/lib/core/choices.py:35 build/lib/core/choices.py:42 core/choices.py:35
|
||||
#: core/choices.py:42
|
||||
msgid "Reader"
|
||||
msgstr "Lettore"
|
||||
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:43 core/choices.py:36
|
||||
#: core/choices.py:43
|
||||
msgid "Editor"
|
||||
msgstr "Editor"
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
msgstr "Amministratore"
|
||||
|
||||
#: build/lib/core/choices.py:45 core/choices.py:45
|
||||
msgid "Owner"
|
||||
msgstr "Proprietario"
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr "Limitato"
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr "Autenticato"
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
msgstr "Pubblico"
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
msgstr ""
|
||||
@@ -125,292 +95,295 @@ msgstr "Sinistra"
|
||||
msgid "Right"
|
||||
msgstr "Destra"
|
||||
|
||||
#: build/lib/core/models.py:79 core/models.py:79
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Reader"
|
||||
msgstr "Lettore"
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
msgid "Editor"
|
||||
msgstr "Editor"
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Administrator"
|
||||
msgstr "Amministratore"
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
msgid "Owner"
|
||||
msgstr "Proprietario"
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
msgid "Restricted"
|
||||
msgstr "Limitato"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "Authenticated"
|
||||
msgstr "Autenticato"
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
msgid "Public"
|
||||
msgstr "Pubblico"
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "id"
|
||||
msgstr "Id"
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "chiave primaria per il record come UUID"
|
||||
|
||||
#: build/lib/core/models.py:86 core/models.py:86
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "created on"
|
||||
msgstr "creato il"
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "data e ora in cui è stato creato un record"
|
||||
|
||||
#: build/lib/core/models.py:92 core/models.py:92
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "updated on"
|
||||
msgstr "aggiornato il"
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "data e ora in cui l’ultimo record è stato aggiornato"
|
||||
|
||||
#: build/lib/core/models.py:129 core/models.py:129
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:148 core/models.py:148
|
||||
#: build/lib/core/models.py:223 core/models.py:223
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr "Richiesto. 255 caratteri o meno. Solo lettere, numeri e @/./+/-/_/: caratteri."
|
||||
|
||||
#: build/lib/core/models.py:159 core/models.py:159
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "full name"
|
||||
msgstr "nome completo"
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
msgid "short name"
|
||||
msgstr "nome"
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
msgid "identity email address"
|
||||
msgstr "indirizzo email di identità"
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "admin email address"
|
||||
msgstr "Indirizzo email dell'amministratore"
|
||||
|
||||
#: build/lib/core/models.py:174 core/models.py:174
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
msgid "language"
|
||||
msgstr "lingua"
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "La lingua in cui l'utente vuole vedere l'interfaccia."
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "Il fuso orario in cui l'utente vuole vedere gli orari."
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
msgid "device"
|
||||
msgstr "dispositivo"
|
||||
|
||||
#: build/lib/core/models.py:188 core/models.py:188
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Se l'utente è un dispositivo o un utente reale."
|
||||
|
||||
#: build/lib/core/models.py:191 core/models.py:191
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
msgid "staff status"
|
||||
msgstr "stato del personale"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Indica se l'utente può accedere a questo sito amministratore."
|
||||
|
||||
#: build/lib/core/models.py:196 core/models.py:196
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
msgid "active"
|
||||
msgstr "attivo"
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "Indica se questo utente deve essere trattato come attivo. Deseleziona invece di eliminare gli account."
|
||||
|
||||
#: build/lib/core/models.py:211 core/models.py:211
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "user"
|
||||
msgstr "utente"
|
||||
|
||||
#: build/lib/core/models.py:212 core/models.py:212
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
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:470 build/lib/core/models.py:1155
|
||||
#: core/models.py:470 core/models.py:1155
|
||||
msgid "title"
|
||||
msgstr "titolo"
|
||||
|
||||
#: build/lib/core/models.py:369 core/models.py:369
|
||||
#: build/lib/core/models.py:471 core/models.py:471
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:418 core/models.py:418
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
msgid "Document"
|
||||
msgstr "Documento"
|
||||
|
||||
#: build/lib/core/models.py:419 core/models.py:419
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
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:532 build/lib/core/models.py:873 core/models.py:532
|
||||
#: core/models.py:873
|
||||
msgid "Untitled Document"
|
||||
msgstr "Documento senza titolo"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:908 core/models.py:908
|
||||
#, 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:912 core/models.py:912
|
||||
#, 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:918 core/models.py:918
|
||||
#, 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:1016 core/models.py:1016
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:1017 core/models.py:1017
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
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:1046 core/models.py:1046
|
||||
msgid "Document favorite"
|
||||
msgstr "Documento preferito"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
msgid "Document favorites"
|
||||
msgstr "Documenti preferiti"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
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:1075 core/models.py:1075
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1082 core/models.py:1082
|
||||
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:1088 core/models.py:1088
|
||||
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:1094 build/lib/core/models.py:1242
|
||||
#: core/models.py:1094 core/models.py:1242
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#, 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:1156 core/models.py:1156
|
||||
msgid "description"
|
||||
msgstr "descrizione"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
msgid "code"
|
||||
msgstr "code"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1158 core/models.py:1158
|
||||
msgid "css"
|
||||
msgstr "css"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1160 core/models.py:1160
|
||||
msgid "public"
|
||||
msgstr "pubblico"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1162 core/models.py:1162
|
||||
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:1168 core/models.py:1168
|
||||
msgid "Template"
|
||||
msgstr "Modello"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1169 core/models.py:1169
|
||||
msgid "Templates"
|
||||
msgstr "Modelli"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1224 core/models.py:1224
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1230 core/models.py:1230
|
||||
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:1236 core/models.py:1236
|
||||
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:1259 core/models.py:1259
|
||||
msgid "email address"
|
||||
msgstr "indirizzo e-mail"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
msgid "Document invitation"
|
||||
msgstr "Invito al documento"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1279 core/models.py:1279
|
||||
msgid "Document invitations"
|
||||
msgstr "Inviti al documento"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1299 core/models.py:1299
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Questa email è già associata a un utente registrato."
|
||||
|
||||
#: core/templates/mail/html/template.html:162
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr "Logo e-mail"
|
||||
|
||||
#: core/templates/mail/html/template.html:209
|
||||
#: core/templates/mail/text/template.txt:10
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr "Apri"
|
||||
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:233
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
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-05-22 12:09+0000\n"
|
||||
"PO-Revision-Date: 2025-05-22 14:16\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"Language: nl_NL\n"
|
||||
@@ -46,61 +46,31 @@ msgstr "Ik ben Eigenaar"
|
||||
msgid "Favorite"
|
||||
msgstr "Favoriete"
|
||||
|
||||
#: build/lib/core/api/serializers.py:467 core/api/serializers.py:467
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Een nieuw document was gecreëerd voor u!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:471 core/api/serializers.py:471
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "U heeft eigenaarschap van een nieuw document:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:608 core/api/serializers.py:608
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
msgid "Body"
|
||||
msgstr "Text"
|
||||
|
||||
#: build/lib/core/api/serializers.py:611 core/api/serializers.py:611
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
msgid "Body type"
|
||||
msgstr "Text type"
|
||||
|
||||
#: build/lib/core/api/serializers.py:617 core/api/serializers.py:617
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
msgid "Format"
|
||||
msgstr "Formaat"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "kopie van {title}"
|
||||
|
||||
#: build/lib/core/choices.py:35 build/lib/core/choices.py:42 core/choices.py:35
|
||||
#: core/choices.py:42
|
||||
msgid "Reader"
|
||||
msgstr "Lezer"
|
||||
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:43 core/choices.py:36
|
||||
#: core/choices.py:43
|
||||
msgid "Editor"
|
||||
msgstr "Bewerker"
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: build/lib/core/choices.py:45 core/choices.py:45
|
||||
msgid "Owner"
|
||||
msgstr "Eigenaar"
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr "Niet toegestaan"
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr "Geauthenticeerd"
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
msgstr "Publiek"
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
msgstr "Eerste node"
|
||||
@@ -125,292 +95,295 @@ msgstr "Links"
|
||||
msgid "Right"
|
||||
msgstr "Rechts"
|
||||
|
||||
#: build/lib/core/models.py:79 core/models.py:79
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Reader"
|
||||
msgstr "Lezer"
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
msgid "Editor"
|
||||
msgstr "Bewerker"
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Administrator"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
msgid "Owner"
|
||||
msgstr "Eigenaar"
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
msgid "Restricted"
|
||||
msgstr "Niet toegestaan"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "Authenticated"
|
||||
msgstr "Geauthenticeerd"
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
msgid "Public"
|
||||
msgstr "Publiek"
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "primaire sleutel voor dossier als UUID"
|
||||
|
||||
#: build/lib/core/models.py:86 core/models.py:86
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "created on"
|
||||
msgstr "gemaakt op"
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "datum en tijd wanneer dossier was gecreëerd"
|
||||
|
||||
#: build/lib/core/models.py:92 core/models.py:92
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "updated on"
|
||||
msgstr "Laatst gewijzigd op"
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "datum en tijd waarop dossier laatst was gewijzigd"
|
||||
|
||||
#: build/lib/core/models.py:129 core/models.py:129
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr "Wij konden geen gebruiker vinden met deze id, maar de email is al geassocieerd met een geregistreerde gebruiker."
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr ".Geef een valide id. De waarde mag alleen letters, nummers en @/./.+/-/_: karakters bevatten."
|
||||
|
||||
#: build/lib/core/models.py:148 core/models.py:148
|
||||
#: build/lib/core/models.py:223 core/models.py:223
|
||||
msgid "sub"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr "Verplicht. 255 karakters of minder. Alleen letters, nummers en @/./+/-/_/: karakters zijn toegestaan."
|
||||
|
||||
#: build/lib/core/models.py:159 core/models.py:159
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "full name"
|
||||
msgstr "volledige naam"
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
msgid "short name"
|
||||
msgstr "gebruikersnaam"
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
msgid "identity email address"
|
||||
msgstr "identiteit email adres"
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "admin email address"
|
||||
msgstr "admin email adres"
|
||||
|
||||
#: build/lib/core/models.py:174 core/models.py:174
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
msgid "language"
|
||||
msgstr "taal"
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "De taal waarin de gebruiker de interface wilt zien."
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "De tijdzone waarin de gebruiker de tijden wilt zien."
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
msgid "device"
|
||||
msgstr "apparaat"
|
||||
|
||||
#: build/lib/core/models.py:188 core/models.py:188
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Of de gebruiker een apparaat is of een echte gebruiker."
|
||||
|
||||
#: build/lib/core/models.py:191 core/models.py:191
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
msgid "staff status"
|
||||
msgstr "beheerder status"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Of de gebruiker kan inloggen in het admin gedeelte."
|
||||
|
||||
#: build/lib/core/models.py:196 core/models.py:196
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
msgid "active"
|
||||
msgstr "actief"
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "Of een gebruiker als actief moet worden beschouwd. Deselecteer dit in plaats van het account te deleten."
|
||||
|
||||
#: build/lib/core/models.py:211 core/models.py:211
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "user"
|
||||
msgstr "gebruiker"
|
||||
|
||||
#: build/lib/core/models.py:212 core/models.py:212
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
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:470 build/lib/core/models.py:1155
|
||||
#: core/models.py:470 core/models.py:1155
|
||||
msgid "title"
|
||||
msgstr "titel"
|
||||
|
||||
#: build/lib/core/models.py:369 core/models.py:369
|
||||
#: build/lib/core/models.py:471 core/models.py:471
|
||||
msgid "excerpt"
|
||||
msgstr "uittreksel"
|
||||
|
||||
#: build/lib/core/models.py:418 core/models.py:418
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
msgid "Document"
|
||||
msgstr "Document"
|
||||
|
||||
#: build/lib/core/models.py:419 core/models.py:419
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
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:532 build/lib/core/models.py:873 core/models.py:532
|
||||
#: core/models.py:873
|
||||
msgid "Untitled Document"
|
||||
msgstr "Naamloos Document"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:908 core/models.py:908
|
||||
#, 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:912 core/models.py:912
|
||||
#, 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:918 core/models.py:918
|
||||
#, 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:1016 core/models.py:1016
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Document/gebruiker url"
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:1017 core/models.py:1017
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Document/gebruiker url"
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
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:1046 core/models.py:1046
|
||||
msgid "Document favorite"
|
||||
msgstr "Document favoriet"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
msgid "Document favorites"
|
||||
msgstr "Document favorieten"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
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:1075 core/models.py:1075
|
||||
msgid "Document/user relation"
|
||||
msgstr "Document/gebruiker relatie"
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "Document/user relations"
|
||||
msgstr "Document/gebruiker relaties"
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1082 core/models.py:1082
|
||||
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:1088 core/models.py:1088
|
||||
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:1094 build/lib/core/models.py:1242
|
||||
#: core/models.py:1094 core/models.py:1242
|
||||
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
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#, 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:1156 core/models.py:1156
|
||||
msgid "description"
|
||||
msgstr "omschrijving"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
msgid "code"
|
||||
msgstr "code"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1158 core/models.py:1158
|
||||
msgid "css"
|
||||
msgstr "css"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1160 core/models.py:1160
|
||||
msgid "public"
|
||||
msgstr "publiek"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1162 core/models.py:1162
|
||||
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:1168 core/models.py:1168
|
||||
msgid "Template"
|
||||
msgstr "Template"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1169 core/models.py:1169
|
||||
msgid "Templates"
|
||||
msgstr "Templates"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
msgid "Template/user relation"
|
||||
msgstr "Template/gebruiker relatie"
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1224 core/models.py:1224
|
||||
msgid "Template/user relations"
|
||||
msgstr "Template/gebruiker relaties"
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1230 core/models.py:1230
|
||||
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:1236 core/models.py:1236
|
||||
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:1259 core/models.py:1259
|
||||
msgid "email address"
|
||||
msgstr "email adres"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
msgid "Document invitation"
|
||||
msgstr "Document uitnodiging"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1279 core/models.py:1279
|
||||
msgid "Document invitations"
|
||||
msgstr "Document uitnodigingen"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1299 core/models.py:1299
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Deze email is al geassocieerd met een geregistreerde gebruiker."
|
||||
|
||||
#: core/templates/mail/html/template.html:162
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr "Logo email"
|
||||
|
||||
#: core/templates/mail/html/template.html:209
|
||||
#: core/templates/mail/text/template.txt:10
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr "Open"
|
||||
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr " Docs, jouw nieuwe essentiële tool voor het organiseren, delen en collaboreren van documenten als team. "
|
||||
|
||||
#: core/templates/mail/html/template.html:233
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
msgstr " Geleverd door %(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-05-22 12:09+0000\n"
|
||||
"PO-Revision-Date: 2025-05-22 14:16\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese\n"
|
||||
"Language: pt_PT\n"
|
||||
@@ -46,61 +46,31 @@ msgstr ""
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:467 core/api/serializers.py:467
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:471 core/api/serializers.py:471
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:608 core/api/serializers.py:608
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
msgid "Body"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:611 core/api/serializers.py:611
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
msgid "Body type"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:617 core/api/serializers.py:617
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
msgid "Format"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:35 build/lib/core/choices.py:42 core/choices.py:35
|
||||
#: core/choices.py:42
|
||||
msgid "Reader"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:43 core/choices.py:36
|
||||
#: core/choices.py:43
|
||||
msgid "Editor"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:45 core/choices.py:45
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
msgstr ""
|
||||
@@ -125,292 +95,295 @@ msgstr ""
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:79 core/models.py:79
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Reader"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
msgid "Editor"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Administrator"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:86 core/models.py:86
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:92 core/models.py:92
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:129 core/models.py:129
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:148 core/models.py:148
|
||||
#: build/lib/core/models.py:223 core/models.py:223
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:159 core/models.py:159
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
msgid "short name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:174 core/models.py:174
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
msgid "device"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:188 core/models.py:188
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:191 core/models.py:191
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:196 core/models.py:196
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:211 core/models.py:211
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:212 core/models.py:212
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
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:470 build/lib/core/models.py:1155
|
||||
#: core/models.py:470 core/models.py:1155
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:369 core/models.py:369
|
||||
#: build/lib/core/models.py:471 core/models.py:471
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:418 core/models.py:418
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:419 core/models.py:419
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
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:532 build/lib/core/models.py:873 core/models.py:532
|
||||
#: core/models.py:873
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:908 core/models.py:908
|
||||
#, 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:912 core/models.py:912
|
||||
#, 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:918 core/models.py:918
|
||||
#, 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:1016 core/models.py:1016
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:1017 core/models.py:1017
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
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:1046 core/models.py:1046
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
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:1075 core/models.py:1075
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1082 core/models.py:1082
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1088 core/models.py:1088
|
||||
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:1094 build/lib/core/models.py:1242
|
||||
#: core/models.py:1094 core/models.py:1242
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#, 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:1156 core/models.py:1156
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1158 core/models.py:1158
|
||||
msgid "css"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1160 core/models.py:1160
|
||||
msgid "public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1162 core/models.py:1162
|
||||
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:1168 core/models.py:1168
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1169 core/models.py:1169
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1224 core/models.py:1224
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1230 core/models.py:1230
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1236 core/models.py:1236
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1259 core/models.py:1259
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1279 core/models.py:1279
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1299 core/models.py:1299
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:162
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:209
|
||||
#: core/templates/mail/text/template.txt:10
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:233
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
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-05-22 12:09+0000\n"
|
||||
"PO-Revision-Date: 2025-05-22 14:16\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Slovenian\n"
|
||||
"Language: sl_SI\n"
|
||||
@@ -46,61 +46,31 @@ msgstr "Ustvaril sem jaz"
|
||||
msgid "Favorite"
|
||||
msgstr "Priljubljena"
|
||||
|
||||
#: build/lib/core/api/serializers.py:467 core/api/serializers.py:467
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Nov dokument je bil ustvarjen v vašem imenu!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:471 core/api/serializers.py:471
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Dodeljeno vam je bilo lastništvo nad novim dokumentom:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:608 core/api/serializers.py:608
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
msgid "Body"
|
||||
msgstr "Telo"
|
||||
|
||||
#: build/lib/core/api/serializers.py:611 core/api/serializers.py:611
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
msgid "Body type"
|
||||
msgstr "Vrsta telesa"
|
||||
|
||||
#: build/lib/core/api/serializers.py:617 core/api/serializers.py:617
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
msgid "Format"
|
||||
msgstr "Oblika"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:35 build/lib/core/choices.py:42 core/choices.py:35
|
||||
#: core/choices.py:42
|
||||
msgid "Reader"
|
||||
msgstr "Bralec"
|
||||
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:43 core/choices.py:36
|
||||
#: core/choices.py:43
|
||||
msgid "Editor"
|
||||
msgstr "Urednik"
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
msgstr "Skrbnik"
|
||||
|
||||
#: build/lib/core/choices.py:45 core/choices.py:45
|
||||
msgid "Owner"
|
||||
msgstr "Lastnik"
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr "Omejeno"
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr "Preverjeno"
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
msgstr "Javno"
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
msgstr "Prvi otrok"
|
||||
@@ -125,292 +95,295 @@ msgstr "Levo"
|
||||
msgid "Right"
|
||||
msgstr "Desno"
|
||||
|
||||
#: build/lib/core/models.py:79 core/models.py:79
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Reader"
|
||||
msgstr "Bralec"
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
msgid "Editor"
|
||||
msgstr "Urednik"
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Administrator"
|
||||
msgstr "Skrbnik"
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
msgid "Owner"
|
||||
msgstr "Lastnik"
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
msgid "Restricted"
|
||||
msgstr "Omejeno"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "Authenticated"
|
||||
msgstr "Preverjeno"
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
msgid "Public"
|
||||
msgstr "Javno"
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "primarni ključ za zapis kot UUID"
|
||||
|
||||
#: build/lib/core/models.py:86 core/models.py:86
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "created on"
|
||||
msgstr "ustvarjen na"
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "datum in čas, ko je bil zapis ustvarjen"
|
||||
|
||||
#: build/lib/core/models.py:92 core/models.py:92
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "updated on"
|
||||
msgstr "posodobljeno dne"
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "datum in čas, ko je bil zapis nazadnje posodobljen"
|
||||
|
||||
#: build/lib/core/models.py:129 core/models.py:129
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr "Nismo mogli najti uporabnika s tem sub, vendar je e-poštni naslov že povezan z registriranim uporabnikom."
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr "Vnesite veljavno sub. Ta vrednost lahko vsebuje samo črke, številke in znake @/./+/-/_/:."
|
||||
|
||||
#: build/lib/core/models.py:148 core/models.py:148
|
||||
#: build/lib/core/models.py:223 core/models.py:223
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr "Obvezno. 255 znakov ali manj. Samo črke, številke in znaki @/./+/-/_/: ."
|
||||
|
||||
#: build/lib/core/models.py:159 core/models.py:159
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "full name"
|
||||
msgstr "polno ime"
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
msgid "short name"
|
||||
msgstr "kratko ime"
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
msgid "identity email address"
|
||||
msgstr "elektronski naslov identitete"
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "admin email address"
|
||||
msgstr "elektronski naslov skrbnika"
|
||||
|
||||
#: build/lib/core/models.py:174 core/models.py:174
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
msgid "language"
|
||||
msgstr "jezik"
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "Jezik, v katerem uporabnik želi videti vmesnik."
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "Časovni pas, v katerem želi uporabnik videti uro."
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
msgid "device"
|
||||
msgstr "naprava"
|
||||
|
||||
#: build/lib/core/models.py:188 core/models.py:188
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Ali je uporabnik naprava ali pravi uporabnik."
|
||||
|
||||
#: build/lib/core/models.py:191 core/models.py:191
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
msgid "staff status"
|
||||
msgstr "kadrovski status"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Ali se uporabnik lahko prijavi na to skrbniško mesto."
|
||||
|
||||
#: build/lib/core/models.py:196 core/models.py:196
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
msgid "active"
|
||||
msgstr "aktivni"
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "Ali je treba tega uporabnika obravnavati kot aktivnega. Namesto brisanja računov počistite to izbiro."
|
||||
|
||||
#: build/lib/core/models.py:211 core/models.py:211
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "user"
|
||||
msgstr "uporabnik"
|
||||
|
||||
#: build/lib/core/models.py:212 core/models.py:212
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
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:470 build/lib/core/models.py:1155
|
||||
#: core/models.py:470 core/models.py:1155
|
||||
msgid "title"
|
||||
msgstr "naslov"
|
||||
|
||||
#: build/lib/core/models.py:369 core/models.py:369
|
||||
#: build/lib/core/models.py:471 core/models.py:471
|
||||
msgid "excerpt"
|
||||
msgstr "odlomek"
|
||||
|
||||
#: build/lib/core/models.py:418 core/models.py:418
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
msgid "Document"
|
||||
msgstr "Dokument"
|
||||
|
||||
#: build/lib/core/models.py:419 core/models.py:419
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
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:532 build/lib/core/models.py:873 core/models.py:532
|
||||
#: core/models.py:873
|
||||
msgid "Untitled Document"
|
||||
msgstr "Dokument brez naslova"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:908 core/models.py:908
|
||||
#, 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:912 core/models.py:912
|
||||
#, 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:918 core/models.py:918
|
||||
#, 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:1016 core/models.py:1016
|
||||
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:1017 core/models.py:1017
|
||||
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:1023 core/models.py:1023
|
||||
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:1046 core/models.py:1046
|
||||
msgid "Document favorite"
|
||||
msgstr "Priljubljeni dokument"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
msgid "Document favorites"
|
||||
msgstr "Priljubljeni dokumenti"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
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:1075 core/models.py:1075
|
||||
msgid "Document/user relation"
|
||||
msgstr "Odnos dokument/uporabnik"
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "Document/user relations"
|
||||
msgstr "Odnosi dokument/uporabnik"
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1082 core/models.py:1082
|
||||
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:1088 core/models.py:1088
|
||||
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:1094 build/lib/core/models.py:1242
|
||||
#: core/models.py:1094 core/models.py:1242
|
||||
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
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#, 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:1156 core/models.py:1156
|
||||
msgid "description"
|
||||
msgstr "opis"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
msgid "code"
|
||||
msgstr "koda"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1158 core/models.py:1158
|
||||
msgid "css"
|
||||
msgstr "css"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1160 core/models.py:1160
|
||||
msgid "public"
|
||||
msgstr "javno"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1162 core/models.py:1162
|
||||
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:1168 core/models.py:1168
|
||||
msgid "Template"
|
||||
msgstr "Predloga"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1169 core/models.py:1169
|
||||
msgid "Templates"
|
||||
msgstr "Predloge"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
msgid "Template/user relation"
|
||||
msgstr "Odnos predloga/uporabnik"
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1224 core/models.py:1224
|
||||
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:1230 core/models.py:1230
|
||||
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:1236 core/models.py:1236
|
||||
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:1259 core/models.py:1259
|
||||
msgid "email address"
|
||||
msgstr "elektronski naslov"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
msgid "Document invitation"
|
||||
msgstr "Vabilo na dokument"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1279 core/models.py:1279
|
||||
msgid "Document invitations"
|
||||
msgstr "Vabila na dokument"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1299 core/models.py:1299
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Ta e-poštni naslov je že povezan z registriranim uporabnikom."
|
||||
|
||||
#: core/templates/mail/html/template.html:162
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr "E-pošta z logotipom"
|
||||
|
||||
#: core/templates/mail/html/template.html:209
|
||||
#: core/templates/mail/text/template.txt:10
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr "Odpri"
|
||||
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr " Dokumenti, vaše novo bistveno orodje za organiziranje, skupno rabo in skupinsko sodelovanje pri dokumentih. "
|
||||
|
||||
#: core/templates/mail/html/template.html:233
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
msgstr " Pod okriljem %(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-05-22 12:09+0000\n"
|
||||
"PO-Revision-Date: 2025-05-22 14:16\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Swedish\n"
|
||||
"Language: sv_SE\n"
|
||||
@@ -46,61 +46,31 @@ msgstr "Skaparen är jag"
|
||||
msgid "Favorite"
|
||||
msgstr "Favoriter"
|
||||
|
||||
#: build/lib/core/api/serializers.py:467 core/api/serializers.py:467
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Ett nytt dokument skapades åt dig!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:471 core/api/serializers.py:471
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Du har beviljats äganderätt till ett nytt dokument:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:608 core/api/serializers.py:608
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
msgid "Body"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:611 core/api/serializers.py:611
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
msgid "Body type"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:617 core/api/serializers.py:617
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
msgid "Format"
|
||||
msgstr "Format"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:35 build/lib/core/choices.py:42 core/choices.py:35
|
||||
#: core/choices.py:42
|
||||
msgid "Reader"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:43 core/choices.py:36
|
||||
#: core/choices.py:43
|
||||
msgid "Editor"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
msgstr "Administratör"
|
||||
|
||||
#: build/lib/core/choices.py:45 core/choices.py:45
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
msgstr "Publik"
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
msgstr ""
|
||||
@@ -125,292 +95,295 @@ msgstr ""
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:79 core/models.py:79
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Reader"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
msgid "Editor"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Administrator"
|
||||
msgstr "Administratör"
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
msgid "Public"
|
||||
msgstr "Publik"
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:86 core/models.py:86
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:92 core/models.py:92
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:129 core/models.py:129
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:148 core/models.py:148
|
||||
#: build/lib/core/models.py:223 core/models.py:223
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:159 core/models.py:159
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
msgid "short name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:174 core/models.py:174
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
msgid "device"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:188 core/models.py:188
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:191 core/models.py:191
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:196 core/models.py:196
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
msgid "active"
|
||||
msgstr "aktiv"
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:211 core/models.py:211
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:212 core/models.py:212
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
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:470 build/lib/core/models.py:1155
|
||||
#: core/models.py:470 core/models.py:1155
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:369 core/models.py:369
|
||||
#: build/lib/core/models.py:471 core/models.py:471
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:418 core/models.py:418
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:419 core/models.py:419
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
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:532 build/lib/core/models.py:873 core/models.py:532
|
||||
#: core/models.py:873
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:908 core/models.py:908
|
||||
#, 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:912 core/models.py:912
|
||||
#, 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:918 core/models.py:918
|
||||
#, 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:1016 core/models.py:1016
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:1017 core/models.py:1017
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
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:1046 core/models.py:1046
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
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:1075 core/models.py:1075
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1082 core/models.py:1082
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1088 core/models.py:1088
|
||||
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:1094 build/lib/core/models.py:1242
|
||||
#: core/models.py:1094 core/models.py:1242
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#, 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:1156 core/models.py:1156
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1158 core/models.py:1158
|
||||
msgid "css"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1160 core/models.py:1160
|
||||
msgid "public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1162 core/models.py:1162
|
||||
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:1168 core/models.py:1168
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1169 core/models.py:1169
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1224 core/models.py:1224
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1230 core/models.py:1230
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1236 core/models.py:1236
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1259 core/models.py:1259
|
||||
msgid "email address"
|
||||
msgstr "e-postadress"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
msgid "Document invitation"
|
||||
msgstr "Bjud in dokument"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1279 core/models.py:1279
|
||||
msgid "Document invitations"
|
||||
msgstr "Inbjudningar dokument"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1299 core/models.py:1299
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Denna e-postadress är redan associerad med en registrerad användare."
|
||||
|
||||
#: core/templates/mail/html/template.html:162
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr "Logotyp e-post"
|
||||
|
||||
#: core/templates/mail/html/template.html:209
|
||||
#: core/templates/mail/text/template.txt:10
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr "Öppna"
|
||||
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:233
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
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-05-22 12:09+0000\n"
|
||||
"PO-Revision-Date: 2025-05-22 14:16\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Turkish\n"
|
||||
"Language: tr_TR\n"
|
||||
@@ -46,61 +46,31 @@ msgstr ""
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:467 core/api/serializers.py:467
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:471 core/api/serializers.py:471
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:608 core/api/serializers.py:608
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
msgid "Body"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:611 core/api/serializers.py:611
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
msgid "Body type"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:617 core/api/serializers.py:617
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
msgid "Format"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:35 build/lib/core/choices.py:42 core/choices.py:35
|
||||
#: core/choices.py:42
|
||||
msgid "Reader"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:43 core/choices.py:36
|
||||
#: core/choices.py:43
|
||||
msgid "Editor"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:45 core/choices.py:45
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
msgstr ""
|
||||
@@ -125,292 +95,295 @@ msgstr ""
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:79 core/models.py:79
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Reader"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
msgid "Editor"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Administrator"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:86 core/models.py:86
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:92 core/models.py:92
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:129 core/models.py:129
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:148 core/models.py:148
|
||||
#: build/lib/core/models.py:223 core/models.py:223
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:159 core/models.py:159
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
msgid "short name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:174 core/models.py:174
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
msgid "device"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:188 core/models.py:188
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:191 core/models.py:191
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:196 core/models.py:196
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:211 core/models.py:211
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:212 core/models.py:212
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
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:470 build/lib/core/models.py:1155
|
||||
#: core/models.py:470 core/models.py:1155
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:369 core/models.py:369
|
||||
#: build/lib/core/models.py:471 core/models.py:471
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:418 core/models.py:418
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:419 core/models.py:419
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
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:532 build/lib/core/models.py:873 core/models.py:532
|
||||
#: core/models.py:873
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:908 core/models.py:908
|
||||
#, 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:912 core/models.py:912
|
||||
#, 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:918 core/models.py:918
|
||||
#, 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:1016 core/models.py:1016
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:1017 core/models.py:1017
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
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:1046 core/models.py:1046
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
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:1075 core/models.py:1075
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1082 core/models.py:1082
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1088 core/models.py:1088
|
||||
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:1094 build/lib/core/models.py:1242
|
||||
#: core/models.py:1094 core/models.py:1242
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#, 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:1156 core/models.py:1156
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1158 core/models.py:1158
|
||||
msgid "css"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1160 core/models.py:1160
|
||||
msgid "public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1162 core/models.py:1162
|
||||
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:1168 core/models.py:1168
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1169 core/models.py:1169
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1224 core/models.py:1224
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1230 core/models.py:1230
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1236 core/models.py:1236
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1259 core/models.py:1259
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1279 core/models.py:1279
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1299 core/models.py:1299
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:162
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:209
|
||||
#: core/templates/mail/text/template.txt:10
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:233
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
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-05-22 12:09+0000\n"
|
||||
"PO-Revision-Date: 2025-05-22 14:16\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"Language: zh_CN\n"
|
||||
@@ -46,61 +46,31 @@ msgstr "创建者是我"
|
||||
msgid "Favorite"
|
||||
msgstr "收藏"
|
||||
|
||||
#: build/lib/core/api/serializers.py:467 core/api/serializers.py:467
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "已为您创建了一份新文档!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:471 core/api/serializers.py:471
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "您已被授予新文档的所有权:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:608 core/api/serializers.py:608
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
msgid "Body"
|
||||
msgstr "正文"
|
||||
|
||||
#: build/lib/core/api/serializers.py:611 core/api/serializers.py:611
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
msgid "Body type"
|
||||
msgstr "正文类型"
|
||||
|
||||
#: build/lib/core/api/serializers.py:617 core/api/serializers.py:617
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
msgid "Format"
|
||||
msgstr "格式"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:943 core/api/viewsets.py:943
|
||||
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "{title} 的副本"
|
||||
|
||||
#: build/lib/core/choices.py:35 build/lib/core/choices.py:42 core/choices.py:35
|
||||
#: core/choices.py:42
|
||||
msgid "Reader"
|
||||
msgstr "阅读者"
|
||||
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:43 core/choices.py:36
|
||||
#: core/choices.py:43
|
||||
msgid "Editor"
|
||||
msgstr "编辑者"
|
||||
|
||||
#: build/lib/core/choices.py:44 core/choices.py:44
|
||||
msgid "Administrator"
|
||||
msgstr "超级管理员"
|
||||
|
||||
#: build/lib/core/choices.py:45 core/choices.py:45
|
||||
msgid "Owner"
|
||||
msgstr "所有者"
|
||||
|
||||
#: build/lib/core/choices.py:56 core/choices.py:56
|
||||
msgid "Restricted"
|
||||
msgstr "受限的"
|
||||
|
||||
#: build/lib/core/choices.py:60 core/choices.py:60
|
||||
msgid "Authenticated"
|
||||
msgstr "已验证"
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Public"
|
||||
msgstr "公开"
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
msgstr "第一个子项"
|
||||
@@ -125,292 +95,295 @@ msgstr "左"
|
||||
msgid "Right"
|
||||
msgstr "右"
|
||||
|
||||
#: build/lib/core/models.py:79 core/models.py:79
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Reader"
|
||||
msgstr "阅读者"
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
msgid "Editor"
|
||||
msgstr "编辑者"
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Administrator"
|
||||
msgstr "超级管理员"
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
msgid "Owner"
|
||||
msgstr "所有者"
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
msgid "Restricted"
|
||||
msgstr "受限的"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "Authenticated"
|
||||
msgstr "已验证"
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
msgid "Public"
|
||||
msgstr "公开"
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "记录的主密钥为 UUID"
|
||||
|
||||
#: build/lib/core/models.py:86 core/models.py:86
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "created on"
|
||||
msgstr "创建时间"
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "记录的创建日期和时间"
|
||||
|
||||
#: build/lib/core/models.py:92 core/models.py:92
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "updated on"
|
||||
msgstr "更新时间"
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "记录的最后更新时间"
|
||||
|
||||
#: build/lib/core/models.py:129 core/models.py:129
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr "未找到具有该 sub 的用户,但该邮箱已关联到一个注册用户。"
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr "请输入有效的 sub。该值只能包含字母、数字及 @/./+/-/_/: 字符。"
|
||||
|
||||
#: build/lib/core/models.py:148 core/models.py:148
|
||||
#: build/lib/core/models.py:223 core/models.py:223
|
||||
msgid "sub"
|
||||
msgstr "sub"
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr "必填。最多 255 个字符,仅允许字母、数字及 @/./+/-/_/: 字符。"
|
||||
|
||||
#: build/lib/core/models.py:159 core/models.py:159
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "full name"
|
||||
msgstr "全名"
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
msgid "short name"
|
||||
msgstr "简称"
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
msgid "identity email address"
|
||||
msgstr "身份电子邮件地址"
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
msgid "admin email address"
|
||||
msgstr "管理员电子邮件地址"
|
||||
|
||||
#: build/lib/core/models.py:174 core/models.py:174
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
msgid "language"
|
||||
msgstr "语言"
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "用户希望看到的界面语言。"
|
||||
|
||||
#: build/lib/core/models.py:183 core/models.py:183
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "用户查看时间希望的时区。"
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
msgid "device"
|
||||
msgstr "设备"
|
||||
|
||||
#: build/lib/core/models.py:188 core/models.py:188
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "用户是设备还是真实用户。"
|
||||
|
||||
#: build/lib/core/models.py:191 core/models.py:191
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
msgid "staff status"
|
||||
msgstr "员工状态"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "用户是否可以登录该管理员站点。"
|
||||
|
||||
#: build/lib/core/models.py:196 core/models.py:196
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
msgid "active"
|
||||
msgstr "激活"
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "是否应将此用户视为活跃用户。取消选择此选项而不是删除账户。"
|
||||
|
||||
#: build/lib/core/models.py:211 core/models.py:211
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "user"
|
||||
msgstr "用户"
|
||||
|
||||
#: build/lib/core/models.py:212 core/models.py:212
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
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:470 build/lib/core/models.py:1155
|
||||
#: core/models.py:470 core/models.py:1155
|
||||
msgid "title"
|
||||
msgstr "标题"
|
||||
|
||||
#: build/lib/core/models.py:369 core/models.py:369
|
||||
#: build/lib/core/models.py:471 core/models.py:471
|
||||
msgid "excerpt"
|
||||
msgstr "摘要"
|
||||
|
||||
#: build/lib/core/models.py:418 core/models.py:418
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
msgid "Document"
|
||||
msgstr "文档"
|
||||
|
||||
#: build/lib/core/models.py:419 core/models.py:419
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
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:532 build/lib/core/models.py:873 core/models.py:532
|
||||
#: core/models.py:873
|
||||
msgid "Untitled Document"
|
||||
msgstr "未命名文档"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: build/lib/core/models.py:908 core/models.py:908
|
||||
#, 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:912 core/models.py:912
|
||||
#, 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:918 core/models.py:918
|
||||
#, 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:1016 core/models.py:1016
|
||||
msgid "Document/user link trace"
|
||||
msgstr "文档/用户链接跟踪"
|
||||
|
||||
#: build/lib/core/models.py:965 core/models.py:965
|
||||
#: build/lib/core/models.py:1017 core/models.py:1017
|
||||
msgid "Document/user link traces"
|
||||
msgstr "个文档/用户链接跟踪"
|
||||
|
||||
#: build/lib/core/models.py:971 core/models.py:971
|
||||
#: build/lib/core/models.py:1023 core/models.py:1023
|
||||
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:1046 core/models.py:1046
|
||||
msgid "Document favorite"
|
||||
msgstr "文档收藏"
|
||||
|
||||
#: build/lib/core/models.py:995 core/models.py:995
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
msgid "Document favorites"
|
||||
msgstr "文档收藏夹"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
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:1075 core/models.py:1075
|
||||
msgid "Document/user relation"
|
||||
msgstr "文档/用户关系"
|
||||
|
||||
#: build/lib/core/models.py:1024 core/models.py:1024
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "Document/user relations"
|
||||
msgstr "文档/用户关系集"
|
||||
|
||||
#: build/lib/core/models.py:1030 core/models.py:1030
|
||||
#: build/lib/core/models.py:1082 core/models.py:1082
|
||||
msgid "This user is already in this document."
|
||||
msgstr "该用户已在此文档中。"
|
||||
|
||||
#: build/lib/core/models.py:1036 core/models.py:1036
|
||||
#: build/lib/core/models.py:1088 core/models.py:1088
|
||||
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:1094 build/lib/core/models.py:1242
|
||||
#: core/models.py:1094 core/models.py:1242
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "必须设置用户或团队之一,不能同时设置两者。"
|
||||
|
||||
#: build/lib/core/models.py:1188 core/models.py:1188
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1189 core/models.py:1189
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1195 core/models.py:1195
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1260 core/models.py:1260
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1264 core/models.py:1264
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1270 core/models.py:1270
|
||||
#, 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:1156 core/models.py:1156
|
||||
msgid "description"
|
||||
msgstr "说明"
|
||||
|
||||
#: build/lib/core/models.py:1283 core/models.py:1283
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
msgid "code"
|
||||
msgstr "代码"
|
||||
|
||||
#: build/lib/core/models.py:1284 core/models.py:1284
|
||||
#: build/lib/core/models.py:1158 core/models.py:1158
|
||||
msgid "css"
|
||||
msgstr "css"
|
||||
|
||||
#: build/lib/core/models.py:1286 core/models.py:1286
|
||||
#: build/lib/core/models.py:1160 core/models.py:1160
|
||||
msgid "public"
|
||||
msgstr "公开"
|
||||
|
||||
#: build/lib/core/models.py:1288 core/models.py:1288
|
||||
#: build/lib/core/models.py:1162 core/models.py:1162
|
||||
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:1168 core/models.py:1168
|
||||
msgid "Template"
|
||||
msgstr "模板"
|
||||
|
||||
#: build/lib/core/models.py:1295 core/models.py:1295
|
||||
#: build/lib/core/models.py:1169 core/models.py:1169
|
||||
msgid "Templates"
|
||||
msgstr "模板"
|
||||
|
||||
#: build/lib/core/models.py:1348 core/models.py:1348
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
msgid "Template/user relation"
|
||||
msgstr "模板/用户关系"
|
||||
|
||||
#: build/lib/core/models.py:1349 core/models.py:1349
|
||||
#: build/lib/core/models.py:1224 core/models.py:1224
|
||||
msgid "Template/user relations"
|
||||
msgstr "模板/用户关系集"
|
||||
|
||||
#: build/lib/core/models.py:1355 core/models.py:1355
|
||||
#: build/lib/core/models.py:1230 core/models.py:1230
|
||||
msgid "This user is already in this template."
|
||||
msgstr "该用户已在此模板中。"
|
||||
|
||||
#: build/lib/core/models.py:1361 core/models.py:1361
|
||||
#: build/lib/core/models.py:1236 core/models.py:1236
|
||||
msgid "This team is already in this template."
|
||||
msgstr "该团队已在此模板中。"
|
||||
|
||||
#: build/lib/core/models.py:1438 core/models.py:1438
|
||||
#: build/lib/core/models.py:1259 core/models.py:1259
|
||||
msgid "email address"
|
||||
msgstr "电子邮件地址"
|
||||
|
||||
#: build/lib/core/models.py:1457 core/models.py:1457
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
msgid "Document invitation"
|
||||
msgstr "文档邀请"
|
||||
|
||||
#: build/lib/core/models.py:1458 core/models.py:1458
|
||||
#: build/lib/core/models.py:1279 core/models.py:1279
|
||||
msgid "Document invitations"
|
||||
msgstr "文档邀请"
|
||||
|
||||
#: build/lib/core/models.py:1478 core/models.py:1478
|
||||
#: build/lib/core/models.py:1299 core/models.py:1299
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "此电子邮件已经与现有注册用户关联。"
|
||||
|
||||
#: core/templates/mail/html/template.html:162
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr "徽标邮件"
|
||||
|
||||
#: core/templates/mail/html/template.html:209
|
||||
#: core/templates/mail/text/template.txt:10
|
||||
#: core/templates/mail/html/invitation.html:209
|
||||
#: core/templates/mail/text/invitation.txt:10
|
||||
msgid "Open"
|
||||
msgstr "打开"
|
||||
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
#: core/templates/mail/html/invitation.html:226
|
||||
#: core/templates/mail/text/invitation.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr " Docs——您的全新必备工具,帮助团队组织、共享和协作处理文档。 "
|
||||
|
||||
#: core/templates/mail/html/template.html:233
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#: core/templates/mail/html/invitation.html:233
|
||||
#: core/templates/mail/text/invitation.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
msgstr " 由 %(brandname)s 倾力打造。 "
|
||||
|
||||
+10
-10
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "impress"
|
||||
version = "3.4.1"
|
||||
version = "3.3.0"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -26,7 +26,7 @@ readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"beautifulsoup4==4.13.4",
|
||||
"boto3==1.39.4",
|
||||
"boto3==1.38.46",
|
||||
"Brotli==1.1.0",
|
||||
"celery[redis]==5.5.3",
|
||||
"django-configurations==2.5.1",
|
||||
@@ -34,12 +34,12 @@ dependencies = [
|
||||
"django-countries==7.6.1",
|
||||
"django-csp==4.0",
|
||||
"django-filter==25.1",
|
||||
"django-lasuite[all]==0.0.11",
|
||||
"django-lasuite[all]==0.0.10",
|
||||
"django-parler==2.3",
|
||||
"django-redis==6.0.0",
|
||||
"django-storages[s3]==1.14.6",
|
||||
"django-timezone-field>=5.1",
|
||||
"django==5.2.4",
|
||||
"django==5.2.3",
|
||||
"django-treebeard==4.7.1",
|
||||
"djangorestframework==3.16.0",
|
||||
"drf_spectacular==0.28.0",
|
||||
@@ -52,9 +52,9 @@ dependencies = [
|
||||
"markdown==3.8.2",
|
||||
"mozilla-django-oidc==4.0.1",
|
||||
"nested-multipart-parser==1.5.0",
|
||||
"openai==1.95.0",
|
||||
"openai==1.93.0",
|
||||
"psycopg[binary]==3.2.9",
|
||||
"pycrdt==0.12.25",
|
||||
"pycrdt==0.12.23",
|
||||
"PyJWT==2.10.1",
|
||||
"python-magic==0.4.27",
|
||||
"redis<6.0.0",
|
||||
@@ -73,10 +73,10 @@ dependencies = [
|
||||
dev = [
|
||||
"django-extensions==4.1",
|
||||
"django-test-migrations==1.5.0",
|
||||
"drf-spectacular-sidecar==2025.7.1",
|
||||
"drf-spectacular-sidecar==2025.6.1",
|
||||
"freezegun==1.5.2",
|
||||
"ipdb==0.13.13",
|
||||
"ipython==9.4.0",
|
||||
"ipython==9.3.0",
|
||||
"pyfakefs==5.9.1",
|
||||
"pylint-django==2.6.1",
|
||||
"pylint==3.3.7",
|
||||
@@ -84,9 +84,9 @@ dev = [
|
||||
"pytest-django==4.11.1",
|
||||
"pytest==8.4.1",
|
||||
"pytest-icdiff==0.9",
|
||||
"pytest-xdist==3.8.0",
|
||||
"pytest-xdist==3.7.0",
|
||||
"responses==0.25.7",
|
||||
"ruff==0.12.2",
|
||||
"ruff==0.12.1",
|
||||
"types-requests==2.32.4.20250611",
|
||||
]
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ export const CONFIG = {
|
||||
AI_FEATURE_ENABLED: true,
|
||||
CRISP_WEBSITE_ID: null,
|
||||
COLLABORATION_WS_URL: 'ws://localhost:4444/collaboration/ws/',
|
||||
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY: true,
|
||||
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY: false,
|
||||
ENVIRONMENT: 'development',
|
||||
FRONTEND_CSS_URL: null,
|
||||
FRONTEND_HOMEPAGE_FEATURE_ENABLED: true,
|
||||
@@ -224,7 +224,7 @@ export const updateDocTitle = async (page: Page, title: string) => {
|
||||
await verifyDocName(page, title);
|
||||
};
|
||||
|
||||
export const waitForResponseCreateDoc = (page: Page) => {
|
||||
export const getWaitForCreateDoc = (page: Page) => {
|
||||
return page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/documents/') &&
|
||||
@@ -243,7 +243,7 @@ export const mockedDocument = async (page: Page, data: object) => {
|
||||
!request.url().includes('accesses') &&
|
||||
!request.url().includes('invitations')
|
||||
) {
|
||||
const { abilities, ...doc } = data as unknown as {
|
||||
const { abilities, ...rest } = data as unknown as {
|
||||
abilities?: Record<string, unknown>;
|
||||
};
|
||||
await route.fulfill({
|
||||
@@ -276,7 +276,8 @@ export const mockedDocument = async (page: Page, data: object) => {
|
||||
ancestors_link_role: null,
|
||||
created_at: '2021-09-01T09:00:00Z',
|
||||
user_role: 'owner',
|
||||
...doc,
|
||||
user_roles: ['owner'],
|
||||
...rest,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@@ -327,6 +328,7 @@ export const mockedInvitations = async (page: Page, json?: object) => {
|
||||
request.url().includes('invitations') &&
|
||||
request.url().includes('page=')
|
||||
) {
|
||||
console.log('GET');
|
||||
await route.fulfill({
|
||||
json: {
|
||||
count: 1,
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import path from 'path';
|
||||
|
||||
import { chromium, expect, test } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
import cs from 'convert-stream';
|
||||
|
||||
import {
|
||||
CONFIG,
|
||||
addNewMember,
|
||||
createDoc,
|
||||
goToGridDoc,
|
||||
mockedDocument,
|
||||
overrideConfig,
|
||||
verifyDocName,
|
||||
} from './common';
|
||||
import { createRootSubPage } from './sub-pages-utils';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
@@ -523,151 +523,52 @@ test.describe('Doc Editor', () => {
|
||||
|
||||
test('it checks block editing when not connected to collab server', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
test.slow();
|
||||
|
||||
/**
|
||||
* The good port is 4444, but we want to simulate a not connected
|
||||
* collaborative server.
|
||||
* So we use a port that is not used by the collaborative server.
|
||||
* The server will not be able to connect to the collaborative server.
|
||||
*/
|
||||
await overrideConfig(page, {
|
||||
COLLABORATION_WS_URL: 'ws://localhost:5555/collaboration/ws/',
|
||||
await page.route('**/api/v1.0/config/', async (route) => {
|
||||
const request = route.request();
|
||||
if (request.method().includes('GET')) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
...CONFIG,
|
||||
COLLABORATION_WS_URL: 'ws://localhost:5555/collaboration/ws/',
|
||||
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const [parentTitle] = await createDoc(
|
||||
page,
|
||||
'editing-blocking',
|
||||
browserName,
|
||||
1,
|
||||
);
|
||||
void page
|
||||
.getByRole('button', {
|
||||
name: 'New doc',
|
||||
})
|
||||
.click();
|
||||
|
||||
const card = page.getByLabel('It is the card information');
|
||||
await expect(
|
||||
card.getByText('Others are editing. Your network prevent changes.'),
|
||||
card.getByText('Your network do not allow you to edit'),
|
||||
).toBeHidden();
|
||||
const editor = page.locator('.ProseMirror');
|
||||
|
||||
await expect(editor).toHaveAttribute('contenteditable', 'true');
|
||||
|
||||
let responseCanEditPromise = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes(`/can-edit/`) && response.status() === 200,
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
await page.getByLabel('Visibility', { exact: true }).click();
|
||||
|
||||
await page
|
||||
.getByRole('menuitem', {
|
||||
name: 'Public',
|
||||
})
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
page.getByText('The document visibility has been updated.'),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByLabel('Visibility mode').click();
|
||||
await page.getByRole('menuitem', { name: 'Editing' }).click();
|
||||
await addNewMember(page, 0, 'Editor', 'impress');
|
||||
|
||||
// Close the modal
|
||||
await page.getByRole('button', { name: 'close' }).first().click();
|
||||
|
||||
const urlParentDoc = page.url();
|
||||
|
||||
const { name: childTitle } = await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'editing-blocking - child',
|
||||
);
|
||||
|
||||
let responseCanEdit = await responseCanEditPromise;
|
||||
expect(responseCanEdit.ok()).toBeTruthy();
|
||||
let jsonCanEdit = (await responseCanEdit.json()) as { can_edit: boolean };
|
||||
expect(jsonCanEdit.can_edit).toBeTruthy();
|
||||
|
||||
const urlChildDoc = page.url();
|
||||
|
||||
/**
|
||||
* We open another browser that will connect to the collaborative server
|
||||
* and will block the current browser to edit the doc.
|
||||
*/
|
||||
const otherBrowser = await chromium.launch({ headless: true });
|
||||
const otherContext = await otherBrowser.newContext({
|
||||
locale: 'en-US',
|
||||
timezoneId: 'Europe/Paris',
|
||||
permissions: [],
|
||||
storageState: {
|
||||
cookies: [],
|
||||
origins: [],
|
||||
},
|
||||
});
|
||||
const otherPage = await otherContext.newPage();
|
||||
|
||||
const webSocketPromise = otherPage.waitForEvent(
|
||||
'websocket',
|
||||
(webSocket) => {
|
||||
return webSocket
|
||||
.url()
|
||||
.includes('ws://localhost:4444/collaboration/ws/?room=');
|
||||
},
|
||||
);
|
||||
|
||||
await otherPage.goto(urlChildDoc);
|
||||
|
||||
const webSocket = await webSocketPromise;
|
||||
expect(webSocket.url()).toContain(
|
||||
'ws://localhost:4444/collaboration/ws/?room=',
|
||||
);
|
||||
|
||||
await verifyDocName(otherPage, childTitle);
|
||||
|
||||
await page.reload();
|
||||
|
||||
responseCanEditPromise = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes(`/can-edit/`) && response.status() === 200,
|
||||
);
|
||||
|
||||
responseCanEdit = await responseCanEditPromise;
|
||||
expect(responseCanEdit.ok()).toBeTruthy();
|
||||
|
||||
jsonCanEdit = (await responseCanEdit.json()) as { can_edit: boolean };
|
||||
expect(jsonCanEdit.can_edit).toBeFalsy();
|
||||
|
||||
await expect(
|
||||
card.getByText('Others are editing. Your network prevent changes.'),
|
||||
card.getByText('Your network do not allow you to edit'),
|
||||
).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
await expect(editor).toHaveAttribute('contenteditable', 'false');
|
||||
|
||||
await page.goto(urlParentDoc);
|
||||
|
||||
await verifyDocName(page, parentTitle);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
await page.getByLabel('Visibility mode').click();
|
||||
await page.getByRole('menuitem', { name: 'Reading' }).click();
|
||||
|
||||
// Close the modal
|
||||
await page.getByRole('button', { name: 'close' }).first().click();
|
||||
|
||||
await page.goto(urlChildDoc);
|
||||
|
||||
await expect(editor).toHaveAttribute('contenteditable', 'true');
|
||||
|
||||
await expect(
|
||||
card.getByText('Others are editing. Your network prevent changes.'),
|
||||
).toBeHidden();
|
||||
});
|
||||
|
||||
test('it checks if callout custom block', async ({ page, browserName }) => {
|
||||
|
||||
@@ -72,7 +72,6 @@ test.describe('Doc Export', () => {
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'download',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
|
||||
@@ -83,7 +82,6 @@ test.describe('Doc Export', () => {
|
||||
void page
|
||||
.getByRole('button', {
|
||||
name: 'Download',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
|
||||
@@ -122,7 +120,6 @@ test.describe('Doc Export', () => {
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'download',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
|
||||
@@ -132,7 +129,6 @@ test.describe('Doc Export', () => {
|
||||
await expect(
|
||||
page.getByRole('button', {
|
||||
name: 'Download',
|
||||
exact: true,
|
||||
}),
|
||||
).toBeVisible();
|
||||
|
||||
@@ -143,7 +139,6 @@ test.describe('Doc Export', () => {
|
||||
void page
|
||||
.getByRole('button', {
|
||||
name: 'Download',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
|
||||
@@ -317,14 +312,12 @@ test.describe('Doc Export', () => {
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'download',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', {
|
||||
name: 'Download',
|
||||
exact: true,
|
||||
}),
|
||||
).toBeVisible();
|
||||
|
||||
@@ -335,7 +328,6 @@ test.describe('Doc Export', () => {
|
||||
void page
|
||||
.getByRole('button', {
|
||||
name: 'Download',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
|
||||
|
||||
@@ -91,22 +91,6 @@ test.describe('Document grid item options', () => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('it checks the share modal', async ({ page, browserName }) => {
|
||||
const [docTitle] = await createDoc(page, `check share modal`, browserName);
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
await expect(page.getByText(docTitle)).toBeVisible();
|
||||
const row = await getGridRow(page, docTitle);
|
||||
await row.getByText(`more_horiz`).click();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Share' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('dialog').getByText('Share the document'),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('it pins a document', async ({ page, browserName }) => {
|
||||
const [docTitle] = await createDoc(page, `Favorite doc`, browserName);
|
||||
|
||||
@@ -242,8 +226,6 @@ test.describe('Documents filters', () => {
|
||||
|
||||
await allDocs.click();
|
||||
|
||||
await page.waitForURL('**/?target=all_docs');
|
||||
|
||||
let url = new URL(page.url());
|
||||
let target = url.searchParams.get('target');
|
||||
expect(target).toBe('all_docs');
|
||||
|
||||
@@ -9,18 +9,56 @@ import {
|
||||
mockedInvitations,
|
||||
verifyDocName,
|
||||
} from './common';
|
||||
import { createRootSubPage } from './sub-pages-utils';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test.describe('Doc Header', () => {
|
||||
test('it checks the element are correctly displayed', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await createDoc(page, 'doc-update', browserName, 1);
|
||||
test('it checks the element are correctly displayed', async ({ page }) => {
|
||||
await mockedDocument(page, {
|
||||
accesses: [
|
||||
{
|
||||
id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg',
|
||||
role: 'owner',
|
||||
user: {
|
||||
email: 'super@owner.com',
|
||||
full_name: 'Super Owner',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg',
|
||||
role: 'admin',
|
||||
user: {
|
||||
email: 'super@admin.com',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg',
|
||||
role: 'owner',
|
||||
user: {
|
||||
email: 'super2@owner.com',
|
||||
},
|
||||
},
|
||||
],
|
||||
abilities: {
|
||||
destroy: true, // Means owner
|
||||
link_configuration: true,
|
||||
versions_destroy: true,
|
||||
versions_list: true,
|
||||
versions_retrieve: true,
|
||||
accesses_manage: true,
|
||||
accesses_view: true,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
},
|
||||
link_reach: 'public',
|
||||
computed_link_reach: 'public',
|
||||
created_at: '2021-09-01T09:00:00Z',
|
||||
});
|
||||
|
||||
await goToGridDoc(page);
|
||||
|
||||
const card = page.getByLabel(
|
||||
'It is the card information about the document.',
|
||||
@@ -29,18 +67,6 @@ test.describe('Doc Header', () => {
|
||||
const docTitle = card.getByRole('textbox', { name: 'doc title input' });
|
||||
await expect(docTitle).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
await page.getByLabel('Visibility', { exact: true }).click();
|
||||
|
||||
await page
|
||||
.getByRole('menuitem', {
|
||||
name: 'Public',
|
||||
})
|
||||
.click();
|
||||
|
||||
await page.getByRole('button', { name: 'close' }).first().click();
|
||||
|
||||
await expect(card.getByText('Public document')).toBeVisible();
|
||||
|
||||
await expect(card.getByText('Owner ·')).toBeVisible();
|
||||
@@ -70,7 +96,11 @@ test.describe('Doc Header', () => {
|
||||
page.getByRole('heading', { name: 'Delete a doc' }),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(page.getByText(`This document and any sub-`)).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(
|
||||
`This document will be permanently deleted. This action is irreversible.`,
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
await page
|
||||
.getByRole('button', {
|
||||
@@ -396,7 +426,7 @@ test.describe('Doc Header', () => {
|
||||
});
|
||||
|
||||
test('it pins a document', async ({ page, browserName }) => {
|
||||
const [docTitle] = await createDoc(page, `Pin doc`, browserName);
|
||||
const [docTitle] = await createDoc(page, `Favorite doc`, browserName);
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
|
||||
@@ -428,61 +458,6 @@ test.describe('Doc Header', () => {
|
||||
await expect(row.getByLabel('Pin document icon')).toBeHidden();
|
||||
await expect(leftPanelFavorites.getByText(docTitle)).toBeHidden();
|
||||
});
|
||||
|
||||
test('it duplicates a document', async ({ page, browserName }) => {
|
||||
const [docTitle] = await createDoc(page, `Duplicate doc`, browserName);
|
||||
|
||||
const editor = page.locator('.ProseMirror');
|
||||
await editor.click();
|
||||
await editor.fill('Hello Duplicated World');
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
||||
await expect(
|
||||
page.getByText('Document duplicated successfully!'),
|
||||
).toBeVisible();
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const duplicateTitle = 'Copy of ' + docTitle;
|
||||
|
||||
const row = await getGridRow(page, duplicateTitle);
|
||||
|
||||
await expect(row.getByText(duplicateTitle)).toBeVisible();
|
||||
|
||||
await row.getByText(`more_horiz`).click();
|
||||
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
||||
const duplicateDuplicateTitle = 'Copy of ' + duplicateTitle;
|
||||
await page.getByText(duplicateDuplicateTitle).click();
|
||||
await expect(page.getByText('Hello Duplicated World')).toBeVisible();
|
||||
});
|
||||
|
||||
test('it duplicates a child document', async ({ page, browserName }) => {
|
||||
await createDoc(page, `Duplicate doc`, browserName);
|
||||
|
||||
const { name: childTitle } = await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'Duplicate doc - child',
|
||||
);
|
||||
|
||||
const editor = page.locator('.ProseMirror');
|
||||
await editor.click();
|
||||
await editor.fill('Hello Duplicated World');
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
||||
await expect(
|
||||
page.getByText('Document duplicated successfully!'),
|
||||
).toBeVisible();
|
||||
|
||||
const duplicateDuplicateTitle = 'Copy of ' + childTitle;
|
||||
await expect(
|
||||
page.getByTestId('doc-tree').getByText(duplicateDuplicateTitle),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Documents Header mobile', () => {
|
||||
|
||||
@@ -1,35 +1,46 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, verifyDocName } from './common';
|
||||
import { createDoc } from './common';
|
||||
import { updateShareLink } from './share-utils';
|
||||
import { createRootSubPage } from './sub-pages-utils';
|
||||
|
||||
test.describe('Inherited share accesses', () => {
|
||||
test('it checks inherited accesses', async ({ page, browserName }) => {
|
||||
await page.goto('/');
|
||||
const [parentTitle] = await createDoc(page, 'root-doc', browserName, 1);
|
||||
await createDoc(page, 'root-doc', browserName, 1);
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
|
||||
// Wait for and intercept the POST request to create a new page
|
||||
const responsePromise = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/documents/') &&
|
||||
response.url().includes('/children/') &&
|
||||
response.request().method() === 'POST',
|
||||
);
|
||||
await createRootSubPage(page, browserName, 'sub-page');
|
||||
|
||||
const response = await responsePromise;
|
||||
expect(response.ok()).toBeTruthy();
|
||||
const subPageJson = await response.json();
|
||||
|
||||
await expect(docTree).toBeVisible();
|
||||
const subPageItem = docTree
|
||||
.getByTestId(`doc-sub-page-item-${subPageJson.id}`)
|
||||
.first();
|
||||
|
||||
await expect(subPageItem).toBeVisible();
|
||||
await subPageItem.click();
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
await expect(
|
||||
page.getByText('People with access via the parent document'),
|
||||
).toBeVisible();
|
||||
|
||||
const user = page.getByTestId(
|
||||
`doc-share-member-row-user@${browserName}.test`,
|
||||
`doc-share-member-row-user@${browserName}.e2e`,
|
||||
);
|
||||
await expect(user).toBeVisible();
|
||||
await expect(user.getByText('E2E Chromium')).toBeVisible();
|
||||
await expect(user.getByText('Owner')).toBeVisible();
|
||||
|
||||
await page
|
||||
.locator('.--docs--doc-inherited-share-content')
|
||||
.getByRole('link')
|
||||
.click();
|
||||
|
||||
await verifyDocName(page, parentTitle);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,4 +66,89 @@ test.describe('Inherited share link', () => {
|
||||
await expect(docVisibilityCard.getByText('Connected')).toBeVisible();
|
||||
await expect(docVisibilityCard.getByText('Reading')).toBeVisible();
|
||||
});
|
||||
|
||||
/**
|
||||
* These tests are temporarily removed because we hide the ability to modify this parameter in sub-pages for now.
|
||||
* There is a high probability that this feature will not change and therefore the test won't either.
|
||||
*/
|
||||
|
||||
// test('it checks warning message when sharing rules differ', async ({
|
||||
// page,
|
||||
// browserName,
|
||||
// }) => {
|
||||
// await page.goto('/');
|
||||
// // Create root doc
|
||||
// await createDoc(page, 'root-doc', browserName, 1);
|
||||
|
||||
// // Update share link
|
||||
// await page.getByRole('button', { name: 'Share' }).click();
|
||||
// await updateShareLink(page, 'Connected', 'Reading');
|
||||
// await page.getByRole('button', { name: 'OK' }).click();
|
||||
|
||||
// // Create sub page
|
||||
// await createRootSubPage(page, browserName, 'sub-page');
|
||||
// await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
// // Update share link to public and edition
|
||||
// await updateShareLink(page, 'Public', 'Edition');
|
||||
// await expect(page.getByText('Sharing rules differ from the')).toBeVisible();
|
||||
// const restoreButton = page.getByRole('button', { name: 'Restore' });
|
||||
// await expect(restoreButton).toBeVisible();
|
||||
// await restoreButton.click();
|
||||
// await expect(
|
||||
// page.getByText('The document visibility has been updated').first(),
|
||||
// ).toBeVisible();
|
||||
// await expect(page.getByText('Sharing rules differ from the')).toBeHidden();
|
||||
// });
|
||||
|
||||
// test('it checks inherited link possibilities', async ({
|
||||
// page,
|
||||
// browserName,
|
||||
// }) => {
|
||||
// await page.goto('/');
|
||||
// // Create root doc
|
||||
// await createDoc(page, 'root-doc', browserName, 1);
|
||||
|
||||
// // Update share link
|
||||
// await page.getByRole('button', { name: 'Share' }).click();
|
||||
// await updateShareLink(page, 'Connected', 'Reading');
|
||||
// await page.getByRole('button', { name: 'OK' }).click();
|
||||
// await expect(
|
||||
// page.getByText('Document accessible to any connected person'),
|
||||
// ).toBeVisible();
|
||||
|
||||
// // Create sub page
|
||||
// const { item: subPageItem } = await createRootSubPage(
|
||||
// page,
|
||||
// browserName,
|
||||
// 'sub-page',
|
||||
// );
|
||||
// await expect(
|
||||
// page.getByText('Document accessible to any connected person'),
|
||||
// ).toBeVisible();
|
||||
|
||||
// // Update share link to public and edition
|
||||
// await page.getByRole('button', { name: 'Share' }).click();
|
||||
// await verifyLinkReachIsDisabled(page, 'Private');
|
||||
// await updateShareLink(page, 'Public', 'Edition');
|
||||
// await page.getByRole('button', { name: 'OK' }).click();
|
||||
// await expect(page.getByText('Public document')).toBeVisible();
|
||||
|
||||
// // Create sub page
|
||||
// await createSubPageFromParent(
|
||||
// page,
|
||||
// browserName,
|
||||
// subPageItem.id,
|
||||
// 'sub-page-2',
|
||||
// );
|
||||
// await expect(page.getByText('Public document')).toBeVisible();
|
||||
|
||||
// // Verify share link and role
|
||||
// await page.getByRole('button', { name: 'Share' }).click();
|
||||
// await verifyLinkReachIsDisabled(page, 'Private');
|
||||
// await verifyLinkReachIsDisabled(page, 'Connected');
|
||||
// await verifyLinkReachIsEnabled(page, 'Public');
|
||||
// await verifyLinkRoleIsDisabled(page, 'Reading');
|
||||
// await verifyLinkRoleIsEnabled(page, 'Edition');
|
||||
// });
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
randomName,
|
||||
verifyDocName,
|
||||
} from './common';
|
||||
import { createRootSubPage } from './sub-pages-utils';
|
||||
|
||||
test.describe('Document create member', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -273,9 +272,7 @@ test.describe('Document create member: Multiple login', () => {
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs Logo Docs' }),
|
||||
).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
).toBeVisible();
|
||||
|
||||
await page.goto(urlDoc);
|
||||
|
||||
@@ -297,61 +294,4 @@ test.describe('Document create member: Multiple login', () => {
|
||||
await expect(page.getByText('Share with 2 users')).toBeVisible();
|
||||
await expect(page.getByText(`E2E ${otherBrowser}`)).toBeVisible();
|
||||
});
|
||||
|
||||
test('It cannot request member access on child doc on a 403 page', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
test.slow();
|
||||
|
||||
await page.goto('/');
|
||||
await keyCloakSignIn(page, browserName);
|
||||
|
||||
const [docParent] = await createDoc(
|
||||
page,
|
||||
'Block Member access request on child doc - parent',
|
||||
browserName,
|
||||
1,
|
||||
);
|
||||
|
||||
await verifyDocName(page, docParent);
|
||||
|
||||
await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'Block Member access request on child doc - child',
|
||||
);
|
||||
|
||||
const urlDoc = page.url();
|
||||
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Logout',
|
||||
})
|
||||
.click();
|
||||
|
||||
const otherBrowser = BROWSERS.find((b) => b !== browserName);
|
||||
|
||||
await keyCloakSignIn(page, otherBrowser!);
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs Logo Docs' }),
|
||||
).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
await page.goto(urlDoc);
|
||||
|
||||
await expect(
|
||||
page.getByText(
|
||||
"You're currently viewing a sub-document. To gain access, please request permission from the main document.",
|
||||
),
|
||||
).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Request access' }),
|
||||
).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -205,6 +205,7 @@ test.describe('Document list members', () => {
|
||||
name: 'doc-role-dropdown',
|
||||
});
|
||||
|
||||
|
||||
await expect(mySelf).toBeVisible();
|
||||
await expect(userOwner).toBeVisible();
|
||||
await expect(userReader).toBeVisible();
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addNewMember,
|
||||
createDoc,
|
||||
expectLoginPage,
|
||||
keyCloakSignIn,
|
||||
@@ -13,11 +12,8 @@ import {
|
||||
import { clickOnAddRootSubPage, createRootSubPage } from './sub-pages-utils';
|
||||
|
||||
test.describe('Doc Tree', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('create new sub pages', async ({ page, browserName }) => {
|
||||
await page.goto('/');
|
||||
const [titleParent] = await createDoc(
|
||||
page,
|
||||
'doc-tree-content',
|
||||
@@ -62,6 +58,7 @@ test.describe('Doc Tree', () => {
|
||||
});
|
||||
|
||||
test('check the reorder of sub pages', async ({ page, browserName }) => {
|
||||
await page.goto('/');
|
||||
await createDoc(page, 'doc-tree-content', browserName, 1);
|
||||
const addButton = page.getByRole('button', { name: 'New doc' });
|
||||
await expect(addButton).toBeVisible();
|
||||
@@ -168,6 +165,7 @@ test.describe('Doc Tree', () => {
|
||||
});
|
||||
|
||||
test('it detaches a document', async ({ page, browserName }) => {
|
||||
await page.goto('/');
|
||||
const [docParent] = await createDoc(
|
||||
page,
|
||||
'doc-tree-detach',
|
||||
@@ -204,55 +202,6 @@ test.describe('Doc Tree', () => {
|
||||
await header.locator('h2').getByText('Docs').click();
|
||||
await expect(page.getByText(docChild)).toBeVisible();
|
||||
});
|
||||
|
||||
test('Only owner can detaches a document', async ({ page, browserName }) => {
|
||||
const [docParent] = await createDoc(
|
||||
page,
|
||||
'doc-tree-detach',
|
||||
browserName,
|
||||
1,
|
||||
);
|
||||
|
||||
await verifyDocName(page, docParent);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
await addNewMember(page, 0, 'Owner', 'impress');
|
||||
|
||||
const list = page.getByTestId('doc-share-quick-search');
|
||||
const currentUser = list.getByTestId(
|
||||
`doc-share-member-row-user@${browserName}.test`,
|
||||
);
|
||||
const currentUserRole = currentUser.getByLabel('doc-role-dropdown');
|
||||
await currentUserRole.click();
|
||||
await page.getByLabel('Administrator').click();
|
||||
await list.click();
|
||||
|
||||
await page.getByRole('button', { name: 'Ok' }).click();
|
||||
|
||||
const { name: docChild } = await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'doc-tree-detach-child',
|
||||
);
|
||||
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
await expect(docTree.getByText(docChild)).toBeVisible();
|
||||
await docTree.click();
|
||||
const child = docTree
|
||||
.getByRole('treeitem')
|
||||
.locator('.--docs-sub-page-item')
|
||||
.filter({
|
||||
hasText: docChild,
|
||||
});
|
||||
await child.hover();
|
||||
const menu = child.getByText(`more_horiz`);
|
||||
await menu.click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Move to my docs' }),
|
||||
).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Doc Tree: Inheritance', () => {
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
keyCloakSignIn,
|
||||
verifyDocName,
|
||||
} from './common';
|
||||
import { createRootSubPage } from './sub-pages-utils';
|
||||
|
||||
test.describe('Doc Visibility', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -263,7 +262,7 @@ test.describe('Doc Visibility: Public', () => {
|
||||
|
||||
await expect(page.locator('h2').getByText(docTitle)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'search' })).toBeHidden();
|
||||
await expect(page.getByRole('button', { name: 'New doc' })).toBeHidden();
|
||||
await expect(page.getByRole('button', { name: 'New padoce' })).toBeHidden();
|
||||
await expect(page.getByRole('button', { name: 'Share' })).toBeVisible();
|
||||
const card = page.getByLabel('It is the card information');
|
||||
await expect(card).toBeVisible();
|
||||
@@ -272,7 +271,7 @@ test.describe('Doc Visibility: Public', () => {
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
await expect(
|
||||
page.getByText(
|
||||
'You can view this document but need additional access to see its members or modify settings.',
|
||||
'You do not have permission to view users sharing this document or modify link settings.',
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
@@ -399,8 +398,6 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
test.slow();
|
||||
|
||||
await page.goto('/');
|
||||
await keyCloakSignIn(page, browserName);
|
||||
|
||||
@@ -438,14 +435,6 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
|
||||
const urlDoc = page.url();
|
||||
|
||||
const { name: childTitle } = await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'Authenticated read only - child',
|
||||
);
|
||||
|
||||
const urlChildDoc = page.url();
|
||||
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Logout',
|
||||
@@ -457,9 +446,7 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs Logo Docs' }),
|
||||
).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
).toBeVisible();
|
||||
|
||||
await page.goto(urlDoc);
|
||||
|
||||
@@ -470,7 +457,7 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
|
||||
await expect(
|
||||
page.getByText(
|
||||
'You can view this document but need additional access to see its members or modify settings.',
|
||||
'You do not have permission to view users sharing this document or modify link settings.',
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
@@ -479,22 +466,6 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Request access' }),
|
||||
).toBeDisabled();
|
||||
|
||||
await page.goto(urlChildDoc);
|
||||
|
||||
await expect(page.locator('h2').getByText(childTitle)).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText(
|
||||
'As this is a sub-document, please request access to the parent document to enable these features.',
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Request access' }),
|
||||
).toBeHidden();
|
||||
});
|
||||
|
||||
test('It checks a authenticated doc in editable mode', async ({
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { Page, expect } from '@playwright/test';
|
||||
|
||||
import { randomName, updateDocTitle, waitForResponseCreateDoc } from './common';
|
||||
import { getWaitForCreateDoc, randomName, updateDocTitle } from './common';
|
||||
|
||||
export const createRootSubPage = async (
|
||||
page: Page,
|
||||
browserName: string,
|
||||
docName: string,
|
||||
) => {
|
||||
// Get add button
|
||||
|
||||
// Get response
|
||||
const responsePromise = waitForResponseCreateDoc(page);
|
||||
const responsePromise = getWaitForCreateDoc(page);
|
||||
await clickOnAddRootSubPage(page);
|
||||
const response = await responsePromise;
|
||||
expect(response.ok()).toBeTruthy();
|
||||
@@ -52,7 +54,7 @@ export const createSubPageFromParent = async (
|
||||
await parentDocTreeItem.hover();
|
||||
|
||||
// Create sub page
|
||||
const responsePromise = waitForResponseCreateDoc(page);
|
||||
const responsePromise = getWaitForCreateDoc(page);
|
||||
await parentDocTreeItem.getByRole('button', { name: 'add_box' }).click();
|
||||
|
||||
// Get response
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-e2e",
|
||||
"version": "3.4.1",
|
||||
"version": "3.3.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.53.1",
|
||||
"@types/node": "*",
|
||||
"@types/pdf-parse": "1.1.5",
|
||||
"eslint-config-impress": "*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-impress",
|
||||
"version": "3.4.1",
|
||||
"version": "3.3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -16,75 +16,75 @@
|
||||
},
|
||||
"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.32.0",
|
||||
"@blocknote/core": "0.32.0",
|
||||
"@blocknote/mantine": "0.32.0",
|
||||
"@blocknote/react": "0.32.0",
|
||||
"@blocknote/xl-docx-exporter": "0.32.0",
|
||||
"@blocknote/xl-pdf-exporter": "0.32.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.8.1",
|
||||
"@hocuspocus/provider": "2.15.2",
|
||||
"@openfun/cunningham-react": "3.1.0",
|
||||
"@react-pdf/renderer": "4.3.0",
|
||||
"@sentry/nextjs": "9.38.0",
|
||||
"@tanstack/react-query": "5.83.0",
|
||||
"@sentry/nextjs": "9.32.0",
|
||||
"@tanstack/react-query": "5.81.2",
|
||||
"canvg": "4.0.3",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.1.1",
|
||||
"crisp-sdk-web": "1.0.25",
|
||||
"docx": "9.5.0",
|
||||
"emoji-mart": "5.6.0",
|
||||
"i18next": "25.3.2",
|
||||
"i18next": "25.2.1",
|
||||
"i18next-browser-languagedetector": "8.2.0",
|
||||
"idb": "8.0.3",
|
||||
"lodash": "4.17.21",
|
||||
"luxon": "3.7.1",
|
||||
"next": "15.4.1",
|
||||
"posthog-js": "1.257.0",
|
||||
"luxon": "3.6.1",
|
||||
"next": "15.3.4",
|
||||
"posthog-js": "1.255.1",
|
||||
"react": "*",
|
||||
"react-aria-components": "1.10.1",
|
||||
"react-dom": "*",
|
||||
"react-i18next": "15.6.0",
|
||||
"react-i18next": "15.5.3",
|
||||
"react-intersection-observer": "9.16.0",
|
||||
"react-select": "5.10.2",
|
||||
"react-select": "5.10.1",
|
||||
"styled-components": "6.1.19",
|
||||
"use-debounce": "10.0.5",
|
||||
"y-protocols": "1.0.6",
|
||||
"yjs": "*",
|
||||
"zustand": "5.0.6"
|
||||
"zustand": "5.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svgr/webpack": "8.1.0",
|
||||
"@tanstack/react-query-devtools": "5.83.0",
|
||||
"@tanstack/react-query-devtools": "5.81.2",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/jest": "30.0.0",
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/lodash": "4.17.19",
|
||||
"@types/luxon": "3.6.2",
|
||||
"@types/node": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "17.2.0",
|
||||
"dotenv": "16.5.0",
|
||||
"eslint-config-impress": "*",
|
||||
"fetch-mock": "9.11.0",
|
||||
"jest": "30.0.4",
|
||||
"jest-environment-jsdom": "30.0.4",
|
||||
"jest": "30.0.3",
|
||||
"jest-environment-jsdom": "30.0.2",
|
||||
"node-fetch": "2.7.0",
|
||||
"prettier": "3.6.2",
|
||||
"stylelint": "16.21.1",
|
||||
"prettier": "3.6.1",
|
||||
"stylelint": "16.21.0",
|
||||
"stylelint-config-standard": "38.0.0",
|
||||
"stylelint-prettier": "5.0.3",
|
||||
"typescript": "*",
|
||||
"webpack": "5.100.1",
|
||||
"webpack": "5.99.9",
|
||||
"workbox-webpack-plugin": "7.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
+9
-10
@@ -1,28 +1,27 @@
|
||||
import { Button, Modal, ModalSize } from '@openfun/cunningham-react';
|
||||
import { ReactNode } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box } from './Box';
|
||||
import { Text } from './Text';
|
||||
import { Box } from '../Box';
|
||||
import { Text } from '../Text';
|
||||
|
||||
export type AlertModalProps = {
|
||||
description: ReactNode;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
title: string;
|
||||
cancelLabel?: string;
|
||||
description: string | React.ReactNode;
|
||||
onConfirm: () => void;
|
||||
confirmLabel?: string;
|
||||
cancelLabel?: string;
|
||||
};
|
||||
|
||||
export const AlertModal = ({
|
||||
cancelLabel,
|
||||
confirmLabel,
|
||||
description,
|
||||
isOpen,
|
||||
onClose,
|
||||
onConfirm,
|
||||
title,
|
||||
description,
|
||||
onConfirm,
|
||||
confirmLabel,
|
||||
cancelLabel,
|
||||
}: AlertModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Loader } from '@openfun/cunningham-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, BoxProps } from './Box';
|
||||
import { Box } from './Box';
|
||||
import { Icon } from './Icon';
|
||||
import { Text } from './Text';
|
||||
|
||||
@@ -36,8 +36,8 @@ export const LoadMoreText = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const Loading = (props: BoxProps) => (
|
||||
<Box $align="center" $justify="center" $height="100%" {...props}>
|
||||
export const Loading = () => (
|
||||
<Box $align="center" $justify="center" $height="100%">
|
||||
<Loader />
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './AlertModal';
|
||||
export * from './Box';
|
||||
export * from './BoxButton';
|
||||
export * from './Card';
|
||||
|
||||
@@ -26,7 +26,6 @@ export const QuickSearchItemContent = ({
|
||||
$align="center"
|
||||
$padding={{ horizontal: '2xs', vertical: '4xs' }}
|
||||
$justify="space-between"
|
||||
$minHeight="34px"
|
||||
$width="100%"
|
||||
>
|
||||
<Box
|
||||
|
||||
@@ -26,6 +26,8 @@ export const QuickSearchStyle = createGlobalStyle`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[cmdk-item] {
|
||||
content-visibility: auto;
|
||||
cursor: pointer;
|
||||
@@ -62,6 +64,8 @@ export const QuickSearchStyle = createGlobalStyle`
|
||||
}
|
||||
|
||||
[cmdk-list] {
|
||||
|
||||
|
||||
flex:1;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
|
||||
+1
-2
@@ -50,10 +50,9 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { isEditable, isLoading } = useIsCollaborativeEditable(doc);
|
||||
const isConnectedToCollabServer = provider.isSynced;
|
||||
const readOnly = !doc.abilities.partial_update || !isEditable || isLoading;
|
||||
|
||||
useSaveDoc(doc.id, provider.document, !readOnly, isConnectedToCollabServer);
|
||||
useSaveDoc(doc.id, provider.document, !readOnly);
|
||||
const { i18n } = useTranslation();
|
||||
const lang = i18n.resolvedLanguage;
|
||||
|
||||
|
||||
+5
-5
@@ -41,7 +41,7 @@ describe('useSaveDoc', () => {
|
||||
|
||||
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
||||
|
||||
renderHook(() => useSaveDoc(docId, yDoc, true, true), {
|
||||
renderHook(() => useSaveDoc(docId, yDoc, true), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
@@ -73,7 +73,7 @@ describe('useSaveDoc', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
renderHook(() => useSaveDoc(docId, yDoc, false, true), {
|
||||
renderHook(() => useSaveDoc(docId, yDoc, false), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
@@ -107,7 +107,7 @@ describe('useSaveDoc', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
renderHook(() => useSaveDoc(docId, yDoc, true, true), {
|
||||
renderHook(() => useSaveDoc(docId, yDoc, true), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
@@ -143,7 +143,7 @@ describe('useSaveDoc', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
renderHook(() => useSaveDoc(docId, yDoc, true, true), {
|
||||
renderHook(() => useSaveDoc(docId, yDoc, true), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
@@ -164,7 +164,7 @@ describe('useSaveDoc', () => {
|
||||
const docId = 'test-doc-id';
|
||||
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
|
||||
|
||||
const { unmount } = renderHook(() => useSaveDoc(docId, yDoc, true, true), {
|
||||
const { unmount } = renderHook(() => useSaveDoc(docId, yDoc, true), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
|
||||
@@ -10,12 +10,7 @@ import { toBase64 } from '../utils';
|
||||
|
||||
const SAVE_INTERVAL = 60000;
|
||||
|
||||
const useSaveDoc = (
|
||||
docId: string,
|
||||
yDoc: Y.Doc,
|
||||
canSave: boolean,
|
||||
isConnectedToCollabServer: boolean,
|
||||
) => {
|
||||
const useSaveDoc = (docId: string, yDoc: Y.Doc, canSave: boolean) => {
|
||||
const { mutate: updateDoc } = useUpdateDoc({
|
||||
listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
|
||||
onSuccess: () => {
|
||||
@@ -54,18 +49,10 @@ const useSaveDoc = (
|
||||
updateDoc({
|
||||
id: docId,
|
||||
content: toBase64(Y.encodeStateAsUpdate(yDoc)),
|
||||
websocket: isConnectedToCollabServer,
|
||||
});
|
||||
|
||||
return true;
|
||||
}, [
|
||||
canSave,
|
||||
isLocalChange,
|
||||
updateDoc,
|
||||
docId,
|
||||
yDoc,
|
||||
isConnectedToCollabServer,
|
||||
]);
|
||||
}, [canSave, yDoc, docId, isLocalChange, updateDoc]);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
const originalEnv = process.env.NEXT_PUBLIC_PUBLISH_AS_MIT;
|
||||
|
||||
jest.mock('@/features/docs/doc-export/utils', () => ({
|
||||
anything: true,
|
||||
}));
|
||||
jest.mock('@/features/docs/doc-export/components/ModalExport', () => ({
|
||||
ModalExport: () => <span>ModalExport</span>,
|
||||
}));
|
||||
|
||||
describe('useModuleExport', () => {
|
||||
afterAll(() => {
|
||||
process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = originalEnv;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it('should return undefined when NEXT_PUBLIC_PUBLISH_AS_MIT is true', async () => {
|
||||
process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'true';
|
||||
const Export = await import('@/features/docs/doc-export/');
|
||||
|
||||
expect(Export.default).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should load modules when NEXT_PUBLIC_PUBLISH_AS_MIT is false', async () => {
|
||||
process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false';
|
||||
const Export = await import('@/features/docs/doc-export/');
|
||||
|
||||
expect(Export.default).toHaveProperty('ModalExport');
|
||||
});
|
||||
});
|
||||
@@ -46,8 +46,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
|
||||
const [format, setFormat] = useState<DocDownloadFormat>(
|
||||
DocDownloadFormat.PDF,
|
||||
);
|
||||
const { untitledDocument } = useTrans();
|
||||
|
||||
const { untitledDocument } = useTrans(doc);
|
||||
const templateOptions = useMemo(() => {
|
||||
const templateOptions = (templates?.pages || [])
|
||||
.map((page) =>
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/**
|
||||
* To import Export modules you must import from the index file.
|
||||
* This is to ensure that the Export modules are only loaded when
|
||||
* the application is not published as MIT.
|
||||
*/
|
||||
export * from './api';
|
||||
export * from './components';
|
||||
export * from './utils';
|
||||
|
||||
import * as ModalExport from './components/ModalExport';
|
||||
|
||||
let modulesExport = undefined;
|
||||
if (process.env.NEXT_PUBLIC_PUBLISH_AS_MIT === 'false') {
|
||||
modulesExport = {
|
||||
...ModalExport,
|
||||
};
|
||||
}
|
||||
|
||||
type ModulesExport = typeof modulesExport;
|
||||
|
||||
export default modulesExport as ModulesExport;
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { AppWrapper } from '@/tests/utils';
|
||||
|
||||
import { DocToolBox } from '../components/DocToolBox';
|
||||
|
||||
const doc = {
|
||||
nb_accesses: 1,
|
||||
abilities: {
|
||||
versions_list: true,
|
||||
destroy: true,
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('@/features/docs/doc-export/', () => ({
|
||||
ModalExport: () => <span>ModalExport</span>,
|
||||
}));
|
||||
|
||||
it('DocToolBox dynamic import: loads DocToolBox when NEXT_PUBLIC_PUBLISH_AS_MIT is false', async () => {
|
||||
process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false';
|
||||
|
||||
render(<DocToolBox doc={doc as any} />, {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
expect(await screen.findByText('download')).toBeInTheDocument();
|
||||
});
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { AppWrapper } from '@/tests/utils';
|
||||
|
||||
const doc = {
|
||||
nb_accesses: 1,
|
||||
abilities: {
|
||||
versions_list: true,
|
||||
destroy: true,
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('@/features/docs/doc-export/', () => ({
|
||||
ModalExport: () => <span>ModalExport</span>,
|
||||
}));
|
||||
|
||||
it('DocToolBox dynamic import: loads DocToolBox when NEXT_PUBLIC_PUBLISH_AS_MIT is true', async () => {
|
||||
process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'true';
|
||||
|
||||
const { DocToolBox } = await import('../components/DocToolBox');
|
||||
|
||||
render(<DocToolBox doc={doc as any} />, {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(screen.queryByText('download')).not.toBeInTheDocument();
|
||||
},
|
||||
{
|
||||
timeout: 1000,
|
||||
},
|
||||
);
|
||||
});
|
||||
+11
-26
@@ -32,7 +32,7 @@ export const AlertNetwork = () => {
|
||||
<Box $direction="row" $gap={spacingsTokens['2xs']} $align="center">
|
||||
<Icon iconName="mobiledata_off" $theme="warning" $variation="600" />
|
||||
<Text $theme="warning" $variation="600" $weight={500}>
|
||||
{t('Others are editing. Your network prevent changes.')}
|
||||
{t('Your network do not allow you to edit')}
|
||||
</Text>
|
||||
</Box>
|
||||
<BoxButton
|
||||
@@ -50,7 +50,7 @@ export const AlertNetwork = () => {
|
||||
$margin={{ top: 'auto' }}
|
||||
/>
|
||||
<Text $theme="warning" $variation="600" $weight="500" $size="xs">
|
||||
{t('Learn more')}
|
||||
{t('Know more')}
|
||||
</Text>
|
||||
</BoxButton>
|
||||
</Box>
|
||||
@@ -74,8 +74,8 @@ export const AlertNetworkModal = ({ onClose }: AlertNetworkModalProps) => {
|
||||
onClose={() => onClose()}
|
||||
rightActions={
|
||||
<>
|
||||
<Button aria-label={t('OK')} onClick={onClose} color="danger">
|
||||
{t('I understand')}
|
||||
<Button aria-label={t('OK')} onClick={onClose}>
|
||||
{t('OK')}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
@@ -88,39 +88,24 @@ export const AlertNetworkModal = ({ onClose }: AlertNetworkModalProps) => {
|
||||
$align="flex-start"
|
||||
$variation="1000"
|
||||
>
|
||||
{t("Why you can't edit the document?")}
|
||||
{t("Why can't I edit?")}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
aria-label={t('Content modal to explain why the user cannot edit')}
|
||||
className="--docs--modal-alert-network"
|
||||
$margin={{ top: 'md' }}
|
||||
$margin={{ top: 'xs' }}
|
||||
>
|
||||
<Text $size="sm" $variation="600">
|
||||
{t(
|
||||
'Others are editing this document. Unfortunately your network blocks WebSockets, the technology enabling real-time co-editing.',
|
||||
'The network configuration of your workstation or internet connection does not allow editing shared documents.',
|
||||
)}
|
||||
</Text>
|
||||
<Text
|
||||
$size="sm"
|
||||
$variation="600"
|
||||
$margin={{ top: 'xs' }}
|
||||
$weight="bold"
|
||||
$display="inline"
|
||||
>
|
||||
{t("This means you can't edit until others leave.")}{' '}
|
||||
<Text
|
||||
$size="sm"
|
||||
$variation="600"
|
||||
$margin={{ top: 'xs' }}
|
||||
$weight="normal"
|
||||
$display="inline"
|
||||
>
|
||||
{t(
|
||||
'If you wish to be able to co-edit in real-time, contact your Information Systems Security Manager about allowing WebSockets.',
|
||||
)}
|
||||
</Text>
|
||||
<Text $size="sm" $variation="600" $margin={{ top: 'xs' }}>
|
||||
{t(
|
||||
'Docs use WebSockets to enable real-time editing. These communication channels allow instant and bidirectional exchanges between your browser and our servers. To access collaborative editing, please contact your IT department to enable WebSockets.',
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
</Modal>
|
||||
|
||||
@@ -34,7 +34,7 @@ export const DocTitle = ({ doc }: DocTitleProps) => {
|
||||
export const DocTitleText = () => {
|
||||
const { isMobile } = useResponsiveStore();
|
||||
const { currentDoc } = useDocStore();
|
||||
const { untitledDocument } = useTrans();
|
||||
const { untitledDocument } = useTrans(currentDoc);
|
||||
|
||||
return (
|
||||
<Text
|
||||
@@ -55,7 +55,7 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
const [titleDisplay, setTitleDisplay] = useState(doc.title);
|
||||
|
||||
const { untitledDocument } = useTrans();
|
||||
const { untitledDocument } = useTrans(doc);
|
||||
|
||||
const { broadcast } = useBroadcastStore();
|
||||
|
||||
|
||||
@@ -1,50 +1,27 @@
|
||||
import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
|
||||
import {
|
||||
Button,
|
||||
VariantType,
|
||||
useModal,
|
||||
useToastProvider,
|
||||
} from '@openfun/cunningham-react';
|
||||
import { Button, useModal } from '@openfun/cunningham-react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import {
|
||||
Box,
|
||||
DropdownMenu,
|
||||
DropdownMenuOption,
|
||||
Icon,
|
||||
IconOptions,
|
||||
} from '@/components';
|
||||
import { Box, Icon } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import Export from '@/docs/doc-export/';
|
||||
import {
|
||||
Doc,
|
||||
KEY_DOC,
|
||||
KEY_LIST_DOC,
|
||||
ModalRemoveDoc,
|
||||
useCopyDocLink,
|
||||
useCreateFavoriteDoc,
|
||||
useDeleteFavoriteDoc,
|
||||
useDuplicateDoc,
|
||||
} from '@/docs/doc-management';
|
||||
import { DocShareModal } from '@/docs/doc-share';
|
||||
import {
|
||||
KEY_LIST_DOC_VERSIONS,
|
||||
ModalSelectVersion,
|
||||
} from '@/docs/doc-versioning';
|
||||
import { useAnalytics } from '@/libs';
|
||||
import { Doc } from '@/docs/doc-management';
|
||||
import { KEY_LIST_DOC_VERSIONS } from '@/docs/doc-versioning';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useCopyCurrentEditorToClipboard } from '../hooks/useCopyCurrentEditorToClipboard';
|
||||
|
||||
const ModalExport = Export?.ModalExport;
|
||||
|
||||
interface DocToolBoxProps {
|
||||
doc: Doc;
|
||||
}
|
||||
|
||||
const DocToolBoxLicence = dynamic(() =>
|
||||
process.env.NEXT_PUBLIC_PUBLISH_AS_MIT === 'false'
|
||||
? import('./DocToolBoxLicenceAGPL').then((mod) => mod.DocToolBoxLicenceAGPL)
|
||||
: import('./DocToolBoxLicenceMIT').then((mod) => mod.DocToolBoxLicenceMIT),
|
||||
);
|
||||
|
||||
export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
const { t } = useTranslation();
|
||||
const treeContext = useTreeContext<Doc>();
|
||||
@@ -62,129 +39,23 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
}, [doc, treeContext?.root]);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { toast } = useToastProvider();
|
||||
|
||||
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
|
||||
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
|
||||
const [isModalExportOpen, setIsModalExportOpen] = useState(false);
|
||||
const selectHistoryModal = useModal();
|
||||
const modalHistory = useModal();
|
||||
const modalShare = useModal();
|
||||
|
||||
const { isSmallMobile, isDesktop } = useResponsiveStore();
|
||||
const copyDocLink = useCopyDocLink(doc.id);
|
||||
const { mutate: duplicateDoc } = useDuplicateDoc({
|
||||
onSuccess: () => {
|
||||
toast(t('Document duplicated successfully!'), VariantType.SUCCESS, {
|
||||
duration: 3000,
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
toast(t('Failed to duplicate the document...'), VariantType.ERROR, {
|
||||
duration: 3000,
|
||||
});
|
||||
},
|
||||
});
|
||||
const { isFeatureFlagActivated } = useAnalytics();
|
||||
const removeFavoriteDoc = useDeleteFavoriteDoc({
|
||||
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
|
||||
});
|
||||
const makeFavoriteDoc = useCreateFavoriteDoc({
|
||||
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
|
||||
});
|
||||
const { isSmallMobile } = useResponsiveStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectHistoryModal.isOpen) {
|
||||
if (modalHistory.isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
void queryClient.resetQueries({
|
||||
queryKey: [KEY_LIST_DOC_VERSIONS],
|
||||
});
|
||||
}, [selectHistoryModal.isOpen, queryClient]);
|
||||
|
||||
const options: DropdownMenuOption[] = [
|
||||
...(isSmallMobile
|
||||
? [
|
||||
{
|
||||
label: t('Share'),
|
||||
icon: 'group',
|
||||
callback: modalShare.open,
|
||||
},
|
||||
{
|
||||
label: t('Export'),
|
||||
icon: 'download',
|
||||
callback: () => {
|
||||
setIsModalExportOpen(true);
|
||||
},
|
||||
show: !!ModalExport,
|
||||
},
|
||||
{
|
||||
label: t('Copy link'),
|
||||
icon: 'add_link',
|
||||
callback: copyDocLink,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: doc.is_favorite ? t('Unpin') : t('Pin'),
|
||||
icon: 'push_pin',
|
||||
callback: () => {
|
||||
if (doc.is_favorite) {
|
||||
removeFavoriteDoc.mutate({ id: doc.id });
|
||||
} else {
|
||||
makeFavoriteDoc.mutate({ id: doc.id });
|
||||
}
|
||||
},
|
||||
testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`,
|
||||
},
|
||||
{
|
||||
label: t('Version history'),
|
||||
icon: 'history',
|
||||
disabled: !doc.abilities.versions_list,
|
||||
callback: () => {
|
||||
selectHistoryModal.open();
|
||||
},
|
||||
show: isDesktop,
|
||||
},
|
||||
{
|
||||
label: t('Copy as {{format}}', { format: 'Markdown' }),
|
||||
icon: 'content_copy',
|
||||
callback: () => {
|
||||
void copyCurrentEditorToClipboard('markdown');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('Copy as {{format}}', { format: 'HTML' }),
|
||||
icon: 'content_copy',
|
||||
callback: () => {
|
||||
void copyCurrentEditorToClipboard('html');
|
||||
},
|
||||
show: isFeatureFlagActivated('CopyAsHTML'),
|
||||
},
|
||||
{
|
||||
label: t('Duplicate'),
|
||||
icon: 'content_copy',
|
||||
disabled: !doc.abilities.duplicate,
|
||||
callback: () => {
|
||||
duplicateDoc({
|
||||
docId: doc.id,
|
||||
with_accesses: false,
|
||||
canSave: doc.abilities.partial_update,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('Delete document'),
|
||||
icon: 'delete',
|
||||
disabled: !doc.abilities.destroy,
|
||||
callback: () => {
|
||||
setIsModalRemoveOpen(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const copyCurrentEditorToClipboard = useCopyCurrentEditorToClipboard();
|
||||
}, [modalHistory.isOpen, queryClient]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -242,59 +113,13 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isSmallMobile && ModalExport && (
|
||||
<Button
|
||||
color="tertiary-text"
|
||||
icon={
|
||||
<Icon iconName="download" $theme="primary" $variation="800" />
|
||||
}
|
||||
onClick={() => {
|
||||
setIsModalExportOpen(true);
|
||||
}}
|
||||
size={isSmallMobile ? 'small' : 'medium'}
|
||||
/>
|
||||
)}
|
||||
<DropdownMenu options={options}>
|
||||
<IconOptions
|
||||
isHorizontal
|
||||
$theme="primary"
|
||||
$padding={{ all: 'xs' }}
|
||||
$css={css`
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
background-color: ${colorsTokens['greyscale-100']};
|
||||
}
|
||||
${isSmallMobile
|
||||
? css`
|
||||
padding: 10px;
|
||||
border: 1px solid ${colorsTokens['greyscale-300']};
|
||||
`
|
||||
: ''}
|
||||
`}
|
||||
aria-label={t('Open the document options')}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
</Box>
|
||||
|
||||
{modalShare.isOpen && (
|
||||
<DocShareModal
|
||||
onClose={() => modalShare.close()}
|
||||
<DocToolBoxLicence
|
||||
doc={doc}
|
||||
modalHistory={modalHistory}
|
||||
modalShare={modalShare}
|
||||
isRootDoc={treeContext?.root?.id === doc.id}
|
||||
/>
|
||||
)}
|
||||
{isModalExportOpen && ModalExport && (
|
||||
<ModalExport onClose={() => setIsModalExportOpen(false)} doc={doc} />
|
||||
)}
|
||||
{isModalRemoveOpen && (
|
||||
<ModalRemoveDoc onClose={() => setIsModalRemoveOpen(false)} doc={doc} />
|
||||
)}
|
||||
{selectHistoryModal.isOpen && (
|
||||
<ModalSelectVersion
|
||||
onClose={() => selectHistoryModal.close()}
|
||||
doc={doc}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
import { Button, useModal } from '@openfun/cunningham-react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuOption,
|
||||
Icon,
|
||||
IconOptions,
|
||||
} from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { ModalExport } from '@/docs/doc-export/';
|
||||
import {
|
||||
Doc,
|
||||
KEY_DOC,
|
||||
KEY_LIST_DOC,
|
||||
ModalRemoveDoc,
|
||||
useCopyDocLink,
|
||||
useCreateFavoriteDoc,
|
||||
useDeleteFavoriteDoc,
|
||||
} from '@/docs/doc-management';
|
||||
import {
|
||||
KEY_LIST_DOC_VERSIONS,
|
||||
ModalSelectVersion,
|
||||
} from '@/docs/doc-versioning';
|
||||
import { useAnalytics } from '@/libs';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { DocShareModal } from '../../doc-share';
|
||||
import { useCopyCurrentEditorToClipboard } from '../hooks/useCopyCurrentEditorToClipboard';
|
||||
|
||||
type ModalType = ReturnType<typeof useModal>;
|
||||
|
||||
interface DocToolBoxLicenceProps {
|
||||
doc: Doc;
|
||||
modalHistory: ModalType;
|
||||
modalShare: ModalType;
|
||||
isRootDoc?: boolean;
|
||||
}
|
||||
|
||||
export const DocToolBoxLicenceAGPL = ({
|
||||
doc,
|
||||
modalHistory,
|
||||
modalShare,
|
||||
isRootDoc = true,
|
||||
}: DocToolBoxLicenceProps) => {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
|
||||
const [isModalExportOpen, setIsModalExportOpen] = useState(false);
|
||||
|
||||
const { isSmallMobile, isDesktop } = useResponsiveStore();
|
||||
const copyDocLink = useCopyDocLink(doc.id);
|
||||
const { isFeatureFlagActivated } = useAnalytics();
|
||||
const removeFavoriteDoc = useDeleteFavoriteDoc({
|
||||
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
|
||||
});
|
||||
const makeFavoriteDoc = useCreateFavoriteDoc({
|
||||
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
|
||||
});
|
||||
const copyCurrentEditorToClipboard = useCopyCurrentEditorToClipboard();
|
||||
|
||||
const options: DropdownMenuOption[] = [
|
||||
...(isSmallMobile
|
||||
? [
|
||||
{
|
||||
label: t('Share'),
|
||||
icon: 'group',
|
||||
callback: modalShare.open,
|
||||
},
|
||||
{
|
||||
label: t('Export'),
|
||||
icon: 'download',
|
||||
callback: () => {
|
||||
setIsModalExportOpen(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('Copy link'),
|
||||
icon: 'add_link',
|
||||
callback: copyDocLink,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: doc.is_favorite ? t('Unpin') : t('Pin'),
|
||||
icon: 'push_pin',
|
||||
callback: () => {
|
||||
if (doc.is_favorite) {
|
||||
removeFavoriteDoc.mutate({ id: doc.id });
|
||||
} else {
|
||||
makeFavoriteDoc.mutate({ id: doc.id });
|
||||
}
|
||||
},
|
||||
testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`,
|
||||
},
|
||||
{
|
||||
label: t('Version history'),
|
||||
icon: 'history',
|
||||
disabled: !doc.abilities.versions_list,
|
||||
callback: () => {
|
||||
modalHistory.open();
|
||||
},
|
||||
show: isDesktop,
|
||||
},
|
||||
|
||||
{
|
||||
label: t('Copy as {{format}}', { format: 'Markdown' }),
|
||||
icon: 'content_copy',
|
||||
callback: () => {
|
||||
void copyCurrentEditorToClipboard('markdown');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('Copy as {{format}}', { format: 'HTML' }),
|
||||
icon: 'content_copy',
|
||||
callback: () => {
|
||||
void copyCurrentEditorToClipboard('html');
|
||||
},
|
||||
show: isFeatureFlagActivated('CopyAsHTML'),
|
||||
},
|
||||
{
|
||||
label: t('Delete document'),
|
||||
icon: 'delete',
|
||||
disabled: !doc.abilities.destroy,
|
||||
callback: () => {
|
||||
setIsModalRemoveOpen(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (modalHistory.isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
void queryClient.resetQueries({
|
||||
queryKey: [KEY_LIST_DOC_VERSIONS],
|
||||
});
|
||||
}, [modalHistory.isOpen, queryClient]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isSmallMobile && (
|
||||
<Button
|
||||
color="tertiary-text"
|
||||
icon={<Icon iconName="download" $theme="primary" $variation="800" />}
|
||||
onClick={() => {
|
||||
setIsModalExportOpen(true);
|
||||
}}
|
||||
size={isSmallMobile ? 'small' : 'medium'}
|
||||
/>
|
||||
)}
|
||||
<DropdownMenu options={options}>
|
||||
<IconOptions
|
||||
isHorizontal
|
||||
$theme="primary"
|
||||
$padding={{ all: 'xs' }}
|
||||
$css={css`
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
background-color: ${colorsTokens['greyscale-100']};
|
||||
}
|
||||
${isSmallMobile
|
||||
? css`
|
||||
padding: 10px;
|
||||
border: 1px solid ${colorsTokens['greyscale-300']};
|
||||
`
|
||||
: ''}
|
||||
`}
|
||||
aria-label={t('Open the document options')}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
|
||||
{modalShare.isOpen && (
|
||||
<DocShareModal
|
||||
onClose={() => modalShare.close()}
|
||||
doc={doc}
|
||||
isRootDoc={isRootDoc}
|
||||
/>
|
||||
)}
|
||||
{isModalExportOpen && (
|
||||
<ModalExport onClose={() => setIsModalExportOpen(false)} doc={doc} />
|
||||
)}
|
||||
{isModalRemoveOpen && (
|
||||
<ModalRemoveDoc onClose={() => setIsModalRemoveOpen(false)} doc={doc} />
|
||||
)}
|
||||
{modalHistory.isOpen && (
|
||||
<ModalSelectVersion onClose={() => modalHistory.close()} doc={doc} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
import { useModal } from '@openfun/cunningham-react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { DropdownMenu, DropdownMenuOption, IconOptions } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import {
|
||||
Doc,
|
||||
KEY_DOC,
|
||||
KEY_LIST_DOC,
|
||||
ModalRemoveDoc,
|
||||
useCopyDocLink,
|
||||
useCreateFavoriteDoc,
|
||||
useDeleteFavoriteDoc,
|
||||
} from '@/docs/doc-management';
|
||||
import {
|
||||
KEY_LIST_DOC_VERSIONS,
|
||||
ModalSelectVersion,
|
||||
} from '@/docs/doc-versioning';
|
||||
import { useAnalytics } from '@/libs';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { DocShareModal } from '../../doc-share';
|
||||
import { useCopyCurrentEditorToClipboard } from '../hooks/useCopyCurrentEditorToClipboard';
|
||||
|
||||
type ModalType = ReturnType<typeof useModal>;
|
||||
|
||||
interface DocToolBoxLicenceProps {
|
||||
doc: Doc;
|
||||
modalHistory: ModalType;
|
||||
modalShare: ModalType;
|
||||
isRootDoc?: boolean;
|
||||
}
|
||||
|
||||
export const DocToolBoxLicenceMIT = ({
|
||||
doc,
|
||||
modalHistory,
|
||||
modalShare,
|
||||
isRootDoc = true,
|
||||
}: DocToolBoxLicenceProps) => {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
|
||||
|
||||
const { isSmallMobile, isDesktop } = useResponsiveStore();
|
||||
const copyDocLink = useCopyDocLink(doc.id);
|
||||
const { isFeatureFlagActivated } = useAnalytics();
|
||||
const removeFavoriteDoc = useDeleteFavoriteDoc({
|
||||
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
|
||||
});
|
||||
const makeFavoriteDoc = useCreateFavoriteDoc({
|
||||
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
|
||||
});
|
||||
const copyCurrentEditorToClipboard = useCopyCurrentEditorToClipboard();
|
||||
|
||||
const options: DropdownMenuOption[] = [
|
||||
...(isSmallMobile
|
||||
? [
|
||||
{
|
||||
label: t('Share'),
|
||||
icon: 'group',
|
||||
callback: modalShare.open,
|
||||
},
|
||||
{
|
||||
label: t('Copy link'),
|
||||
icon: 'add_link',
|
||||
callback: copyDocLink,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: doc.is_favorite ? t('Unpin') : t('Pin'),
|
||||
icon: 'push_pin',
|
||||
callback: () => {
|
||||
if (doc.is_favorite) {
|
||||
removeFavoriteDoc.mutate({ id: doc.id });
|
||||
} else {
|
||||
makeFavoriteDoc.mutate({ id: doc.id });
|
||||
}
|
||||
},
|
||||
testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`,
|
||||
},
|
||||
{
|
||||
label: t('Version history'),
|
||||
icon: 'history',
|
||||
disabled: !doc.abilities.versions_list,
|
||||
callback: () => {
|
||||
modalHistory.open();
|
||||
},
|
||||
show: isDesktop,
|
||||
},
|
||||
|
||||
{
|
||||
label: t('Copy as {{format}}', { format: 'Markdown' }),
|
||||
icon: 'content_copy',
|
||||
callback: () => {
|
||||
void copyCurrentEditorToClipboard('markdown');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('Copy as {{format}}', { format: 'HTML' }),
|
||||
icon: 'content_copy',
|
||||
callback: () => {
|
||||
void copyCurrentEditorToClipboard('html');
|
||||
},
|
||||
show: isFeatureFlagActivated('CopyAsHTML'),
|
||||
},
|
||||
{
|
||||
label: t('Delete document'),
|
||||
icon: 'delete',
|
||||
disabled: !doc.abilities.destroy,
|
||||
callback: () => {
|
||||
setIsModalRemoveOpen(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (modalHistory.isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
void queryClient.resetQueries({
|
||||
queryKey: [KEY_LIST_DOC_VERSIONS],
|
||||
});
|
||||
}, [modalHistory.isOpen, queryClient]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu options={options}>
|
||||
<IconOptions
|
||||
isHorizontal
|
||||
$theme="primary"
|
||||
$padding={{ all: 'xs' }}
|
||||
$css={css`
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
background-color: ${colorsTokens['greyscale-100']};
|
||||
}
|
||||
${isSmallMobile
|
||||
? css`
|
||||
padding: 10px;
|
||||
border: 1px solid ${colorsTokens['greyscale-300']};
|
||||
`
|
||||
: ''}
|
||||
`}
|
||||
aria-label={t('Open the document options')}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
|
||||
{modalShare.isOpen && (
|
||||
<DocShareModal
|
||||
onClose={() => modalShare.close()}
|
||||
doc={doc}
|
||||
isRootDoc={isRootDoc}
|
||||
/>
|
||||
)}
|
||||
{isModalRemoveOpen && (
|
||||
<ModalRemoveDoc onClose={() => setIsModalRemoveOpen(false)} doc={doc} />
|
||||
)}
|
||||
{modalHistory.isOpen && (
|
||||
<ModalSelectVersion onClose={() => modalHistory.close()} doc={doc} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -5,6 +5,5 @@ export * from './useDoc';
|
||||
export * from './useDocOptions';
|
||||
export * from './useDocs';
|
||||
export * from './useSubDocs';
|
||||
export * from './useDuplicateDoc';
|
||||
export * from './useUpdateDoc';
|
||||
export * from './useUpdateDocLink';
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
import { Doc } from '@/docs/doc-management';
|
||||
import { Doc } from '@/features/docs';
|
||||
|
||||
export type CreateFavoriteDocParams = Pick<Doc, 'id'>;
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
import { Doc } from '@/docs/doc-management';
|
||||
import { Doc } from '@/features/docs';
|
||||
|
||||
export type DeleteFavoriteDocParams = Pick<Doc, 'id'>;
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
|
||||
type DocCanEditResponse = { can_edit: boolean };
|
||||
|
||||
export const docCanEdit = async (id: string): Promise<DocCanEditResponse> => {
|
||||
const response = await fetchAPI(`documents/${id}/can-edit/`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new APIError('Failed to get the doc', await errorCauses(response));
|
||||
}
|
||||
|
||||
return response.json() as Promise<DocCanEditResponse>;
|
||||
};
|
||||
|
||||
export const KEY_CAN_EDIT = 'doc-can-edit';
|
||||
|
||||
export function useDocCanEdit(
|
||||
param: string,
|
||||
queryConfig?: UseQueryOptions<
|
||||
DocCanEditResponse,
|
||||
APIError,
|
||||
DocCanEditResponse
|
||||
>,
|
||||
) {
|
||||
return useQuery<DocCanEditResponse, APIError, DocCanEditResponse>({
|
||||
queryKey: [KEY_CAN_EDIT, param],
|
||||
queryFn: () => docCanEdit(param),
|
||||
...queryConfig,
|
||||
});
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import {
|
||||
UseMutationOptions,
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
import { toBase64 } from '@/docs/doc-editor';
|
||||
import { KEY_DOC_TREE } from '@/docs/doc-tree';
|
||||
import { KEY_LIST_DOC_VERSIONS } from '@/docs/doc-versioning';
|
||||
|
||||
import { useProviderStore } from '../stores';
|
||||
import { Doc } from '../types';
|
||||
|
||||
import { KEY_LIST_DOC } from './useDocs';
|
||||
import { useUpdateDoc } from './useUpdateDoc';
|
||||
|
||||
interface DuplicateDocPayload {
|
||||
docId: string;
|
||||
with_accesses?: boolean;
|
||||
}
|
||||
|
||||
type DuplicateDocResponse = Pick<Doc, 'id'>;
|
||||
|
||||
export const duplicateDoc = async ({
|
||||
docId,
|
||||
with_accesses,
|
||||
}: DuplicateDocPayload): Promise<DuplicateDocResponse> => {
|
||||
const response = await fetchAPI(`documents/${docId}/duplicate/`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ with_accesses }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new APIError(
|
||||
'Failed to duplicate the doc',
|
||||
await errorCauses(response),
|
||||
);
|
||||
}
|
||||
|
||||
return response.json() as Promise<DuplicateDocResponse>;
|
||||
};
|
||||
|
||||
type DuplicateDocParams = DuplicateDocPayload & {
|
||||
canSave: boolean;
|
||||
};
|
||||
|
||||
type DuplicateDocOptions = UseMutationOptions<
|
||||
DuplicateDocResponse,
|
||||
APIError,
|
||||
DuplicateDocParams
|
||||
>;
|
||||
|
||||
export function useDuplicateDoc(options: DuplicateDocOptions) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { provider } = useProviderStore();
|
||||
|
||||
const { mutateAsync: updateDoc } = useUpdateDoc({
|
||||
listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
|
||||
});
|
||||
|
||||
return useMutation<DuplicateDocResponse, APIError, DuplicateDocParams>({
|
||||
mutationFn: async (variables) => {
|
||||
try {
|
||||
// Save the document if we can first, to ensure the latest state is duplicated
|
||||
if (
|
||||
variables.canSave &&
|
||||
provider &&
|
||||
provider.document.guid === variables.docId
|
||||
) {
|
||||
await updateDoc({
|
||||
id: variables.docId,
|
||||
content: toBase64(Y.encodeStateAsUpdate(provider.document)),
|
||||
});
|
||||
}
|
||||
|
||||
return await duplicateDoc(variables);
|
||||
} catch (error) {
|
||||
// If save fails, throw the error to prevent duplication
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
onSuccess: (data, variables, context) => {
|
||||
void queryClient.resetQueries({
|
||||
queryKey: [KEY_LIST_DOC],
|
||||
});
|
||||
void queryClient.resetQueries({
|
||||
queryKey: [KEY_DOC_TREE],
|
||||
});
|
||||
void options.onSuccess?.(data, variables, context);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,18 +1,10 @@
|
||||
import {
|
||||
UseMutationOptions,
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
import { Doc } from '@/docs/doc-management';
|
||||
|
||||
import { KEY_CAN_EDIT } from './useDocCanEdit';
|
||||
import { Doc } from '@/features/docs';
|
||||
|
||||
export type UpdateDocParams = Pick<Doc, 'id'> &
|
||||
Partial<Pick<Doc, 'content' | 'title'>> & {
|
||||
websocket?: boolean;
|
||||
};
|
||||
Partial<Pick<Doc, 'content' | 'title'>>;
|
||||
|
||||
export const updateDoc = async ({
|
||||
id,
|
||||
@@ -32,30 +24,25 @@ export const updateDoc = async ({
|
||||
return response.json() as Promise<Doc>;
|
||||
};
|
||||
|
||||
type UseUpdateDoc = UseMutationOptions<Doc, APIError, Partial<Doc>> & {
|
||||
interface UpdateDocProps {
|
||||
onSuccess?: (data: Doc) => void;
|
||||
listInvalideQueries?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export function useUpdateDoc(queryConfig?: UseUpdateDoc) {
|
||||
export function useUpdateDoc({
|
||||
onSuccess,
|
||||
listInvalideQueries,
|
||||
}: UpdateDocProps = {}) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<Doc, APIError, UpdateDocParams>({
|
||||
mutationFn: updateDoc,
|
||||
...queryConfig,
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryConfig?.listInvalideQueries?.forEach((queryKey) => {
|
||||
onSuccess: (data) => {
|
||||
listInvalideQueries?.forEach((queryKey) => {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [queryKey],
|
||||
});
|
||||
});
|
||||
|
||||
if (queryConfig?.onSuccess) {
|
||||
void queryConfig.onSuccess(data, variables, context);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_CAN_EDIT],
|
||||
});
|
||||
onSuccess?.(data);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+11
-8
@@ -5,9 +5,9 @@ import {
|
||||
VariantType,
|
||||
useToastProvider,
|
||||
} from '@openfun/cunningham-react';
|
||||
import { t } from 'i18next';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, Text, TextErrors } from '@/components';
|
||||
|
||||
@@ -26,9 +26,11 @@ export const ModalRemoveDoc = ({
|
||||
afterDelete,
|
||||
}: ModalRemoveDocProps) => {
|
||||
const { toast } = useToastProvider();
|
||||
const { t } = useTranslation();
|
||||
const { push } = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const hasChildren = doc.numchild && doc.numchild > 0;
|
||||
|
||||
const {
|
||||
mutate: removeDoc,
|
||||
isError,
|
||||
@@ -98,12 +100,13 @@ export const ModalRemoveDoc = ({
|
||||
className="--docs--modal-remove-doc"
|
||||
>
|
||||
{!isError && (
|
||||
<Text $size="sm" $variation="600" $display="inline-block">
|
||||
<Trans t={t}>
|
||||
This document and <strong>any sub-documents</strong> will be
|
||||
permanently deleted. This action is irreversible.
|
||||
</Trans>
|
||||
</Text>
|
||||
<>
|
||||
<Text $size="sm" $variation="600">
|
||||
{t(
|
||||
'This document will be permanently deleted. This action is irreversible.',
|
||||
)}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isError && <TextErrors causes={error.cause} />}
|
||||
|
||||
+20
-47
@@ -1,70 +1,43 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useConfig } from '@/core';
|
||||
import { useIsOffline } from '@/features/service-worker';
|
||||
|
||||
import { KEY_CAN_EDIT, useDocCanEdit } from '../api/useDocCanEdit';
|
||||
import { useProviderStore } from '../stores';
|
||||
import { Doc, LinkReach, LinkRole } from '../types';
|
||||
import { Doc, LinkReach } from '../types';
|
||||
|
||||
export const useIsCollaborativeEditable = (doc: Doc) => {
|
||||
const { isConnected } = useProviderStore();
|
||||
const { data: conf } = useConfig();
|
||||
|
||||
const docIsPublic =
|
||||
doc.computed_link_reach === LinkReach.PUBLIC &&
|
||||
doc.computed_link_role === LinkRole.EDITOR;
|
||||
const docIsAuth =
|
||||
doc.computed_link_reach === LinkReach.AUTHENTICATED &&
|
||||
doc.computed_link_role === LinkRole.EDITOR;
|
||||
const docHasMember =
|
||||
doc.nb_accesses_direct > 1 || doc.nb_accesses_ancestors > 1;
|
||||
const isUserReader = !doc.abilities.partial_update;
|
||||
const docIsPublic = doc.link_reach === LinkReach.PUBLIC;
|
||||
const docIsAuth = doc.link_reach === LinkReach.AUTHENTICATED;
|
||||
const docHasMember = doc.nb_accesses_direct > 1;
|
||||
const isShared = docIsPublic || docIsAuth || docHasMember;
|
||||
const { isOffline } = useIsOffline();
|
||||
const _isEditable = isUserReader || isConnected || !isShared || isOffline;
|
||||
const [isEditable, setIsEditable] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(!_isEditable);
|
||||
const timeout = useRef<NodeJS.Timeout | null>(null);
|
||||
const { data: editingRight, isLoading: isLoadingCanEdit } = useDocCanEdit(
|
||||
doc.id,
|
||||
{
|
||||
enabled: !_isEditable,
|
||||
queryKey: [KEY_CAN_EDIT, doc.id],
|
||||
staleTime: 0,
|
||||
},
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const { isOffline } = useIsOffline();
|
||||
|
||||
/**
|
||||
* Connection can take a few seconds
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (isLoadingCanEdit || _isEditable || !editingRight) {
|
||||
return;
|
||||
}
|
||||
const _isEditable = isConnected || !isShared || isOffline;
|
||||
setIsLoading(true);
|
||||
|
||||
// Connection to the WebSocket can take some time, so we set a timeout to ensure the loading state is cleared after a reasonable time.
|
||||
timeout.current = setTimeout(() => {
|
||||
setIsEditable(editingRight.can_edit);
|
||||
if (_isEditable) {
|
||||
setIsEditable(true);
|
||||
setIsLoading(false);
|
||||
}, 1500);
|
||||
|
||||
return () => {
|
||||
if (timeout.current) {
|
||||
clearTimeout(timeout.current);
|
||||
}
|
||||
};
|
||||
}, [editingRight, isLoadingCanEdit, _isEditable]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!_isEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeout.current) {
|
||||
clearTimeout(timeout.current);
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
setIsEditable(false);
|
||||
setIsLoading(false);
|
||||
}, 5000);
|
||||
|
||||
setIsEditable(true);
|
||||
setIsLoading(false);
|
||||
}, [_isEditable]);
|
||||
return () => clearTimeout(timer);
|
||||
}, [isConnected, isOffline, isShared]);
|
||||
|
||||
if (!conf?.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY) {
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Role } from '../types';
|
||||
import { Doc, Role } from '../types';
|
||||
|
||||
export const useTrans = () => {
|
||||
export const useTrans = (doc?: Doc) => {
|
||||
const { t } = useTranslation();
|
||||
const isChild = doc && doc.nb_accesses_ancestors > 1;
|
||||
|
||||
const translatedRoles = {
|
||||
[Role.READER]: t('Reader'),
|
||||
@@ -16,7 +17,7 @@ export const useTrans = () => {
|
||||
transRole: (role: Role) => {
|
||||
return translatedRoles[role];
|
||||
},
|
||||
untitledDocument: t('Untitled document'),
|
||||
untitledDocument: isChild ? t('Untitled page') : t('Untitled document'),
|
||||
translatedRoles,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -70,6 +70,7 @@ export interface Doc {
|
||||
numchild: number;
|
||||
updated_at: string;
|
||||
user_role: Role;
|
||||
user_roles: Role[];
|
||||
abilities: {
|
||||
accesses_manage: boolean;
|
||||
accesses_view: boolean;
|
||||
@@ -80,7 +81,6 @@ export interface Doc {
|
||||
children_list: boolean;
|
||||
collaboration_auth: boolean;
|
||||
destroy: boolean;
|
||||
duplicate: boolean;
|
||||
favorite: boolean;
|
||||
invite_owner: boolean;
|
||||
link_configuration: boolean;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { Doc, LinkReach, LinkRole, Role } from './types';
|
||||
import { Doc, Role } from './types';
|
||||
|
||||
export const currentDocRole = (abilities: Doc['abilities']): Role => {
|
||||
return abilities.destroy
|
||||
@@ -23,10 +23,28 @@ export const base64ToBlocknoteXmlFragment = (base64: string) => {
|
||||
return base64ToYDoc(base64).getXmlFragment('document-store');
|
||||
};
|
||||
|
||||
export const getDocLinkReach = (doc: Doc): LinkReach => {
|
||||
return doc.computed_link_reach ?? doc.link_reach;
|
||||
export const getDocLinkReach = (doc: Doc) => {
|
||||
if (doc.computed_link_reach) {
|
||||
return doc.computed_link_reach;
|
||||
}
|
||||
return doc.link_reach;
|
||||
};
|
||||
|
||||
export const getDocLinkRole = (doc: Doc): LinkRole => {
|
||||
return doc.computed_link_role ?? doc.link_role;
|
||||
export const getDocLinkRole = (doc: Doc) => {
|
||||
if (doc.computed_link_role) {
|
||||
return doc.computed_link_role;
|
||||
}
|
||||
return doc.link_role;
|
||||
};
|
||||
|
||||
export const docLinkIsDesync = (doc: Doc) => {
|
||||
// If the document has no ancestors
|
||||
if (!doc.ancestors_link_reach) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
doc.computed_link_reach !== doc.ancestors_link_reach ||
|
||||
doc.computed_link_role !== doc.ancestors_link_role
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
|
||||
import { Access } from '@/docs/doc-management';
|
||||
|
||||
export type DocAccessesParams = {
|
||||
@@ -8,10 +9,12 @@ export type DocAccessesParams = {
|
||||
ordering?: string;
|
||||
};
|
||||
|
||||
export type DocAccessesAPIParams = DocAccessesParams & {};
|
||||
|
||||
export const getDocAccesses = async ({
|
||||
docId,
|
||||
ordering,
|
||||
}: DocAccessesParams): Promise<Access[]> => {
|
||||
}: DocAccessesAPIParams): Promise<Access[]> => {
|
||||
let url = `documents/${docId}/accesses/`;
|
||||
|
||||
if (ordering) {
|
||||
@@ -33,7 +36,7 @@ export const getDocAccesses = async ({
|
||||
export const KEY_LIST_DOC_ACCESSES = 'docs-accesses';
|
||||
|
||||
export function useDocAccesses(
|
||||
params: DocAccessesParams,
|
||||
params: DocAccessesAPIParams,
|
||||
queryConfig?: UseQueryOptions<Access[], APIError, Access[]>,
|
||||
) {
|
||||
return useQuery<Access[], APIError, Access[]>({
|
||||
|
||||
@@ -15,7 +15,7 @@ export type DocInvitationsParams = {
|
||||
};
|
||||
|
||||
export type DocInvitationsAPIParams = DocInvitationsParams & {
|
||||
page: number;
|
||||
page?: number;
|
||||
};
|
||||
|
||||
type DocInvitationsResponse = APIList<Invitation>;
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="desynchro">
|
||||
<path
|
||||
id="ô€™ "
|
||||
d="M5.99986 12.2256C5.99986 11.7946 6.25238 11.5791 6.75741 11.5791H10.4145C10.802 11.5791 11.2156 11.4811 11.6553 11.2852C12.0994 11.0893 12.5413 10.8302 12.981 10.508C13.4208 10.1902 13.8235 9.84192 14.1892 9.46314C14.5593 9.08437 14.864 8.70778 15.1035 8.33336L15.4496 7.80438C15.5802 7.59105 15.7718 7.48438 16.0243 7.48438C16.1897 7.48438 16.3334 7.5388 16.4553 7.64765C16.5816 7.76084 16.6447 7.91758 16.6447 8.11785C16.6447 8.30506 16.5837 8.48791 16.4618 8.66641L16.2398 9.01254C15.996 9.37389 15.7086 9.7309 15.3778 10.0835C15.0469 10.4362 14.6964 10.7606 14.3263 11.0566C13.9606 11.357 13.6058 11.6073 13.2619 11.8076C12.9179 12.0122 12.6153 12.1472 12.3541 12.2125V12.2386C12.6153 12.2996 12.9179 12.4324 13.2619 12.637C13.6058 12.8416 13.9606 13.0941 14.3263 13.3945C14.6921 13.695 15.0404 14.0193 15.3712 14.3676C15.7065 14.7203 15.996 15.0794 16.2398 15.4451L16.4618 15.7847C16.5837 15.9632 16.6447 16.1461 16.6447 16.3333C16.6447 16.5292 16.5837 16.6838 16.4618 16.797C16.3443 16.9102 16.1963 16.9668 16.0178 16.9668C15.7696 16.9668 15.5802 16.8601 15.4496 16.6468L15.1035 16.1243C14.864 15.7499 14.5593 15.3711 14.1892 14.988C13.8235 14.6092 13.4208 14.2588 12.981 13.9366C12.5413 13.6188 12.0994 13.3619 11.6553 13.166C11.2156 12.9701 10.802 12.8721 10.4145 12.8721H6.75741C6.25238 12.8721 5.99986 12.6566 5.99986 12.2256ZM14.3068 7.64112C14.1065 7.48438 14.0303 7.32329 14.0782 7.15785C14.1261 6.99677 14.2697 6.88139 14.5092 6.81173L17.0822 6.03459C17.2782 5.97364 17.4392 5.99323 17.5655 6.09337C17.6918 6.1935 17.7505 6.34371 17.7418 6.54398L17.6308 9.23457C17.6221 9.48274 17.5437 9.64818 17.3957 9.7309C17.252 9.81362 17.0822 9.78097 16.8863 9.63294L14.3068 7.64112ZM14.3198 16.7251L16.9516 14.8117C17.1519 14.6637 17.3239 14.6354 17.4675 14.7268C17.6112 14.8182 17.6831 14.9858 17.6831 15.2296L17.7157 17.9268C17.7157 18.127 17.6504 18.2729 17.5198 18.3643C17.3935 18.4601 17.2324 18.4753 17.0365 18.41L14.4896 17.5545C14.2545 17.4805 14.1152 17.3608 14.0717 17.1953C14.0281 17.0299 14.1108 16.8732 14.3198 16.7251Z"
|
||||
fill="#000091"
|
||||
/>
|
||||
</g>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="desynchro">
|
||||
<path id="ô€™ " d="M5.99986 12.2256C5.99986 11.7946 6.25238 11.5791 6.75741 11.5791H10.4145C10.802 11.5791 11.2156 11.4811 11.6553 11.2852C12.0994 11.0893 12.5413 10.8302 12.981 10.508C13.4208 10.1902 13.8235 9.84192 14.1892 9.46314C14.5593 9.08437 14.864 8.70778 15.1035 8.33336L15.4496 7.80438C15.5802 7.59105 15.7718 7.48438 16.0243 7.48438C16.1897 7.48438 16.3334 7.5388 16.4553 7.64765C16.5816 7.76084 16.6447 7.91758 16.6447 8.11785C16.6447 8.30506 16.5837 8.48791 16.4618 8.66641L16.2398 9.01254C15.996 9.37389 15.7086 9.7309 15.3778 10.0835C15.0469 10.4362 14.6964 10.7606 14.3263 11.0566C13.9606 11.357 13.6058 11.6073 13.2619 11.8076C12.9179 12.0122 12.6153 12.1472 12.3541 12.2125V12.2386C12.6153 12.2996 12.9179 12.4324 13.2619 12.637C13.6058 12.8416 13.9606 13.0941 14.3263 13.3945C14.6921 13.695 15.0404 14.0193 15.3712 14.3676C15.7065 14.7203 15.996 15.0794 16.2398 15.4451L16.4618 15.7847C16.5837 15.9632 16.6447 16.1461 16.6447 16.3333C16.6447 16.5292 16.5837 16.6838 16.4618 16.797C16.3443 16.9102 16.1963 16.9668 16.0178 16.9668C15.7696 16.9668 15.5802 16.8601 15.4496 16.6468L15.1035 16.1243C14.864 15.7499 14.5593 15.3711 14.1892 14.988C13.8235 14.6092 13.4208 14.2588 12.981 13.9366C12.5413 13.6188 12.0994 13.3619 11.6553 13.166C11.2156 12.9701 10.802 12.8721 10.4145 12.8721H6.75741C6.25238 12.8721 5.99986 12.6566 5.99986 12.2256ZM14.3068 7.64112C14.1065 7.48438 14.0303 7.32329 14.0782 7.15785C14.1261 6.99677 14.2697 6.88139 14.5092 6.81173L17.0822 6.03459C17.2782 5.97364 17.4392 5.99323 17.5655 6.09337C17.6918 6.1935 17.7505 6.34371 17.7418 6.54398L17.6308 9.23457C17.6221 9.48274 17.5437 9.64818 17.3957 9.7309C17.252 9.81362 17.0822 9.78097 16.8863 9.63294L14.3068 7.64112ZM14.3198 16.7251L16.9516 14.8117C17.1519 14.6637 17.3239 14.6354 17.4675 14.7268C17.6112 14.8182 17.6831 14.9858 17.6831 15.2296L17.7157 17.9268C17.7157 18.127 17.6504 18.2729 17.5198 18.3643C17.3935 18.4601 17.2324 18.4753 17.0365 18.41L14.4896 17.5545C14.2545 17.4805 14.1152 17.3608 14.0717 17.1953C14.0281 17.0299 14.1108 16.8732 14.3198 16.7251Z" fill="#000091"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1,15 +1,5 @@
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="undo">
|
||||
<path
|
||||
id="v"
|
||||
d="M4.6665 12.667V11.3337H9.39984C10.0998 11.3337 10.7082 11.1114 11.2248 10.667C11.7415 10.2225 11.9998 9.66699 11.9998 9.00033C11.9998 8.33366 11.7415 7.7781 11.2248 7.33366C10.7082 6.88921 10.0998 6.66699 9.39984 6.66699H5.19984L6.93317 8.40033L5.99984 9.33366L2.6665 6.00033L5.99984 2.66699L6.93317 3.60033L5.19984 5.33366H9.39984C10.4776 5.33366 11.4026 5.68366 12.1748 6.38366C12.9471 7.08366 13.3332 7.95588 13.3332 9.00033C13.3332 10.0448 12.9471 10.917 12.1748 11.617C11.4026 12.317 10.4776 12.667 9.39984 12.667H4.6665Z"
|
||||
fill="#000091"
|
||||
/>
|
||||
</g>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="undo">
|
||||
<path id="v" d="M4.6665 12.667V11.3337H9.39984C10.0998 11.3337 10.7082 11.1114 11.2248 10.667C11.7415 10.2225 11.9998 9.66699 11.9998 9.00033C11.9998 8.33366 11.7415 7.7781 11.2248 7.33366C10.7082 6.88921 10.0998 6.66699 9.39984 6.66699H5.19984L6.93317 8.40033L5.99984 9.33366L2.6665 6.00033L5.99984 2.66699L6.93317 3.60033L5.19984 5.33366H9.39984C10.4776 5.33366 11.4026 5.68366 12.1748 6.38366C12.9471 7.08366 13.3332 7.95588 13.3332 9.00033C13.3332 10.0448 12.9471 10.917 12.1748 11.617C11.4026 12.317 10.4776 12.667 9.39984 12.667H4.6665Z" fill="#000091"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 725 B After Width: | Height: | Size: 683 B |
+36
-31
@@ -1,24 +1,24 @@
|
||||
import { Button } from '@openfun/cunningham-react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Fragment } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, HorizontalSeparator, Icon, StyledLink, Text } from '@/components';
|
||||
import { Box, HorizontalSeparator, Icon, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
import { Access, useDocStore } from '../../doc-management';
|
||||
|
||||
import { DocShareMemberItem } from './DocShareMember';
|
||||
import { DocShareMemberItem } from './DocShareMemberItem';
|
||||
|
||||
type DocInheritedShareContentProps = {
|
||||
type Props = {
|
||||
rawAccesses: Access[];
|
||||
};
|
||||
|
||||
export const DocInheritedShareContent = ({
|
||||
rawAccesses,
|
||||
}: DocInheritedShareContentProps) => {
|
||||
export const DocInheritedShareContent = ({ rawAccesses }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const { currentDoc } = useDocStore();
|
||||
const router = useRouter();
|
||||
|
||||
// Check if accesses map is empty
|
||||
const hasAccesses = rawAccesses.length > 0;
|
||||
@@ -28,25 +28,26 @@ export const DocInheritedShareContent = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
$gap={spacingsTokens.sm}
|
||||
$padding={{ top: spacingsTokens.sm }}
|
||||
className="--docs--doc-inherited-share-content"
|
||||
>
|
||||
<HorizontalSeparator $withPadding={false} />
|
||||
<Box
|
||||
$gap={spacingsTokens.sm}
|
||||
$padding={{
|
||||
horizontal: spacingsTokens.base,
|
||||
}}
|
||||
>
|
||||
<Box $direction="row" $align="center" $gap={spacingsTokens['4xs']}>
|
||||
<Text $variation="1000" $weight="bold" $size="sm">
|
||||
{t('People with access via the parent document')}
|
||||
</Text>
|
||||
<Box>
|
||||
<StyledLink href={`/docs/${rawAccesses[0].document.id}`}>
|
||||
<>
|
||||
<Box $gap={spacingsTokens.sm} $padding={{ top: spacingsTokens.sm }}>
|
||||
<HorizontalSeparator $withPadding={false} />
|
||||
<Box
|
||||
$gap={spacingsTokens.sm}
|
||||
$padding={{
|
||||
horizontal: spacingsTokens.base,
|
||||
// vertical: spacingsTokens.sm,
|
||||
// bottom: '0px',
|
||||
}}
|
||||
>
|
||||
<Box $direction="row" $align="center" $gap={spacingsTokens['4xs']}>
|
||||
<Text $variation="1000" $weight="bold" $size="sm">
|
||||
{t('People with access via the parent document')}
|
||||
</Text>
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
void router.push(`/docs/${rawAccesses[0].document.id}`);
|
||||
}}
|
||||
size="small"
|
||||
icon={
|
||||
<Icon
|
||||
@@ -57,15 +58,19 @@ export const DocInheritedShareContent = ({
|
||||
}
|
||||
color="tertiary-text"
|
||||
/>
|
||||
</StyledLink>
|
||||
</div>
|
||||
</Box>
|
||||
{rawAccesses.map((access) => (
|
||||
<Fragment key={access.id}>
|
||||
<DocShareMemberItem
|
||||
doc={currentDoc}
|
||||
access={access}
|
||||
isInherited
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</Box>
|
||||
{rawAccesses.map((access) => (
|
||||
<Fragment key={access.id}>
|
||||
<DocShareMemberItem doc={currentDoc} access={access} isInherited />
|
||||
</Fragment>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-3
@@ -24,7 +24,6 @@ type DocRoleDropdownProps = {
|
||||
message?: string;
|
||||
onSelectRole: (role: Role) => void;
|
||||
rolesAllowed?: Role[];
|
||||
isLastOwner?: boolean;
|
||||
};
|
||||
|
||||
export const DocRoleDropdown = ({
|
||||
@@ -35,7 +34,6 @@ export const DocRoleDropdown = ({
|
||||
doc,
|
||||
rolesAllowed,
|
||||
access,
|
||||
isLastOwner = false,
|
||||
}: DocRoleDropdownProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { transRole, translatedRoles } = useTrans();
|
||||
@@ -48,6 +46,7 @@ export const DocRoleDropdown = ({
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('doc HERE', doc);
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_SUB_PAGE, { id: doc.id }],
|
||||
});
|
||||
@@ -120,7 +119,6 @@ export const DocRoleDropdown = ({
|
||||
callback: () => onSelectRole?.(key as Role),
|
||||
isSelected: currentRole === (key as Role),
|
||||
showSeparator: isLast,
|
||||
disabled: isLastOwner && key !== 'owner',
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
+6
-31
@@ -8,14 +8,7 @@ import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
import {
|
||||
Box,
|
||||
BoxButton,
|
||||
Icon,
|
||||
LoadMoreText,
|
||||
Loading,
|
||||
Text,
|
||||
} from '@/components';
|
||||
import { Box, BoxButton, Icon, LoadMoreText } from '@/components';
|
||||
import { QuickSearchData, QuickSearchGroup } from '@/components/quick-search';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { AccessRequest, Doc } from '@/docs/doc-management/';
|
||||
@@ -168,11 +161,7 @@ export const ButtonAccessRequest = ({
|
||||
...buttonProps
|
||||
}: ButtonAccessRequestProps) => {
|
||||
const { authenticated } = useAuth();
|
||||
const {
|
||||
data: requests,
|
||||
error: docAccessError,
|
||||
isLoading,
|
||||
} = useDocAccessRequests({
|
||||
const { data: requests } = useDocAccessRequests({
|
||||
docId,
|
||||
page: 1,
|
||||
});
|
||||
@@ -186,28 +175,14 @@ export const ButtonAccessRequest = ({
|
||||
},
|
||||
});
|
||||
|
||||
if (!authenticated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (docAccessError?.status === 404) {
|
||||
return (
|
||||
<Text $maxWidth="320px" $textAlign="center" $variation="600" $size="sm">
|
||||
{t(
|
||||
'As this is a sub-document, please request access to the parent document to enable these features.',
|
||||
)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading $height="auto" />;
|
||||
}
|
||||
|
||||
const hasRequested = !!(
|
||||
requests && requests?.results.find((request) => request.document === docId)
|
||||
);
|
||||
|
||||
if (!authenticated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={() => createRequest({ docId })}
|
||||
|
||||
+1
-1
@@ -11,8 +11,8 @@ import { css } from 'styled-components';
|
||||
import { APIError } from '@/api';
|
||||
import { Box } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc, KEY_SUB_PAGE, Role } from '@/docs/doc-management';
|
||||
import { User } from '@/features/auth';
|
||||
import { Doc, KEY_SUB_PAGE, Role } from '@/features/docs';
|
||||
|
||||
import { useCreateDocAccess, useCreateDocInvitation } from '../api';
|
||||
import { OptionType } from '../types';
|
||||
|
||||
+4
-47
@@ -1,15 +1,12 @@
|
||||
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { QuickSearchData } from '@/components/quick-search';
|
||||
import { QuickSearchGroup } from '@/components/quick-search/QuickSearchGroup';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Access, Doc, KEY_SUB_PAGE, Role } from '@/docs/doc-management/';
|
||||
|
||||
import { useDocAccesses, useUpdateDocAccess } from '../api';
|
||||
import { useUpdateDocAccess } from '../api';
|
||||
import { useWhoAmI } from '../hooks/';
|
||||
|
||||
import { DocRoleDropdown } from './DocRoleDropdown';
|
||||
@@ -65,7 +62,9 @@ export const DocShareMemberItem = ({
|
||||
});
|
||||
};
|
||||
|
||||
const canUpdate = isInherited ? false : !!doc?.abilities.accesses_manage;
|
||||
const canUpdate = isInherited
|
||||
? false
|
||||
: (doc?.abilities.accesses_manage ?? false);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -81,7 +80,6 @@ export const DocShareMemberItem = ({
|
||||
<DocRoleDropdown
|
||||
currentRole={isInherited ? access.max_role : access.role}
|
||||
onSelectRole={onUpdate}
|
||||
isLastOwner={isLastOwner}
|
||||
canUpdate={canUpdate}
|
||||
message={message}
|
||||
rolesAllowed={access.abilities.set_role_to}
|
||||
@@ -94,44 +92,3 @@ export const DocShareMemberItem = ({
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
interface QuickSearchGroupMemberProps {
|
||||
doc: Doc;
|
||||
}
|
||||
|
||||
export const QuickSearchGroupMember = ({
|
||||
doc,
|
||||
}: QuickSearchGroupMemberProps) => {
|
||||
const { t } = useTranslation();
|
||||
const membersQuery = useDocAccesses({
|
||||
docId: doc.id,
|
||||
});
|
||||
|
||||
const membersData: QuickSearchData<Access> = useMemo(() => {
|
||||
const members = membersQuery.data || [];
|
||||
|
||||
const count = members.length;
|
||||
|
||||
return {
|
||||
groupName:
|
||||
count === 1
|
||||
? t('Document owner')
|
||||
: t('Share with {{count}} users', {
|
||||
count: count,
|
||||
}),
|
||||
elements: members,
|
||||
endActions: undefined,
|
||||
};
|
||||
}, [membersQuery, t]);
|
||||
|
||||
return (
|
||||
<Box aria-label={t('List members card')} $padding={{ bottom: '3xs' }}>
|
||||
<QuickSearchGroup
|
||||
group={membersData}
|
||||
renderElement={(access) => (
|
||||
<DocShareMemberItem doc={doc} access={access} />
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
+126
-26
@@ -10,24 +10,27 @@ import {
|
||||
QuickSearchData,
|
||||
QuickSearchGroup,
|
||||
} from '@/components/quick-search/';
|
||||
import { Doc } from '@/docs/doc-management';
|
||||
import { User } from '@/features/auth';
|
||||
import { Access, Doc } from '@/features/docs';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
import { isValidEmail } from '@/utils';
|
||||
|
||||
import { KEY_LIST_USER, useDocAccesses, useUsers } from '../api';
|
||||
import {
|
||||
KEY_LIST_USER,
|
||||
useDocAccesses,
|
||||
useDocInvitationsInfinite,
|
||||
useUsers,
|
||||
} from '../api';
|
||||
import { Invitation } from '../types';
|
||||
|
||||
import { DocInheritedShareContent } from './DocInheritedShareContent';
|
||||
import {
|
||||
ButtonAccessRequest,
|
||||
QuickSearchGroupAccessRequest,
|
||||
} from './DocShareAccessRequest';
|
||||
import { ButtonAccessRequest } from './DocShareAccessRequest';
|
||||
import { DocShareAddMemberList } from './DocShareAddMemberList';
|
||||
import {
|
||||
DocShareInvitationItem,
|
||||
DocShareModalInviteUserRow,
|
||||
QuickSearchGroupInvitation,
|
||||
} from './DocShareInvitation';
|
||||
import { QuickSearchGroupMember } from './DocShareMember';
|
||||
import { DocShareMemberItem } from './DocShareMember';
|
||||
import { DocShareModalFooter } from './DocShareModalFooter';
|
||||
|
||||
const ShareModalStyle = createGlobalStyle`
|
||||
@@ -83,6 +86,23 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
||||
},
|
||||
);
|
||||
|
||||
const membersData: QuickSearchData<Access> = useMemo(() => {
|
||||
const members: Access[] =
|
||||
membersQuery?.filter((access) => access.document.id === doc.id) ?? [];
|
||||
|
||||
const count = doc.nb_accesses_direct > 1 ? doc.nb_accesses_direct : 1;
|
||||
|
||||
return {
|
||||
groupName:
|
||||
count === 1
|
||||
? t('Document owner')
|
||||
: t('Share with {{count}} users', {
|
||||
count: count,
|
||||
}),
|
||||
elements: members,
|
||||
};
|
||||
}, [membersQuery, doc.id, doc.nb_accesses_direct, t]);
|
||||
|
||||
const onFilter = useDebouncedCallback((str: string) => {
|
||||
setUserQuery(str);
|
||||
}, 300);
|
||||
@@ -179,7 +199,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
||||
$size="sm"
|
||||
>
|
||||
{t(
|
||||
'You can view this document but need additional access to see its members or modify settings.',
|
||||
'You do not have permission to view users sharing this document or modify link settings.',
|
||||
)}
|
||||
</Text>
|
||||
<ButtonAccessRequest
|
||||
@@ -200,24 +220,23 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
||||
loading={searchUsersQuery.isLoading}
|
||||
placeholder={t('Type a name or email')}
|
||||
>
|
||||
{showInheritedShareContent && (
|
||||
<DocInheritedShareContent
|
||||
rawAccesses={
|
||||
membersQuery?.filter(
|
||||
(access) => access.document.id !== doc.id,
|
||||
) ?? []
|
||||
}
|
||||
{inheritedAccesses.length > 0 &&
|
||||
showInheritedShareContent && (
|
||||
<DocInheritedShareContent
|
||||
rawAccesses={
|
||||
membersQuery?.filter(
|
||||
(access) => access.document.id !== doc.id,
|
||||
) ?? []
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{showMemberSection ? (
|
||||
<QuickSearchMemberSection
|
||||
doc={doc}
|
||||
hasInheritedShareContent={inheritedAccesses.length > 0}
|
||||
membersData={membersData}
|
||||
/>
|
||||
)}
|
||||
{showMemberSection && isRootDoc && (
|
||||
<Box $padding={{ horizontal: 'base' }}>
|
||||
<QuickSearchGroupAccessRequest doc={doc} />
|
||||
<QuickSearchGroupInvitation doc={doc} />
|
||||
<QuickSearchGroupMember doc={doc} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{!showMemberSection && canShare && (
|
||||
) : (
|
||||
<QuickSearchInviteInputSection
|
||||
searchUsersRawData={searchUsersQuery.data}
|
||||
onSelect={onSelect}
|
||||
@@ -298,3 +317,84 @@ const QuickSearchInviteInputSection = ({
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
interface QuickSearchMemberSectionProps {
|
||||
doc: Doc;
|
||||
membersData: QuickSearchData<Access>;
|
||||
hasInheritedShareContent?: boolean;
|
||||
}
|
||||
|
||||
const QuickSearchMemberSection = ({
|
||||
doc,
|
||||
membersData,
|
||||
hasInheritedShareContent = false,
|
||||
}: QuickSearchMemberSectionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { data, hasNextPage, fetchNextPage } = useDocInvitationsInfinite({
|
||||
docId: doc.id,
|
||||
});
|
||||
|
||||
console.log('data', data);
|
||||
|
||||
const invitationsData: QuickSearchData<Invitation> = useMemo(() => {
|
||||
const invitations = data?.pages.flatMap((page) => page.results) || [];
|
||||
console.log('invitations', invitations);
|
||||
|
||||
return {
|
||||
groupName: t('Pending invitations'),
|
||||
elements: invitations,
|
||||
endActions: hasNextPage
|
||||
? [
|
||||
{
|
||||
content: <Text data-testid="load-more-invitations" />,
|
||||
onSelect: () => void fetchNextPage(),
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
};
|
||||
}, [data?.pages, fetchNextPage, hasNextPage, t]);
|
||||
|
||||
const showSeparator =
|
||||
invitationsData.elements.length > 0 && membersData.elements.length > 0;
|
||||
|
||||
if (
|
||||
invitationsData.elements.length === 0 &&
|
||||
membersData.elements.length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasInheritedShareContent && <HorizontalSeparator $withPadding={false} />}
|
||||
{invitationsData.elements.length > 0 && (
|
||||
<Box
|
||||
aria-label={t('List invitation card')}
|
||||
$padding={{ horizontal: 'base' }}
|
||||
$margin={{ bottom: showSeparator ? 'md' : undefined }}
|
||||
>
|
||||
<QuickSearchGroup
|
||||
group={invitationsData}
|
||||
renderElement={(invitation) => (
|
||||
<DocShareInvitationItem doc={doc} invitation={invitation} />
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{showSeparator && <HorizontalSeparator $withPadding={false} />}
|
||||
|
||||
<Box
|
||||
aria-label={t('List members card')}
|
||||
$padding={{ horizontal: 'base', bottom: '3xs' }}
|
||||
>
|
||||
<QuickSearchGroup
|
||||
group={membersData}
|
||||
renderElement={(access) => (
|
||||
<DocShareMemberItem doc={doc} access={access} />
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, HorizontalSeparator } from '@/components';
|
||||
import { Doc, useCopyDocLink } from '@/docs/doc-management';
|
||||
import { Doc, useCopyDocLink } from '@/features/docs';
|
||||
|
||||
import { DocVisibility } from './DocVisibility';
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ import {
|
||||
KEY_LIST_DOC,
|
||||
LinkReach,
|
||||
LinkRole,
|
||||
docLinkIsDesync,
|
||||
getDocLinkReach,
|
||||
useUpdateDocLink,
|
||||
} from '@/docs/doc-management';
|
||||
import { useTreeUtils } from '@/docs/doc-tree';
|
||||
} from '@/features/docs';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useTranslatedShareSettings } from '../hooks/';
|
||||
@@ -47,7 +47,6 @@ export const DocVisibility = ({ doc, canEdit = true }: DocVisibilityProps) => {
|
||||
const [docLinkRole, setDocLinkRole] = useState<LinkRole>(
|
||||
doc.computed_link_role ?? LinkRole.READER,
|
||||
);
|
||||
const { isDesyncronized } = useTreeUtils(doc);
|
||||
|
||||
const { linkModeTranslations, linkReachChoices, linkReachTranslations } =
|
||||
useTranslatedShareSettings();
|
||||
@@ -155,6 +154,10 @@ export const DocVisibility = ({ doc, canEdit = true }: DocVisibilityProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const showDesync = useMemo(() => {
|
||||
return docLinkIsDesync(doc);
|
||||
}, [doc]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
$padding={{ horizontal: 'base' }}
|
||||
@@ -165,7 +168,7 @@ export const DocVisibility = ({ doc, canEdit = true }: DocVisibilityProps) => {
|
||||
<Text $weight="700" $size="sm" $variation="700">
|
||||
{t('Link parameters')}
|
||||
</Text>
|
||||
{isDesyncronized && (
|
||||
{showDesync && (
|
||||
<Box
|
||||
$background={colorsTokens['primary-100']}
|
||||
$padding="3xs"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Role } from '@/docs/doc-management';
|
||||
import { User } from '@/features/auth';
|
||||
import { Role } from '@/features/docs';
|
||||
|
||||
export interface Invitation {
|
||||
id: string;
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
export * from './useCreateChildren';
|
||||
export * from './useDocChildren';
|
||||
export * from './useDocTree';
|
||||
export * from './useMove';
|
||||
|
||||
@@ -25,7 +25,7 @@ export const getDocTree = async ({ docId }: DocsTreeParams): Promise<Doc> => {
|
||||
return response.json() as Promise<Doc>;
|
||||
};
|
||||
|
||||
export const KEY_DOC_TREE = 'doc-tree';
|
||||
export const KEY_LIST_DOC_CHILDREN = 'doc-tree';
|
||||
|
||||
export function useDocTree(
|
||||
params: DocsTreeParams,
|
||||
@@ -35,7 +35,7 @@ export function useDocTree(
|
||||
>,
|
||||
) {
|
||||
return useQuery<Doc, APIError, Doc>({
|
||||
queryKey: [KEY_DOC_TREE, params],
|
||||
queryKey: [KEY_LIST_DOC_CHILDREN, params],
|
||||
queryFn: () => getDocTree(params),
|
||||
staleTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
|
||||
@@ -16,9 +16,8 @@ import {
|
||||
useTrans,
|
||||
} from '@/features/docs/doc-management';
|
||||
import { useLeftPanelStore } from '@/features/left-panel';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import SubPageIcon from './../assets/sub-page-logo.svg';
|
||||
import Logo from './../assets/sub-page-logo.svg';
|
||||
import { DocTreeItemActions } from './DocTreeItemActions';
|
||||
|
||||
const ItemTextCss = css`
|
||||
@@ -35,10 +34,9 @@ type Props = TreeViewNodeProps<Doc>;
|
||||
export const DocSubPageItem = (props: Props) => {
|
||||
const doc = props.node.data.value as Doc;
|
||||
const treeContext = useTreeContext<Doc>();
|
||||
const { untitledDocument } = useTrans();
|
||||
const { untitledDocument } = useTrans(doc);
|
||||
const { node } = props;
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const [actionsOpen, setActionsOpen] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
@@ -99,25 +97,13 @@ export const DocSubPageItem = (props: Props) => {
|
||||
return (
|
||||
<Box
|
||||
className="--docs-sub-page-item"
|
||||
$position="relative"
|
||||
$css={css`
|
||||
background-color: ${actionsOpen
|
||||
? 'var(--c--theme--colors--greyscale-100)'
|
||||
: 'var(--c--theme--colors--greyscale-000)'};
|
||||
|
||||
.light-doc-item-actions {
|
||||
display: ${actionsOpen || !isDesktop ? 'flex' : 'none'};
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background: ${isDesktop
|
||||
? 'var(--c--theme--colors--greyscale-100)'
|
||||
: 'var(--c--theme--colors--greyscale-000)'};
|
||||
}
|
||||
|
||||
.c__tree-view--node.isSelected {
|
||||
.light-doc-item-actions {
|
||||
background: var(--c--theme--colors--greyscale-100);
|
||||
}
|
||||
display: ${actionsOpen ? 'flex' : 'none'};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -126,7 +112,6 @@ export const DocSubPageItem = (props: Props) => {
|
||||
|
||||
.light-doc-item-actions {
|
||||
display: flex;
|
||||
background: var(--c--theme--colors--greyscale-100);
|
||||
}
|
||||
}
|
||||
`}
|
||||
@@ -149,7 +134,7 @@ export const DocSubPageItem = (props: Props) => {
|
||||
$minHeight="24px"
|
||||
>
|
||||
<Box $width="16px" $height="16px">
|
||||
<SubPageIcon />
|
||||
<Logo />
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
|
||||
@@ -10,11 +10,12 @@ import { css } from 'styled-components';
|
||||
|
||||
import { Box, StyledLink } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc, KEY_SUB_PAGE, useDoc, useDocStore } from '@/docs/doc-management';
|
||||
import { SimpleDocItem } from '@/docs/docs-grid';
|
||||
|
||||
import { Doc, KEY_SUB_PAGE, useDoc, useDocStore } from '../../doc-management';
|
||||
import { SimpleDocItem } from '../../docs-grid';
|
||||
import { useDocTree } from '../api/useDocTree';
|
||||
import { useMoveDoc } from '../api/useMove';
|
||||
import { canDrag, canDrop, serializeDocToSubPage } from '../utils';
|
||||
|
||||
import { DocSubPageItem } from './DocSubPageItem';
|
||||
import { DocTreeItemActions } from './DocTreeItemActions';
|
||||
@@ -24,6 +25,7 @@ type DocTreeProps = {
|
||||
};
|
||||
export const DocTree = ({ initialTargetId }: DocTreeProps) => {
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
|
||||
const [rootActionsOpen, setRootActionsOpen] = useState(false);
|
||||
const treeContext = useTreeContext<Doc>();
|
||||
const { currentDoc } = useDocStore();
|
||||
@@ -108,18 +110,22 @@ export const DocTree = ({ initialTargetId }: DocTreeProps) => {
|
||||
treeContext?.setRoot(currentDoc);
|
||||
treeContext?.setInitialTargetId(currentDoc.id);
|
||||
} else if (item) {
|
||||
const { children: _children, ...leftDoc } = currentDoc;
|
||||
treeContext?.treeData.updateNode(currentDoc.id, {
|
||||
...leftDoc,
|
||||
childrenCount: leftDoc.numchild,
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { children, ...rest } = currentDoc;
|
||||
treeContext?.treeData.updateNode(
|
||||
currentDoc.id,
|
||||
serializeDocToSubPage(rest),
|
||||
);
|
||||
}
|
||||
if (currentDoc?.id && currentDoc?.id !== previousDocId.current) {
|
||||
previousDocId.current = currentDoc?.id;
|
||||
}
|
||||
|
||||
treeContext?.treeData.setSelectedNode(currentDoc);
|
||||
}, [currentDoc, rootNode?.id, treeContext]);
|
||||
|
||||
// we don't need to run this effect on every change of treeContext.data because it cause an infinite loop
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentDoc, rootNode?.id]);
|
||||
|
||||
const rootIsSelected =
|
||||
treeContext?.treeData.selectedNode?.id === treeContext?.root?.id;
|
||||
@@ -136,10 +142,6 @@ export const DocTree = ({ initialTargetId }: DocTreeProps) => {
|
||||
.c__tree-view--container {
|
||||
z-index: 1;
|
||||
margin-top: -10px;
|
||||
|
||||
.c__tree-view {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
}
|
||||
`}
|
||||
>
|
||||
@@ -232,13 +234,13 @@ export const DocTree = ({ initialTargetId }: DocTreeProps) => {
|
||||
}
|
||||
const parentDoc = parentNode?.data.value as Doc;
|
||||
if (!parentDoc) {
|
||||
return rootNode?.abilities.move;
|
||||
return canDrop(rootNode);
|
||||
}
|
||||
return parentDoc?.abilities.move;
|
||||
return canDrop(parentDoc);
|
||||
}}
|
||||
canDrag={(node) => {
|
||||
const doc = node.value as Doc;
|
||||
return doc.abilities.move;
|
||||
return canDrag(doc);
|
||||
}}
|
||||
rootNodeId={treeContext.root?.id ?? ''}
|
||||
renderNode={DocSubPageItem}
|
||||
|
||||
+6
-9
@@ -11,16 +11,12 @@ import { css } from 'styled-components';
|
||||
|
||||
import { Box, BoxButton, Icon } from '@/components';
|
||||
|
||||
import {
|
||||
Doc,
|
||||
ModalRemoveDoc,
|
||||
Role,
|
||||
useCopyDocLink,
|
||||
} from '../../doc-management';
|
||||
import { Doc, ModalRemoveDoc, useCopyDocLink } from '../../doc-management';
|
||||
import { useCreateChildrenDoc } from '../api/useCreateChildren';
|
||||
import { useDetachDoc } from '../api/useDetach';
|
||||
import MoveDocIcon from '../assets/doc-extract-bold.svg';
|
||||
import { useTreeUtils } from '../hooks';
|
||||
import { isOwnerOrAdmin } from '../utils';
|
||||
|
||||
type DocTreeItemActionsProps = {
|
||||
doc: Doc;
|
||||
@@ -42,6 +38,7 @@ export const DocTreeItemActions = ({
|
||||
const deleteModal = useModal();
|
||||
|
||||
const copyLink = useCopyDocLink(doc.id);
|
||||
const canUpdate = isOwnerOrAdmin(doc);
|
||||
const { isCurrentParent } = useTreeUtils(doc);
|
||||
const { mutate: detachDoc } = useDetachDoc();
|
||||
const treeContext = useTreeContext<Doc>();
|
||||
@@ -75,7 +72,7 @@ export const DocTreeItemActions = ({
|
||||
? [
|
||||
{
|
||||
label: t('Move to my docs'),
|
||||
isDisabled: doc.user_role !== Role.OWNER,
|
||||
isDisabled: !canUpdate,
|
||||
icon: (
|
||||
<Box
|
||||
$css={css`
|
||||
@@ -91,7 +88,7 @@ export const DocTreeItemActions = ({
|
||||
: []),
|
||||
{
|
||||
label: t('Delete'),
|
||||
isDisabled: !doc.abilities.destroy,
|
||||
isDisabled: !canUpdate,
|
||||
icon: <Icon iconName="delete" $size="24px" />,
|
||||
callback: deleteModal.open,
|
||||
},
|
||||
@@ -141,7 +138,7 @@ export const DocTreeItemActions = ({
|
||||
$variation="600"
|
||||
/>
|
||||
</DropdownMenu>
|
||||
{doc.abilities.children_create && (
|
||||
{canUpdate && (
|
||||
<BoxButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -9,11 +9,5 @@ export const useTreeUtils = (doc: Doc) => {
|
||||
isParent: doc.nb_accesses_ancestors <= 1, // it is a parent
|
||||
isChild: doc.nb_accesses_ancestors > 1, // it is a child
|
||||
isCurrentParent: treeContext?.root?.id === doc.id || doc.depth === 1, // it can be a child but not for the current user
|
||||
isDesyncronized: !!(
|
||||
doc.ancestors_link_reach &&
|
||||
doc.ancestors_link_role &&
|
||||
(doc.computed_link_reach !== doc.ancestors_link_reach ||
|
||||
doc.computed_link_role !== doc.ancestors_link_role)
|
||||
),
|
||||
} as const;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { TreeViewDataType } from '@gouvfr-lasuite/ui-kit';
|
||||
|
||||
import { Doc } from '../doc-management';
|
||||
import { Doc, Role } from '../doc-management';
|
||||
|
||||
export const serializeDocToSubPage = (doc: Doc): Doc => {
|
||||
return { ...doc, childrenCount: doc.numchild };
|
||||
};
|
||||
|
||||
export const subPageToTree = (children: Doc[]): TreeViewDataType<Doc>[] => {
|
||||
children.forEach((child) => {
|
||||
@@ -9,3 +13,16 @@ export const subPageToTree = (children: Doc[]): TreeViewDataType<Doc>[] => {
|
||||
});
|
||||
return children;
|
||||
};
|
||||
|
||||
export const isOwnerOrAdmin = (doc: Doc): boolean => {
|
||||
const userRole = doc.user_role;
|
||||
return userRole === Role.OWNER || userRole === Role.ADMIN;
|
||||
};
|
||||
|
||||
export const canDrag = (doc: Doc): boolean => {
|
||||
return isOwnerOrAdmin(doc);
|
||||
};
|
||||
|
||||
export const canDrop = (doc: Doc): boolean => {
|
||||
return isOwnerOrAdmin(doc);
|
||||
};
|
||||
|
||||
+14
-9
@@ -6,16 +6,17 @@ import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { AlertModal, Box, Text } from '@/components';
|
||||
import { Doc, KEY_LIST_DOC } from '@/docs/doc-management';
|
||||
import { Box, Text } from '@/components';
|
||||
import { AlertModal } from '@/components/AlertModal/AlertModal';
|
||||
import { Doc, KEY_LIST_DOC, Role } from '@/docs/doc-management';
|
||||
import { useMoveDoc } from '@/docs/doc-tree/api/useMove';
|
||||
|
||||
import {
|
||||
getDocAccesses,
|
||||
getDocInvitations,
|
||||
useDeleteDocAccess,
|
||||
useDeleteDocInvitation,
|
||||
} from '@/docs/doc-share';
|
||||
import { useMoveDoc } from '@/docs/doc-tree';
|
||||
|
||||
} from '../../doc-share';
|
||||
import { useDeleteDocInvitation } from '../../doc-share/api/useDeleteDocInvitation';
|
||||
import { DocDragEndData, useDragAndDrop } from '../hooks/useDragAndDrop';
|
||||
|
||||
import { DocsGridItem } from './DocsGridItem';
|
||||
@@ -236,13 +237,17 @@ export const DraggableDocGridItem = ({
|
||||
canDrag,
|
||||
updateCanDrop,
|
||||
}: DocGridItemProps) => {
|
||||
const canDrop = doc.abilities.move;
|
||||
const userRole = doc.user_role;
|
||||
const canDropItem =
|
||||
userRole === Role.ADMIN ||
|
||||
userRole === Role.OWNER ||
|
||||
userRole === Role.EDITOR;
|
||||
|
||||
return (
|
||||
<Droppable
|
||||
enabledDrop={canDrag}
|
||||
canDrop={canDrag && canDrop}
|
||||
onOver={(isOver) => updateCanDrop(canDrop, isOver)}
|
||||
canDrop={canDrag && canDropItem}
|
||||
onOver={(isOver) => updateCanDrop(canDropItem, isOver)}
|
||||
id={doc.id}
|
||||
data={doc}
|
||||
>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user