mirror of
https://github.com/docmost/docmost.git
synced 2026-05-20 00:14:10 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bb83d12c8b | |||
| 0f29eb8842 |
+2
-3
@@ -1,6 +1,5 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.git
|
.git
|
||||||
|
.gitignore
|
||||||
dist
|
dist
|
||||||
/data
|
data
|
||||||
.env*
|
|
||||||
.nx
|
|
||||||
|
|||||||
@@ -43,16 +43,7 @@ POSTMARK_TOKEN=
|
|||||||
# for custom drawio server
|
# for custom drawio server
|
||||||
DRAWIO_URL=
|
DRAWIO_URL=
|
||||||
|
|
||||||
# Gotenberg URL for server-side PDF export
|
|
||||||
GOTENBERG_URL=
|
|
||||||
|
|
||||||
DISABLE_TELEMETRY=false
|
DISABLE_TELEMETRY=false
|
||||||
|
|
||||||
# Enable debug logging in production (default: false)
|
# Enable debug logging in production (default: false)
|
||||||
DEBUG_MODE=false
|
DEBUG_MODE=false
|
||||||
|
|
||||||
# Log database queries
|
|
||||||
DEBUG_DB=false
|
|
||||||
|
|
||||||
# Log http requests
|
|
||||||
LOG_HTTP=false
|
|
||||||
|
|||||||
@@ -1,154 +0,0 @@
|
|||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: 'Version tag (e.g. v0.25.3)'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
env:
|
|
||||||
VERSION: ${{ inputs.version || github.ref_name }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- platform: linux/amd64
|
|
||||||
runner: ubuntu-latest
|
|
||||||
suffix: amd64
|
|
||||||
- platform: linux/arm64
|
|
||||||
runner: ubuntu-24.04-arm
|
|
||||||
suffix: arm64
|
|
||||||
runs-on: ${{ matrix.runner }}
|
|
||||||
steps:
|
|
||||||
- name: Generate token
|
|
||||||
id: app-token
|
|
||||||
uses: actions/create-github-app-token@v1
|
|
||||||
with:
|
|
||||||
app-id: ${{ secrets.BUILD_APP_ID }}
|
|
||||||
private-key: ${{ secrets.BUILD_APP_PRIVATE_KEY }}
|
|
||||||
owner: ${{ github.repository_owner }}
|
|
||||||
|
|
||||||
- name: Checkout with submodules
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
token: ${{ steps.app-token.outputs.token }}
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push by digest
|
|
||||||
id: build
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: ${{ matrix.platform }}
|
|
||||||
outputs: type=image,name=docmost/docmost,push-by-digest=true,name-canonical=true,push=true
|
|
||||||
cache-from: type=gha,scope=${{ matrix.suffix }}
|
|
||||||
cache-to: type=gha,scope=${{ matrix.suffix }},mode=max
|
|
||||||
|
|
||||||
- name: Export digest
|
|
||||||
run: |
|
|
||||||
mkdir -p /tmp/digests
|
|
||||||
digest="${{ steps.build.outputs.digest }}"
|
|
||||||
touch "/tmp/digests/${digest#sha256:}"
|
|
||||||
|
|
||||||
- name: Upload digest
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: digest-${{ matrix.suffix }}
|
|
||||||
path: /tmp/digests/*
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Strip v prefix
|
|
||||||
id: strip-v
|
|
||||||
run: echo "version=${VERSION#v}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Export Docker image
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: ${{ matrix.platform }}
|
|
||||||
push: false
|
|
||||||
tags: |
|
|
||||||
docmost/docmost:latest
|
|
||||||
docmost/docmost:${{ steps.strip-v.outputs.version }}
|
|
||||||
outputs: type=docker,dest=docmost-${{ matrix.suffix }}.docker.tar
|
|
||||||
cache-from: type=gha,scope=${{ matrix.suffix }}
|
|
||||||
|
|
||||||
- name: Compress image
|
|
||||||
run: gzip docmost-${{ matrix.suffix }}.docker.tar
|
|
||||||
|
|
||||||
- name: Upload image archive
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: docker-image-${{ matrix.suffix }}
|
|
||||||
path: docmost-${{ matrix.suffix }}.docker.tar.gz
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
release:
|
|
||||||
needs: build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Download digests
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
pattern: digest-*
|
|
||||||
path: /tmp/digests
|
|
||||||
merge-multiple: true
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract metadata for tags
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: docmost/docmost
|
|
||||||
tags: |
|
|
||||||
type=semver,pattern={{version}},value=${{ env.VERSION }}
|
|
||||||
type=semver,pattern={{major}}.{{minor}},value=${{ env.VERSION }},enable=${{ !contains(env.VERSION, '-') }}
|
|
||||||
type=raw,value=latest,enable=${{ !contains(env.VERSION, '-') }}
|
|
||||||
|
|
||||||
- name: Create manifest list and push
|
|
||||||
working-directory: /tmp/digests
|
|
||||||
run: |
|
|
||||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
|
||||||
$(printf 'docmost/docmost@sha256:%s ' *)
|
|
||||||
|
|
||||||
- name: Download image archives
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
pattern: docker-image-*
|
|
||||||
path: /tmp/images
|
|
||||||
merge-multiple: true
|
|
||||||
|
|
||||||
- name: Create GitHub Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
tag_name: ${{ env.VERSION }}
|
|
||||||
files: |
|
|
||||||
/tmp/images/docmost-amd64.docker.tar.gz
|
|
||||||
/tmp/images/docmost-arm64.docker.tar.gz
|
|
||||||
draft: true
|
|
||||||
+5
-7
@@ -1,22 +1,19 @@
|
|||||||
FROM node:22-slim AS base
|
FROM node:22-alpine AS base
|
||||||
LABEL org.opencontainers.image.source="https://github.com/docmost/docmost"
|
LABEL org.opencontainers.image.source="https://github.com/docmost/docmost"
|
||||||
|
|
||||||
RUN npm install -g pnpm@10.4.0
|
|
||||||
|
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
RUN npm install -g pnpm@10.4.0
|
||||||
RUN pnpm install --frozen-lockfile
|
RUN pnpm install --frozen-lockfile
|
||||||
RUN pnpm build
|
RUN pnpm build
|
||||||
|
|
||||||
FROM base AS installer
|
FROM base AS installer
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apk add --no-cache curl bash
|
||||||
&& apt-get install -y --no-install-recommends curl bash \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -32,11 +29,12 @@ COPY --from=builder /app/packages/editor-ext/package.json /app/packages/editor-e
|
|||||||
# Copy root package files
|
# Copy root package files
|
||||||
COPY --from=builder /app/package.json /app/package.json
|
COPY --from=builder /app/package.json /app/package.json
|
||||||
COPY --from=builder /app/pnpm*.yaml /app/
|
COPY --from=builder /app/pnpm*.yaml /app/
|
||||||
COPY --from=builder /app/.npmrc /app/.npmrc
|
|
||||||
|
|
||||||
# Copy patches
|
# Copy patches
|
||||||
COPY --from=builder /app/patches /app/patches
|
COPY --from=builder /app/patches /app/patches
|
||||||
|
|
||||||
|
RUN npm install -g pnpm@10.4.0
|
||||||
|
|
||||||
RUN chown -R node:node /app
|
RUN chown -R node:node /app
|
||||||
|
|
||||||
USER node
|
USER node
|
||||||
|
|||||||
+3
-11
@@ -2,18 +2,10 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32x32.png" />
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png" />
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Docmost</title>
|
<title>Docmost</title>
|
||||||
<meta name="theme-color" content="#1f1f1f" media="(prefers-color-scheme: dark)" />
|
|
||||||
<meta name="theme-color" content="#f6f7f9" media="(prefers-color-scheme: light)" />
|
|
||||||
<link rel="manifest" href="/manifest.json" />
|
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
|
||||||
<meta name="apple-touch-fullscreen" content="yes" />
|
|
||||||
<meta name="apple-mobile-web-app-title" content="Docmost" />
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
|
||||||
<!--meta-tags-->
|
<!--meta-tags-->
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+46
-45
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.80.1",
|
"version": "0.22.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
@@ -10,75 +10,76 @@
|
|||||||
"format": "prettier --write \"src/**/*.tsx\" \"src/**/*.ts\""
|
"format": "prettier --write \"src/**/*.tsx\" \"src/**/*.ts\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@casl/react": "^5.0.1",
|
"@casl/ability": "^6.7.2",
|
||||||
|
"@casl/react": "^4.0.0",
|
||||||
"@docmost/editor-ext": "workspace:*",
|
"@docmost/editor-ext": "workspace:*",
|
||||||
"@emoji-mart/data": "^1.2.1",
|
"@emoji-mart/data": "^1.2.1",
|
||||||
"@emoji-mart/react": "^1.1.1",
|
"@emoji-mart/react": "^1.1.1",
|
||||||
"@excalidraw/excalidraw": "0.18.0-3a5ef40",
|
"@excalidraw/excalidraw": "0.18.0-864353b",
|
||||||
"@mantine/core": "^8.3.18",
|
"@mantine/core": "^8.1.3",
|
||||||
"@mantine/dates": "^8.3.18",
|
"@mantine/form": "^8.1.3",
|
||||||
"@mantine/form": "^8.3.18",
|
"@mantine/hooks": "^8.1.3",
|
||||||
"@mantine/hooks": "^8.3.18",
|
"@mantine/modals": "^8.1.3",
|
||||||
"@mantine/modals": "^8.3.18",
|
"@mantine/notifications": "^8.1.3",
|
||||||
"@mantine/notifications": "^8.3.18",
|
"@mantine/spotlight": "^8.1.3",
|
||||||
"@mantine/spotlight": "^8.3.18",
|
"@tabler/icons-react": "^3.34.0",
|
||||||
"@tabler/icons-react": "^3.40.0",
|
"@tanstack/react-query": "^5.80.6",
|
||||||
"@tanstack/react-query": "5.90.17",
|
"@tiptap/extension-character-count": "^2.10.3",
|
||||||
"alfaaz": "^1.1.0",
|
"alfaaz": "^1.1.0",
|
||||||
"axios": "1.16.0",
|
"axios": "^1.9.0",
|
||||||
"blueimp-load-image": "^5.16.0",
|
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"emoji-mart": "^5.6.0",
|
"emoji-mart": "^5.6.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"highlightjs-sap-abap": "^0.3.0",
|
"highlightjs-sap-abap": "^0.3.0",
|
||||||
"i18next": "25.10.1",
|
"i18next": "^23.14.0",
|
||||||
"i18next-http-backend": "3.0.6",
|
"i18next-http-backend": "^2.6.1",
|
||||||
"jotai": "^2.18.1",
|
"jotai": "^2.12.5",
|
||||||
"jotai-optics": "^0.4.0",
|
"jotai-optics": "^0.4.0",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"katex": "0.16.40",
|
"katex": "0.16.22",
|
||||||
"lowlight": "^3.3.0",
|
"lowlight": "^3.3.0",
|
||||||
"mantine-form-zod-resolver": "^1.3.0",
|
"mantine-form-zod-resolver": "^1.3.0",
|
||||||
"mermaid": "^11.13.0",
|
"mermaid": "^11.6.0",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"posthog-js": "1.372.2",
|
"posthog-js": "^1.255.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-arborist": "3.4.0",
|
"react-arborist": "3.4.0",
|
||||||
"react-clear-modal": "^2.0.18",
|
"react-clear-modal": "^2.0.15",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-drawio": "^1.0.7",
|
"react-drawio": "^1.0.1",
|
||||||
"react-error-boundary": "^6.1.1",
|
"react-error-boundary": "^4.1.2",
|
||||||
"react-helmet-async": "^3.0.0",
|
"react-helmet-async": "^2.0.5",
|
||||||
"react-i18next": "16.5.8",
|
"react-i18next": "^15.0.1",
|
||||||
"react-router-dom": "^7.13.1",
|
"react-router-dom": "^7.0.1",
|
||||||
"semver": "^7.7.4",
|
"semver": "^7.7.2",
|
||||||
"socket.io-client": "^4.8.3",
|
"socket.io-client": "^4.8.1",
|
||||||
"zod": "^4.3.6"
|
"tippy.js": "^6.3.7",
|
||||||
|
"tiptap-extension-global-drag-handle": "^0.1.18",
|
||||||
|
"zod": "^3.25.56"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.28.0",
|
"@eslint/js": "^9.16.0",
|
||||||
"@tanstack/eslint-plugin-query": "^5.94.4",
|
"@tanstack/eslint-plugin-query": "^5.62.1",
|
||||||
"@types/blueimp-load-image": "^5.16.6",
|
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/katex": "^0.16.8",
|
"@types/katex": "^0.16.7",
|
||||||
"@types/node": "22.19.1",
|
"@types/node": "22.10.0",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^4.4.1",
|
||||||
"eslint": "^9.28.0",
|
"eslint": "^9.15.0",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.2",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
"eslint-plugin-react-refresh": "^0.5.2",
|
"eslint-plugin-react-refresh": "^0.4.16",
|
||||||
"globals": "^15.13.0",
|
"globals": "^15.13.0",
|
||||||
"optics-ts": "^2.4.1",
|
"optics-ts": "^2.4.1",
|
||||||
"postcss": "^8.5.12",
|
"postcss": "^8.4.49",
|
||||||
"postcss-preset-mantine": "^1.18.0",
|
"postcss-preset-mantine": "^1.17.0",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.4.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.7.2",
|
||||||
"typescript-eslint": "^8.57.1",
|
"typescript-eslint": "^8.17.0",
|
||||||
"vite": "8.0.5"
|
"vite": "^6.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 562 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 509 B |
Binary file not shown.
|
Before Width: | Height: | Size: 881 B |
@@ -7,7 +7,6 @@
|
|||||||
"Add members": "Mitglieder hinzufügen",
|
"Add members": "Mitglieder hinzufügen",
|
||||||
"Add to groups": "Zu Gruppen hinzufügen",
|
"Add to groups": "Zu Gruppen hinzufügen",
|
||||||
"Add space members": "Bereichsmitglieder hinzufügen",
|
"Add space members": "Bereichsmitglieder hinzufügen",
|
||||||
"Add to favorites": "Zu Favoriten hinzufügen",
|
|
||||||
"Admin": "Administrator",
|
"Admin": "Administrator",
|
||||||
"Are you sure you want to delete this group? Members will lose access to resources this group has access to.": "Sind Sie sicher, dass Sie diese Gruppe löschen möchten? Mitglieder verlieren den Zugang zu den Ressourcen, auf die diese Gruppe zugreifen kann.",
|
"Are you sure you want to delete this group? Members will lose access to resources this group has access to.": "Sind Sie sicher, dass Sie diese Gruppe löschen möchten? Mitglieder verlieren den Zugang zu den Ressourcen, auf die diese Gruppe zugreifen kann.",
|
||||||
"Are you sure you want to delete this page?": "Sind Sie sicher, dass Sie diese Seite löschen möchten?",
|
"Are you sure you want to delete this page?": "Sind Sie sicher, dass Sie diese Seite löschen möchten?",
|
||||||
@@ -30,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Wählen Sie Ihre bevorzugte Benutzersprache.",
|
"Choose your preferred interface language.": "Wählen Sie Ihre bevorzugte Benutzersprache.",
|
||||||
"Choose your preferred page width.": "Wählen Sie Ihre bevorzugte Seitenbreite.",
|
"Choose your preferred page width.": "Wählen Sie Ihre bevorzugte Seitenbreite.",
|
||||||
"Confirm": "Bestätigen",
|
"Confirm": "Bestätigen",
|
||||||
"Copy as Markdown": "Als Markdown kopieren",
|
|
||||||
"Copy link": "Link kopieren",
|
"Copy link": "Link kopieren",
|
||||||
"Create": "Erstellen",
|
"Create": "Erstellen",
|
||||||
"Create group": "Gruppe erstellen",
|
"Create group": "Gruppe erstellen",
|
||||||
@@ -42,43 +40,38 @@
|
|||||||
"Date": "Datum",
|
"Date": "Datum",
|
||||||
"Delete": "Löschen",
|
"Delete": "Löschen",
|
||||||
"Delete group": "Gruppe löschen",
|
"Delete group": "Gruppe löschen",
|
||||||
"Are you sure you want to delete this page? This will delete its children and page history. This action is irreversible.": "Sind Sie sicher, dass Sie diese Seite löschen möchten? Dabei werden auch alle Unterseiten und der Seitenverlauf gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.",
|
"Are you sure you want to delete this page? This will delete its children and page history. This action is irreversible.": "Sind Sie sicher, dass Sie diese Seite löschen möchten? Dadurch werden ihre Unterseiten und die Seitengeschichte gelöscht. Diese Aktion ist unwiderruflich.",
|
||||||
"Description": "Beschreibung",
|
"Description": "Beschreibung",
|
||||||
"Details": "Details",
|
"Details": "Einzelheiten",
|
||||||
"e.g ACME": "z. B. ACME",
|
"e.g ACME": "z.B. ACME",
|
||||||
"e.g ACME Inc": "z. B. ACME GmbH",
|
"e.g ACME Inc": "z.B. ACME Inc.",
|
||||||
"e.g Developers": "z. B. Entwickler",
|
"e.g Developers": "z.B. Entwickler",
|
||||||
"e.g Group for developers": "z. B. Gruppe für Entwickler",
|
"e.g Group for developers": "z.B. Gruppe für Entwickler",
|
||||||
"e.g product": "z. B. Produkt",
|
"e.g product": "z.B. Produkt",
|
||||||
"e.g Product Team": "z. B. Produktteam",
|
"e.g Product Team": "z.B. Produktteam",
|
||||||
"e.g Sales": "z. B. Vertrieb",
|
"e.g Sales": "z.B. Vertrieb",
|
||||||
"e.g Space for product team": "z. B. Bereich für das Produktteam",
|
"e.g Space for product team": "z.B. Bereich für das Produktteam",
|
||||||
"e.g Space for sales team to collaborate": "z. B. Bereich zur Zusammenarbeit für das Vertriebsteam",
|
"e.g Space for sales team to collaborate": "z.B. Bereich für das Vertriebsteam zur Zusammenarbeit",
|
||||||
"Edit": "Bearbeiten",
|
"Edit": "Bearbeiten",
|
||||||
"Read": "Lesen",
|
|
||||||
"Edit group": "Gruppe bearbeiten",
|
"Edit group": "Gruppe bearbeiten",
|
||||||
"Email": "E-Mail",
|
"Email": "E-Mail",
|
||||||
"Enter a strong password": "Geben Sie ein starkes Passwort ein",
|
"Enter a strong password": "Geben Sie ein starkes Passwort ein",
|
||||||
"Enter valid email addresses separated by comma or space max_50": "Geben Sie gültige E-Mail-Adressen ein, getrennt durch Kommas oder Leerzeichen [max: 50]",
|
"Enter valid email addresses separated by comma or space max_50": "Geben Sie gültige E-Mail-Adressen ein, getrennt durch Kommas oder Leerzeichen [max: 50]",
|
||||||
"enter valid emails addresses": "Geben Sie gültige E-Mail-Adressen ein",
|
"enter valid emails addresses": "gültige E-Mail-Adressen eingeben",
|
||||||
"Enter your current password": "Geben Sie Ihr aktuelles Passwort ein",
|
"Enter your current password": "Geben Sie Ihr aktuelles Passwort ein",
|
||||||
"enter your full name": "Geben Sie Ihren vollständigen Namen ein",
|
"enter your full name": "Geben Sie Ihren vollständigen Namen ein",
|
||||||
"Enter your new password": "Geben Sie Ihr neues Passwort ein",
|
"Enter your new password": "Geben Sie Ihr neues Passwort ein",
|
||||||
"Enter your new preferred email": "Geben Sie Ihre neue bevorzugte E-Mail ein",
|
"Enter your new preferred email": "Geben Sie Ihre neue bevorzugte E-Mail ein",
|
||||||
"Enter your password": "Geben Sie Ihr Passwort ein",
|
"Enter your password": "Geben Sie Ihr Passwort ein",
|
||||||
"Error fetching page data.": "Fehler beim Abrufen der Seitendaten.",
|
"Error fetching page data.": "Fehler beim Abrufen der Seitendaten.",
|
||||||
"Error loading page history.": "Fehler beim Laden des Seitenverlaufs.",
|
"Error loading page history.": "Fehler beim Laden der Seitengeschichte.",
|
||||||
"Export": "Exportieren",
|
"Export": "Exportieren",
|
||||||
"Failed to create page": "Erstellung der Seite fehlgeschlagen",
|
"Failed to create page": "Erstellung der Seite fehlgeschlagen",
|
||||||
"Failed to delete page": "Löschen der Seite fehlgeschlagen",
|
"Failed to delete page": "Löschen der Seite fehlgeschlagen",
|
||||||
"Failed to restore page": "Failed to restore page",
|
|
||||||
"Failed to fetch recent pages": "Fehler beim Abrufen der letzten Seiten",
|
"Failed to fetch recent pages": "Fehler beim Abrufen der letzten Seiten",
|
||||||
"Failed to import pages": "Import der Seiten fehlgeschlagen",
|
"Failed to import pages": "Import der Seiten fehlgeschlagen",
|
||||||
"Failed to load page. An error occurred.": "Seite konnte nicht geladen werden. Es ist ein Fehler aufgetreten.",
|
"Failed to load page. An error occurred.": "Seite konnte nicht geladen werden. Es ist ein Fehler aufgetreten.",
|
||||||
"Failed to update data": "Aktualisierung der Daten fehlgeschlagen",
|
"Failed to update data": "Aktualisierung der Daten fehlgeschlagen",
|
||||||
"Favorite spaces": "Favorisierte Bereiche",
|
|
||||||
"Favorite spaces appear here": "Favorisierte Bereiche werden hier angezeigt",
|
|
||||||
"Favorites": "Favoriten",
|
|
||||||
"Full access": "Voller Zugriff",
|
"Full access": "Voller Zugriff",
|
||||||
"Full page width": "Volle Seitenbreite",
|
"Full page width": "Volle Seitenbreite",
|
||||||
"Full width": "Volle Breite",
|
"Full width": "Volle Breite",
|
||||||
@@ -92,12 +85,11 @@
|
|||||||
"Import pages": "Seiten importieren",
|
"Import pages": "Seiten importieren",
|
||||||
"Import pages & space settings": "Seiten und Bereichseinstellungen importieren",
|
"Import pages & space settings": "Seiten und Bereichseinstellungen importieren",
|
||||||
"Importing pages": "Seiten werden importiert",
|
"Importing pages": "Seiten werden importiert",
|
||||||
"invalid invitation link": "Ungültiger Einladungslink",
|
"invalid invitation link": "ungültiger Einladungslink",
|
||||||
"Invitation signup": "Einladung zur Anmeldung",
|
"Invitation signup": "Einladung zur Anmeldung",
|
||||||
"Invite by email": "Einladen per E-Mail",
|
"Invite by email": "Einladen per E-Mail",
|
||||||
"Invite members": "Mitglieder einladen",
|
"Invite members": "Mitglieder einladen",
|
||||||
"Invite new members": "Neue Mitglieder einladen",
|
"Invite new members": "Neue Mitglieder einladen",
|
||||||
"Invite People": "Personen einladen",
|
|
||||||
"Invited members who are yet to accept their invitation will appear here.": "Eingeladene Mitglieder, die ihre Einladung noch nicht angenommen haben, werden hier angezeigt.",
|
"Invited members who are yet to accept their invitation will appear here.": "Eingeladene Mitglieder, die ihre Einladung noch nicht angenommen haben, werden hier angezeigt.",
|
||||||
"Invited members will be granted access to spaces the groups can access": "Eingeladene Mitglieder erhalten Zugriff auf die Bereiche, auf die die Gruppen zugreifen können",
|
"Invited members will be granted access to spaces the groups can access": "Eingeladene Mitglieder erhalten Zugriff auf die Bereiche, auf die die Gruppen zugreifen können",
|
||||||
"Join the workspace": "Dem Arbeitsbereich beitreten",
|
"Join the workspace": "Dem Arbeitsbereich beitreten",
|
||||||
@@ -112,7 +104,7 @@
|
|||||||
"Member": "Mitglied",
|
"Member": "Mitglied",
|
||||||
"members": "Mitglieder",
|
"members": "Mitglieder",
|
||||||
"Members": "Mitglieder",
|
"Members": "Mitglieder",
|
||||||
"My preferences": "Meine Einstellungen",
|
"My preferences": "Meine Voreinstellungen",
|
||||||
"My Profile": "Mein Profil",
|
"My Profile": "Mein Profil",
|
||||||
"My profile": "Mein Profil",
|
"My profile": "Mein Profil",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
@@ -120,32 +112,27 @@
|
|||||||
"New page": "Neue Seite",
|
"New page": "Neue Seite",
|
||||||
"New password": "Neues Passwort",
|
"New password": "Neues Passwort",
|
||||||
"No group found": "Keine Gruppe gefunden",
|
"No group found": "Keine Gruppe gefunden",
|
||||||
"No page history saved yet.": "Es wurde noch kein Seitenverlauf gespeichert.",
|
"No page history saved yet.": "Es wurde noch keine Seitengeschichte gespeichert.",
|
||||||
"No pages yet": "Noch keine Seiten",
|
"No pages yet": "Noch keine Seiten",
|
||||||
"No shared pages": "Keine freigegebenen Seiten",
|
|
||||||
"No results found...": "Keine Ergebnisse gefunden...",
|
"No results found...": "Keine Ergebnisse gefunden...",
|
||||||
"No user found": "Kein Benutzer gefunden",
|
"No user found": "Kein Benutzer gefunden",
|
||||||
"Overview": "Überblick",
|
"Overview": "Überblick",
|
||||||
"Owner": "Besitzer",
|
"Owner": "Besitzer",
|
||||||
"page": "Seite",
|
"page": "Seite",
|
||||||
"Page deleted successfully": "Seite erfolgreich gelöscht",
|
"Page deleted successfully": "Seite erfolgreich gelöscht",
|
||||||
"Page history": "Seitenverlauf",
|
"Page history": "Seitengeschichte",
|
||||||
"Select version": "Version auswählen",
|
|
||||||
"Highlight changes": "Änderungen hervorheben",
|
|
||||||
"Page import is in progress. Please do not close this tab.": "Der Seitenimport läuft. Bitte schließen Sie diesen Tab nicht.",
|
"Page import is in progress. Please do not close this tab.": "Der Seitenimport läuft. Bitte schließen Sie diesen Tab nicht.",
|
||||||
"Pages": "Seiten",
|
"Pages": "Seiten",
|
||||||
"pages": "Seiten",
|
"pages": "Seiten",
|
||||||
"Password": "Passwort",
|
"Password": "Passwort",
|
||||||
"Password changed successfully": "Passwort erfolgreich geändert",
|
"Password changed successfully": "Passwort erfolgreich geändert",
|
||||||
"People": "Personen",
|
|
||||||
"Pending": "Ausstehend",
|
"Pending": "Ausstehend",
|
||||||
"Please confirm your action": "Bitte bestätigen Sie Ihre Aktion",
|
"Please confirm your action": "Bitte bestätigen Sie Ihre Aktion",
|
||||||
"Preferences": "Einstellungen",
|
"Preferences": "Vorlieben",
|
||||||
"Print PDF": "PDF drucken",
|
"Print PDF": "PDF drucken",
|
||||||
"Profile": "Profil",
|
"Profile": "Profil",
|
||||||
"Recently updated": "Kürzlich aktualisiert",
|
"Recently updated": "Kürzlich aktualisiert",
|
||||||
"Remove": "Entfernen",
|
"Remove": "Entfernen",
|
||||||
"Remove from favorites": "Aus Favoriten entfernen",
|
|
||||||
"Remove group member": "Gruppenmitglied entfernen",
|
"Remove group member": "Gruppenmitglied entfernen",
|
||||||
"Remove space member": "Bereichsmitglied entfernen",
|
"Remove space member": "Bereichsmitglied entfernen",
|
||||||
"Restore": "Wiederherstellen",
|
"Restore": "Wiederherstellen",
|
||||||
@@ -158,12 +145,12 @@
|
|||||||
"Search...": "Suche...",
|
"Search...": "Suche...",
|
||||||
"Select language": "Sprache auswählen",
|
"Select language": "Sprache auswählen",
|
||||||
"Select role": "Rolle auswählen",
|
"Select role": "Rolle auswählen",
|
||||||
"Select role to assign to all invited members": "Wählen Sie die Rolle aus, die allen eingeladenen Mitgliedern zugewiesen werden soll",
|
"Select role to assign to all invited members": "Rolle für alle eingeladenen Mitglieder auswählen",
|
||||||
"Select theme": "Design auswählen",
|
"Select theme": "Design auswählen",
|
||||||
"Send invitation": "Einladung senden",
|
"Send invitation": "Einladung senden",
|
||||||
"Invitation sent": "Einladung gesendet",
|
"Invitation sent": "Einladung gesendet",
|
||||||
"Settings": "Einstellungen",
|
"Settings": "Einstellungen",
|
||||||
"Setup workspace": "Workspace einrichten",
|
"Setup workspace": "Arbeitsbereich einrichten",
|
||||||
"Sign In": "Anmelden",
|
"Sign In": "Anmelden",
|
||||||
"Sign Up": "Registrieren",
|
"Sign Up": "Registrieren",
|
||||||
"Slug": "Slug",
|
"Slug": "Slug",
|
||||||
@@ -172,17 +159,16 @@
|
|||||||
"Space menu": "Bereichsmenü",
|
"Space menu": "Bereichsmenü",
|
||||||
"Space name": "Bereichsname",
|
"Space name": "Bereichsname",
|
||||||
"Space settings": "Bereichseinstellungen",
|
"Space settings": "Bereichseinstellungen",
|
||||||
"Space slug": "Bereichs-Slug",
|
"Space slug": "Slug des Bereichs",
|
||||||
"Spaces": "Bereiche",
|
"Spaces": "Bereiche",
|
||||||
"Spaces you belong to": "Bereiche, zu denen Sie gehören",
|
"Spaces you belong to": "Bereiche, denen Sie angehören",
|
||||||
"No space found": "Kein Bereich gefunden",
|
"No space found": "Keine Bereiche gefunden",
|
||||||
"Search for spaces": "Nach Bereichen suchen",
|
"Search for spaces": "Nach Bereichen suchen",
|
||||||
"Start typing to search...": "Anfangen zu tippen, um zu suchen...",
|
"Start typing to search...": "Anfangen zu tippen, um zu suchen...",
|
||||||
"Status": "Status",
|
"Status": "Status",
|
||||||
"Successfully imported": "Erfolgreich importiert",
|
"Successfully imported": "Erfolgreich importiert",
|
||||||
"Successfully restored": "Erfolgreich wiederhergestellt",
|
"Successfully restored": "Erfolgreich wiederhergestellt",
|
||||||
"System settings": "Systemeinstellungen",
|
"System settings": "Systemeinstellungen",
|
||||||
"Templates": "Vorlagen",
|
|
||||||
"Theme": "Design",
|
"Theme": "Design",
|
||||||
"To change your email, you have to enter your password and new email.": "Um Ihre E-Mail-Adresse zu ändern, müssen Sie Ihr Passwort und Ihre neue E-Mail-Adresse eingeben.",
|
"To change your email, you have to enter your password and new email.": "Um Ihre E-Mail-Adresse zu ändern, müssen Sie Ihr Passwort und Ihre neue E-Mail-Adresse eingeben.",
|
||||||
"Toggle full page width": "Volle Seitenbreite umschalten",
|
"Toggle full page width": "Volle Seitenbreite umschalten",
|
||||||
@@ -191,9 +177,9 @@
|
|||||||
"Untitled": "Ohne Titel",
|
"Untitled": "Ohne Titel",
|
||||||
"Updated successfully": "Erfolgreich aktualisiert",
|
"Updated successfully": "Erfolgreich aktualisiert",
|
||||||
"User": "Benutzer",
|
"User": "Benutzer",
|
||||||
"Workspace": "Workspace",
|
"Workspace": "Arbeitsbereich",
|
||||||
"Workspace Name": "Workspace-Name",
|
"Workspace Name": "Arbeitsbereichsname",
|
||||||
"Workspace settings": "Workspace-Einstellungen",
|
"Workspace settings": "Arbeitsbereich-Einstellungen",
|
||||||
"You can change your password here.": "Hier können Sie Ihr Passwort ändern.",
|
"You can change your password here.": "Hier können Sie Ihr Passwort ändern.",
|
||||||
"Your Email": "Ihre E-Mail",
|
"Your Email": "Ihre E-Mail",
|
||||||
"Your import is complete.": "Ihr Import ist abgeschlossen.",
|
"Your import is complete.": "Ihr Import ist abgeschlossen.",
|
||||||
@@ -217,14 +203,9 @@
|
|||||||
"Reply...": "Antworten...",
|
"Reply...": "Antworten...",
|
||||||
"Error loading comments.": "Fehler beim Laden der Kommentare.",
|
"Error loading comments.": "Fehler beim Laden der Kommentare.",
|
||||||
"No comments yet.": "Noch keine Kommentare.",
|
"No comments yet.": "Noch keine Kommentare.",
|
||||||
"No open comments.": "Keine offenen Kommentare.",
|
|
||||||
"No resolved comments.": "Keine gelösten Kommentare.",
|
|
||||||
"Add a comment...": "Kommentar hinzufügen...",
|
|
||||||
"Edit comment": "Kommentar bearbeiten",
|
"Edit comment": "Kommentar bearbeiten",
|
||||||
"Delete comment": "Kommentar löschen",
|
"Delete comment": "Kommentar löschen",
|
||||||
"Are you sure you want to delete this comment?": "Sind Sie sicher, dass Sie diesen Kommentar löschen möchten?",
|
"Are you sure you want to delete this comment?": "Sind Sie sicher, dass Sie diesen Kommentar löschen möchten?",
|
||||||
"Delete chat": "Chat löschen",
|
|
||||||
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "Sind Sie sicher, dass Sie '{{title}}' löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
|
|
||||||
"Comment created successfully": "Kommentar erfolgreich erstellt",
|
"Comment created successfully": "Kommentar erfolgreich erstellt",
|
||||||
"Error creating comment": "Fehler beim Erstellen des Kommentars",
|
"Error creating comment": "Fehler beim Erstellen des Kommentars",
|
||||||
"Comment updated successfully": "Kommentar erfolgreich aktualisiert",
|
"Comment updated successfully": "Kommentar erfolgreich aktualisiert",
|
||||||
@@ -233,16 +214,17 @@
|
|||||||
"Failed to delete comment": "Löschen des Kommentars fehlgeschlagen",
|
"Failed to delete comment": "Löschen des Kommentars fehlgeschlagen",
|
||||||
"Comment resolved successfully": "Kommentar erfolgreich gelöst",
|
"Comment resolved successfully": "Kommentar erfolgreich gelöst",
|
||||||
"Comment re-opened successfully": "Kommentar erfolgreich wieder geöffnet",
|
"Comment re-opened successfully": "Kommentar erfolgreich wieder geöffnet",
|
||||||
"Comment unresolved successfully": "Kommentar erfolgreich als ungelöst markiert",
|
"Comment unresolved successfully": "Kommentar erfolgreich ungelöst",
|
||||||
"Failed to resolve comment": "Lösen des Kommentars fehlgeschlagen",
|
"Failed to resolve comment": "Lösen des Kommentars fehlgeschlagen",
|
||||||
"Resolve comment": "Kommentar lösen",
|
"Resolve comment": "Kommentar lösen",
|
||||||
"Unresolve comment": "Kommentar als ungelöst markieren",
|
"Unresolve comment": "Kommentar nicht lösen",
|
||||||
"Resolve Comment Thread": "Kommentarthread lösen",
|
"Resolve Comment Thread": "Kommentarthread lösen",
|
||||||
"Unresolve Comment Thread": "Kommentarthread als ungelöst markieren",
|
"Unresolve Comment Thread": "Kommentarthread nicht lösen",
|
||||||
"Are you sure you want to resolve this comment thread? This will mark it as completed.": "Sind Sie sicher, dass Sie diesen Kommentarthread lösen möchten? Dies wird als abgeschlossen markiert.",
|
"Are you sure you want to resolve this comment thread? This will mark it as completed.": "Sind Sie sicher, dass Sie diesen Kommentarthread lösen möchten? Dies wird als abgeschlossen markiert.",
|
||||||
"Are you sure you want to unresolve this comment thread?": "Sind Sie sicher, dass Sie diesen Kommentarthread nicht lösen möchten?",
|
"Are you sure you want to unresolve this comment thread?": "Sind Sie sicher, dass Sie diesen Kommentarthread nicht lösen möchten?",
|
||||||
"Resolved": "Gelöst",
|
"Resolved": "Gelöst",
|
||||||
"No active comments.": "Keine aktiven Kommentare.",
|
"No active comments.": "Keine aktiven Kommentare.",
|
||||||
|
"No resolved comments.": "Keine gelösten Kommentare.",
|
||||||
"Revoke invitation": "Einladung widerrufen",
|
"Revoke invitation": "Einladung widerrufen",
|
||||||
"Revoke": "Widerrufen",
|
"Revoke": "Widerrufen",
|
||||||
"Don't": "Nicht",
|
"Don't": "Nicht",
|
||||||
@@ -251,7 +233,7 @@
|
|||||||
"Anyone with this link can join this workspace.": "Jeder mit diesem Link kann dem Arbeitsbereich beitreten.",
|
"Anyone with this link can join this workspace.": "Jeder mit diesem Link kann dem Arbeitsbereich beitreten.",
|
||||||
"Invite link": "Einladungslink",
|
"Invite link": "Einladungslink",
|
||||||
"Copy": "Kopieren",
|
"Copy": "Kopieren",
|
||||||
"Copy to space": "In Bereich kopieren",
|
"Copy to space": "In Raum kopieren",
|
||||||
"Copied": "Kopiert",
|
"Copied": "Kopiert",
|
||||||
"Duplicate": "Duplizieren",
|
"Duplicate": "Duplizieren",
|
||||||
"Select a user": "Benutzer auswählen",
|
"Select a user": "Benutzer auswählen",
|
||||||
@@ -261,7 +243,7 @@
|
|||||||
"Are you sure you want to delete this space?": "Sind Sie sicher, dass Sie diesen Bereich löschen möchten?",
|
"Are you sure you want to delete this space?": "Sind Sie sicher, dass Sie diesen Bereich löschen möchten?",
|
||||||
"Delete this space with all its pages and data.": "Diesen Bereich mit allen Seiten und Daten löschen.",
|
"Delete this space with all its pages and data.": "Diesen Bereich mit allen Seiten und Daten löschen.",
|
||||||
"All pages, comments, attachments and permissions in this space will be deleted irreversibly.": "Alle Seiten, Kommentare, Anhänge und Berechtigungen in diesem Bereich werden unwiderruflich gelöscht.",
|
"All pages, comments, attachments and permissions in this space will be deleted irreversibly.": "Alle Seiten, Kommentare, Anhänge und Berechtigungen in diesem Bereich werden unwiderruflich gelöscht.",
|
||||||
"Confirm space name": "Bereichsnamen bestätigen",
|
"Confirm space name": "Bestätigen Sie den Namen des Arbeitsbereichs",
|
||||||
"Type the space name <b>{{spaceName}}</b> to confirm your action.": "Geben Sie den Namen des Bereichs <b>{{spaceName}}</b> ein, um Ihre Aktion zu bestätigen.",
|
"Type the space name <b>{{spaceName}}</b> to confirm your action.": "Geben Sie den Namen des Bereichs <b>{{spaceName}}</b> ein, um Ihre Aktion zu bestätigen.",
|
||||||
"Format": "Format",
|
"Format": "Format",
|
||||||
"Include subpages": "Unterseiten einbeziehen",
|
"Include subpages": "Unterseiten einbeziehen",
|
||||||
@@ -270,7 +252,6 @@
|
|||||||
"Export failed:": "Export fehlgeschlagen:",
|
"Export failed:": "Export fehlgeschlagen:",
|
||||||
"export error": "Exportfehler",
|
"export error": "Exportfehler",
|
||||||
"Export page": "Seite exportieren",
|
"Export page": "Seite exportieren",
|
||||||
"Export successful": "Export erfolgreich",
|
|
||||||
"Export space": "Bereich exportieren",
|
"Export space": "Bereich exportieren",
|
||||||
"Export {{type}}": "Exportiere {{type}}",
|
"Export {{type}}": "Exportiere {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "Datei überschreitet das Anhängelimit von {{limit}}",
|
"File exceeds the {{limit}} attachment limit": "Datei überschreitet das Anhängelimit von {{limit}}",
|
||||||
@@ -287,21 +268,7 @@
|
|||||||
"Add row above": "Zeile oben hinzufügen",
|
"Add row above": "Zeile oben hinzufügen",
|
||||||
"Add row below": "Zeile unten hinzufügen",
|
"Add row below": "Zeile unten hinzufügen",
|
||||||
"Delete table": "Tabelle löschen",
|
"Delete table": "Tabelle löschen",
|
||||||
"Add column left": "Add column left",
|
|
||||||
"Add column right": "Add column right",
|
|
||||||
"Clear cell": "Clear cell",
|
|
||||||
"Clear cells": "Clear cells",
|
|
||||||
"Toggle header cell": "Toggle header cell",
|
|
||||||
"Toggle header column": "Toggle header column",
|
|
||||||
"Toggle header row": "Toggle header row",
|
|
||||||
"Move column left": "Move column left",
|
|
||||||
"Move column right": "Move column right",
|
|
||||||
"Move row down": "Move row down",
|
|
||||||
"Move row up": "Move row up",
|
|
||||||
"Sort A → Z": "Sort A → Z",
|
|
||||||
"Sort Z → A": "Sort Z → A",
|
|
||||||
"Info": "Info",
|
"Info": "Info",
|
||||||
"Note": "Hinweis",
|
|
||||||
"Success": "Erfolg",
|
"Success": "Erfolg",
|
||||||
"Warning": "Warnung",
|
"Warning": "Warnung",
|
||||||
"Danger": "Gefahr",
|
"Danger": "Gefahr",
|
||||||
@@ -312,11 +279,6 @@
|
|||||||
"Save & Exit": "Speichern & Beenden",
|
"Save & Exit": "Speichern & Beenden",
|
||||||
"Double-click to edit Excalidraw diagram": "Zum Bearbeiten des Excalidraw-Diagramms doppelklicken",
|
"Double-click to edit Excalidraw diagram": "Zum Bearbeiten des Excalidraw-Diagramms doppelklicken",
|
||||||
"Paste link": "Link einfügen",
|
"Paste link": "Link einfügen",
|
||||||
"Paste link or search pages": "Link einfügen oder Seiten durchsuchen",
|
|
||||||
"Link to web page": "Link zur Webseite",
|
|
||||||
"Recents": "Zuletzt verwendet",
|
|
||||||
"Page or URL": "Seite oder URL",
|
|
||||||
"Link title": "Linktitel",
|
|
||||||
"Edit link": "Link bearbeiten",
|
"Edit link": "Link bearbeiten",
|
||||||
"Remove link": "Link entfernen",
|
"Remove link": "Link entfernen",
|
||||||
"Add link": "Link hinzufügen",
|
"Add link": "Link hinzufügen",
|
||||||
@@ -362,14 +324,9 @@
|
|||||||
"Create block quote.": "Erstellen Sie ein Blockzitat.",
|
"Create block quote.": "Erstellen Sie ein Blockzitat.",
|
||||||
"Insert code snippet.": "Code-Snippet einfügen.",
|
"Insert code snippet.": "Code-Snippet einfügen.",
|
||||||
"Insert horizontal rule divider": "Horizontale Trennlinie einfügen",
|
"Insert horizontal rule divider": "Horizontale Trennlinie einfügen",
|
||||||
"Page break": "Page break",
|
|
||||||
"Insert a page break for printing.": "Insert a page break for printing.",
|
|
||||||
"Upload any image from your device.": "Laden Sie ein beliebiges Bild von Ihrem Gerät hoch.",
|
"Upload any image from your device.": "Laden Sie ein beliebiges Bild von Ihrem Gerät hoch.",
|
||||||
"Upload any video from your device.": "Laden Sie ein beliebiges Video von Ihrem Gerät hoch.",
|
"Upload any video from your device.": "Laden Sie ein beliebiges Video von Ihrem Gerät hoch.",
|
||||||
"Upload any audio from your device.": "Laden Sie beliebige Audiodateien von Ihrem Gerät hoch.",
|
|
||||||
"Upload any file from your device.": "Laden Sie eine beliebige Datei von Ihrem Gerät hoch.",
|
"Upload any file from your device.": "Laden Sie eine beliebige Datei von Ihrem Gerät hoch.",
|
||||||
"Uploading {{name}}": "Lade {{name}} hoch",
|
|
||||||
"Uploading file": "Datei wird hochgeladen",
|
|
||||||
"Table": "Tabelle",
|
"Table": "Tabelle",
|
||||||
"Insert a table.": "Tabelle einfügen.",
|
"Insert a table.": "Tabelle einfügen.",
|
||||||
"Insert collapsible block.": "Einklappbaren Block einfügen.",
|
"Insert collapsible block.": "Einklappbaren Block einfügen.",
|
||||||
@@ -377,49 +334,29 @@
|
|||||||
"Divider": "Trennlinie",
|
"Divider": "Trennlinie",
|
||||||
"Quote": "Zitat",
|
"Quote": "Zitat",
|
||||||
"Image": "Bild",
|
"Image": "Bild",
|
||||||
"Audio": "Audio",
|
|
||||||
"Embed PDF": "PDF einbetten",
|
|
||||||
"Upload and embed a PDF file.": "Laden Sie eine PDF-Datei hoch und betten Sie sie ein.",
|
|
||||||
"Embed as PDF": "Als PDF einbetten",
|
|
||||||
"Failed to load PDF": "Fehler beim Laden der PDF",
|
|
||||||
"Convert to attachment": "In Anhang umwandeln",
|
|
||||||
"File attachment": "Dateianhang",
|
"File attachment": "Dateianhang",
|
||||||
"Toggle block": "Umschaltblock",
|
"Toggle block": "Block umschalten",
|
||||||
"Callout": "Hinweisblock",
|
"Callout": "Hinweisbox",
|
||||||
"Insert callout notice.": "Hinweisbox einfügen.",
|
"Insert callout notice.": "Hinweisbox einfügen.",
|
||||||
"Math inline": "Mathe inline",
|
"Math inline": "Mathe inline",
|
||||||
"Insert inline math equation.": "Mathe-Gleichung inline einfügen.",
|
"Insert inline math equation.": "Mathe-Gleichung inline einfügen.",
|
||||||
"Math block": "Matheblock",
|
"Math block": "Matheblock",
|
||||||
"Insert math equation": "Mathematische Gleichung einfügen",
|
"Insert math equation": "Mathe-Gleichung einfügen",
|
||||||
"Mermaid diagram": "Mermaid-Diagramm",
|
"Mermaid diagram": "Mermaid-Diagramm",
|
||||||
"Insert mermaid diagram": "Mermaid-Diagramm einfügen",
|
"Insert mermaid diagram": "Mermaid-Diagramm einfügen",
|
||||||
"Insert and design Drawio diagrams": "Drawio-Diagramme einfügen und gestalten",
|
"Insert and design Drawio diagrams": "Drawio-Diagramme einfügen und gestalten",
|
||||||
"Insert current date": "Aktuelles Datum einfügen",
|
"Insert current date": "Aktuelles Datum einfügen",
|
||||||
"Draw and sketch excalidraw diagrams": "Excalidraw-Diagramme zeichnen und skizzieren",
|
"Draw and sketch excalidraw diagrams": "Excalidraw-Diagramme zeichnen und skizzieren",
|
||||||
"Multiple": "Mehrfach",
|
"Multiple": "Mehrere",
|
||||||
"Turn into": "In verwandeln",
|
|
||||||
"Text align": "Text ausrichten",
|
|
||||||
"This page may have been deleted, moved, or you may not have access.": "\"Diese Seite wurde möglicherweise gelöscht, verschoben oder Sie haben keinen Zugriff darauf.\"",
|
|
||||||
"Go to homepage": "Zur Startseite",
|
|
||||||
"Pages you create will show up here.": "\"Die von Ihnen erstellten Seiten werden hier angezeigt.\"",
|
|
||||||
"Heading {{level}}": "Überschrift {{level}}",
|
"Heading {{level}}": "Überschrift {{level}}",
|
||||||
"Toggle title": "Titel umschalten",
|
"Toggle title": "Titel umschalten",
|
||||||
"Write anything. Enter \"/\" for commands": "Schreiben Sie etwas. Geben Sie \"/\" für Befehle ein",
|
"Write anything. Enter \"/\" for commands": "Schreiben Sie irgendetwas. Geben Sie \"/\" für Befehle ein",
|
||||||
"Write...": "\"Schreiben...\"",
|
|
||||||
"Column count": "Spaltenanzahl",
|
|
||||||
"{{count}} Columns": "{{count}} Spalten",
|
|
||||||
"Equal columns": "Gleich breite Spalten",
|
|
||||||
"Left sidebar": "Linke Seitenleiste",
|
|
||||||
"Right sidebar": "Rechte Seitenleiste",
|
|
||||||
"Wide center": "Breiter Mittelbereich",
|
|
||||||
"Left wide": "Breiter linker Bereich",
|
|
||||||
"Right wide": "Breiter rechter Bereich",
|
|
||||||
"Names do not match": "Namen stimmen nicht überein",
|
"Names do not match": "Namen stimmen nicht überein",
|
||||||
"Today, {{time}}": "Heute, {{time}}",
|
"Today, {{time}}": "Heute, {{time}}",
|
||||||
"Yesterday, {{time}}": "Gestern, {{time}}",
|
"Yesterday, {{time}}": "Gestern, {{time}}",
|
||||||
"Space created successfully": "Bereich erfolgreich erstellt",
|
"Space created successfully": "Der Bereich wurde erfolgreich erstellt",
|
||||||
"Space updated successfully": "Bereich erfolgreich aktualisiert",
|
"Space updated successfully": "Der Bereich wurde erfolgreich aktualisiert",
|
||||||
"Space deleted successfully": "Bereich erfolgreich gelöscht",
|
"Space deleted successfully": "Der Bereich wurde erfolgreich gelöscht",
|
||||||
"Members added successfully": "Mitglieder erfolgreich hinzugefügt",
|
"Members added successfully": "Mitglieder erfolgreich hinzugefügt",
|
||||||
"Member removed successfully": "Mitglied erfolgreich entfernt",
|
"Member removed successfully": "Mitglied erfolgreich entfernt",
|
||||||
"Member role updated successfully": "Mitgliederrolle erfolgreich aktualisiert",
|
"Member role updated successfully": "Mitgliederrolle erfolgreich aktualisiert",
|
||||||
@@ -427,23 +364,15 @@
|
|||||||
"Created at: {{time}}": "Erstellt am: {{time}}",
|
"Created at: {{time}}": "Erstellt am: {{time}}",
|
||||||
"Edited by {{name}} {{time}}": "Bearbeitet von {{name}} {{time}}",
|
"Edited by {{name}} {{time}}": "Bearbeitet von {{name}} {{time}}",
|
||||||
"Word count: {{wordCount}}": "Wortanzahl: {{wordCount}}",
|
"Word count: {{wordCount}}": "Wortanzahl: {{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Zeichenanzahl: {{characterCount}}",
|
"Character count: {{characterCount}}": "Zeichenzahl: {{characterCount}}",
|
||||||
"New update": "Neues Update",
|
"New update": "Neues Update",
|
||||||
"{{latestVersion}} is available": "{{latestVersion}} ist verfügbar",
|
"{{latestVersion}} is available": "{{latestVersion}} ist verfügbar",
|
||||||
"Default page edit mode": "Standard-Bearbeitungsmodus für Seiten",
|
"Default page edit mode": "Standard-Seitenbearbeitungsmodus",
|
||||||
"Choose your preferred page edit mode. Avoid accidental edits.": "Wählen Sie Ihren bevorzugten Seitenbearbeitungsmodus. Vermeiden Sie versehentliche Bearbeitungen.",
|
"Choose your preferred page edit mode. Avoid accidental edits.": "Wählen Sie Ihren bevorzugten Seitenbearbeitungsmodus. Vermeiden Sie versehentliche Bearbeitungen.",
|
||||||
"Choose {{format}} file": "{{format}}-Datei auswählen",
|
|
||||||
"Reading": "Lesen",
|
"Reading": "Lesen",
|
||||||
"Delete member": "Mitglied löschen",
|
"Delete member": "Mitglied löschen",
|
||||||
"Member deleted successfully": "Mitglied erfolgreich gelöscht",
|
"Member deleted successfully": "Mitglied erfolgreich gelöscht",
|
||||||
"Are you sure you want to delete this workspace member? This action is irreversible.": "Sind Sie sicher, dass Sie dieses Arbeitsbereichsmitglied löschen möchten? Diese Aktion ist unwiderruflich.",
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "Sind Sie sicher, dass Sie dieses Arbeitsbereichsmitglied löschen möchten? Diese Aktion ist unwiderruflich.",
|
||||||
"Deactivate member": "Mitglied deaktivieren",
|
|
||||||
"Activate member": "Mitglied aktivieren",
|
|
||||||
"Are you sure you want to deactivate this workspace member? They will no longer be able to access this workspace.": "Sind Sie sicher, dass Sie dieses Mitglied des Arbeitsbereichs deaktivieren möchten? Dieses Mitglied kann danach nicht mehr auf diesen Arbeitsbereich zugreifen.",
|
|
||||||
"Are you sure you want to activate this workspace member?": "Sind Sie sicher, dass Sie dieses Mitglied des Arbeitsbereichs aktivieren möchten?",
|
|
||||||
"Deactivate": "Deaktivieren",
|
|
||||||
"Activate": "Aktivieren",
|
|
||||||
"Deactivated": "Deaktiviert",
|
|
||||||
"Move": "Verschieben",
|
"Move": "Verschieben",
|
||||||
"Move page": "Seite verschieben",
|
"Move page": "Seite verschieben",
|
||||||
"Move page to a different space.": "Seite in einen anderen Bereich verschieben.",
|
"Move page to a different space.": "Seite in einen anderen Bereich verschieben.",
|
||||||
@@ -451,129 +380,109 @@
|
|||||||
"Table of contents": "Inhaltsverzeichnis",
|
"Table of contents": "Inhaltsverzeichnis",
|
||||||
"Add headings (H1, H2, H3) to generate a table of contents.": "Fügen Sie Überschriften (H1, H2, H3) hinzu, um ein Inhaltsverzeichnis zu erstellen.",
|
"Add headings (H1, H2, H3) to generate a table of contents.": "Fügen Sie Überschriften (H1, H2, H3) hinzu, um ein Inhaltsverzeichnis zu erstellen.",
|
||||||
"Share": "Teilen",
|
"Share": "Teilen",
|
||||||
"Public sharing": "Öffentliche Freigabe",
|
"Public sharing": "Öffentliches Teilen",
|
||||||
"Shared by": "Geteilt von",
|
"Shared by": "Geteilt von",
|
||||||
"Shared at": "Geteilt am",
|
"Shared at": "Geteilt am",
|
||||||
"Inherits public sharing from": "Übernimmt öffentliche Freigabe von",
|
"Inherits public sharing from": "Erbt das öffentliche Teilen von",
|
||||||
"Share to web": "Im Web teilen",
|
"Share to web": "Im Web teilen",
|
||||||
"Shared to web": "Im Web geteilt",
|
"Shared to web": "Im Web geteilt",
|
||||||
"Anyone with the link can view this page": "Jeder mit dem Link kann diese Seite ansehen",
|
"Anyone with the link can view this page": "Jeder mit dem Link kann diese Seite ansehen",
|
||||||
"Make this page publicly accessible": "Diese Seite öffentlich zugänglich machen",
|
"Make this page publicly accessible": "Diese Seite öffentlich zugänglich machen",
|
||||||
"Include sub-pages": "Unterseiten einschließen",
|
"Include sub-pages": "Unterseiten einbeziehen",
|
||||||
"Make sub-pages public too": "Unterseiten ebenfalls öffentlich machen",
|
"Make sub-pages public too": "Unterseiten auch öffentlich machen",
|
||||||
"Allow search engines to index page": "Suchmaschinen das Indexieren der Seite erlauben",
|
"Allow search engines to index page": "Suchmaschinen erlauben, die Seite zu indexieren",
|
||||||
"Open page": "Seite öffnen",
|
"Open page": "Seite öffnen",
|
||||||
"Page": "Seite",
|
"Page": "Seite",
|
||||||
"Delete public share link": "Öffentlichen Freigabelink löschen",
|
"Delete public share link": "Öffentlichen Freigabelink löschen",
|
||||||
"Delete share": "Freigabe löschen",
|
"Delete share": "Freigabe löschen",
|
||||||
"Are you sure you want to delete this shared link?": "Möchten Sie diesen Freigabelink wirklich löschen?",
|
"Are you sure you want to delete this shared link?": "Möchten Sie diesen Freigabelink wirklich löschen?",
|
||||||
"Publicly shared pages from spaces you are a member of will appear here": "Öffentlich freigegebene Seiten aus Bereichen, in denen Sie Mitglied sind, werden hier angezeigt",
|
"Publicly shared pages from spaces you are a member of will appear here": "Öffentlich geteilte Seiten aus Bereichen, in denen Sie Mitglied sind, erscheinen hier",
|
||||||
"Share deleted successfully": "Freigabe erfolgreich gelöscht",
|
"Share deleted successfully": "Freigabe erfolgreich gelöscht",
|
||||||
"Share not found": "Freigabe nicht gefunden",
|
"Share not found": "Freigabe nicht gefunden",
|
||||||
"Failed to share page": "Seite konnte nicht geteilt werden",
|
"Failed to share page": "Fehler beim Teilen der Seite",
|
||||||
"Disable public sharing": "Öffentliches Teilen deaktivieren",
|
|
||||||
"Prevent members from sharing pages publicly.": "Verhindern Sie, dass Mitglieder Seiten öffentlich teilen.",
|
|
||||||
"Toggle public sharing": "Öffentliches Teilen umschalten",
|
|
||||||
"Toggle space public sharing": "Öffentliches Teilen im Bereich umschalten",
|
|
||||||
"Allow viewers to comment": "Zuschauern erlauben, Kommentare zu hinterlassen",
|
|
||||||
"Allow viewers to add comments on pages in this space.": "Erlauben Sie Zuschauern, Kommentare auf Seiten in diesem Bereich hinzuzufügen.",
|
|
||||||
"Toggle viewer comments": "Zuschauerkommentare umschalten",
|
|
||||||
"Public sharing is disabled at the workspace level": "Öffentliches Teilen ist auf der Arbeitsbereichsebene deaktiviert",
|
|
||||||
"Prevent pages in this space from being shared publicly.": "Verhindern Sie, dass Seiten in diesem Bereich öffentlich geteilt werden.",
|
|
||||||
"Page permissions": "Seitenberechtigungen",
|
|
||||||
"Control who can view and edit individual pages. Available with an enterprise license.": "Steuern Sie, wer einzelne Seiten ansehen und bearbeiten kann. Verfügbar mit einer Enterprise-Lizenz.",
|
|
||||||
"Enable public sharing": "Öffentliches Teilen aktivieren",
|
|
||||||
"Are you sure you want to enable public sharing? Members will be able to share pages publicly.": "Sind Sie sicher, dass Sie das öffentliche Teilen aktivieren möchten? Mitglieder können Seiten öffentlich teilen.",
|
|
||||||
"Are you sure you want to disable public sharing? All existing shared links in this workspace will be deleted.": "Sind Sie sicher, dass Sie das öffentliche Teilen deaktivieren möchten? Alle bestehenden Freigabelinks in diesem Arbeitsbereich werden gelöscht.",
|
|
||||||
"Are you sure you want to enable public sharing for this space?": "Sind Sie sicher, dass Sie das öffentliche Teilen für diesen Bereich aktivieren möchten?",
|
|
||||||
"Are you sure you want to disable public sharing? All existing shared links in this space will be deleted.": "Sind Sie sicher, dass Sie das öffentliche Teilen deaktivieren möchten? Alle bestehenden Freigabelinks in diesem Bereich werden gelöscht.",
|
|
||||||
"Public sharing is disabled": "Öffentliches Teilen ist deaktiviert",
|
|
||||||
"Public sharing has been disabled at the workspace level.": "Das öffentliche Teilen wurde auf der Arbeitsbereichsebene deaktiviert.",
|
|
||||||
"Public sharing has been disabled for this space.": "Das öffentliche Teilen wurde für diesen Bereich deaktiviert.",
|
|
||||||
"Copy page": "Seite kopieren",
|
"Copy page": "Seite kopieren",
|
||||||
"Copy page to a different space.": "Seite in einen anderen Bereich kopieren.",
|
"Copy page to a different space.": "Seite in einen anderen Bereich kopieren.",
|
||||||
"Page copied successfully": "Seite erfolgreich kopiert",
|
"Page copied successfully": "Seite erfolgreich kopiert",
|
||||||
"Page duplicated successfully": "Seite erfolgreich dupliziert",
|
"Page duplicated successfully": "Seite erfolgreich dupliziert",
|
||||||
"Find": "Suchen",
|
"Find": "Finden",
|
||||||
"Not found": "Nicht gefunden",
|
"Not found": "Nicht gefunden",
|
||||||
"Previous Match (Shift+Enter)": "Vorheriger Treffer (Umschalt+Eingabe)",
|
"Previous Match (Shift+Enter)": "Vorheriger Treffer (Shift+Enter)",
|
||||||
"Next match (Enter)": "Nächster Treffer (Eingabe)",
|
"Next match (Enter)": "Nächster Treffer (Enter)",
|
||||||
"Match case (Alt+C)": "Groß-/Kleinschreibung beachten (Alt+C)",
|
"Match case (Alt+C)": "Groß-/Kleinschreibung beachten (Alt+C)",
|
||||||
"Replace": "Ersetzen",
|
"Replace": "Ersetzen",
|
||||||
"Close (Escape)": "Schließen (Escape)",
|
"Close (Escape)": "Schließen (Escape)",
|
||||||
"Replace (Enter)": "Ersetzen (Eingabe)",
|
"Replace (Enter)": "Ersetzen (Enter)",
|
||||||
"Replace all (Ctrl+Alt+Enter)": "Alle ersetzen (Strg+Alt+Eingabe)",
|
"Replace all (Ctrl+Alt+Enter)": "Alle ersetzen (Ctrl+Alt+Enter)",
|
||||||
"Replace all": "Alle ersetzen",
|
"Replace all": "Alle ersetzen",
|
||||||
"View all": "Alle anzeigen",
|
"View all spaces": "Alle Räume anzeigen",
|
||||||
"View all spaces": "Alle Bereiche anzeigen",
|
|
||||||
"Error": "Fehler",
|
"Error": "Fehler",
|
||||||
"Failed to disable MFA": "MFA konnte nicht deaktiviert werden",
|
"Failed to disable MFA": "Deaktivierung der MFA fehlgeschlagen",
|
||||||
"Disable two-factor authentication": "Zwei-Faktor-Authentifizierung deaktivieren",
|
"Disable two-factor authentication": "Zwei-Faktor-Authentifizierung deaktivieren",
|
||||||
"Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Die Deaktivierung der Zwei-Faktor-Authentifizierung macht Ihr Konto weniger sicher. Sie benötigen nur Ihr Passwort, um sich anzumelden.",
|
"Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Die Deaktivierung der Zwei-Faktor-Authentifizierung macht Ihr Konto weniger sicher. Sie benötigen nur Ihr Passwort, um sich anzumelden.",
|
||||||
"Please enter your password to disable two-factor authentication:": "Bitte geben Sie Ihr Passwort ein, um die Zwei-Faktor-Authentifizierung zu deaktivieren:",
|
"Please enter your password to disable two-factor authentication:": "Bitte geben Sie Ihr Passwort ein, um die Zwei-Faktor-Authentifizierung zu deaktivieren:",
|
||||||
"Two-factor authentication has been enabled": "Die Zwei-Faktor-Authentifizierung wurde aktiviert",
|
"Two-factor authentication has been enabled": "Zwei-Faktor-Authentifizierung wurde aktiviert",
|
||||||
"Two-factor authentication has been disabled": "Die Zwei-Faktor-Authentifizierung wurde deaktiviert",
|
"Two-factor authentication has been disabled": "Zwei-Faktor-Authentifizierung wurde deaktiviert",
|
||||||
"2-step verification": "Bestätigung in zwei Schritten",
|
"2-step verification": "2-Schritt-Verifizierung",
|
||||||
"Protect your account with an additional verification layer when signing in.": "Schützen Sie Ihr Konto mit einer zusätzlichen Verifizierungsschicht beim Anmelden.",
|
"Protect your account with an additional verification layer when signing in.": "Schützen Sie Ihr Konto mit einer zusätzlichen Verifizierungsschicht beim Anmelden.",
|
||||||
"Two-factor authentication is active on your account.": "Die Zwei-Faktor-Authentifizierung ist auf Ihrem Konto aktiv.",
|
"Two-factor authentication is active on your account.": "Die Zwei-Faktor-Authentifizierung ist auf Ihrem Konto aktiv.",
|
||||||
"Add 2FA method": "2FA-Methode hinzufügen",
|
"Add 2FA method": "2FA-Methode hinzufügen",
|
||||||
"Backup codes": "Backup-Codes",
|
"Backup codes": "Sicherungscodes",
|
||||||
"Disable": "Deaktivieren",
|
"Disable": "Deaktivieren",
|
||||||
"Invalid verification code": "Ungültiger Bestätigungscode",
|
"Invalid verification code": "Ungültiger Bestätigungscode",
|
||||||
"New backup codes have been generated": "Neue Backup-Codes wurden erstellt",
|
"New backup codes have been generated": "Neue Sicherungscodes wurden generiert",
|
||||||
"Failed to regenerate backup codes": "Backup-Codes konnten nicht neu erstellt werden",
|
"Failed to regenerate backup codes": "Fehler beim Generieren neuer Sicherungscodes",
|
||||||
"About backup codes": "Über Backup-Codes",
|
"About backup codes": "Über Sicherungscodes",
|
||||||
"Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Sicherungscodes können verwendet werden, um auf Ihr Konto zuzugreifen, wenn Sie den Zugang zu Ihrer Authenticator-App verlieren. Jeder Code kann nur einmal verwendet werden.",
|
"Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Sicherungscodes können verwendet werden, um auf Ihr Konto zuzugreifen, wenn Sie den Zugang zu Ihrer Authenticator-App verlieren. Jeder Code kann nur einmal verwendet werden.",
|
||||||
"You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Sie können jederzeit neue Sicherungscodes generieren. Dies wird alle vorhandenen Codes ungültig machen.",
|
"You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Sie können jederzeit neue Sicherungscodes generieren. Dies wird alle vorhandenen Codes ungültig machen.",
|
||||||
"Confirm password": "Passwort bestätigen",
|
"Confirm password": "Passwort bestätigen",
|
||||||
"Generate new backup codes": "Neue Backup-Codes erstellen",
|
"Generate new backup codes": "Neue Sicherungscodes generieren",
|
||||||
"Save your new backup codes": "Speichern Sie Ihre neuen Backup-Codes",
|
"Save your new backup codes": "Speichern Sie Ihre neuen Sicherungscodes",
|
||||||
"Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Speichern Sie diese Codes an einem sicheren Ort. Ihre alten Sicherungscodes sind nicht mehr gültig.",
|
"Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Speichern Sie diese Codes an einem sicheren Ort. Ihre alten Sicherungscodes sind nicht mehr gültig.",
|
||||||
"Your new backup codes": "Ihre neuen Backup-Codes",
|
"Your new backup codes": "Ihre neuen Sicherungscodes",
|
||||||
"I've saved my backup codes": "Ich habe meine Backup-Codes gespeichert",
|
"I've saved my backup codes": "Ich habe meine Sicherungscodes gespeichert",
|
||||||
"Failed to setup MFA": "MFA konnte nicht eingerichtet werden",
|
"Failed to setup MFA": "Fehler beim Einrichten der MFA",
|
||||||
"Setup & Verify": "Einrichten und bestätigen",
|
"Setup & Verify": "Einrichten & Überprüfen",
|
||||||
"Add to authenticator": "Zum Authenticator hinzufügen",
|
"Add to authenticator": "Zum Authenticator hinzufügen",
|
||||||
"1. Scan this QR code with your authenticator app": "1. Scannen Sie diesen QR-Code mit Ihrer Authenticator-App",
|
"1. Scan this QR code with your authenticator app": "1. Scannen Sie diesen QR-Code mit Ihrer Authenticator-App",
|
||||||
"Can't scan the code?": "Code kann nicht gescannt werden?",
|
"Can't scan the code?": "Code kann nicht gescannt werden?",
|
||||||
"Enter this code manually in your authenticator app:": "Geben Sie diesen Code manuell in Ihrer Authenticator-App ein:",
|
"Enter this code manually in your authenticator app:": "Geben Sie diesen Code manuell in Ihrer Authenticator-App ein:",
|
||||||
"2. Enter the 6-digit code from your authenticator": "2. Geben Sie den 6-stelligen Code aus Ihrem Authenticator ein",
|
"2. Enter the 6-digit code from your authenticator": "2. Geben Sie den 6-stelligen Code aus Ihrem Authenticator ein",
|
||||||
"Verify and enable": "Bestätigen und aktivieren",
|
"Verify and enable": "Überprüfen und aktivieren",
|
||||||
"Failed to generate QR code. Please try again.": "Fehler beim Generieren des QR-Codes. Bitte versuchen Sie es erneut.",
|
"Failed to generate QR code. Please try again.": "Fehler beim Generieren des QR-Codes. Bitte versuchen Sie es erneut.",
|
||||||
"Backup": "Backup",
|
"Backup": "Sicherung",
|
||||||
"Save codes": "Codes speichern",
|
"Save codes": "Codes speichern",
|
||||||
"Save your backup codes": "Speichern Sie Ihre Backup-Codes",
|
"Save your backup codes": "Speichern Sie Ihre Sicherungscodes",
|
||||||
"These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Diese Codes können verwendet werden, um auf Ihr Konto zuzugreifen, wenn Sie den Zugang zu Ihrer Authenticator-App verlieren. Jeder Code kann nur einmal verwendet werden.",
|
"These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Diese Codes können verwendet werden, um auf Ihr Konto zuzugreifen, wenn Sie den Zugang zu Ihrer Authenticator-App verlieren. Jeder Code kann nur einmal verwendet werden.",
|
||||||
"Print": "Drucken",
|
"Print": "Drucken",
|
||||||
"Two-factor authentication has been set up. Please log in again.": "Zwei-Faktor-Authentifizierung wurde eingerichtet. Bitte melden Sie sich erneut an.",
|
"Two-factor authentication has been set up. Please log in again.": "Zwei-Faktor-Authentifizierung wurde eingerichtet. Bitte melden Sie sich erneut an.",
|
||||||
"Two-Factor authentication required": "Zwei-Faktor-Authentifizierung erforderlich",
|
"Two-Factor authentication required": "Zwei-Faktor-Authentifizierung erforderlich",
|
||||||
"Your workspace requires two-factor authentication for all users": "Ihr Workspace erfordert Zwei-Faktor-Authentifizierung für alle Benutzer",
|
"Your workspace requires two-factor authentication for all users": "Ihr Arbeitsbereich erfordert die Zwei-Faktor-Authentifizierung für alle Benutzer",
|
||||||
"To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Um weiterhin auf Ihren Arbeitsbereich zuzugreifen, müssen Sie die Zwei-Faktor-Authentifizierung einrichten. Dies fügt Ihrem Konto eine zusätzliche Sicherheitsebene hinzu.",
|
"To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Um weiterhin auf Ihren Arbeitsbereich zuzugreifen, müssen Sie die Zwei-Faktor-Authentifizierung einrichten. Dies fügt Ihrem Konto eine zusätzliche Sicherheitsebene hinzu.",
|
||||||
"Set up two-factor authentication": "Zwei-Faktor-Authentifizierung einrichten",
|
"Set up two-factor authentication": "Zwei-Faktor-Authentifizierung einrichten",
|
||||||
"Cancel and logout": "Abbrechen und abmelden",
|
"Cancel and logout": "Abbrechen und abmelden",
|
||||||
"Your workspace requires two-factor authentication. Please set it up to continue.": "Ihr Arbeitsbereich erfordert eine Zwei-Faktor-Authentifizierung. Bitte richten Sie diese ein, um fortzufahren.",
|
"Your workspace requires two-factor authentication. Please set it up to continue.": "Ihr Arbeitsbereich erfordert eine Zwei-Faktor-Authentifizierung. Bitte richten Sie diese ein, um fortzufahren.",
|
||||||
"This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Dadurch wird Ihrem Konto eine zusätzliche Sicherheitsebene hinzugefügt, indem ein Bestätigungscode von Ihrer Authenticator-App verlangt wird.",
|
"This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Dadurch wird Ihrem Konto eine zusätzliche Sicherheitsebene hinzugefügt, indem ein Bestätigungscode von Ihrer Authenticator-App verlangt wird.",
|
||||||
"Password is required": "Passwort ist erforderlich",
|
"Password is required": "Passwort erforderlich",
|
||||||
"Password must be at least 8 characters": "Das Passwort muss mindestens 8 Zeichen lang sein",
|
"Password must be at least 8 characters": "Passwort muss mindestens 8 Zeichen lang sein",
|
||||||
"Please enter a 6-digit code": "Bitte geben Sie einen 6-stelligen Code ein",
|
"Please enter a 6-digit code": "Bitte geben Sie einen 6-stelligen Code ein",
|
||||||
"Code must be exactly 6 digits": "Der Code muss genau 6 Ziffern haben",
|
"Code must be exactly 6 digits": "Code muss genau 6-stellig sein",
|
||||||
"Enter the 6-digit code found in your authenticator app": "Geben Sie den 6-stelligen Code aus Ihrer Authenticator-App ein",
|
"Enter the 6-digit code found in your authenticator app": "Geben Sie den 6-stelligen Code ein, der in Ihrer Authenticator-App zu finden ist",
|
||||||
"Need help authenticating?": "Brauchen Sie Hilfe bei der Authentifizierung?",
|
"Need help authenticating?": "Brauchen Sie Hilfe bei der Authentifizierung?",
|
||||||
"MFA QR Code": "MFA-QR-Code",
|
"MFA QR Code": "MFA QR-Code",
|
||||||
"Account created successfully. Please log in to set up two-factor authentication.": "Konto erfolgreich erstellt. Bitte melden Sie sich an, um die Zwei-Faktor-Authentifizierung einzurichten.",
|
"Account created successfully. Please log in to set up two-factor authentication.": "Konto erfolgreich erstellt. Bitte melden Sie sich an, um die Zwei-Faktor-Authentifizierung einzurichten.",
|
||||||
"Password reset successful. Please log in with your new password and complete two-factor authentication.": "Passwort erfolgreich zurückgesetzt. Bitte melden Sie sich mit Ihrem neuen Passwort an und führen Sie die Zwei-Faktor-Authentifizierung durch.",
|
"Password reset successful. Please log in with your new password and complete two-factor authentication.": "Passwort erfolgreich zurückgesetzt. Bitte melden Sie sich mit Ihrem neuen Passwort an und führen Sie die Zwei-Faktor-Authentifizierung durch.",
|
||||||
"Password reset successful. Please log in with your new password to set up two-factor authentication.": "Passwort erfolgreich zurückgesetzt. Bitte melden Sie sich mit Ihrem neuen Passwort an, um die Zwei-Faktor-Authentifizierung einzurichten.",
|
"Password reset successful. Please log in with your new password to set up two-factor authentication.": "Passwort erfolgreich zurückgesetzt. Bitte melden Sie sich mit Ihrem neuen Passwort an, um die Zwei-Faktor-Authentifizierung einzurichten.",
|
||||||
"Password reset was successful. Please log in with your new password.": "Passwort erfolgreich zurückgesetzt. Bitte melden Sie sich mit Ihrem neuen Passwort an.",
|
"Password reset was successful. Please log in with your new password.": "Passwort erfolgreich zurückgesetzt. Bitte melden Sie sich mit Ihrem neuen Passwort an.",
|
||||||
"Two-factor authentication": "Zwei-Faktor-Authentifizierung",
|
"Two-factor authentication": "Zwei-Faktor-Authentifizierung",
|
||||||
"Use authenticator app instead": "Stattdessen Authenticator-App verwenden",
|
"Use authenticator app instead": "Stattdessen Authenticator-App verwenden",
|
||||||
"Verify backup code": "Backup-Code bestätigen",
|
"Verify backup code": "Sicherungscode überprüfen",
|
||||||
"Use backup code": "Backup-Code verwenden",
|
"Use backup code": "Sicherungscode verwenden",
|
||||||
"Enter one of your backup codes": "Geben Sie einen Ihrer Backup-Codes ein",
|
"Enter one of your backup codes": "Geben Sie einen Ihrer Sicherungscodes ein",
|
||||||
"Backup code": "Backup-Code",
|
"Backup code": "Sicherungscode",
|
||||||
"Enter one of your backup codes. Each backup code can only be used once.": "Geben Sie einen Ihrer Sicherungscodes ein. Jeder Sicherungscode kann nur einmal verwendet werden.",
|
"Enter one of your backup codes. Each backup code can only be used once.": "Geben Sie einen Ihrer Sicherungscodes ein. Jeder Sicherungscode kann nur einmal verwendet werden.",
|
||||||
"Verify": "Bestätigen",
|
"Verify": "Überprüfen",
|
||||||
"Trash": "Papierkorb",
|
"Trash": "Papierkorb",
|
||||||
"Pages in trash will be permanently deleted after {{count}} days.": "Seiten im Papierkorb werden nach {{count}} Tagen endgültig gelöscht.",
|
"Pages in trash will be permanently deleted after 30 days.": "Seiten im Papierkorb werden nach 30 Tagen endgültig gelöscht.",
|
||||||
"Deleted": "Gelöscht",
|
"Deleted": "Gelöscht",
|
||||||
"No pages in trash": "Keine Seiten im Papierkorb",
|
"No pages in trash": "Keine Seiten im Papierkorb",
|
||||||
"Permanently delete page?": "Seite endgültig löschen?",
|
"Permanently delete page?": "Seite endgültig löschen?",
|
||||||
@@ -582,470 +491,9 @@
|
|||||||
"Move to trash": "In den Papierkorb verschieben",
|
"Move to trash": "In den Papierkorb verschieben",
|
||||||
"Move this page to trash?": "Diese Seite in den Papierkorb verschieben?",
|
"Move this page to trash?": "Diese Seite in den Papierkorb verschieben?",
|
||||||
"Restore page": "Seite wiederherstellen",
|
"Restore page": "Seite wiederherstellen",
|
||||||
"Permanently delete": "Permanently delete",
|
|
||||||
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> moved this page to Trash {{time}}.",
|
|
||||||
"Page moved to trash": "Seite in den Papierkorb verschoben",
|
"Page moved to trash": "Seite in den Papierkorb verschoben",
|
||||||
"Page restored successfully": "Seite erfolgreich wiederhergestellt",
|
"Page restored successfully": "Seite erfolgreich wiederhergestellt",
|
||||||
"Deleted by": "Gelöscht von",
|
"Deleted by": "Gelöscht von",
|
||||||
"Deleted at": "Gelöscht am",
|
"Deleted at": "Gelöscht am",
|
||||||
"Preview": "Vorschau",
|
"Preview": "Vorschau"
|
||||||
"Subpages": "Unterseiten",
|
|
||||||
"Failed to load subpages": "Unterseiten konnten nicht geladen werden",
|
|
||||||
"No subpages": "Keine Unterseiten",
|
|
||||||
"Subpages (Child pages)": "Unterseiten (untergeordnete Seiten)",
|
|
||||||
"List all subpages of the current page": "Alle Unterseiten der aktuellen Seite auflisten",
|
|
||||||
"Attachments": "Anhänge",
|
|
||||||
"All spaces": "Alle Bereiche",
|
|
||||||
"Unknown": "Unbekannt",
|
|
||||||
"Find a space": "Einen Bereich finden",
|
|
||||||
"Search in all your spaces": "In all Ihren Bereichen suchen",
|
|
||||||
"Type": "Typ",
|
|
||||||
"Enterprise": "Enterprise",
|
|
||||||
"Download attachment": "Anhang herunterladen",
|
|
||||||
"Allowed email domains": "Erlaubte E-Mail-Domains",
|
|
||||||
"Only users with email addresses from these domains can signup via SSO.": "Nur Benutzer mit E-Mail-Adressen aus diesen Domains können sich per SSO registrieren.",
|
|
||||||
"Enter valid domain names separated by comma or space": "Geben Sie gültige Domainnamen ein, getrennt durch Komma oder Leerzeichen",
|
|
||||||
"Enforce two-factor authentication": "Zwei-Faktor-Authentifizierung erzwingen",
|
|
||||||
"Once enforced, all members must enable two-factor authentication to access the workspace.": "Sobald es erzwungen wird, müssen alle Mitglieder die Zwei-Faktor-Authentifizierung aktivieren, um auf den Arbeitsbereich zugreifen zu können.",
|
|
||||||
"Toggle MFA enforcement": "MFA-Erzwingung umschalten",
|
|
||||||
"Display name": "Anzeigename",
|
|
||||||
"Allow signup": "Registrierung erlauben",
|
|
||||||
"Enabled": "Aktiviert",
|
|
||||||
"Advanced Settings": "Erweiterte Einstellungen",
|
|
||||||
"Enable TLS/SSL": "TLS/SSL aktivieren",
|
|
||||||
"Use secure connection to LDAP server": "Sichere Verbindung zum LDAP-Server verwenden",
|
|
||||||
"Group sync": "Gruppensynchronisierung",
|
|
||||||
"No SSO providers found.": "Keine SSO-Anbieter gefunden.",
|
|
||||||
"Delete SSO provider": "SSO-Anbieter löschen",
|
|
||||||
"Are you sure you want to delete this SSO provider?": "Sind Sie sicher, dass Sie diesen SSO-Anbieter löschen möchten?",
|
|
||||||
"Action": "Aktion",
|
|
||||||
"{{ssoProviderType}} configuration": "{{ssoProviderType}}-Konfiguration",
|
|
||||||
"Icon": "Symbol",
|
|
||||||
"Upload image": "Bild hochladen",
|
|
||||||
"Remove image": "Bild entfernen",
|
|
||||||
"Failed to remove image": "Fehler beim Entfernen des Bildes",
|
|
||||||
"Image exceeds 10MB limit.": "Bild überschreitet das Limit von 10 MB.",
|
|
||||||
"Image removed successfully": "Bild erfolgreich entfernt",
|
|
||||||
"API key": "API-Schlüssel",
|
|
||||||
"API keys": "API-Schlüssel",
|
|
||||||
"API management": "API-Verwaltung",
|
|
||||||
"Custom expiration date": "Benutzerdefiniertes Ablaufdatum",
|
|
||||||
"Enter a descriptive token name": "Geben Sie einen beschreibenden Token-Namen ein",
|
|
||||||
"Expiration": "Ablauf",
|
|
||||||
"Expired": "Abgelaufen",
|
|
||||||
"Expires": "Läuft ab",
|
|
||||||
"Last use": "Zuletzt verwendet",
|
|
||||||
"No API keys found": "Keine API-Schlüssel gefunden",
|
|
||||||
"No expiration": "Kein Ablauf",
|
|
||||||
"Revoked successfully": "Erfolgreich widerrufen",
|
|
||||||
"Select expiration date": "Ablaufdatum wählen",
|
|
||||||
"This action cannot be undone. Any applications using this API key will stop working.": "Diese Aktion kann nicht rückgängig gemacht werden. Alle Anwendungen, die diesen API-Schlüssel verwenden, werden nicht mehr funktionieren.",
|
|
||||||
"Update": "Aktualisieren",
|
|
||||||
"Update {{credential}}": "{{credential}} aktualisieren",
|
|
||||||
"Manage API keys for all users in the workspace": "Verwalten Sie API-Schlüssel für alle Benutzer im Arbeitsbereich",
|
|
||||||
"Restrict API key creation to admins": "API-Schlüsselerstellung auf Administratoren beschränken",
|
|
||||||
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Nur Administratoren und Eigentümer können neue API-Schlüssel erstellen. Bestehende Mitgliederschlüssel funktionieren weiterhin.",
|
|
||||||
"Toggle restrict API keys to admins": "Beschränkung der API-Schlüssel auf Administratoren umschalten",
|
|
||||||
"API key creation is restricted to admins by your workspace administrator.": "Die Erstellung von API-Schlüsseln ist durch Ihren Workspace-Administrator auf Administratoren beschränkt.",
|
|
||||||
"AI settings": "KI-Einstellungen",
|
|
||||||
"AI search": "KI-Suche",
|
|
||||||
"AI Answer": "KI-Antwort",
|
|
||||||
"Ask AI": "KI fragen",
|
|
||||||
"AI is thinking...": "Die KI überlegt...",
|
|
||||||
"Thinking": "Denkt nach",
|
|
||||||
"Ask a question...": "Fragen stellen...",
|
|
||||||
"AI Answers": "KI-Antworten",
|
|
||||||
"AI-powered search (AI Answers)": "KI-unterstützte Suche (KI-Antworten)",
|
|
||||||
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "Die KI-Suche verwendet Vektor-Einbettungen, um semantische Suchfunktionen in Ihrem Arbeitsbereich bereitzustellen.",
|
|
||||||
"Toggle AI search": "KI-Suche umschalten",
|
|
||||||
"Generative AI (Ask AI)": "Generative KI (KI fragen)",
|
|
||||||
"Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Aktivieren Sie die KI-unterstützte Inhaltserstellung im Editor. Ermöglicht Benutzern das Erzeugen, Verbessern, Übersetzen und Transformieren von Text.",
|
|
||||||
"Toggle generative AI": "Generative KI umschalten",
|
|
||||||
"Upgrade your plan": "Upgrade Ihres Plans",
|
|
||||||
"Available with a paid license": "Verfügbar mit einer kostenpflichtigen Lizenz",
|
|
||||||
"Upgrade your license tier.": "Stufen Sie Ihre Lizenz hoch.",
|
|
||||||
"AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "KI ist nur in der Docmost Enterprise-Edition verfügbar. Kontaktieren Sie sales@docmost.com.",
|
|
||||||
"AI & MCP": "KI & MCP",
|
|
||||||
"AI": "KI",
|
|
||||||
"MCP": "MCP",
|
|
||||||
"Model Context Protocol (MCP)": "Model Context Protocol (MCP)",
|
|
||||||
"Enable the MCP server to allow AI assistants and tools to interact with your workspace content.": "Aktivieren Sie den MCP-Server, damit KI-Assistenten und -Tools mit den Inhalten Ihres Arbeitsbereichs interagieren können.",
|
|
||||||
"MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "MCP ist nur in der Docmost Enterprise-Edition verfügbar. Kontaktieren Sie sales@docmost.com.",
|
|
||||||
"MCP Server URL": "MCP-Server-URL",
|
|
||||||
"Use your API key for authentication. You can manage API keys in your account settings.": "Verwenden Sie Ihren API-Schlüssel zur Authentifizierung. API-Schlüssel können in Ihren Kontoeinstellungen verwaltet werden.",
|
|
||||||
"Supported tools": "Unterstützte Tools",
|
|
||||||
"Your workspace has MCP enabled. Use your API key to connect AI assistants.": "In Ihrem Arbeitsbereich ist MCP aktiviert. Verwenden Sie Ihren API-Schlüssel, um KI-Assistenten anzubinden.",
|
|
||||||
"MCP server URL:": "MCP-Server-URL:",
|
|
||||||
"Learn more": "Mehr erfahren",
|
|
||||||
"Manage API keys for all users in the workspace. View the <anchor>API documentation</anchor> for usage details.": "Verwalten Sie API-Schlüssel für alle Nutzer im Arbeitsbereich. Siehe die <anchor>API-Dokumentation</anchor> für Details zur Verwendung.",
|
|
||||||
"View the <anchor>API documentation</anchor> for usage details.": "Siehe die <anchor>API-Dokumentation</anchor> für Details zur Verwendung.",
|
|
||||||
"View the <anchor>MCP documentation</anchor>.": "Sehen Sie die <anchor>MCP-Dokumentation</anchor> ein.",
|
|
||||||
"Sources": "Quellen",
|
|
||||||
"AI Answers not available for attachments": "KI-Antworten sind für Anhänge nicht verfügbar",
|
|
||||||
"No answer available": "Keine Antwort verfügbar",
|
|
||||||
"Background color": "Hintergrundfarbe",
|
|
||||||
"Highlight color": "Hervorhebungsfarbe",
|
|
||||||
"Remove color": "Farbe entfernen",
|
|
||||||
"Notifications": "Benachrichtigungen",
|
|
||||||
"No notifications": "Keine Benachrichtigungen",
|
|
||||||
"No unread notifications": "Keine ungelesenen Benachrichtigungen",
|
|
||||||
"All notifications": "Alle Benachrichtigungen",
|
|
||||||
"Unread only": "Nur ungelesen",
|
|
||||||
"Mark all as read": "Alle als gelesen markieren",
|
|
||||||
"Mark as read": "Als gelesen markieren",
|
|
||||||
"More options": "Weitere Optionen",
|
|
||||||
"<bold>{{name}}</bold> mentioned you in a comment": "<bold>{{name}}</bold> hat Sie in einem Kommentar erwähnt",
|
|
||||||
"<bold>{{name}}</bold> commented on a page": "<bold>{{name}}</bold> hat einen Kommentar auf einer Seite hinterlassen",
|
|
||||||
"<bold>{{name}}</bold> resolved a comment": "<bold>{{name}}</bold> hat einen Kommentar gelöst",
|
|
||||||
"<bold>{{name}}</bold> mentioned you on a page": "<bold>{{name}}</bold> hat Sie auf einer Seite erwähnt",
|
|
||||||
"<bold>{{name}}</bold> gave you edit access to a page": "<bold>{{name}}</bold> hat Ihnen Bearbeitungszugriff auf eine Seite gegeben",
|
|
||||||
"<bold>{{name}}</bold> gave you view access to a page": "<bold>{{name}}</bold> hat Ihnen Ansichtszugriff auf eine Seite gegeben",
|
|
||||||
"<bold>{{name}}</bold> updated a page": "<bold>{{name}}</bold> hat eine Seite aktualisiert",
|
|
||||||
"Watch page": "Seite beobachten",
|
|
||||||
"Stop watching": "Nicht mehr beobachten",
|
|
||||||
"Watch space": "Bereich beobachten",
|
|
||||||
"Stop watching space": "Bereich nicht mehr beobachten",
|
|
||||||
"Email notifications": "E-Mail-Benachrichtigungen",
|
|
||||||
"Page updates": "Seitenaktualisierungen",
|
|
||||||
"Get notified when pages you watch are updated.": "Erhalten Sie eine Benachrichtigung, wenn Seiten, die Sie beobachten, aktualisiert werden.",
|
|
||||||
"Page mentions": "Seiten-Erwähnungen",
|
|
||||||
"Get notified when someone mentions you on a page.": "Erhalten Sie eine Benachrichtigung, wenn Sie jemand auf einer Seite erwähnt.",
|
|
||||||
"Comment mentions": "Kommentar-Erwähnungen",
|
|
||||||
"Get notified when someone mentions you in a comment.": "Erhalten Sie eine Benachrichtigung, wenn Sie jemand in einem Kommentar erwähnt.",
|
|
||||||
"New comments": "Neue Kommentare",
|
|
||||||
"Get notified about new comments on threads you participate in.": "Erhalten Sie eine Benachrichtigung über neue Kommentare in Threads, an denen Sie teilnehmen.",
|
|
||||||
"Resolved comments": "Erledigte Kommentare",
|
|
||||||
"Get notified when your comment is resolved.": "Erhalten Sie eine Benachrichtigung, wenn Ihr Kommentar erledigt wurde.",
|
|
||||||
"You are now watching this page": "Sie beobachten diese Seite jetzt",
|
|
||||||
"You are no longer watching this page": "Sie beobachten diese Seite nicht mehr",
|
|
||||||
"You are now watching this space": "Sie beobachten diesen Bereich jetzt",
|
|
||||||
"You are no longer watching this space": "Sie beobachten diesen Bereich nicht mehr",
|
|
||||||
"Direct": "Direkt",
|
|
||||||
"Updates": "Aktualisierungen",
|
|
||||||
"Today": "Heute",
|
|
||||||
"Yesterday": "Gestern",
|
|
||||||
"This week": "Diese Woche",
|
|
||||||
"Older": "Älter",
|
|
||||||
"Restricted page": "Eingeschränkte Seite",
|
|
||||||
"Restricted pages cannot be shared publicly.": "Eingeschränkte Seiten können nicht öffentlich geteilt werden.",
|
|
||||||
"Restricted by parent": "Eingeschränkt durch die übergeordnete Seite",
|
|
||||||
"Restricted": "Eingeschränkt",
|
|
||||||
"Open": "Offen",
|
|
||||||
"Inherits restrictions from ancestor page": "Erbt Einschränkungen von einer übergeordneten Seite",
|
|
||||||
"Only people listed below can access this page": "Nur die unten aufgeführten Personen können auf diese Seite zugreifen.",
|
|
||||||
"Everyone in this space can access": "Jeder in diesem Bereich kann darauf zugreifen.",
|
|
||||||
"No additional restrictions on this page": "Keine zusätzlichen Einschränkungen auf dieser Seite",
|
|
||||||
"Only specific people can access": "Nur bestimmte Personen können zugreifen",
|
|
||||||
"Use only inherited restrictions": "Nur geerbte Einschränkungen verwenden",
|
|
||||||
"Add restrictions on top of inherited": "Einschränkungen zusätzlich zu den geerbten hinzufügen",
|
|
||||||
"Inherited restriction": "Geerbte Einschränkung",
|
|
||||||
"Access limited by": "Zugriff beschränkt durch",
|
|
||||||
"Restrict access to control who can view and edit this page": "Beschränken Sie den Zugriff, um festzulegen, wer diese Seite ansehen und bearbeiten kann.",
|
|
||||||
"Add additional restrictions specific to this page": "Fügen Sie zusätzliche, für diese Seite spezifische Einschränkungen hinzu.",
|
|
||||||
"Access": "Zugriff",
|
|
||||||
"People with access": "Personen mit Zugriff",
|
|
||||||
"Remove all": "Alle entfernen",
|
|
||||||
"Remove access": "Zugriff entfernen",
|
|
||||||
"Remove all access": "Alle Zugriffsrechte entfernen",
|
|
||||||
"Are you sure you want to remove this member's access to the page?": "Sind Sie sicher, dass Sie den Zugriff dieses Mitglieds auf die Seite entfernen möchten?",
|
|
||||||
"Are you sure you want to remove all specific access? This will make the page open to everyone in the space.": "Sind Sie sicher, dass Sie alle spezifischen Zugriffsrechte entfernen möchten? Dadurch wird die Seite für alle in diesem Bereich zugänglich.",
|
|
||||||
"Trash retention": "Aufbewahrungsdauer des Papierkorbs",
|
|
||||||
"Pages in trash will be permanently deleted after this period.": "Seiten im Papierkorb werden nach Ablauf dieses Zeitraums endgültig gelöscht.",
|
|
||||||
"Trash retention updated": "Aufbewahrungsdauer des Papierkorbs aktualisiert",
|
|
||||||
"Failed to update trash retention": "Aktualisierung der Aufbewahrungsdauer des Papierkorbs fehlgeschlagen",
|
|
||||||
"Removed page restriction": "Seitenbeschränkung entfernt",
|
|
||||||
"Added page permission": "Seitenberechtigung hinzugefügt",
|
|
||||||
"Removed page permission": "Seitenberechtigung entfernt",
|
|
||||||
"day": "Tag",
|
|
||||||
"days": "Tage",
|
|
||||||
"week": "Woche",
|
|
||||||
"weeks": "Wochen",
|
|
||||||
"month": "Monat",
|
|
||||||
"months": "Monate",
|
|
||||||
"year": "Jahr",
|
|
||||||
"years": "Jahre",
|
|
||||||
"Period": "Zeitraum",
|
|
||||||
"Fixed date": "Festes Datum",
|
|
||||||
"Indefinitely": "Unbegrenzt",
|
|
||||||
"Days": "Tage",
|
|
||||||
"Weeks": "Wochen",
|
|
||||||
"Months": "Monate",
|
|
||||||
"Years": "Jahre",
|
|
||||||
"Pick a date": "Datum auswählen",
|
|
||||||
"Maximum is {{max}} {{unit}} for this unit": "Das Maximum für diese Einheit beträgt {{max}} {{unit}}",
|
|
||||||
"Never expires. Verifiers can re-verify at any time.": "Läuft nie ab. Prüfer können die Seite jederzeit erneut verifizieren.",
|
|
||||||
"Verified": "Verifiziert",
|
|
||||||
"Review needed": "Prüfung erforderlich",
|
|
||||||
"Verification expired": "Verifizierung abgelaufen",
|
|
||||||
"Draft": "Entwurf",
|
|
||||||
"In Approval": "In Genehmigung",
|
|
||||||
"In approval": "In Genehmigung",
|
|
||||||
"Approved": "Genehmigt",
|
|
||||||
"Obsolete": "Veraltet",
|
|
||||||
"Expiring": "Läuft bald ab",
|
|
||||||
"Set up verification": "Verifizierung einrichten",
|
|
||||||
"Verify page": "Seite verifizieren",
|
|
||||||
"Page verification": "Seitenverifizierung",
|
|
||||||
"Add verification": "Verifizierung hinzufügen",
|
|
||||||
"Edit verification": "Verifizierung bearbeiten",
|
|
||||||
"Search by title": "Nach Titel suchen",
|
|
||||||
"Choose how this page should stay accurate.": "Wählen Sie aus, wie diese Seite aktuell gehalten werden soll.",
|
|
||||||
"Recurring verification": "Wiederkehrende Verifizierung",
|
|
||||||
"Verifiers re-confirm this page on a schedule.": "Prüfer bestätigen diese Seite nach einem Zeitplan erneut.",
|
|
||||||
"Re-verify on a schedule (e.g every 30 days )": "Nach einem Zeitplan erneut verifizieren (z. B. alle 30 Tage)",
|
|
||||||
"Page stays editable at all times": "Die Seite bleibt jederzeit bearbeitbar",
|
|
||||||
"Best for runbooks, FAQs, living documentation": "Am besten für Runbooks, FAQs und lebende Dokumentation geeignet",
|
|
||||||
"Approval workflow": "Genehmigungsworkflow",
|
|
||||||
"Formal document lifecycle with named approvers.": "Formaler Dokumentenlebenszyklus mit benannten Genehmigern.",
|
|
||||||
"Draft → In approval → Approved → Obsolete": "Entwurf → In Genehmigung → Genehmigt → Veraltet",
|
|
||||||
"Locked once approved, with full history": "Nach der Genehmigung gesperrt, mit vollständiger Historie",
|
|
||||||
"Designed for ISO 9001, ISO 13485, and FDA": "Entwickelt für ISO 9001, ISO 13485 und FDA",
|
|
||||||
"Best for SOPs and controlled documents": "Am besten für SOPs und kontrollierte Dokumente geeignet",
|
|
||||||
"Back": "Zurück",
|
|
||||||
"Quality management": "Qualitätsmanagement",
|
|
||||||
"Recurring": "Wiederkehrend",
|
|
||||||
"Pages move through draft, approval, and approved stages.": "Seiten durchlaufen die Phasen Entwurf, Genehmigung und Genehmigt.",
|
|
||||||
"Verifiers": "Prüfer",
|
|
||||||
"Add verifier": "Prüfer hinzufügen",
|
|
||||||
"I've reviewed this page for accuracy": "Ich habe diese Seite auf Richtigkeit geprüft",
|
|
||||||
"Set up": "Einrichten",
|
|
||||||
"Remove verification": "Verifizierung entfernen",
|
|
||||||
"Are you sure you want to remove verification from this page?": "Möchten Sie die Verifizierung wirklich von dieser Seite entfernen?",
|
|
||||||
"Assigned verifiers must periodically re-verify this page.": "Zugewiesene Prüfer müssen diese Seite regelmäßig erneut verifizieren.",
|
|
||||||
"Last verified by {{name}} {{time}} (expired)": "Zuletzt von {{name}} {{time}} verifiziert (abgelaufen)",
|
|
||||||
"The fixed expiration date has passed.": "Das feste Ablaufdatum ist überschritten.",
|
|
||||||
"Verified by {{name}} {{time}}": "Verifiziert von {{name}} {{time}}",
|
|
||||||
"Expires {{date}}": "Läuft ab am {{date}}",
|
|
||||||
"Expired {{date}}": "Abgelaufen am {{date}}",
|
|
||||||
"Mark as obsolete": "Als veraltet markieren",
|
|
||||||
"Mark obsolete": "Als veraltet markieren",
|
|
||||||
"Returned by {{name}} {{time}}": "Zurückgegeben von {{name}} {{time}}",
|
|
||||||
"No approval has been requested yet.": "Es wurde noch keine Genehmigung angefordert.",
|
|
||||||
"Submitted by {{name}} {{time}}": "Eingereicht von {{name}} {{time}}",
|
|
||||||
"Someone": "Jemand",
|
|
||||||
"Approved by {{name}} {{time}}": "Genehmigt von {{name}} {{time}}",
|
|
||||||
"This document has been marked as obsolete.": "Dieses Dokument wurde als veraltet markiert.",
|
|
||||||
"Rejection comment": "Ablehnungskommentar",
|
|
||||||
"Reason for returning this document...": "Grund für die Rückgabe dieses Dokuments...",
|
|
||||||
"Confirm rejection": "Ablehnung bestätigen",
|
|
||||||
"Submit for approval": "Zur Genehmigung einreichen",
|
|
||||||
"Reject": "Ablehnen",
|
|
||||||
"Approve": "Genehmigen",
|
|
||||||
"Re-submit for approval": "Erneut zur Genehmigung einreichen",
|
|
||||||
"Verified until": "Verifiziert bis",
|
|
||||||
"QMS": "QMS",
|
|
||||||
"Verified pages": "Verifizierte Seiten",
|
|
||||||
"Search pages...": "Seiten suchen...",
|
|
||||||
"Filter by space": "Nach Bereich filtern",
|
|
||||||
"Filter by type": "Nach Typ filtern",
|
|
||||||
"<bold>{{name}}</bold> verified a page": "<bold>{{name}}</bold> hat eine Seite verifiziert",
|
|
||||||
"<bold>{{name}}</bold> submitted a page for your approval": "<bold>{{name}}</bold> hat eine Seite zu Ihrer Genehmigung eingereicht",
|
|
||||||
"<bold>{{name}}</bold> returned a page for revision": "<bold>{{name}}</bold> hat eine Seite zur Überarbeitung zurückgegeben",
|
|
||||||
"Page verification expires soon": "Die Seitenverifizierung läuft bald ab",
|
|
||||||
"Page verification has expired": "Die Seitenverifizierung ist abgelaufen",
|
|
||||||
"Verifying your email": "Ihre E-Mail wird bestätigt",
|
|
||||||
"Please wait...": "Bitte warten...",
|
|
||||||
"Verification failed. The link may have expired.": "Überprüfung fehlgeschlagen. Der Link ist möglicherweise abgelaufen.",
|
|
||||||
"Check your email": "Prüfen Sie Ihre E-Mails",
|
|
||||||
"We sent a verification link to {{email}}.": "Wir haben einen Bestätigungslink an {{email}} gesendet.",
|
|
||||||
"We sent a verification link to your email.": "Wir haben einen Bestätigungslink an Ihre E-Mail-Adresse gesendet.",
|
|
||||||
"Click the link to verify your email and access your workspace.": "Klicken Sie auf den Link, um Ihre E-Mail zu bestätigen und auf Ihren Arbeitsbereich zuzugreifen.",
|
|
||||||
"Resend verification email": "Bestätigungs-E-Mail erneut senden",
|
|
||||||
"Verification email sent. Please check your inbox.": "Bestätigungs-E-Mail gesendet. Bitte überprüfen Sie Ihr Postfach.",
|
|
||||||
"Failed to resend verification email. Please try again.": "Fehler beim erneuten Senden der Bestätigungs-E-Mail. Bitte versuchen Sie es erneut.",
|
|
||||||
"We've sent you an email with your associated workspaces.": "Wir haben Ihnen eine E-Mail mit Ihren zugehörigen Arbeitsbereichen gesendet.",
|
|
||||||
"Load more": "Mehr laden",
|
|
||||||
"Log out of all devices": "Auf allen Geräten abmelden",
|
|
||||||
"Log out of all sessions except this device": "Von allen Sitzungen außer diesem Gerät abmelden",
|
|
||||||
"This Device": "Dieses Gerät",
|
|
||||||
"Unknown device": "Unbekanntes Gerät",
|
|
||||||
"No active sessions": "Keine aktiven Sitzungen",
|
|
||||||
"Session revoked": "Sitzung widerrufen",
|
|
||||||
"All other sessions revoked": "Alle anderen Sitzungen widerrufen",
|
|
||||||
"Last used": "Zuletzt verwendet",
|
|
||||||
"Created": "Erstellt",
|
|
||||||
"Rename": "Umbenennen",
|
|
||||||
"Publish": "Veröffentlichen",
|
|
||||||
"Security": "Sicherheit",
|
|
||||||
"Enforce SSO": "SSO erzwingen",
|
|
||||||
"Once enforced, members will not be able to login with email and password.": "Sobald dies erzwungen wird, können sich Mitglieder nicht mehr mit E-Mail und Passwort anmelden.",
|
|
||||||
"AI-generated content may not be accurate.": "KI-generierte Inhalte sind möglicherweise nicht korrekt.",
|
|
||||||
"AI Chat": "KI-Chat",
|
|
||||||
"Analyze for insights": "Für Erkenntnisse analysieren",
|
|
||||||
"Ask anything...": "Fragen Sie irgendetwas...",
|
|
||||||
"Chat history": "Chatverlauf",
|
|
||||||
"Chat name": "Chatname",
|
|
||||||
"Close": "Schließen",
|
|
||||||
"Docmost AI": "Docmost KI",
|
|
||||||
"Failed to load chat. An error occurred.": "Chat konnte nicht geladen werden. Ein Fehler ist aufgetreten.",
|
|
||||||
"Failed to render this message.": "Diese Nachricht konnte nicht dargestellt werden.",
|
|
||||||
"How can I help you today?": "Wie kann ich Ihnen heute helfen?",
|
|
||||||
"New chat": "Neuer Chat",
|
|
||||||
"No chat history": "Kein Chatverlauf",
|
|
||||||
"No chats found": "Keine Chats gefunden",
|
|
||||||
"No conversations yet": "Noch keine Unterhaltungen",
|
|
||||||
"Open full page": "Ganze Seite öffnen",
|
|
||||||
"Previous 7 days": "Letzte 7 Tage",
|
|
||||||
"Previous 30 days": "Letzte 30 Tage",
|
|
||||||
"Search chats...": "Chats durchsuchen...",
|
|
||||||
"Search chats": "Chats durchsuchen",
|
|
||||||
"Ask anything... Use @ to mention pages": "Frag etwas ... Verwende @, um Seiten zu erwähnen",
|
|
||||||
"Ask anything or search your workspace": "Fragen Sie etwas oder durchsuchen Sie Ihren Workspace",
|
|
||||||
"Welcome to {{name}}": "Willkommen bei {{name}}",
|
|
||||||
"Add files": "Dateien hinzufügen",
|
|
||||||
"Mention a page": "Eine Seite einfügen",
|
|
||||||
"Start a new chat to see it here.": "Starten Sie einen neuen Chat, damit er hier angezeigt wird.",
|
|
||||||
"Summarize this page": "Diese Seite zusammenfassen",
|
|
||||||
"Toggle AI Chat": "KI-Chat umschalten",
|
|
||||||
"Translate this page": "Diese Seite übersetzen",
|
|
||||||
"Try a different search term.": "Versuchen Sie einen anderen Suchbegriff.",
|
|
||||||
"Try again": "Erneut versuchen",
|
|
||||||
"Untitled chat": "Chat ohne Titel",
|
|
||||||
"What can I help you with?": "Womit kann ich Ihnen helfen?",
|
|
||||||
"Are you sure you want to revoke this {{credential}}": "Sind Sie sicher, dass Sie diese(n) {{credential}} widerrufen möchten?",
|
|
||||||
"Automatically provision users and groups from your identity provider via SCIM.": "Stellen Sie Benutzer und Gruppen automatisch über SCIM von Ihrem Identitätsanbieter bereit.",
|
|
||||||
"Configure your identity provider with this URL to provision users and groups.": "Konfigurieren Sie Ihren Identitätsanbieter mit dieser URL, um Benutzer und Gruppen bereitzustellen.",
|
|
||||||
"Create {{credential}}": "{{credential}} erstellen",
|
|
||||||
"{{credential}} created": "{{credential}} erstellt",
|
|
||||||
"{{credential}} created successfully": "{{credential}} erfolgreich erstellt",
|
|
||||||
"Created by": "Erstellt von",
|
|
||||||
"Custom": "Benutzerdefiniert",
|
|
||||||
"Enable SCIM": "SCIM aktivieren",
|
|
||||||
"Enter a descriptive name": "Geben Sie einen beschreibenden Namen ein",
|
|
||||||
"I've saved my {{credential}}": "Ich habe meine(n) {{credential}} gespeichert",
|
|
||||||
"Important": "Wichtig",
|
|
||||||
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Stellen Sie sicher, dass Sie Ihre(n) {{credential}} jetzt kopieren. Sie können sie/ihn später nicht erneut anzeigen!",
|
|
||||||
"Never": "Nie",
|
|
||||||
"Revoke {{credential}}": "{{credential}} widerrufen",
|
|
||||||
"SCIM endpoint URL": "SCIM-Endpunkt-URL",
|
|
||||||
"SCIM provisioning": "SCIM-Bereitstellung",
|
|
||||||
"SCIM takes precedence over SSO group sync while enabled.": "SCIM hat Vorrang vor der SSO-Gruppensynchronisierung, solange es aktiviert ist.",
|
|
||||||
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "Sie haben die maximale Anzahl von {{max}} SCIM-Token erreicht. Löschen Sie ein vorhandenes Token, um ein neues zu erstellen.",
|
|
||||||
"SCIM token": "SCIM-Token",
|
|
||||||
"SCIM tokens": "SCIM-Token",
|
|
||||||
"This action cannot be undone. Your identity provider will stop syncing immediately.": "Diese Aktion kann nicht rückgängig gemacht werden. Ihr Identitätsanbieter wird die Synchronisierung sofort beenden.",
|
|
||||||
"Toggle SCIM provisioning": "SCIM-Bereitstellung umschalten",
|
|
||||||
"Token": "Token",
|
|
||||||
"Page menu": "Seitenmenü",
|
|
||||||
"Expand": "Erweitern",
|
|
||||||
"Collapse": "Reduzieren",
|
|
||||||
"Comment menu": "Kommentarmenü",
|
|
||||||
"Group menu": "Gruppenmenü",
|
|
||||||
"Show hidden breadcrumbs": "Ausgeblendete Breadcrumbs anzeigen",
|
|
||||||
"Breadcrumbs": "Navigationspfade",
|
|
||||||
"Page actions": "Seitenaktionen",
|
|
||||||
"Pick emoji": "Emoji auswählen",
|
|
||||||
"Template menu": "Vorlagenmenü",
|
|
||||||
"Use": "Use",
|
|
||||||
"Use template": "Use template",
|
|
||||||
"Preview template: {{title}}": "Preview template: {{title}}",
|
|
||||||
"Use a template": "Use a template",
|
|
||||||
"Search templates...": "Search templates...",
|
|
||||||
"Search spaces...": "Search spaces...",
|
|
||||||
"No templates found": "No templates found",
|
|
||||||
"No spaces found": "No spaces found",
|
|
||||||
"Browse all templates": "Browse all templates",
|
|
||||||
"This space": "This space",
|
|
||||||
"All templates": "All templates",
|
|
||||||
"Global": "Global",
|
|
||||||
"New template": "New template",
|
|
||||||
"Edit template": "Edit template",
|
|
||||||
"Are you sure you want to delete this template?": "Are you sure you want to delete this template?",
|
|
||||||
"Template scope updated": "Template scope updated",
|
|
||||||
"Choose which space this template belongs to": "Choose which space this template belongs to",
|
|
||||||
"Scope": "Scope",
|
|
||||||
"Select scope": "Select scope",
|
|
||||||
"Title": "Title",
|
|
||||||
"Saving...": "Saving...",
|
|
||||||
"Saved": "Saved",
|
|
||||||
"Save failed. Retry": "Save failed. Retry",
|
|
||||||
"By {{name}}": "By {{name}}",
|
|
||||||
"Updated {{time}}": "Updated {{time}}",
|
|
||||||
"Choose destination": "Choose destination",
|
|
||||||
"Search pages and spaces...": "Search pages and spaces...",
|
|
||||||
"No results found": "No results found",
|
|
||||||
"You don't have permission to create pages here": "You don't have permission to create pages here",
|
|
||||||
"Chat menu": "Chatmenü",
|
|
||||||
"API key menu": "API-Schlüssel-Menü",
|
|
||||||
"Jump to comment selection": "Zur Kommentarauswahl springen",
|
|
||||||
"Slash commands": "Slash-Befehle",
|
|
||||||
"Mention suggestions": "Erwähnungsvorschläge",
|
|
||||||
"Link suggestions": "Linkvorschläge",
|
|
||||||
"Diagram editor": "Diagrammeditor",
|
|
||||||
"Add comment": "Kommentar hinzufügen",
|
|
||||||
"Find and replace": "Suchen und ersetzen",
|
|
||||||
"Main navigation": "Hauptnavigation",
|
|
||||||
"Space navigation": "Bereichsnavigation",
|
|
||||||
"Settings navigation": "Einstellungsnavigation",
|
|
||||||
"AI navigation": "KI-Navigation",
|
|
||||||
"Breadcrumb": "Navigationspfad",
|
|
||||||
"Synced block": "Synchronisierter Block",
|
|
||||||
"Create a block that stays in sync across pages.": "Erstellt einen Block der über mehrere Seiten synchronisiert wird",
|
|
||||||
"Editing original": "Original bearbeiten",
|
|
||||||
"Copy synced block": "Synchronisierten Block kopieren",
|
|
||||||
"Unsync": "Synchronisierung aufheben",
|
|
||||||
"Delete synced block": "Synchronisierten Block löschen",
|
|
||||||
"Synced to {{count}} other page_one": "Mit {{count}} anderer Seite synchronisiert",
|
|
||||||
"Synced to {{count}} other page_other": "Mit {{count}} anderen Seiten synchronisiert",
|
|
||||||
"ORIGINAL": "ORIGINAL",
|
|
||||||
"THIS PAGE": "DIESE SEITE",
|
|
||||||
"No pages": "Keine Seiten",
|
|
||||||
"The original synced block no longer exists": "Der originale synchronisierte Block existiert nicht mehr",
|
|
||||||
"You don't have access to this synced block": "Sie haben keinen Zugriff auf diesen synchronisierten Block",
|
|
||||||
"Failed to load this synced block": "Dieser synchronisierte Block konnte nicht geladen werden",
|
|
||||||
"Fixed editor toolbar": "Fixierte Editor-Symbolleiste",
|
|
||||||
"Show a formatting toolbar above the editor with quick access to common actions.": "Anzeige einer Formatierungs-Symbolleiste über dem Editor für schnellen Zugriff auf Aktionen.",
|
|
||||||
"Toggle fixed editor toolbar": "Fixierte Editor-Symbolleiste ein/aus",
|
|
||||||
"Normal text": "Normaler Text",
|
|
||||||
"More inline formatting": "Weitere Formatierung",
|
|
||||||
"Subscript": "Tiefgestellt",
|
|
||||||
"Superscript": "Hochgestellt",
|
|
||||||
"Inline code": "Inline-Code",
|
|
||||||
"Insert media": "Medien einfügen",
|
|
||||||
"Mention": "Erwähnung",
|
|
||||||
"Emoji": "Emoji",
|
|
||||||
"Columns": "Spalten",
|
|
||||||
"More inserts": "Weiteren Inhalt einfügen",
|
|
||||||
"Embeds": "Einbettungen",
|
|
||||||
"Diagrams": "Diagramme",
|
|
||||||
"Advanced": "Erweitert",
|
|
||||||
"Utility": "Utility",
|
|
||||||
"Decrease indent": "Einzug verkleinern",
|
|
||||||
"Increase indent": "Einzug vergrößern",
|
|
||||||
"Clear formatting": "Formatierung zurücksetzen",
|
|
||||||
"Code block": "Codeblock",
|
|
||||||
"Experimental": "Experimentell",
|
|
||||||
"Strikethrough": "Durchgestrichen",
|
|
||||||
"Undo": "Rückgängig",
|
|
||||||
"Redo": "Wiederholen",
|
|
||||||
"Backlinks": "Rückverweise",
|
|
||||||
"Last updated by": "Zuletzt aktualisiert von",
|
|
||||||
"Last updated": "Zuletzt aktualisiert",
|
|
||||||
"Stats": "Statistiken",
|
|
||||||
"Word count": "Wörter",
|
|
||||||
"Characters": "Zeichen",
|
|
||||||
"Incoming links": "Eingehende Links",
|
|
||||||
"Outgoing links": "Ausgehende Links",
|
|
||||||
"Incoming links ({{count}})": "Eingehende Links ({{count}})",
|
|
||||||
"Outgoing links ({{count}})": "Ausgehende Links ({{count}})",
|
|
||||||
"No pages link here yet.": "Aktuell verlinken keine Seiten hierher.",
|
|
||||||
"This page doesn't link to other pages yet.": "Diese Seite verlinkt noch nicht auf andere Seiten.",
|
|
||||||
"Verified until {{date}}": "Verifiziert bis zum {{date}}",
|
|
||||||
"Labels": "Labels",
|
|
||||||
"Add label": "Add label",
|
|
||||||
"No labels yet": "No labels yet",
|
|
||||||
"Already added": "Already added",
|
|
||||||
"Invalid label name": "Invalid label name",
|
|
||||||
"No matches": "No matches",
|
|
||||||
"Search or create…": "Search or create…",
|
|
||||||
"Remove label {{name}}": "Remove label {{name}}",
|
|
||||||
"Failed to add label": "Failed to add label",
|
|
||||||
"Failed to remove label": "Failed to remove label",
|
|
||||||
"No pages with this label": "No pages with this label",
|
|
||||||
"Pages tagged with this label will appear here.": "Pages tagged with this label will appear here.",
|
|
||||||
"No pages match your search.": "Es konnten keine Seiten gefunden werden, die mit Ihrer Suche übereinstimmen.",
|
|
||||||
"Updated {{date}}": "Aktualisiert am {{date}}",
|
|
||||||
"Cell actions": "Cell actions",
|
|
||||||
"Column actions": "Column actions",
|
|
||||||
"Row actions": "Row actions"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
"Add members": "Add members",
|
"Add members": "Add members",
|
||||||
"Add to groups": "Add to groups",
|
"Add to groups": "Add to groups",
|
||||||
"Add space members": "Add space members",
|
"Add space members": "Add space members",
|
||||||
"Add to favorites": "Add to favorites",
|
|
||||||
"Admin": "Admin",
|
"Admin": "Admin",
|
||||||
"Are you sure you want to delete this group? Members will lose access to resources this group has access to.": "Are you sure you want to delete this group? Members will lose access to resources this group has access to.",
|
"Are you sure you want to delete this group? Members will lose access to resources this group has access to.": "Are you sure you want to delete this group? Members will lose access to resources this group has access to.",
|
||||||
"Are you sure you want to delete this page?": "Are you sure you want to delete this page?",
|
"Are you sure you want to delete this page?": "Are you sure you want to delete this page?",
|
||||||
@@ -30,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Choose your preferred interface language.",
|
"Choose your preferred interface language.": "Choose your preferred interface language.",
|
||||||
"Choose your preferred page width.": "Choose your preferred page width.",
|
"Choose your preferred page width.": "Choose your preferred page width.",
|
||||||
"Confirm": "Confirm",
|
"Confirm": "Confirm",
|
||||||
"Copy as Markdown": "Copy as Markdown",
|
|
||||||
"Copy link": "Copy link",
|
"Copy link": "Copy link",
|
||||||
"Create": "Create",
|
"Create": "Create",
|
||||||
"Create group": "Create group",
|
"Create group": "Create group",
|
||||||
@@ -55,7 +53,6 @@
|
|||||||
"e.g Space for product team": "e.g Space for product team",
|
"e.g Space for product team": "e.g Space for product team",
|
||||||
"e.g Space for sales team to collaborate": "e.g Space for sales team to collaborate",
|
"e.g Space for sales team to collaborate": "e.g Space for sales team to collaborate",
|
||||||
"Edit": "Edit",
|
"Edit": "Edit",
|
||||||
"Read": "Read",
|
|
||||||
"Edit group": "Edit group",
|
"Edit group": "Edit group",
|
||||||
"Email": "Email",
|
"Email": "Email",
|
||||||
"Enter a strong password": "Enter a strong password",
|
"Enter a strong password": "Enter a strong password",
|
||||||
@@ -71,14 +68,10 @@
|
|||||||
"Export": "Export",
|
"Export": "Export",
|
||||||
"Failed to create page": "Failed to create page",
|
"Failed to create page": "Failed to create page",
|
||||||
"Failed to delete page": "Failed to delete page",
|
"Failed to delete page": "Failed to delete page",
|
||||||
"Failed to restore page": "Failed to restore page",
|
|
||||||
"Failed to fetch recent pages": "Failed to fetch recent pages",
|
"Failed to fetch recent pages": "Failed to fetch recent pages",
|
||||||
"Failed to import pages": "Failed to import pages",
|
"Failed to import pages": "Failed to import pages",
|
||||||
"Failed to load page. An error occurred.": "Failed to load page. An error occurred.",
|
"Failed to load page. An error occurred.": "Failed to load page. An error occurred.",
|
||||||
"Failed to update data": "Failed to update data",
|
"Failed to update data": "Failed to update data",
|
||||||
"Favorite spaces": "Favorite spaces",
|
|
||||||
"Favorite spaces appear here": "Favorite spaces appear here",
|
|
||||||
"Favorites": "Favorites",
|
|
||||||
"Full access": "Full access",
|
"Full access": "Full access",
|
||||||
"Full page width": "Full page width",
|
"Full page width": "Full page width",
|
||||||
"Full width": "Full width",
|
"Full width": "Full width",
|
||||||
@@ -97,7 +90,6 @@
|
|||||||
"Invite by email": "Invite by email",
|
"Invite by email": "Invite by email",
|
||||||
"Invite members": "Invite members",
|
"Invite members": "Invite members",
|
||||||
"Invite new members": "Invite new members",
|
"Invite new members": "Invite new members",
|
||||||
"Invite People": "Invite People",
|
|
||||||
"Invited members who are yet to accept their invitation will appear here.": "Invited members who are yet to accept their invitation will appear here.",
|
"Invited members who are yet to accept their invitation will appear here.": "Invited members who are yet to accept their invitation will appear here.",
|
||||||
"Invited members will be granted access to spaces the groups can access": "Invited members will be granted access to spaces the groups can access",
|
"Invited members will be granted access to spaces the groups can access": "Invited members will be granted access to spaces the groups can access",
|
||||||
"Join the workspace": "Join the workspace",
|
"Join the workspace": "Join the workspace",
|
||||||
@@ -122,7 +114,6 @@
|
|||||||
"No group found": "No group found",
|
"No group found": "No group found",
|
||||||
"No page history saved yet.": "No page history saved yet.",
|
"No page history saved yet.": "No page history saved yet.",
|
||||||
"No pages yet": "No pages yet",
|
"No pages yet": "No pages yet",
|
||||||
"No shared pages": "No shared pages",
|
|
||||||
"No results found...": "No results found...",
|
"No results found...": "No results found...",
|
||||||
"No user found": "No user found",
|
"No user found": "No user found",
|
||||||
"Overview": "Overview",
|
"Overview": "Overview",
|
||||||
@@ -130,14 +121,11 @@
|
|||||||
"page": "page",
|
"page": "page",
|
||||||
"Page deleted successfully": "Page deleted successfully",
|
"Page deleted successfully": "Page deleted successfully",
|
||||||
"Page history": "Page history",
|
"Page history": "Page history",
|
||||||
"Select version": "Select version",
|
|
||||||
"Highlight changes": "Highlight changes",
|
|
||||||
"Page import is in progress. Please do not close this tab.": "Page import is in progress. Please do not close this tab.",
|
"Page import is in progress. Please do not close this tab.": "Page import is in progress. Please do not close this tab.",
|
||||||
"Pages": "Pages",
|
"Pages": "Pages",
|
||||||
"pages": "pages",
|
"pages": "pages",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
"Password changed successfully": "Password changed successfully",
|
"Password changed successfully": "Password changed successfully",
|
||||||
"People": "People",
|
|
||||||
"Pending": "Pending",
|
"Pending": "Pending",
|
||||||
"Please confirm your action": "Please confirm your action",
|
"Please confirm your action": "Please confirm your action",
|
||||||
"Preferences": "Preferences",
|
"Preferences": "Preferences",
|
||||||
@@ -145,7 +133,6 @@
|
|||||||
"Profile": "Profile",
|
"Profile": "Profile",
|
||||||
"Recently updated": "Recently updated",
|
"Recently updated": "Recently updated",
|
||||||
"Remove": "Remove",
|
"Remove": "Remove",
|
||||||
"Remove from favorites": "Remove from favorites",
|
|
||||||
"Remove group member": "Remove group member",
|
"Remove group member": "Remove group member",
|
||||||
"Remove space member": "Remove space member",
|
"Remove space member": "Remove space member",
|
||||||
"Restore": "Restore",
|
"Restore": "Restore",
|
||||||
@@ -182,7 +169,6 @@
|
|||||||
"Successfully imported": "Successfully imported",
|
"Successfully imported": "Successfully imported",
|
||||||
"Successfully restored": "Successfully restored",
|
"Successfully restored": "Successfully restored",
|
||||||
"System settings": "System settings",
|
"System settings": "System settings",
|
||||||
"Templates": "Templates",
|
|
||||||
"Theme": "Theme",
|
"Theme": "Theme",
|
||||||
"To change your email, you have to enter your password and new email.": "To change your email, you have to enter your password and new email.",
|
"To change your email, you have to enter your password and new email.": "To change your email, you have to enter your password and new email.",
|
||||||
"Toggle full page width": "Toggle full page width",
|
"Toggle full page width": "Toggle full page width",
|
||||||
@@ -217,14 +203,9 @@
|
|||||||
"Reply...": "Reply...",
|
"Reply...": "Reply...",
|
||||||
"Error loading comments.": "Error loading comments.",
|
"Error loading comments.": "Error loading comments.",
|
||||||
"No comments yet.": "No comments yet.",
|
"No comments yet.": "No comments yet.",
|
||||||
"No open comments.": "No open comments.",
|
|
||||||
"No resolved comments.": "No resolved comments.",
|
|
||||||
"Add a comment...": "Add a comment...",
|
|
||||||
"Edit comment": "Edit comment",
|
"Edit comment": "Edit comment",
|
||||||
"Delete comment": "Delete comment",
|
"Delete comment": "Delete comment",
|
||||||
"Are you sure you want to delete this comment?": "Are you sure you want to delete this comment?",
|
"Are you sure you want to delete this comment?": "Are you sure you want to delete this comment?",
|
||||||
"Delete chat": "Delete chat",
|
|
||||||
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "Are you sure you want to delete '{{title}}'? This action cannot be undone.",
|
|
||||||
"Comment created successfully": "Comment created successfully",
|
"Comment created successfully": "Comment created successfully",
|
||||||
"Error creating comment": "Error creating comment",
|
"Error creating comment": "Error creating comment",
|
||||||
"Comment updated successfully": "Comment updated successfully",
|
"Comment updated successfully": "Comment updated successfully",
|
||||||
@@ -243,6 +224,7 @@
|
|||||||
"Are you sure you want to unresolve this comment thread?": "Are you sure you want to unresolve this comment thread?",
|
"Are you sure you want to unresolve this comment thread?": "Are you sure you want to unresolve this comment thread?",
|
||||||
"Resolved": "Resolved",
|
"Resolved": "Resolved",
|
||||||
"No active comments.": "No active comments.",
|
"No active comments.": "No active comments.",
|
||||||
|
"No resolved comments.": "No resolved comments.",
|
||||||
"Revoke invitation": "Revoke invitation",
|
"Revoke invitation": "Revoke invitation",
|
||||||
"Revoke": "Revoke",
|
"Revoke": "Revoke",
|
||||||
"Don't": "Don't",
|
"Don't": "Don't",
|
||||||
@@ -270,7 +252,6 @@
|
|||||||
"Export failed:": "Export failed:",
|
"Export failed:": "Export failed:",
|
||||||
"export error": "export error",
|
"export error": "export error",
|
||||||
"Export page": "Export page",
|
"Export page": "Export page",
|
||||||
"Export successful": "Export successful",
|
|
||||||
"Export space": "Export space",
|
"Export space": "Export space",
|
||||||
"Export {{type}}": "Export {{type}}",
|
"Export {{type}}": "Export {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "File exceeds the {{limit}} attachment limit",
|
"File exceeds the {{limit}} attachment limit": "File exceeds the {{limit}} attachment limit",
|
||||||
@@ -287,21 +268,7 @@
|
|||||||
"Add row above": "Add row above",
|
"Add row above": "Add row above",
|
||||||
"Add row below": "Add row below",
|
"Add row below": "Add row below",
|
||||||
"Delete table": "Delete table",
|
"Delete table": "Delete table",
|
||||||
"Add column left": "Add column left",
|
|
||||||
"Add column right": "Add column right",
|
|
||||||
"Clear cell": "Clear cell",
|
|
||||||
"Clear cells": "Clear cells",
|
|
||||||
"Toggle header cell": "Toggle header cell",
|
|
||||||
"Toggle header column": "Toggle header column",
|
|
||||||
"Toggle header row": "Toggle header row",
|
|
||||||
"Move column left": "Move column left",
|
|
||||||
"Move column right": "Move column right",
|
|
||||||
"Move row down": "Move row down",
|
|
||||||
"Move row up": "Move row up",
|
|
||||||
"Sort A → Z": "Sort A → Z",
|
|
||||||
"Sort Z → A": "Sort Z → A",
|
|
||||||
"Info": "Info",
|
"Info": "Info",
|
||||||
"Note": "Note",
|
|
||||||
"Success": "Success",
|
"Success": "Success",
|
||||||
"Warning": "Warning",
|
"Warning": "Warning",
|
||||||
"Danger": "Danger",
|
"Danger": "Danger",
|
||||||
@@ -312,11 +279,6 @@
|
|||||||
"Save & Exit": "Save & Exit",
|
"Save & Exit": "Save & Exit",
|
||||||
"Double-click to edit Excalidraw diagram": "Double-click to edit Excalidraw diagram",
|
"Double-click to edit Excalidraw diagram": "Double-click to edit Excalidraw diagram",
|
||||||
"Paste link": "Paste link",
|
"Paste link": "Paste link",
|
||||||
"Paste link or search pages": "Paste link or search pages",
|
|
||||||
"Link to web page": "Link to web page",
|
|
||||||
"Recents": "Recents",
|
|
||||||
"Page or URL": "Page or URL",
|
|
||||||
"Link title": "Link title",
|
|
||||||
"Edit link": "Edit link",
|
"Edit link": "Edit link",
|
||||||
"Remove link": "Remove link",
|
"Remove link": "Remove link",
|
||||||
"Add link": "Add link",
|
"Add link": "Add link",
|
||||||
@@ -362,14 +324,9 @@
|
|||||||
"Create block quote.": "Create block quote.",
|
"Create block quote.": "Create block quote.",
|
||||||
"Insert code snippet.": "Insert code snippet.",
|
"Insert code snippet.": "Insert code snippet.",
|
||||||
"Insert horizontal rule divider": "Insert horizontal rule divider",
|
"Insert horizontal rule divider": "Insert horizontal rule divider",
|
||||||
"Page break": "Page break",
|
|
||||||
"Insert a page break for printing.": "Insert a page break for printing.",
|
|
||||||
"Upload any image from your device.": "Upload any image from your device.",
|
"Upload any image from your device.": "Upload any image from your device.",
|
||||||
"Upload any video from your device.": "Upload any video from your device.",
|
"Upload any video from your device.": "Upload any video from your device.",
|
||||||
"Upload any audio from your device.": "Upload any audio from your device.",
|
|
||||||
"Upload any file from your device.": "Upload any file from your device.",
|
"Upload any file from your device.": "Upload any file from your device.",
|
||||||
"Uploading {{name}}": "Uploading {{name}}",
|
|
||||||
"Uploading file": "Uploading file",
|
|
||||||
"Table": "Table",
|
"Table": "Table",
|
||||||
"Insert a table.": "Insert a table.",
|
"Insert a table.": "Insert a table.",
|
||||||
"Insert collapsible block.": "Insert collapsible block.",
|
"Insert collapsible block.": "Insert collapsible block.",
|
||||||
@@ -377,12 +334,6 @@
|
|||||||
"Divider": "Divider",
|
"Divider": "Divider",
|
||||||
"Quote": "Quote",
|
"Quote": "Quote",
|
||||||
"Image": "Image",
|
"Image": "Image",
|
||||||
"Audio": "Audio",
|
|
||||||
"Embed PDF": "Embed PDF",
|
|
||||||
"Upload and embed a PDF file.": "Upload and embed a PDF file.",
|
|
||||||
"Embed as PDF": "Embed as PDF",
|
|
||||||
"Failed to load PDF": "Failed to load PDF",
|
|
||||||
"Convert to attachment": "Convert to attachment",
|
|
||||||
"File attachment": "File attachment",
|
"File attachment": "File attachment",
|
||||||
"Toggle block": "Toggle block",
|
"Toggle block": "Toggle block",
|
||||||
"Callout": "Callout",
|
"Callout": "Callout",
|
||||||
@@ -397,23 +348,9 @@
|
|||||||
"Insert current date": "Insert current date",
|
"Insert current date": "Insert current date",
|
||||||
"Draw and sketch excalidraw diagrams": "Draw and sketch excalidraw diagrams",
|
"Draw and sketch excalidraw diagrams": "Draw and sketch excalidraw diagrams",
|
||||||
"Multiple": "Multiple",
|
"Multiple": "Multiple",
|
||||||
"Turn into": "Turn into",
|
|
||||||
"Text align": "Text align",
|
|
||||||
"This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.",
|
|
||||||
"Go to homepage": "Go to homepage",
|
|
||||||
"Pages you create will show up here.": "Pages you create will show up here.",
|
|
||||||
"Heading {{level}}": "Heading {{level}}",
|
"Heading {{level}}": "Heading {{level}}",
|
||||||
"Toggle title": "Toggle title",
|
"Toggle title": "Toggle title",
|
||||||
"Write anything. Enter \"/\" for commands": "Write anything. Enter \"/\" for commands",
|
"Write anything. Enter \"/\" for commands": "Write anything. Enter \"/\" for commands",
|
||||||
"Write...": "Write...",
|
|
||||||
"Column count": "Column count",
|
|
||||||
"{{count}} Columns": "{{count}} Columns",
|
|
||||||
"Equal columns": "Equal columns",
|
|
||||||
"Left sidebar": "Left sidebar",
|
|
||||||
"Right sidebar": "Right sidebar",
|
|
||||||
"Wide center": "Wide center",
|
|
||||||
"Left wide": "Left wide",
|
|
||||||
"Right wide": "Right wide",
|
|
||||||
"Names do not match": "Names do not match",
|
"Names do not match": "Names do not match",
|
||||||
"Today, {{time}}": "Today, {{time}}",
|
"Today, {{time}}": "Today, {{time}}",
|
||||||
"Yesterday, {{time}}": "Yesterday, {{time}}",
|
"Yesterday, {{time}}": "Yesterday, {{time}}",
|
||||||
@@ -432,18 +369,10 @@
|
|||||||
"{{latestVersion}} is available": "{{latestVersion}} is available",
|
"{{latestVersion}} is available": "{{latestVersion}} is available",
|
||||||
"Default page edit mode": "Default page edit mode",
|
"Default page edit mode": "Default page edit mode",
|
||||||
"Choose your preferred page edit mode. Avoid accidental edits.": "Choose your preferred page edit mode. Avoid accidental edits.",
|
"Choose your preferred page edit mode. Avoid accidental edits.": "Choose your preferred page edit mode. Avoid accidental edits.",
|
||||||
"Choose {{format}} file": "Choose {{format}} file",
|
|
||||||
"Reading": "Reading",
|
"Reading": "Reading",
|
||||||
"Delete member": "Delete member",
|
"Delete member": "Delete member",
|
||||||
"Member deleted successfully": "Member deleted successfully",
|
"Member deleted successfully": "Member deleted successfully",
|
||||||
"Are you sure you want to delete this workspace member? This action is irreversible.": "Are you sure you want to delete this workspace member? This action is irreversible.",
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "Are you sure you want to delete this workspace member? This action is irreversible.",
|
||||||
"Deactivate member": "Deactivate member",
|
|
||||||
"Activate member": "Activate member",
|
|
||||||
"Are you sure you want to deactivate this workspace member? They will no longer be able to access this workspace.": "Are you sure you want to deactivate this workspace member? They will no longer be able to access this workspace.",
|
|
||||||
"Are you sure you want to activate this workspace member?": "Are you sure you want to activate this workspace member?",
|
|
||||||
"Deactivate": "Deactivate",
|
|
||||||
"Activate": "Activate",
|
|
||||||
"Deactivated": "Deactivated",
|
|
||||||
"Move": "Move",
|
"Move": "Move",
|
||||||
"Move page": "Move page",
|
"Move page": "Move page",
|
||||||
"Move page to a different space.": "Move page to a different space.",
|
"Move page to a different space.": "Move page to a different space.",
|
||||||
@@ -471,25 +400,6 @@
|
|||||||
"Share deleted successfully": "Share deleted successfully",
|
"Share deleted successfully": "Share deleted successfully",
|
||||||
"Share not found": "Share not found",
|
"Share not found": "Share not found",
|
||||||
"Failed to share page": "Failed to share page",
|
"Failed to share page": "Failed to share page",
|
||||||
"Disable public sharing": "Disable public sharing",
|
|
||||||
"Prevent members from sharing pages publicly.": "Prevent members from sharing pages publicly.",
|
|
||||||
"Toggle public sharing": "Toggle public sharing",
|
|
||||||
"Toggle space public sharing": "Toggle space public sharing",
|
|
||||||
"Allow viewers to comment": "Allow viewers to comment",
|
|
||||||
"Allow viewers to add comments on pages in this space.": "Allow viewers to add comments on pages in this space.",
|
|
||||||
"Toggle viewer comments": "Toggle viewer comments",
|
|
||||||
"Public sharing is disabled at the workspace level": "Public sharing is disabled at the workspace level",
|
|
||||||
"Prevent pages in this space from being shared publicly.": "Prevent pages in this space from being shared publicly.",
|
|
||||||
"Page permissions": "Page permissions",
|
|
||||||
"Control who can view and edit individual pages. Available with an enterprise license.": "Control who can view and edit individual pages. Available with an enterprise license.",
|
|
||||||
"Enable public sharing": "Enable public sharing",
|
|
||||||
"Are you sure you want to enable public sharing? Members will be able to share pages publicly.": "Are you sure you want to enable public sharing? Members will be able to share pages publicly.",
|
|
||||||
"Are you sure you want to disable public sharing? All existing shared links in this workspace will be deleted.": "Are you sure you want to disable public sharing? All existing shared links in this workspace will be deleted.",
|
|
||||||
"Are you sure you want to enable public sharing for this space?": "Are you sure you want to enable public sharing for this space?",
|
|
||||||
"Are you sure you want to disable public sharing? All existing shared links in this space will be deleted.": "Are you sure you want to disable public sharing? All existing shared links in this space will be deleted.",
|
|
||||||
"Public sharing is disabled": "Public sharing is disabled",
|
|
||||||
"Public sharing has been disabled at the workspace level.": "Public sharing has been disabled at the workspace level.",
|
|
||||||
"Public sharing has been disabled for this space.": "Public sharing has been disabled for this space.",
|
|
||||||
"Copy page": "Copy page",
|
"Copy page": "Copy page",
|
||||||
"Copy page to a different space.": "Copy page to a different space.",
|
"Copy page to a different space.": "Copy page to a different space.",
|
||||||
"Page copied successfully": "Page copied successfully",
|
"Page copied successfully": "Page copied successfully",
|
||||||
@@ -504,7 +414,6 @@
|
|||||||
"Replace (Enter)": "Replace (Enter)",
|
"Replace (Enter)": "Replace (Enter)",
|
||||||
"Replace all (Ctrl+Alt+Enter)": "Replace all (Ctrl+Alt+Enter)",
|
"Replace all (Ctrl+Alt+Enter)": "Replace all (Ctrl+Alt+Enter)",
|
||||||
"Replace all": "Replace all",
|
"Replace all": "Replace all",
|
||||||
"View all": "View all",
|
|
||||||
"View all spaces": "View all spaces",
|
"View all spaces": "View all spaces",
|
||||||
"Error": "Error",
|
"Error": "Error",
|
||||||
"Failed to disable MFA": "Failed to disable MFA",
|
"Failed to disable MFA": "Failed to disable MFA",
|
||||||
@@ -573,7 +482,7 @@
|
|||||||
"Enter one of your backup codes. Each backup code can only be used once.": "Enter one of your backup codes. Each backup code can only be used once.",
|
"Enter one of your backup codes. Each backup code can only be used once.": "Enter one of your backup codes. Each backup code can only be used once.",
|
||||||
"Verify": "Verify",
|
"Verify": "Verify",
|
||||||
"Trash": "Trash",
|
"Trash": "Trash",
|
||||||
"Pages in trash will be permanently deleted after {{count}} days.": "Pages in trash will be permanently deleted after {{count}} days.",
|
"Pages in trash will be permanently deleted after 30 days.": "Pages in trash will be permanently deleted after 30 days.",
|
||||||
"Deleted": "Deleted",
|
"Deleted": "Deleted",
|
||||||
"No pages in trash": "No pages in trash",
|
"No pages in trash": "No pages in trash",
|
||||||
"Permanently delete page?": "Permanently delete page?",
|
"Permanently delete page?": "Permanently delete page?",
|
||||||
@@ -582,470 +491,9 @@
|
|||||||
"Move to trash": "Move to trash",
|
"Move to trash": "Move to trash",
|
||||||
"Move this page to trash?": "Move this page to trash?",
|
"Move this page to trash?": "Move this page to trash?",
|
||||||
"Restore page": "Restore page",
|
"Restore page": "Restore page",
|
||||||
"Permanently delete": "Permanently delete",
|
|
||||||
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> moved this page to Trash {{time}}.",
|
|
||||||
"Page moved to trash": "Page moved to trash",
|
"Page moved to trash": "Page moved to trash",
|
||||||
"Page restored successfully": "Page restored successfully",
|
"Page restored successfully": "Page restored successfully",
|
||||||
"Deleted by": "Deleted by",
|
"Deleted by": "Deleted by",
|
||||||
"Deleted at": "Deleted at",
|
"Deleted at": "Deleted at",
|
||||||
"Preview": "Preview",
|
"Preview": "Preview"
|
||||||
"Subpages": "Subpages",
|
|
||||||
"Failed to load subpages": "Failed to load subpages",
|
|
||||||
"No subpages": "No subpages",
|
|
||||||
"Subpages (Child pages)": "Subpages (Child pages)",
|
|
||||||
"List all subpages of the current page": "List all subpages of the current page",
|
|
||||||
"Attachments": "Attachments",
|
|
||||||
"All spaces": "All spaces",
|
|
||||||
"Unknown": "Unknown",
|
|
||||||
"Find a space": "Find a space",
|
|
||||||
"Search in all your spaces": "Search in all your spaces",
|
|
||||||
"Type": "Type",
|
|
||||||
"Enterprise": "Enterprise",
|
|
||||||
"Download attachment": "Download attachment",
|
|
||||||
"Allowed email domains": "Allowed email domains",
|
|
||||||
"Only users with email addresses from these domains can signup via SSO.": "Only users with email addresses from these domains can signup via SSO.",
|
|
||||||
"Enter valid domain names separated by comma or space": "Enter valid domain names separated by comma or space",
|
|
||||||
"Enforce two-factor authentication": "Enforce two-factor authentication",
|
|
||||||
"Once enforced, all members must enable two-factor authentication to access the workspace.": "Once enforced, all members must enable two-factor authentication to access the workspace.",
|
|
||||||
"Toggle MFA enforcement": "Toggle MFA enforcement",
|
|
||||||
"Display name": "Display name",
|
|
||||||
"Allow signup": "Allow signup",
|
|
||||||
"Enabled": "Enabled",
|
|
||||||
"Advanced Settings": "Advanced Settings",
|
|
||||||
"Enable TLS/SSL": "Enable TLS/SSL",
|
|
||||||
"Use secure connection to LDAP server": "Use secure connection to LDAP server",
|
|
||||||
"Group sync": "Group sync",
|
|
||||||
"No SSO providers found.": "No SSO providers found.",
|
|
||||||
"Delete SSO provider": "Delete SSO provider",
|
|
||||||
"Are you sure you want to delete this SSO provider?": "Are you sure you want to delete this SSO provider?",
|
|
||||||
"Action": "Action",
|
|
||||||
"{{ssoProviderType}} configuration": "{{ssoProviderType}} configuration",
|
|
||||||
"Icon": "Icon",
|
|
||||||
"Upload image": "Upload image",
|
|
||||||
"Remove image": "Remove image",
|
|
||||||
"Failed to remove image": "Failed to remove image",
|
|
||||||
"Image exceeds 10MB limit.": "Image exceeds 10MB limit.",
|
|
||||||
"Image removed successfully": "Image removed successfully",
|
|
||||||
"API key": "API key",
|
|
||||||
"API keys": "API keys",
|
|
||||||
"API management": "API management",
|
|
||||||
"Custom expiration date": "Custom expiration date",
|
|
||||||
"Enter a descriptive token name": "Enter a descriptive token name",
|
|
||||||
"Expiration": "Expiration",
|
|
||||||
"Expired": "Expired",
|
|
||||||
"Expires": "Expires",
|
|
||||||
"Last use": "Last Used",
|
|
||||||
"No API keys found": "No API keys found",
|
|
||||||
"No expiration": "No expiration",
|
|
||||||
"Revoked successfully": "Revoked successfully",
|
|
||||||
"Select expiration date": "Select expiration date",
|
|
||||||
"This action cannot be undone. Any applications using this API key will stop working.": "This action cannot be undone. Any applications using this API key will stop working.",
|
|
||||||
"Update": "Update",
|
|
||||||
"Update {{credential}}": "Update {{credential}}",
|
|
||||||
"Manage API keys for all users in the workspace": "Manage API keys for all users in the workspace",
|
|
||||||
"Restrict API key creation to admins": "Restrict API key creation to admins",
|
|
||||||
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Only admins and owners can create new API keys. Existing member keys will continue to work.",
|
|
||||||
"Toggle restrict API keys to admins": "Toggle restrict API keys to admins",
|
|
||||||
"API key creation is restricted to admins by your workspace administrator.": "API key creation is restricted to admins by your workspace administrator.",
|
|
||||||
"AI settings": "AI settings",
|
|
||||||
"AI search": "AI search",
|
|
||||||
"AI Answer": "AI Answer",
|
|
||||||
"Ask AI": "Ask AI",
|
|
||||||
"AI is thinking...": "AI is thinking...",
|
|
||||||
"Thinking": "Thinking",
|
|
||||||
"Ask a question...": "Ask a question...",
|
|
||||||
"AI Answers": "AI Answers",
|
|
||||||
"AI-powered search (AI Answers)": "AI-powered search (AI Answers)",
|
|
||||||
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.",
|
|
||||||
"Toggle AI search": "Toggle AI search",
|
|
||||||
"Generative AI (Ask AI)": "Generative AI (Ask AI)",
|
|
||||||
"Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.",
|
|
||||||
"Toggle generative AI": "Toggle generative AI",
|
|
||||||
"Upgrade your plan": "Upgrade your plan",
|
|
||||||
"Available with a paid license": "Available with a paid license",
|
|
||||||
"Upgrade your license tier.": "Upgrade your license tier.",
|
|
||||||
"AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.",
|
|
||||||
"AI & MCP": "AI & MCP",
|
|
||||||
"AI": "AI",
|
|
||||||
"MCP": "MCP",
|
|
||||||
"Model Context Protocol (MCP)": "Model Context Protocol (MCP)",
|
|
||||||
"Enable the MCP server to allow AI assistants and tools to interact with your workspace content.": "Enable the MCP server to allow AI assistants and tools to interact with your workspace content.",
|
|
||||||
"MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.",
|
|
||||||
"MCP Server URL": "MCP Server URL",
|
|
||||||
"Use your API key for authentication. You can manage API keys in your account settings.": "Use your API key for authentication. You can manage API keys in your account settings.",
|
|
||||||
"Supported tools": "Supported tools",
|
|
||||||
"Your workspace has MCP enabled. Use your API key to connect AI assistants.": "Your workspace has MCP enabled. Use your API key to connect AI assistants.",
|
|
||||||
"MCP server URL:": "MCP server URL:",
|
|
||||||
"Learn more": "Learn more",
|
|
||||||
"Manage API keys for all users in the workspace. View the <anchor>API documentation</anchor> for usage details.": "Manage API keys for all users in the workspace. View the <anchor>API documentation</anchor> for usage details.",
|
|
||||||
"View the <anchor>API documentation</anchor> for usage details.": "View the <anchor>API documentation</anchor> for usage details.",
|
|
||||||
"View the <anchor>MCP documentation</anchor>.": "View the <anchor>MCP documentation</anchor>.",
|
|
||||||
"Sources": "Sources",
|
|
||||||
"AI Answers not available for attachments": "AI Answers not available for attachments",
|
|
||||||
"No answer available": "No answer available",
|
|
||||||
"Background color": "Background color",
|
|
||||||
"Highlight color": "Highlight color",
|
|
||||||
"Remove color": "Remove color",
|
|
||||||
"Notifications": "Notifications",
|
|
||||||
"No notifications": "No notifications",
|
|
||||||
"No unread notifications": "No unread notifications",
|
|
||||||
"All notifications": "All notifications",
|
|
||||||
"Unread only": "Unread only",
|
|
||||||
"Mark all as read": "Mark all as read",
|
|
||||||
"Mark as read": "Mark as read",
|
|
||||||
"More options": "More options",
|
|
||||||
"<bold>{{name}}</bold> mentioned you in a comment": "<bold>{{name}}</bold> mentioned you in a comment",
|
|
||||||
"<bold>{{name}}</bold> commented on a page": "<bold>{{name}}</bold> commented on a page",
|
|
||||||
"<bold>{{name}}</bold> resolved a comment": "<bold>{{name}}</bold> resolved a comment",
|
|
||||||
"<bold>{{name}}</bold> mentioned you on a page": "<bold>{{name}}</bold> mentioned you on a page",
|
|
||||||
"<bold>{{name}}</bold> gave you edit access to a page": "<bold>{{name}}</bold> gave you edit access to a page",
|
|
||||||
"<bold>{{name}}</bold> gave you view access to a page": "<bold>{{name}}</bold> gave you view access to a page",
|
|
||||||
"<bold>{{name}}</bold> updated a page": "<bold>{{name}}</bold> updated a page",
|
|
||||||
"Watch page": "Watch page",
|
|
||||||
"Stop watching": "Stop watching",
|
|
||||||
"Watch space": "Watch space",
|
|
||||||
"Stop watching space": "Stop watching space",
|
|
||||||
"Email notifications": "Email notifications",
|
|
||||||
"Page updates": "Page updates",
|
|
||||||
"Get notified when pages you watch are updated.": "Receive notifications when the pages you watch are updated.",
|
|
||||||
"Page mentions": "Page mentions",
|
|
||||||
"Get notified when someone mentions you on a page.": "Receive notifications when someone mentions you on a page.",
|
|
||||||
"Comment mentions": "Comment mentions",
|
|
||||||
"Get notified when someone mentions you in a comment.": "Receive notifications when someone mentions you in a comment.",
|
|
||||||
"New comments": "New comments",
|
|
||||||
"Get notified about new comments on threads you participate in.": "Receive notifications about new comments in threads you are participating in.",
|
|
||||||
"Resolved comments": "Resolved comments",
|
|
||||||
"Get notified when your comment is resolved.": "Receive a notification when your comment is resolved.",
|
|
||||||
"You are now watching this page": "You’re now watching this page",
|
|
||||||
"You are no longer watching this page": "You’re no longer watching this page",
|
|
||||||
"You are now watching this space": "You’re now watching this space",
|
|
||||||
"You are no longer watching this space": "You’re no longer watching this space",
|
|
||||||
"Direct": "Direct",
|
|
||||||
"Updates": "Updates",
|
|
||||||
"Today": "Today",
|
|
||||||
"Yesterday": "Yesterday",
|
|
||||||
"This week": "This week",
|
|
||||||
"Older": "Older",
|
|
||||||
"Restricted page": "Restricted page",
|
|
||||||
"Restricted pages cannot be shared publicly.": "Restricted pages cannot be shared publicly.",
|
|
||||||
"Restricted by parent": "Restricted by parent",
|
|
||||||
"Restricted": "Restricted",
|
|
||||||
"Open": "Open",
|
|
||||||
"Inherits restrictions from ancestor page": "Inherits restrictions from ancestor page",
|
|
||||||
"Only people listed below can access this page": "Only people listed below can access this page",
|
|
||||||
"Everyone in this space can access": "Everyone in this space can access",
|
|
||||||
"No additional restrictions on this page": "No additional restrictions on this page",
|
|
||||||
"Only specific people can access": "Only specific people can access",
|
|
||||||
"Use only inherited restrictions": "Use only inherited restrictions",
|
|
||||||
"Add restrictions on top of inherited": "Add restrictions on top of inherited",
|
|
||||||
"Inherited restriction": "Inherited restriction",
|
|
||||||
"Access limited by": "Access limited by",
|
|
||||||
"Restrict access to control who can view and edit this page": "Restrict access to control who can view and edit this page",
|
|
||||||
"Add additional restrictions specific to this page": "Add additional restrictions specific to this page",
|
|
||||||
"Access": "Access",
|
|
||||||
"People with access": "People with access",
|
|
||||||
"Remove all": "Remove all",
|
|
||||||
"Remove access": "Remove access",
|
|
||||||
"Remove all access": "Remove all access",
|
|
||||||
"Are you sure you want to remove this member's access to the page?": "Are you sure you want to remove this member's access to the page?",
|
|
||||||
"Are you sure you want to remove all specific access? This will make the page open to everyone in the space.": "Are you sure you want to remove all specific access? This will make the page open to everyone in the space.",
|
|
||||||
"Trash retention": "Trash retention",
|
|
||||||
"Pages in trash will be permanently deleted after this period.": "Pages in trash will be permanently deleted after this period.",
|
|
||||||
"Trash retention updated": "Trash retention updated",
|
|
||||||
"Failed to update trash retention": "Failed to update trash retention",
|
|
||||||
"Removed page restriction": "Removed page restriction",
|
|
||||||
"Added page permission": "Added page permission",
|
|
||||||
"Removed page permission": "Removed page permission",
|
|
||||||
"day": "day",
|
|
||||||
"days": "days",
|
|
||||||
"week": "week",
|
|
||||||
"weeks": "weeks",
|
|
||||||
"month": "month",
|
|
||||||
"months": "months",
|
|
||||||
"year": "year",
|
|
||||||
"years": "years",
|
|
||||||
"Period": "Period",
|
|
||||||
"Fixed date": "Fixed date",
|
|
||||||
"Indefinitely": "Indefinitely",
|
|
||||||
"Days": "Days",
|
|
||||||
"Weeks": "Weeks",
|
|
||||||
"Months": "Months",
|
|
||||||
"Years": "Years",
|
|
||||||
"Pick a date": "Pick a date",
|
|
||||||
"Maximum is {{max}} {{unit}} for this unit": "Maximum is {{max}} {{unit}} for this unit",
|
|
||||||
"Never expires. Verifiers can re-verify at any time.": "Never expires. Verifiers can re-verify at any time.",
|
|
||||||
"Verified": "Verified",
|
|
||||||
"Review needed": "Review needed",
|
|
||||||
"Verification expired": "Verification expired",
|
|
||||||
"Draft": "Draft",
|
|
||||||
"In Approval": "In Approval",
|
|
||||||
"In approval": "In approval",
|
|
||||||
"Approved": "Approved",
|
|
||||||
"Obsolete": "Obsolete",
|
|
||||||
"Expiring": "Expiring",
|
|
||||||
"Set up verification": "Set up verification",
|
|
||||||
"Verify page": "Verify page",
|
|
||||||
"Page verification": "Page verification",
|
|
||||||
"Add verification": "Add verification",
|
|
||||||
"Edit verification": "Edit verification",
|
|
||||||
"Search by title": "Search by title",
|
|
||||||
"Choose how this page should stay accurate.": "Choose how this page should stay accurate.",
|
|
||||||
"Recurring verification": "Recurring verification",
|
|
||||||
"Verifiers re-confirm this page on a schedule.": "Verifiers re-confirm this page on a schedule.",
|
|
||||||
"Re-verify on a schedule (e.g every 30 days )": "Re-verify on a schedule (e.g every 30 days )",
|
|
||||||
"Page stays editable at all times": "Page stays editable at all times",
|
|
||||||
"Best for runbooks, FAQs, living documentation": "Best for runbooks, FAQs, living documentation",
|
|
||||||
"Approval workflow": "Approval workflow",
|
|
||||||
"Formal document lifecycle with named approvers.": "Formal document lifecycle with named approvers.",
|
|
||||||
"Draft → In approval → Approved → Obsolete": "Draft → In approval → Approved → Obsolete",
|
|
||||||
"Locked once approved, with full history": "Locked once approved, with full history",
|
|
||||||
"Designed for ISO 9001, ISO 13485, and FDA": "Designed for ISO 9001, ISO 13485, and FDA",
|
|
||||||
"Best for SOPs and controlled documents": "Best for SOPs and controlled documents",
|
|
||||||
"Back": "Back",
|
|
||||||
"Quality management": "Quality management",
|
|
||||||
"Recurring": "Recurring",
|
|
||||||
"Pages move through draft, approval, and approved stages.": "Pages move through draft, approval, and approved stages.",
|
|
||||||
"Verifiers": "Verifiers",
|
|
||||||
"Add verifier": "Add verifier",
|
|
||||||
"I've reviewed this page for accuracy": "I've reviewed this page for accuracy",
|
|
||||||
"Set up": "Set up",
|
|
||||||
"Remove verification": "Remove verification",
|
|
||||||
"Are you sure you want to remove verification from this page?": "Are you sure you want to remove verification from this page?",
|
|
||||||
"Assigned verifiers must periodically re-verify this page.": "Assigned verifiers must periodically re-verify this page.",
|
|
||||||
"Last verified by {{name}} {{time}} (expired)": "Last verified by {{name}} {{time}} (expired)",
|
|
||||||
"The fixed expiration date has passed.": "The fixed expiration date has passed.",
|
|
||||||
"Verified by {{name}} {{time}}": "Verified by {{name}} {{time}}",
|
|
||||||
"Expires {{date}}": "Expires {{date}}",
|
|
||||||
"Expired {{date}}": "Expired {{date}}",
|
|
||||||
"Mark as obsolete": "Mark as obsolete",
|
|
||||||
"Mark obsolete": "Mark obsolete",
|
|
||||||
"Returned by {{name}} {{time}}": "Returned by {{name}} {{time}}",
|
|
||||||
"No approval has been requested yet.": "No approval has been requested yet.",
|
|
||||||
"Submitted by {{name}} {{time}}": "Submitted by {{name}} {{time}}",
|
|
||||||
"Someone": "Someone",
|
|
||||||
"Approved by {{name}} {{time}}": "Approved by {{name}} {{time}}",
|
|
||||||
"This document has been marked as obsolete.": "This document has been marked as obsolete.",
|
|
||||||
"Rejection comment": "Rejection comment",
|
|
||||||
"Reason for returning this document...": "Reason for returning this document...",
|
|
||||||
"Confirm rejection": "Confirm rejection",
|
|
||||||
"Submit for approval": "Submit for approval",
|
|
||||||
"Reject": "Reject",
|
|
||||||
"Approve": "Approve",
|
|
||||||
"Re-submit for approval": "Re-submit for approval",
|
|
||||||
"Verified until": "Verified until",
|
|
||||||
"QMS": "QMS",
|
|
||||||
"Verified pages": "Verified pages",
|
|
||||||
"Search pages...": "Search pages...",
|
|
||||||
"Filter by space": "Filter by space",
|
|
||||||
"Filter by type": "Filter by type",
|
|
||||||
"<bold>{{name}}</bold> verified a page": "<bold>{{name}}</bold> verified a page",
|
|
||||||
"<bold>{{name}}</bold> submitted a page for your approval": "<bold>{{name}}</bold> submitted a page for your approval",
|
|
||||||
"<bold>{{name}}</bold> returned a page for revision": "<bold>{{name}}</bold> returned a page for revision",
|
|
||||||
"Page verification expires soon": "Page verification expires soon",
|
|
||||||
"Page verification has expired": "Page verification has expired",
|
|
||||||
"Verifying your email": "Verifying your email",
|
|
||||||
"Please wait...": "Please wait...",
|
|
||||||
"Verification failed. The link may have expired.": "Verification failed. The link may have expired.",
|
|
||||||
"Check your email": "Check your email",
|
|
||||||
"We sent a verification link to {{email}}.": "We sent a verification link to {{email}}.",
|
|
||||||
"We sent a verification link to your email.": "We sent a verification link to your email.",
|
|
||||||
"Click the link to verify your email and access your workspace.": "Click the link to verify your email and access your workspace.",
|
|
||||||
"Resend verification email": "Resend verification email",
|
|
||||||
"Verification email sent. Please check your inbox.": "Verification email sent. Please check your inbox.",
|
|
||||||
"Failed to resend verification email. Please try again.": "Failed to resend verification email. Please try again.",
|
|
||||||
"We've sent you an email with your associated workspaces.": "We've sent you an email with your associated workspaces.",
|
|
||||||
"Load more": "Load more",
|
|
||||||
"Log out of all devices": "Log out of all devices",
|
|
||||||
"Log out of all sessions except this device": "Log out of all sessions except this device",
|
|
||||||
"This Device": "This Device",
|
|
||||||
"Unknown device": "Unknown device",
|
|
||||||
"No active sessions": "No active sessions",
|
|
||||||
"Session revoked": "Session revoked",
|
|
||||||
"All other sessions revoked": "All other sessions revoked",
|
|
||||||
"Last used": "Last used",
|
|
||||||
"Created": "Created",
|
|
||||||
"Rename": "Rename",
|
|
||||||
"Publish": "Publish",
|
|
||||||
"Security": "Security",
|
|
||||||
"Enforce SSO": "Enforce SSO",
|
|
||||||
"Once enforced, members will not be able to login with email and password.": "Once enforced, members will not be able to login with email and password.",
|
|
||||||
"AI-generated content may not be accurate.": "AI-generated content may not be accurate.",
|
|
||||||
"AI Chat": "AI Chat",
|
|
||||||
"Analyze for insights": "Analyze for insights",
|
|
||||||
"Ask anything...": "Ask anything...",
|
|
||||||
"Chat history": "Chat history",
|
|
||||||
"Chat name": "Chat name",
|
|
||||||
"Close": "Close",
|
|
||||||
"Docmost AI": "Docmost AI",
|
|
||||||
"Failed to load chat. An error occurred.": "Failed to load chat. An error occurred.",
|
|
||||||
"Failed to render this message.": "Failed to render this message.",
|
|
||||||
"How can I help you today?": "How can I help you today?",
|
|
||||||
"New chat": "New chat",
|
|
||||||
"No chat history": "No chat history",
|
|
||||||
"No chats found": "No chats found",
|
|
||||||
"No conversations yet": "No conversations yet",
|
|
||||||
"Open full page": "Open full page",
|
|
||||||
"Previous 7 days": "Previous 7 days",
|
|
||||||
"Previous 30 days": "Previous 30 days",
|
|
||||||
"Search chats...": "Search chats...",
|
|
||||||
"Search chats": "Search chats",
|
|
||||||
"Ask anything... Use @ to mention pages": "Ask anything... Use @ to mention pages",
|
|
||||||
"Ask anything or search your workspace": "Ask anything or search your workspace",
|
|
||||||
"Welcome to {{name}}": "Welcome to {{name}}",
|
|
||||||
"Add files": "Add files",
|
|
||||||
"Mention a page": "Mention a page",
|
|
||||||
"Start a new chat to see it here.": "Start a new chat to see it here.",
|
|
||||||
"Summarize this page": "Summarize this page",
|
|
||||||
"Toggle AI Chat": "Toggle AI Chat",
|
|
||||||
"Translate this page": "Translate this page",
|
|
||||||
"Try a different search term.": "Try a different search term.",
|
|
||||||
"Try again": "Try again",
|
|
||||||
"Untitled chat": "Untitled chat",
|
|
||||||
"What can I help you with?": "What can I help you with?",
|
|
||||||
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
|
|
||||||
"Automatically provision users and groups from your identity provider via SCIM.": "Automatically provision users and groups from your identity provider via SCIM.",
|
|
||||||
"Configure your identity provider with this URL to provision users and groups.": "Configure your identity provider with this URL to provision users and groups.",
|
|
||||||
"Create {{credential}}": "Create {{credential}}",
|
|
||||||
"{{credential}} created": "{{credential}} created",
|
|
||||||
"{{credential}} created successfully": "{{credential}} created successfully",
|
|
||||||
"Created by": "Created by",
|
|
||||||
"Custom": "Custom",
|
|
||||||
"Enable SCIM": "Enable SCIM",
|
|
||||||
"Enter a descriptive name": "Enter a descriptive name",
|
|
||||||
"I've saved my {{credential}}": "I've saved my {{credential}}",
|
|
||||||
"Important": "Important",
|
|
||||||
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Make sure to copy your {{credential}} now. You won't be able to see it again!",
|
|
||||||
"Never": "Never",
|
|
||||||
"Revoke {{credential}}": "Revoke {{credential}}",
|
|
||||||
"SCIM endpoint URL": "SCIM endpoint URL",
|
|
||||||
"SCIM provisioning": "SCIM provisioning",
|
|
||||||
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
|
|
||||||
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
|
|
||||||
"SCIM token": "SCIM token",
|
|
||||||
"SCIM tokens": "SCIM tokens",
|
|
||||||
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
|
|
||||||
"Toggle SCIM provisioning": "Toggle SCIM provisioning",
|
|
||||||
"Token": "Token",
|
|
||||||
"Page menu": "Page menu",
|
|
||||||
"Expand": "Expand",
|
|
||||||
"Collapse": "Collapse",
|
|
||||||
"Comment menu": "Comment menu",
|
|
||||||
"Group menu": "Group menu",
|
|
||||||
"Show hidden breadcrumbs": "Show hidden breadcrumbs",
|
|
||||||
"Breadcrumbs": "Breadcrumbs",
|
|
||||||
"Page actions": "Page actions",
|
|
||||||
"Pick emoji": "Pick emoji",
|
|
||||||
"Template menu": "Template menu",
|
|
||||||
"Use": "Use",
|
|
||||||
"Use template": "Use template",
|
|
||||||
"Preview template: {{title}}": "Preview template: {{title}}",
|
|
||||||
"Use a template": "Use a template",
|
|
||||||
"Search templates...": "Search templates...",
|
|
||||||
"Search spaces...": "Search spaces...",
|
|
||||||
"No templates found": "No templates found",
|
|
||||||
"No spaces found": "No spaces found",
|
|
||||||
"Browse all templates": "Browse all templates",
|
|
||||||
"This space": "This space",
|
|
||||||
"All templates": "All templates",
|
|
||||||
"Global": "Global",
|
|
||||||
"New template": "New template",
|
|
||||||
"Edit template": "Edit template",
|
|
||||||
"Are you sure you want to delete this template?": "Are you sure you want to delete this template?",
|
|
||||||
"Template scope updated": "Template scope updated",
|
|
||||||
"Choose which space this template belongs to": "Choose which space this template belongs to",
|
|
||||||
"Scope": "Scope",
|
|
||||||
"Select scope": "Select scope",
|
|
||||||
"Title": "Title",
|
|
||||||
"Saving...": "Saving...",
|
|
||||||
"Saved": "Saved",
|
|
||||||
"Save failed. Retry": "Save failed. Retry",
|
|
||||||
"By {{name}}": "By {{name}}",
|
|
||||||
"Updated {{time}}": "Updated {{time}}",
|
|
||||||
"Choose destination": "Choose destination",
|
|
||||||
"Search pages and spaces...": "Search pages and spaces...",
|
|
||||||
"No results found": "No results found",
|
|
||||||
"You don't have permission to create pages here": "You don't have permission to create pages here",
|
|
||||||
"Chat menu": "Chat menu",
|
|
||||||
"API key menu": "API key menu",
|
|
||||||
"Jump to comment selection": "Jump to comment selection",
|
|
||||||
"Slash commands": "Slash commands",
|
|
||||||
"Mention suggestions": "Mention suggestions",
|
|
||||||
"Link suggestions": "Link suggestions",
|
|
||||||
"Diagram editor": "Diagram editor",
|
|
||||||
"Add comment": "Add comment",
|
|
||||||
"Find and replace": "Find and replace",
|
|
||||||
"Main navigation": "Main navigation",
|
|
||||||
"Space navigation": "Space navigation",
|
|
||||||
"Settings navigation": "Settings navigation",
|
|
||||||
"AI navigation": "AI navigation",
|
|
||||||
"Breadcrumb": "Breadcrumb",
|
|
||||||
"Synced block": "Synced block",
|
|
||||||
"Create a block that stays in sync across pages.": "Create a block that stays in sync across pages.",
|
|
||||||
"Editing original": "Editing original",
|
|
||||||
"Copy synced block": "Copy synced block",
|
|
||||||
"Unsync": "Unsync",
|
|
||||||
"Delete synced block": "Delete synced block",
|
|
||||||
"Synced to {{count}} other page_one": "Synced to {{count}} other page",
|
|
||||||
"Synced to {{count}} other page_other": "Synced to {{count}} other pages",
|
|
||||||
"ORIGINAL": "ORIGINAL",
|
|
||||||
"THIS PAGE": "THIS PAGE",
|
|
||||||
"No pages": "No pages",
|
|
||||||
"The original synced block no longer exists": "The original synced block no longer exists",
|
|
||||||
"You don't have access to this synced block": "You don't have access to this synced block",
|
|
||||||
"Failed to load this synced block": "Failed to load this synced block",
|
|
||||||
"Fixed editor toolbar": "Fixed editor toolbar",
|
|
||||||
"Show a formatting toolbar above the editor with quick access to common actions.": "Show a formatting toolbar above the editor with quick access to common actions.",
|
|
||||||
"Toggle fixed editor toolbar": "Toggle fixed editor toolbar",
|
|
||||||
"Normal text": "Normal text",
|
|
||||||
"More inline formatting": "More inline formatting",
|
|
||||||
"Subscript": "Subscript",
|
|
||||||
"Superscript": "Superscript",
|
|
||||||
"Inline code": "Inline code",
|
|
||||||
"Insert media": "Insert media",
|
|
||||||
"Mention": "Mention",
|
|
||||||
"Emoji": "Emoji",
|
|
||||||
"Columns": "Columns",
|
|
||||||
"More inserts": "More inserts",
|
|
||||||
"Embeds": "Embeds",
|
|
||||||
"Diagrams": "Diagrams",
|
|
||||||
"Advanced": "Advanced",
|
|
||||||
"Utility": "Utility",
|
|
||||||
"Decrease indent": "Decrease indent",
|
|
||||||
"Increase indent": "Increase indent",
|
|
||||||
"Clear formatting": "Clear formatting",
|
|
||||||
"Code block": "Code block",
|
|
||||||
"Experimental": "Experimental",
|
|
||||||
"Strikethrough": "Strikethrough",
|
|
||||||
"Undo": "Undo",
|
|
||||||
"Redo": "Redo",
|
|
||||||
"Backlinks": "Backlinks",
|
|
||||||
"Last updated by": "Last updated by",
|
|
||||||
"Last updated": "Last updated",
|
|
||||||
"Stats": "Stats",
|
|
||||||
"Word count": "Word count",
|
|
||||||
"Characters": "Characters",
|
|
||||||
"Incoming links": "Incoming links",
|
|
||||||
"Outgoing links": "Outgoing links",
|
|
||||||
"Incoming links ({{count}})": "Incoming links ({{count}})",
|
|
||||||
"Outgoing links ({{count}})": "Outgoing links ({{count}})",
|
|
||||||
"No pages link here yet.": "No pages link here yet.",
|
|
||||||
"This page doesn't link to other pages yet.": "This page doesn't link to other pages yet.",
|
|
||||||
"Verified until {{date}}": "Verified until {{date}}",
|
|
||||||
"Labels": "Labels",
|
|
||||||
"Add label": "Add label",
|
|
||||||
"No labels yet": "No labels yet",
|
|
||||||
"Already added": "Already added",
|
|
||||||
"Invalid label name": "Invalid label name",
|
|
||||||
"No matches": "No matches",
|
|
||||||
"Search or create…": "Search or create…",
|
|
||||||
"Remove label {{name}}": "Remove label {{name}}",
|
|
||||||
"Failed to add label": "Failed to add label",
|
|
||||||
"Failed to remove label": "Failed to remove label",
|
|
||||||
"No pages with this label": "No pages with this label",
|
|
||||||
"Pages tagged with this label will appear here.": "Pages tagged with this label will appear here.",
|
|
||||||
"No pages match your search.": "No pages match your search.",
|
|
||||||
"Updated {{date}}": "Updated {{date}}",
|
|
||||||
"Cell actions": "Cell actions",
|
|
||||||
"Column actions": "Column actions",
|
|
||||||
"Row actions": "Row actions"
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@
|
|||||||
"Add members": "Aggiungi membri",
|
"Add members": "Aggiungi membri",
|
||||||
"Add to groups": "Aggiungi ai gruppi",
|
"Add to groups": "Aggiungi ai gruppi",
|
||||||
"Add space members": "Aggiungi membri allo spazio",
|
"Add space members": "Aggiungi membri allo spazio",
|
||||||
"Add to favorites": "Aggiungi ai preferiti",
|
|
||||||
"Admin": "Amministratore",
|
"Admin": "Amministratore",
|
||||||
"Are you sure you want to delete this group? Members will lose access to resources this group has access to.": "Sei sicuro di voler eliminare questo gruppo? I membri perderanno l'accesso alle risorse accessibili da questo gruppo.",
|
"Are you sure you want to delete this group? Members will lose access to resources this group has access to.": "Sei sicuro di voler eliminare questo gruppo? I membri perderanno l'accesso alle risorse accessibili da questo gruppo.",
|
||||||
"Are you sure you want to delete this page?": "Sei sicuro di voler eliminare questa pagina?",
|
"Are you sure you want to delete this page?": "Sei sicuro di voler eliminare questa pagina?",
|
||||||
@@ -30,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Scegli la lingua da utilizzare per l'interfaccia.",
|
"Choose your preferred interface language.": "Scegli la lingua da utilizzare per l'interfaccia.",
|
||||||
"Choose your preferred page width.": "Scegli la larghezza della pagina che preferisci.",
|
"Choose your preferred page width.": "Scegli la larghezza della pagina che preferisci.",
|
||||||
"Confirm": "Conferma",
|
"Confirm": "Conferma",
|
||||||
"Copy as Markdown": "Copia come Markdown",
|
|
||||||
"Copy link": "Copia link",
|
"Copy link": "Copia link",
|
||||||
"Create": "Crea",
|
"Create": "Crea",
|
||||||
"Create group": "Crea gruppo",
|
"Create group": "Crea gruppo",
|
||||||
@@ -48,19 +46,18 @@
|
|||||||
"e.g ACME": "es. ACME",
|
"e.g ACME": "es. ACME",
|
||||||
"e.g ACME Inc": "es. ACME Inc",
|
"e.g ACME Inc": "es. ACME Inc",
|
||||||
"e.g Developers": "es. Sviluppatori",
|
"e.g Developers": "es. Sviluppatori",
|
||||||
"e.g Group for developers": "es. Gruppo per sviluppatori",
|
"e.g Group for developers": "es. Gruppo per gli sviluppatori",
|
||||||
"e.g product": "es. prodotto",
|
"e.g product": "es. prodotto",
|
||||||
"e.g Product Team": "es. Team di prodotto",
|
"e.g Product Team": "es. Team di Prodotto",
|
||||||
"e.g Sales": "es. Vendite",
|
"e.g Sales": "es. Vendite",
|
||||||
"e.g Space for product team": "es. Spazio per il team di prodotto",
|
"e.g Space for product team": "es. Spazio per il team di prodotto",
|
||||||
"e.g Space for sales team to collaborate": "es. Spazio per la collaborazione del team vendite",
|
"e.g Space for sales team to collaborate": "es. Spazio per la collaborazione del team di vendita",
|
||||||
"Edit": "Modifica",
|
"Edit": "Modifica",
|
||||||
"Read": "Leggi",
|
|
||||||
"Edit group": "Modifica gruppo",
|
"Edit group": "Modifica gruppo",
|
||||||
"Email": "Email",
|
"Email": "Email",
|
||||||
"Enter a strong password": "Inserisci una password sicura",
|
"Enter a strong password": "Inserisci una password sicura",
|
||||||
"Enter valid email addresses separated by comma or space max_50": "Inserisci degli indirizzi email validi separati da virgola o spazio [max: 50]",
|
"Enter valid email addresses separated by comma or space max_50": "Inserisci degli indirizzi email validi separati da virgola o spazio [max: 50]",
|
||||||
"enter valid emails addresses": "inserisci indirizzi email validi",
|
"enter valid emails addresses": "inserisci degli indirizzi email validi",
|
||||||
"Enter your current password": "Inserisci la tua password attuale",
|
"Enter your current password": "Inserisci la tua password attuale",
|
||||||
"enter your full name": "inserisci il tuo nome completo",
|
"enter your full name": "inserisci il tuo nome completo",
|
||||||
"Enter your new password": "Inserisci la tua nuova password",
|
"Enter your new password": "Inserisci la tua nuova password",
|
||||||
@@ -71,14 +68,10 @@
|
|||||||
"Export": "Esporta",
|
"Export": "Esporta",
|
||||||
"Failed to create page": "Impossibile creare la pagina",
|
"Failed to create page": "Impossibile creare la pagina",
|
||||||
"Failed to delete page": "Impossibile eliminare la pagina",
|
"Failed to delete page": "Impossibile eliminare la pagina",
|
||||||
"Failed to restore page": "Failed to restore page",
|
|
||||||
"Failed to fetch recent pages": "Impossibile recuperare le pagine recenti",
|
"Failed to fetch recent pages": "Impossibile recuperare le pagine recenti",
|
||||||
"Failed to import pages": "Impossibile importare le pagine",
|
"Failed to import pages": "Impossibile importare le pagine",
|
||||||
"Failed to load page. An error occurred.": "Il caricamento della pagina è fallito. Si è verificato un errore.",
|
"Failed to load page. An error occurred.": "Il caricamento della pagina è fallito. Si è verificato un errore.",
|
||||||
"Failed to update data": "Impossibile aggiornare i dati",
|
"Failed to update data": "Impossibile aggiornare i dati",
|
||||||
"Favorite spaces": "Spazi preferiti",
|
|
||||||
"Favorite spaces appear here": "Gli spazi preferiti appariranno qui",
|
|
||||||
"Favorites": "Preferiti",
|
|
||||||
"Full access": "Accesso completo",
|
"Full access": "Accesso completo",
|
||||||
"Full page width": "Pagina a larghezza intera",
|
"Full page width": "Pagina a larghezza intera",
|
||||||
"Full width": "Larghezza intera",
|
"Full width": "Larghezza intera",
|
||||||
@@ -97,7 +90,6 @@
|
|||||||
"Invite by email": "Invita tramite email",
|
"Invite by email": "Invita tramite email",
|
||||||
"Invite members": "Invita membri",
|
"Invite members": "Invita membri",
|
||||||
"Invite new members": "Invita nuovi membri",
|
"Invite new members": "Invita nuovi membri",
|
||||||
"Invite People": "Invita persone",
|
|
||||||
"Invited members who are yet to accept their invitation will appear here.": "I membri invitati che non hanno ancora accettato il loro invito appariranno qui.",
|
"Invited members who are yet to accept their invitation will appear here.": "I membri invitati che non hanno ancora accettato il loro invito appariranno qui.",
|
||||||
"Invited members will be granted access to spaces the groups can access": "I membri invitati avranno accesso agli spazi a cui i gruppi possono accedere",
|
"Invited members will be granted access to spaces the groups can access": "I membri invitati avranno accesso agli spazi a cui i gruppi possono accedere",
|
||||||
"Join the workspace": "Unisciti all'area di lavoro",
|
"Join the workspace": "Unisciti all'area di lavoro",
|
||||||
@@ -122,7 +114,6 @@
|
|||||||
"No group found": "Nessun gruppo trovato",
|
"No group found": "Nessun gruppo trovato",
|
||||||
"No page history saved yet.": "La pagina non ha una cronologia per ora.",
|
"No page history saved yet.": "La pagina non ha una cronologia per ora.",
|
||||||
"No pages yet": "Nessuna pagina per ora",
|
"No pages yet": "Nessuna pagina per ora",
|
||||||
"No shared pages": "Nessuna pagina condivisa.",
|
|
||||||
"No results found...": "Nessun risultato trovato...",
|
"No results found...": "Nessun risultato trovato...",
|
||||||
"No user found": "Nessun utente trovato",
|
"No user found": "Nessun utente trovato",
|
||||||
"Overview": "Panoramica",
|
"Overview": "Panoramica",
|
||||||
@@ -130,14 +121,11 @@
|
|||||||
"page": "pagina",
|
"page": "pagina",
|
||||||
"Page deleted successfully": "Pagina eliminata con successo",
|
"Page deleted successfully": "Pagina eliminata con successo",
|
||||||
"Page history": "Cronologia della pagina",
|
"Page history": "Cronologia della pagina",
|
||||||
"Select version": "Seleziona versione",
|
|
||||||
"Highlight changes": "Evidenzia modifiche",
|
|
||||||
"Page import is in progress. Please do not close this tab.": "L'importazione della pagina è in corso. Si prega di non chiudere questa scheda.",
|
"Page import is in progress. Please do not close this tab.": "L'importazione della pagina è in corso. Si prega di non chiudere questa scheda.",
|
||||||
"Pages": "Pagine",
|
"Pages": "Pagine",
|
||||||
"pages": "pagine",
|
"pages": "pagine",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
"Password changed successfully": "Password cambiata con successo",
|
"Password changed successfully": "Password cambiata con successo",
|
||||||
"People": "Persone",
|
|
||||||
"Pending": "In sospeso",
|
"Pending": "In sospeso",
|
||||||
"Please confirm your action": "Si prega di confermare la propria azione",
|
"Please confirm your action": "Si prega di confermare la propria azione",
|
||||||
"Preferences": "Preferenze",
|
"Preferences": "Preferenze",
|
||||||
@@ -145,7 +133,6 @@
|
|||||||
"Profile": "Profilo",
|
"Profile": "Profilo",
|
||||||
"Recently updated": "Aggiornato di recente",
|
"Recently updated": "Aggiornato di recente",
|
||||||
"Remove": "Rimuovi",
|
"Remove": "Rimuovi",
|
||||||
"Remove from favorites": "Rimuovi dai preferiti",
|
|
||||||
"Remove group member": "Rimuovi membro dal gruppo",
|
"Remove group member": "Rimuovi membro dal gruppo",
|
||||||
"Remove space member": "Rimuovi membro dallo spazio",
|
"Remove space member": "Rimuovi membro dallo spazio",
|
||||||
"Restore": "Ripristina",
|
"Restore": "Ripristina",
|
||||||
@@ -156,56 +143,55 @@
|
|||||||
"Search for users": "Cerca un utente",
|
"Search for users": "Cerca un utente",
|
||||||
"Search for users and groups": "Cerca un utente o un gruppo",
|
"Search for users and groups": "Cerca un utente o un gruppo",
|
||||||
"Search...": "Cerca...",
|
"Search...": "Cerca...",
|
||||||
"Select language": "Seleziona lingua",
|
"Select language": "Seleziona una lingua",
|
||||||
"Select role": "Seleziona ruolo",
|
"Select role": "Seleziona un ruolo",
|
||||||
"Select role to assign to all invited members": "Seleziona il ruolo da assegnare a tutti i membri invitati",
|
"Select role to assign to all invited members": "Seleziona il ruolo da assegnare a tutti i membri invitati",
|
||||||
"Select theme": "Seleziona tema",
|
"Select theme": "Seleziona un tema",
|
||||||
"Send invitation": "Invia invito",
|
"Send invitation": "Invia invito",
|
||||||
"Invitation sent": "Invito inviato",
|
"Invitation sent": "Invito inviato",
|
||||||
"Settings": "Impostazioni",
|
"Settings": "Impostazioni",
|
||||||
"Setup workspace": "Configura workspace",
|
"Setup workspace": "Configura l'area di lavoro",
|
||||||
"Sign In": "Accedi",
|
"Sign In": "Accedi",
|
||||||
"Sign Up": "Registrati",
|
"Sign Up": "Registrati",
|
||||||
"Slug": "Slug",
|
"Slug": "Slug",
|
||||||
"Space": "Spazio",
|
"Space": "Spazio",
|
||||||
"Space description": "Descrizione dello spazio",
|
"Space description": "Descrizione dello spazio",
|
||||||
"Space menu": "Menu dello spazio",
|
"Space menu": "Menu spazio",
|
||||||
"Space name": "Nome dello spazio",
|
"Space name": "Nome dello spazio",
|
||||||
"Space settings": "Impostazioni dello spazio",
|
"Space settings": "Impostazioni dello spazio",
|
||||||
"Space slug": "Slug dello spazio",
|
"Space slug": "Slug dello spazio",
|
||||||
"Spaces": "Spazi",
|
"Spaces": "Spazi",
|
||||||
"Spaces you belong to": "Spazi di cui fai parte",
|
"Spaces you belong to": "Spazi a cui appartieni",
|
||||||
"No space found": "Nessuno spazio trovato",
|
"No space found": "Nessuno spazio trovato",
|
||||||
"Search for spaces": "Cerca spazi",
|
"Search for spaces": "Cerca uno spazio",
|
||||||
"Start typing to search...": "Inizia a digitare per cercare...",
|
"Start typing to search...": "Inizia a digitare per cercare...",
|
||||||
"Status": "Stato",
|
"Status": "Stato",
|
||||||
"Successfully imported": "Importato con successo",
|
"Successfully imported": "Importato con successo",
|
||||||
"Successfully restored": "Ripristinato con successo",
|
"Successfully restored": "Ripristinato con successo",
|
||||||
"System settings": "Impostazioni di sistema",
|
"System settings": "Impostazioni di sistema",
|
||||||
"Templates": "Modelli",
|
|
||||||
"Theme": "Tema",
|
"Theme": "Tema",
|
||||||
"To change your email, you have to enter your password and new email.": "Per cambiare la tua email, devi inserire la tua password e la nuova email.",
|
"To change your email, you have to enter your password and new email.": "Per cambiare la tua email, devi inserire la tua password e la nuova email.",
|
||||||
"Toggle full page width": "Attiva/disattiva larghezza completa della pagina",
|
"Toggle full page width": "Attiva/disattiva pagina a larghezza intera",
|
||||||
"Unable to import pages. Please try again.": "Impossibile importare le pagine. Riprova.",
|
"Unable to import pages. Please try again.": "Impossibile importare le pagine. Riprova.",
|
||||||
"untitled": "senza titolo",
|
"untitled": "senza titolo",
|
||||||
"Untitled": "Senza titolo",
|
"Untitled": "Senza titolo",
|
||||||
"Updated successfully": "Aggiornato con successo",
|
"Updated successfully": "Aggiornato con successo",
|
||||||
"User": "Utente",
|
"User": "Utente",
|
||||||
"Workspace": "Workspace",
|
"Workspace": "Area di lavoro",
|
||||||
"Workspace Name": "Nome del workspace",
|
"Workspace Name": "Nome dell'area di lavoro",
|
||||||
"Workspace settings": "Impostazioni del workspace",
|
"Workspace settings": "Impostazioni dell'area di lavoro",
|
||||||
"You can change your password here.": "Qui puoi cambiare la tua password.",
|
"You can change your password here.": "Qui puoi cambiare la tua password.",
|
||||||
"Your Email": "La tua email",
|
"Your Email": "La tua email",
|
||||||
"Your import is complete.": "La tua importazione è completata.",
|
"Your import is complete.": "La tua importazione è completata.",
|
||||||
"Your name": "Il tuo nome",
|
"Your name": "Il tuo nome",
|
||||||
"Your Name": "Il tuo nome",
|
"Your Name": "Il Tuo Nome",
|
||||||
"Your password": "La tua password",
|
"Your password": "La tua password",
|
||||||
"Your password must be a minimum of 8 characters.": "La tua password deve contenere almeno 8 caratteri.",
|
"Your password must be a minimum of 8 characters.": "La tua password deve contenere almeno 8 caratteri.",
|
||||||
"Sidebar toggle": "Attiva/disattiva barra laterale",
|
"Sidebar toggle": "Attiva/disattiva barra laterale",
|
||||||
"Comments": "Commenti",
|
"Comments": "Commenti",
|
||||||
"404 page not found": "404 pagina non trovata",
|
"404 page not found": "404 pagina non trovata",
|
||||||
"Sorry, we can't find the page you are looking for.": "Siamo spiacenti, non riusciamo a trovare la pagina che stai cercando.",
|
"Sorry, we can't find the page you are looking for.": "Siamo spiacenti, non riusciamo a trovare la pagina che stai cercando.",
|
||||||
"Take me back to homepage": "Torna alla homepage",
|
"Take me back to homepage": "Torna all'homepage",
|
||||||
"Forgot password": "Password dimenticata",
|
"Forgot password": "Password dimenticata",
|
||||||
"Forgot your password?": "Hai dimenticato la password?",
|
"Forgot your password?": "Hai dimenticato la password?",
|
||||||
"A password reset link has been sent to your email. Please check your inbox.": "Un link per il reset della password è stato inviato al tuo indirizzo email. Per favore, controlla la tua casella di posta.",
|
"A password reset link has been sent to your email. Please check your inbox.": "Un link per il reset della password è stato inviato al tuo indirizzo email. Per favore, controlla la tua casella di posta.",
|
||||||
@@ -217,14 +203,9 @@
|
|||||||
"Reply...": "Rispondi...",
|
"Reply...": "Rispondi...",
|
||||||
"Error loading comments.": "Si è verificato un errore durante il caricamento dei commenti.",
|
"Error loading comments.": "Si è verificato un errore durante il caricamento dei commenti.",
|
||||||
"No comments yet.": "Nessun commento per ora.",
|
"No comments yet.": "Nessun commento per ora.",
|
||||||
"No open comments.": "Nessun commento aperto.",
|
|
||||||
"No resolved comments.": "Nessun commento risolto.",
|
|
||||||
"Add a comment...": "Aggiungi un commento...",
|
|
||||||
"Edit comment": "Modifica commento",
|
"Edit comment": "Modifica commento",
|
||||||
"Delete comment": "Elimina commento",
|
"Delete comment": "Elimina commento",
|
||||||
"Are you sure you want to delete this comment?": "Sei sicuro di voler eliminare questo commento?",
|
"Are you sure you want to delete this comment?": "Sei sicuro di voler eliminare questo commento?",
|
||||||
"Delete chat": "Elimina chat",
|
|
||||||
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "Sei sicuro di voler eliminare '{{title}}'? Questa azione non può essere annullata.",
|
|
||||||
"Comment created successfully": "Commento creato con successo",
|
"Comment created successfully": "Commento creato con successo",
|
||||||
"Error creating comment": "Si è verificato un errore durante la creazione del commento",
|
"Error creating comment": "Si è verificato un errore durante la creazione del commento",
|
||||||
"Comment updated successfully": "Commento aggiornato con successo",
|
"Comment updated successfully": "Commento aggiornato con successo",
|
||||||
@@ -233,16 +214,17 @@
|
|||||||
"Failed to delete comment": "Impossibile eliminare il commento",
|
"Failed to delete comment": "Impossibile eliminare il commento",
|
||||||
"Comment resolved successfully": "Commento risolto con successo",
|
"Comment resolved successfully": "Commento risolto con successo",
|
||||||
"Comment re-opened successfully": "Commento riaperto con successo",
|
"Comment re-opened successfully": "Commento riaperto con successo",
|
||||||
"Comment unresolved successfully": "Commento contrassegnato come non risolto con successo",
|
"Comment unresolved successfully": "Commento non risolto con successo",
|
||||||
"Failed to resolve comment": "Impossibile risolvere il commento",
|
"Failed to resolve comment": "Impossibile risolvere il commento",
|
||||||
"Resolve comment": "Risolvi commento",
|
"Resolve comment": "Risolvi commento",
|
||||||
"Unresolve comment": "Segna commento come non risolto",
|
"Unresolve comment": "Annulla risoluzione commento",
|
||||||
"Resolve Comment Thread": "Risolvi discussione del commento",
|
"Resolve Comment Thread": "Risolvi discussione commenti",
|
||||||
"Unresolve Comment Thread": "Segna discussione del commento come non risolta",
|
"Unresolve Comment Thread": "Annulla risoluzione discussione commenti",
|
||||||
"Are you sure you want to resolve this comment thread? This will mark it as completed.": "Sei sicuro di voler risolvere questa discussione di commenti? Questo la contrassegnerà come completata.",
|
"Are you sure you want to resolve this comment thread? This will mark it as completed.": "Sei sicuro di voler risolvere questa discussione di commenti? Questo la contrassegnerà come completata.",
|
||||||
"Are you sure you want to unresolve this comment thread?": "Sei sicuro di voler annullare la risoluzione di questa discussione di commenti?",
|
"Are you sure you want to unresolve this comment thread?": "Sei sicuro di voler annullare la risoluzione di questa discussione di commenti?",
|
||||||
"Resolved": "Risolto",
|
"Resolved": "Risolto",
|
||||||
"No active comments.": "Nessun commento attivo.",
|
"No active comments.": "Nessun commento attivo.",
|
||||||
|
"No resolved comments.": "Nessun commento risolto.",
|
||||||
"Revoke invitation": "Revoca invito",
|
"Revoke invitation": "Revoca invito",
|
||||||
"Revoke": "Revoca",
|
"Revoke": "Revoca",
|
||||||
"Don't": "Non",
|
"Don't": "Non",
|
||||||
@@ -261,7 +243,7 @@
|
|||||||
"Are you sure you want to delete this space?": "Sei sicuro di voler eliminare questo spazio?",
|
"Are you sure you want to delete this space?": "Sei sicuro di voler eliminare questo spazio?",
|
||||||
"Delete this space with all its pages and data.": "Elimina questo spazio con tutte le sue pagine e i suoi dati.",
|
"Delete this space with all its pages and data.": "Elimina questo spazio con tutte le sue pagine e i suoi dati.",
|
||||||
"All pages, comments, attachments and permissions in this space will be deleted irreversibly.": "Tutte le pagine, i commenti, gli allegati e i permessi di questo spazio verranno eliminati irreversibilmente.",
|
"All pages, comments, attachments and permissions in this space will be deleted irreversibly.": "Tutte le pagine, i commenti, gli allegati e i permessi di questo spazio verranno eliminati irreversibilmente.",
|
||||||
"Confirm space name": "Conferma nome dello spazio",
|
"Confirm space name": "Conferma nome spazio",
|
||||||
"Type the space name <b>{{spaceName}}</b> to confirm your action.": "Digita il nome dello spazio <b>{{spaceName}}</b> per confermare la tua azione.",
|
"Type the space name <b>{{spaceName}}</b> to confirm your action.": "Digita il nome dello spazio <b>{{spaceName}}</b> per confermare la tua azione.",
|
||||||
"Format": "Formato",
|
"Format": "Formato",
|
||||||
"Include subpages": "Includi sottopagine",
|
"Include subpages": "Includi sottopagine",
|
||||||
@@ -270,7 +252,6 @@
|
|||||||
"Export failed:": "Esportazione fallita:",
|
"Export failed:": "Esportazione fallita:",
|
||||||
"export error": "errore di esportazione",
|
"export error": "errore di esportazione",
|
||||||
"Export page": "Esporta pagina",
|
"Export page": "Esporta pagina",
|
||||||
"Export successful": "Esportazione riuscita",
|
|
||||||
"Export space": "Esporta spazio",
|
"Export space": "Esporta spazio",
|
||||||
"Export {{type}}": "Esporta {{type}}",
|
"Export {{type}}": "Esporta {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "Il file supera il limite per gli allegati di {{limit}}",
|
"File exceeds the {{limit}} attachment limit": "Il file supera il limite per gli allegati di {{limit}}",
|
||||||
@@ -287,21 +268,7 @@
|
|||||||
"Add row above": "Aggiungi riga sopra",
|
"Add row above": "Aggiungi riga sopra",
|
||||||
"Add row below": "Aggiungi riga sotto",
|
"Add row below": "Aggiungi riga sotto",
|
||||||
"Delete table": "Elimina tabella",
|
"Delete table": "Elimina tabella",
|
||||||
"Add column left": "Add column left",
|
|
||||||
"Add column right": "Add column right",
|
|
||||||
"Clear cell": "Clear cell",
|
|
||||||
"Clear cells": "Clear cells",
|
|
||||||
"Toggle header cell": "Toggle header cell",
|
|
||||||
"Toggle header column": "Toggle header column",
|
|
||||||
"Toggle header row": "Toggle header row",
|
|
||||||
"Move column left": "Move column left",
|
|
||||||
"Move column right": "Move column right",
|
|
||||||
"Move row down": "Move row down",
|
|
||||||
"Move row up": "Move row up",
|
|
||||||
"Sort A → Z": "Sort A → Z",
|
|
||||||
"Sort Z → A": "Sort Z → A",
|
|
||||||
"Info": "Informazioni",
|
"Info": "Informazioni",
|
||||||
"Note": "Nota",
|
|
||||||
"Success": "Successo",
|
"Success": "Successo",
|
||||||
"Warning": "Avviso",
|
"Warning": "Avviso",
|
||||||
"Danger": "Pericolo",
|
"Danger": "Pericolo",
|
||||||
@@ -312,11 +279,6 @@
|
|||||||
"Save & Exit": "Salva ed esci",
|
"Save & Exit": "Salva ed esci",
|
||||||
"Double-click to edit Excalidraw diagram": "Fai doppio clic per modificare il diagramma di Excalidraw",
|
"Double-click to edit Excalidraw diagram": "Fai doppio clic per modificare il diagramma di Excalidraw",
|
||||||
"Paste link": "Incolla link",
|
"Paste link": "Incolla link",
|
||||||
"Paste link or search pages": "Incolla il link o cerca le pagine",
|
|
||||||
"Link to web page": "Collega a una pagina web",
|
|
||||||
"Recents": "Recenti",
|
|
||||||
"Page or URL": "Pagina o URL",
|
|
||||||
"Link title": "Titolo del link",
|
|
||||||
"Edit link": "Modifica link",
|
"Edit link": "Modifica link",
|
||||||
"Remove link": "Rimuovi link",
|
"Remove link": "Rimuovi link",
|
||||||
"Add link": "Aggiungi link",
|
"Add link": "Aggiungi link",
|
||||||
@@ -335,7 +297,7 @@
|
|||||||
"Pink": "Rosa",
|
"Pink": "Rosa",
|
||||||
"Gray": "Grigio",
|
"Gray": "Grigio",
|
||||||
"Embed link": "Incorpora collegamento",
|
"Embed link": "Incorpora collegamento",
|
||||||
"Invalid {{provider}} embed link": "Link incorporato {{provider}} non valido",
|
"Invalid {{provider}} embed link": "Link di incorporamento {{provider}} non valido",
|
||||||
"Embed {{provider}}": "Incorpora {{provider}}",
|
"Embed {{provider}}": "Incorpora {{provider}}",
|
||||||
"Enter {{provider}} link to embed": "Inserisci il link {{provider}} per incorporare",
|
"Enter {{provider}} link to embed": "Inserisci il link {{provider}} per incorporare",
|
||||||
"Bold": "Grassetto",
|
"Bold": "Grassetto",
|
||||||
@@ -362,58 +324,33 @@
|
|||||||
"Create block quote.": "Crea blocco citazione.",
|
"Create block quote.": "Crea blocco citazione.",
|
||||||
"Insert code snippet.": "Inserisci frammento di codice.",
|
"Insert code snippet.": "Inserisci frammento di codice.",
|
||||||
"Insert horizontal rule divider": "Inserisci divisore di regola orizzontale",
|
"Insert horizontal rule divider": "Inserisci divisore di regola orizzontale",
|
||||||
"Page break": "Page break",
|
|
||||||
"Insert a page break for printing.": "Insert a page break for printing.",
|
|
||||||
"Upload any image from your device.": "Carica un'immagine dal tuo dispositivo.",
|
"Upload any image from your device.": "Carica un'immagine dal tuo dispositivo.",
|
||||||
"Upload any video from your device.": "Carica qualsiasi video dal tuo dispositivo.",
|
"Upload any video from your device.": "Carica qualsiasi video dal tuo dispositivo.",
|
||||||
"Upload any audio from your device.": "Carica qualsiasi audio dal tuo dispositivo.",
|
|
||||||
"Upload any file from your device.": "Carica qualsiasi file dal tuo dispositivo.",
|
"Upload any file from your device.": "Carica qualsiasi file dal tuo dispositivo.",
|
||||||
"Uploading {{name}}": "Caricamento di {{name}}",
|
|
||||||
"Uploading file": "Caricamento file",
|
|
||||||
"Table": "Tabella",
|
"Table": "Tabella",
|
||||||
"Insert a table.": "Inserisci una tabella.",
|
"Insert a table.": "Inserisci una tabella.",
|
||||||
"Insert collapsible block.": "Inserisci blocco comprimibile.",
|
"Insert collapsible block.": "Inserisci blocco comprimibile.",
|
||||||
"Video": "Video",
|
"Video": "Video",
|
||||||
"Divider": "Separatore",
|
"Divider": "Divisore",
|
||||||
"Quote": "Citazione",
|
"Quote": "Preventivo",
|
||||||
"Image": "Immagine",
|
"Image": "Immagine",
|
||||||
"Audio": "Audio",
|
|
||||||
"Embed PDF": "Incorpora PDF",
|
|
||||||
"Upload and embed a PDF file.": "Carica e incorpora un file PDF.",
|
|
||||||
"Embed as PDF": "Incorpora come PDF",
|
|
||||||
"Failed to load PDF": "Caricamento del PDF non riuscito",
|
|
||||||
"Convert to attachment": "Converti in allegato",
|
|
||||||
"File attachment": "Allegato file",
|
"File attachment": "Allegato file",
|
||||||
"Toggle block": "Blocco a comparsa",
|
"Toggle block": "Attiva blocco",
|
||||||
"Callout": "Riquadro evidenziato",
|
"Callout": "Avviso",
|
||||||
"Insert callout notice.": "Inserisci avviso di richiamo.",
|
"Insert callout notice.": "Inserisci avviso di richiamo.",
|
||||||
"Math inline": "Formula matematica in linea",
|
"Math inline": "Matematica in linea",
|
||||||
"Insert inline math equation.": "Inserisci equazione matematica in linea.",
|
"Insert inline math equation.": "Inserisci equazione matematica in linea.",
|
||||||
"Math block": "Blocco matematico",
|
"Math block": "Blocco matematico",
|
||||||
"Insert math equation": "Inserisci equazione matematica",
|
"Insert math equation": "Inserisci equazione matematica",
|
||||||
"Mermaid diagram": "Diagramma Mermaid",
|
"Mermaid diagram": "Diagramma di Mermaid",
|
||||||
"Insert mermaid diagram": "Inserisci diagramma Mermaid",
|
"Insert mermaid diagram": "Inserisci un diagramma di Mermaid",
|
||||||
"Insert and design Drawio diagrams": "Inserisci e progetta diagrammi Drawio",
|
"Insert and design Drawio diagrams": "Inserisci e progetta diagrammi Drawio",
|
||||||
"Insert current date": "Inserisci data corrente",
|
"Insert current date": "Inserisci la data corrente",
|
||||||
"Draw and sketch excalidraw diagrams": "Disegna e abbozza diagrammi Excalidraw",
|
"Draw and sketch excalidraw diagrams": "Disegna e schizza diagrammi excalidraw",
|
||||||
"Multiple": "Multiplo",
|
"Multiple": "Multiplo",
|
||||||
"Turn into": "Trasforma in",
|
|
||||||
"Text align": "Allinea testo",
|
|
||||||
"This page may have been deleted, moved, or you may not have access.": "Questa pagina potrebbe essere stata eliminata o spostata, oppure potresti non avere accesso.",
|
|
||||||
"Go to homepage": "Vai alla pagina principale",
|
|
||||||
"Pages you create will show up here.": "Le pagine che crei appariranno qui.",
|
|
||||||
"Heading {{level}}": "Intestazione {{level}}",
|
"Heading {{level}}": "Intestazione {{level}}",
|
||||||
"Toggle title": "Attiva/disattiva titolo",
|
"Toggle title": "Attiva/disattiva titolo",
|
||||||
"Write anything. Enter \"/\" for commands": "Scrivi qualsiasi cosa. Digita \"/\" per i comandi",
|
"Write anything. Enter \"/\" for commands": "Scrivi qualcosa. Digita \"/\" per i comandi",
|
||||||
"Write...": "Scrivi...",
|
|
||||||
"Column count": "Numero di colonne",
|
|
||||||
"{{count}} Columns": "{{count}} colonne",
|
|
||||||
"Equal columns": "Colonne uguali",
|
|
||||||
"Left sidebar": "Barra laterale sinistra",
|
|
||||||
"Right sidebar": "Barra laterale destra",
|
|
||||||
"Wide center": "Centro ampio",
|
|
||||||
"Left wide": "Ampia a sinistra",
|
|
||||||
"Right wide": "Ampia a destra",
|
|
||||||
"Names do not match": "I nomi non corrispondono",
|
"Names do not match": "I nomi non corrispondono",
|
||||||
"Today, {{time}}": "Oggi, {{time}}",
|
"Today, {{time}}": "Oggi, {{time}}",
|
||||||
"Yesterday, {{time}}": "Ieri, {{time}}",
|
"Yesterday, {{time}}": "Ieri, {{time}}",
|
||||||
@@ -425,103 +362,75 @@
|
|||||||
"Member role updated successfully": "Ruolo del membro aggiornato con successo",
|
"Member role updated successfully": "Ruolo del membro aggiornato con successo",
|
||||||
"Created by: <b>{{creatorName}}</b>": "Creato da: <b>{{creatorName}}</b>",
|
"Created by: <b>{{creatorName}}</b>": "Creato da: <b>{{creatorName}}</b>",
|
||||||
"Created at: {{time}}": "Creato il: {{time}}",
|
"Created at: {{time}}": "Creato il: {{time}}",
|
||||||
"Edited by {{name}} {{time}}": "Modificato da {{name}} {{time}}",
|
"Edited by {{name}} {{time}}": "Modificato da {{name}} il {{time}}",
|
||||||
"Word count: {{wordCount}}": "Conteggio parole: {{wordCount}}",
|
"Word count: {{wordCount}}": "Conteggio parole: {{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Conteggio caratteri: {{characterCount}}",
|
"Character count: {{characterCount}}": "Conteggio caratteri: {{characterCount}}",
|
||||||
"New update": "Nuovo aggiornamento",
|
"New update": "Nuovo aggiornamento",
|
||||||
"{{latestVersion}} is available": "{{latestVersion}} è disponibile",
|
"{{latestVersion}} is available": "{{latestVersion}} è disponibile",
|
||||||
"Default page edit mode": "Modalità di modifica predefinita della pagina",
|
"Default page edit mode": "Modalità di modifica pagina predefinita",
|
||||||
"Choose your preferred page edit mode. Avoid accidental edits.": "Scegli la tua modalità di modifica della pagina preferita. Evita modifiche accidentali.",
|
"Choose your preferred page edit mode. Avoid accidental edits.": "Scegli la tua modalità di modifica della pagina preferita. Evita modifiche accidentali.",
|
||||||
"Choose {{format}} file": "Scegli file {{format}}",
|
|
||||||
"Reading": "Lettura",
|
"Reading": "Lettura",
|
||||||
"Delete member": "Elimina membro",
|
"Delete member": "Elimina membro",
|
||||||
"Member deleted successfully": "Membro eliminato con successo",
|
"Member deleted successfully": "Membro eliminato con successo",
|
||||||
"Are you sure you want to delete this workspace member? This action is irreversible.": "Sei sicuro di voler eliminare questo membro del workspace? Questa azione è irreversibile.",
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "Sei sicuro di voler eliminare questo membro del workspace? Questa azione è irreversibile.",
|
||||||
"Deactivate member": "Disattiva membro",
|
|
||||||
"Activate member": "Attiva membro",
|
|
||||||
"Are you sure you want to deactivate this workspace member? They will no longer be able to access this workspace.": "Sei sicuro di voler disattivare questo membro dello spazio di lavoro? Non potrà più accedere a questo spazio di lavoro.",
|
|
||||||
"Are you sure you want to activate this workspace member?": "Sei sicuro di voler attivare questo membro dello spazio di lavoro?",
|
|
||||||
"Deactivate": "Disattiva",
|
|
||||||
"Activate": "Attiva",
|
|
||||||
"Deactivated": "Disattivato",
|
|
||||||
"Move": "Sposta",
|
"Move": "Sposta",
|
||||||
"Move page": "Sposta pagina",
|
"Move page": "Sposta pagina",
|
||||||
"Move page to a different space.": "Sposta la pagina in un altro spazio.",
|
"Move page to a different space.": "Sposta la pagina in un altro spazio.",
|
||||||
"Real-time editor connection lost. Retrying...": "Connessione all'editor in tempo reale persa. Riprovo...",
|
"Real-time editor connection lost. Retrying...": "Connessione all'editor in tempo reale persa. Riprovo...",
|
||||||
"Table of contents": "Indice",
|
"Table of contents": "Indice dei contenuti",
|
||||||
"Add headings (H1, H2, H3) to generate a table of contents.": "Aggiungi intestazioni (H1, H2, H3) per generare un sommario.",
|
"Add headings (H1, H2, H3) to generate a table of contents.": "Aggiungi intestazioni (H1, H2, H3) per generare un sommario.",
|
||||||
"Share": "Condividi",
|
"Share": "Condividi",
|
||||||
"Public sharing": "Condivisione pubblica",
|
"Public sharing": "Condivisione pubblica",
|
||||||
"Shared by": "Condiviso da",
|
"Shared by": "Condiviso da",
|
||||||
"Shared at": "Condiviso il",
|
"Shared at": "Condiviso il",
|
||||||
"Inherits public sharing from": "Eredita la condivisione pubblica da",
|
"Inherits public sharing from": "Eredita la condivisione pubblica da",
|
||||||
"Share to web": "Condividi sul web",
|
"Share to web": "Condividi su web",
|
||||||
"Shared to web": "Condiviso sul web",
|
"Shared to web": "Condiviso su web",
|
||||||
"Anyone with the link can view this page": "Chiunque abbia il link può visualizzare questa pagina",
|
"Anyone with the link can view this page": "Chiunque abbia il link può visualizzare questa pagina",
|
||||||
"Make this page publicly accessible": "Rendi questa pagina accessibile pubblicamente",
|
"Make this page publicly accessible": "Rendi questa pagina accessibile pubblicamente",
|
||||||
"Include sub-pages": "Includi sottopagine",
|
"Include sub-pages": "Includi sotto-pagine",
|
||||||
"Make sub-pages public too": "Rendi pubbliche anche le sottopagine",
|
"Make sub-pages public too": "Rendi pubbliche anche le sotto-pagine",
|
||||||
"Allow search engines to index page": "Consenti ai motori di ricerca di indicizzare la pagina",
|
"Allow search engines to index page": "Permetti ai motori di ricerca di indicizzare la pagina",
|
||||||
"Open page": "Apri pagina",
|
"Open page": "Apri pagina",
|
||||||
"Page": "Pagina",
|
"Page": "Pagina",
|
||||||
"Delete public share link": "Elimina link di condivisione pubblica",
|
"Delete public share link": "Elimina il link di condivisione pubblica",
|
||||||
"Delete share": "Elimina condivisione",
|
"Delete share": "Elimina condivisione",
|
||||||
"Are you sure you want to delete this shared link?": "Sei sicuro di voler eliminare questo link condiviso?",
|
"Are you sure you want to delete this shared link?": "Sei sicuro di voler eliminare questo link condiviso?",
|
||||||
"Publicly shared pages from spaces you are a member of will appear here": "Le pagine condivise pubblicamente degli spazi di cui fai parte appariranno qui",
|
"Publicly shared pages from spaces you are a member of will appear here": "Le pagine condivise pubblicamente dagli spazi di cui sei membro appariranno qui",
|
||||||
"Share deleted successfully": "Condivisione eliminata con successo",
|
"Share deleted successfully": "Condivisione eliminata con successo",
|
||||||
"Share not found": "Condivisione non trovata",
|
"Share not found": "Condivisione non trovata",
|
||||||
"Failed to share page": "Condivisione della pagina non riuscita",
|
"Failed to share page": "Condivisione della pagina fallita",
|
||||||
"Disable public sharing": "Disabilita la condivisione pubblica",
|
|
||||||
"Prevent members from sharing pages publicly.": "Impedisci ai membri di condividere pubblicamente le pagine.",
|
|
||||||
"Toggle public sharing": "Attiva/disattiva la condivisione pubblica",
|
|
||||||
"Toggle space public sharing": "Attiva/disattiva la condivisione pubblica nello spazio",
|
|
||||||
"Allow viewers to comment": "Consenti agli utenti di commentare",
|
|
||||||
"Allow viewers to add comments on pages in this space.": "Consenti agli utenti di aggiungere commenti alle pagine in questo spazio.",
|
|
||||||
"Toggle viewer comments": "Attiva/disattiva i commenti degli utenti",
|
|
||||||
"Public sharing is disabled at the workspace level": "La condivisione pubblica è disabilitata a livello di area di lavoro",
|
|
||||||
"Prevent pages in this space from being shared publicly.": "Impedisci che le pagine in questo spazio vengano condivise pubblicamente.",
|
|
||||||
"Page permissions": "Autorizzazioni della pagina.",
|
|
||||||
"Control who can view and edit individual pages. Available with an enterprise license.": "Controlla chi può visualizzare e modificare le singole pagine. Disponibile con una licenza Enterprise.",
|
|
||||||
"Enable public sharing": "Abilita la condivisione pubblica",
|
|
||||||
"Are you sure you want to enable public sharing? Members will be able to share pages publicly.": "Sei sicuro di voler abilitare la condivisione pubblica? I membri potranno condividere le pagine pubblicamente.",
|
|
||||||
"Are you sure you want to disable public sharing? All existing shared links in this workspace will be deleted.": "Sei sicuro di voler disabilitare la condivisione pubblica? Tutti i link condivisi esistenti in questa area di lavoro verranno eliminati.",
|
|
||||||
"Are you sure you want to enable public sharing for this space?": "Sei sicuro di voler abilitare la condivisione pubblica per questo spazio?",
|
|
||||||
"Are you sure you want to disable public sharing? All existing shared links in this space will be deleted.": "Sei sicuro di voler disabilitare la condivisione pubblica? Tutti i link condivisi esistenti in questo spazio verranno eliminati.",
|
|
||||||
"Public sharing is disabled": "La condivisione pubblica è disabilitata",
|
|
||||||
"Public sharing has been disabled at the workspace level.": "La condivisione pubblica è stata disabilitata a livello di area di lavoro.",
|
|
||||||
"Public sharing has been disabled for this space.": "La condivisione pubblica è stata disabilitata per questo spazio.",
|
|
||||||
"Copy page": "Copia pagina",
|
"Copy page": "Copia pagina",
|
||||||
"Copy page to a different space.": "Copia pagina in un altro spazio.",
|
"Copy page to a different space.": "Copia pagina in un altro spazio.",
|
||||||
"Page copied successfully": "Pagina copiata con successo",
|
"Page copied successfully": "Pagina copiata con successo",
|
||||||
"Page duplicated successfully": "Pagina duplicata con successo",
|
"Page duplicated successfully": "Pagina duplicata con successo",
|
||||||
"Find": "Trova",
|
"Find": "Trova",
|
||||||
"Not found": "Non trovato",
|
"Not found": "Non trovato",
|
||||||
"Previous Match (Shift+Enter)": "Corrispondenza precedente (Maiusc+Invio)",
|
"Previous Match (Shift+Enter)": "Corrispondenza precedente (Shift+Invio)",
|
||||||
"Next match (Enter)": "Corrispondenza successiva (Invio)",
|
"Next match (Enter)": "Corrispondenza successiva (Invio)",
|
||||||
"Match case (Alt+C)": "Distingui maiuscole/minuscole (Alt+C)",
|
"Match case (Alt+C)": "Maiuscole/minuscole (Alt+C)",
|
||||||
"Replace": "Sostituisci",
|
"Replace": "Sostituisci",
|
||||||
"Close (Escape)": "Chiudi (Esc)",
|
"Close (Escape)": "Chiudi (Esc)",
|
||||||
"Replace (Enter)": "Sostituisci (Invio)",
|
"Replace (Enter)": "Sostituisci (Invio)",
|
||||||
"Replace all (Ctrl+Alt+Enter)": "Sostituisci tutto (Ctrl+Alt+Invio)",
|
"Replace all (Ctrl+Alt+Enter)": "Sostituisci tutto (Ctrl+Alt+Invio)",
|
||||||
"Replace all": "Sostituisci tutto",
|
"Replace all": "Sostituisci tutto",
|
||||||
"View all": "Visualizza tutto",
|
|
||||||
"View all spaces": "Visualizza tutti gli spazi",
|
"View all spaces": "Visualizza tutti gli spazi",
|
||||||
"Error": "Errore",
|
"Error": "Errore",
|
||||||
"Failed to disable MFA": "Disattivazione MFA non riuscita",
|
"Failed to disable MFA": "Disabilitazione MFA non riuscita",
|
||||||
"Disable two-factor authentication": "Disattiva autenticazione a due fattori",
|
"Disable two-factor authentication": "Disabilita autenticazione a due fattori",
|
||||||
"Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Disabilitare l'autenticazione a due fattori renderà il tuo account meno sicuro. Avrai bisogno solo della tua password per accedere.",
|
"Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Disabilitare l'autenticazione a due fattori renderà il tuo account meno sicuro. Avrai bisogno solo della tua password per accedere.",
|
||||||
"Please enter your password to disable two-factor authentication:": "Inserisci la tua password per disabilitare l'autenticazione a due fattori:",
|
"Please enter your password to disable two-factor authentication:": "Inserisci la tua password per disabilitare l'autenticazione a due fattori:",
|
||||||
"Two-factor authentication has been enabled": "L'autenticazione a due fattori è stata abilitata",
|
"Two-factor authentication has been enabled": "Autenticazione a due fattori abilitata",
|
||||||
"Two-factor authentication has been disabled": "L'autenticazione a due fattori è stata disabilitata",
|
"Two-factor authentication has been disabled": "Autenticazione a due fattori disabilitata",
|
||||||
"2-step verification": "Verifica in 2 passaggi",
|
"2-step verification": "Verifica in 2 passaggi",
|
||||||
"Protect your account with an additional verification layer when signing in.": "Proteggi il tuo account con un ulteriore livello di verifica durante l'accesso.",
|
"Protect your account with an additional verification layer when signing in.": "Proteggi il tuo account con un ulteriore livello di verifica durante l'accesso.",
|
||||||
"Two-factor authentication is active on your account.": "L'autenticazione a due fattori è attiva sul tuo account.",
|
"Two-factor authentication is active on your account.": "L'autenticazione a due fattori è attiva sul tuo account.",
|
||||||
"Add 2FA method": "Aggiungi metodo 2FA",
|
"Add 2FA method": "Aggiungi metodo 2FA",
|
||||||
"Backup codes": "Codici di backup",
|
"Backup codes": "Codici di backup",
|
||||||
"Disable": "Disattiva",
|
"Disable": "Disabilita",
|
||||||
"Invalid verification code": "Codice di verifica non valido",
|
"Invalid verification code": "Codice di verifica non valido",
|
||||||
"New backup codes have been generated": "Sono stati generati nuovi codici di backup",
|
"New backup codes have been generated": "Nuovi codici di backup generati",
|
||||||
"Failed to regenerate backup codes": "Rigenerazione dei codici di backup non riuscita",
|
"Failed to regenerate backup codes": "Rigenerazione codici di backup non riuscita",
|
||||||
"About backup codes": "Informazioni sui codici di backup",
|
"About backup codes": "Informazioni sui codici di backup",
|
||||||
"Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "I codici di backup possono essere utilizzati per accedere al tuo account se perdi l'accesso alla tua app di autenticazione. Ogni codice può essere usato solo una volta.",
|
"Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "I codici di backup possono essere utilizzati per accedere al tuo account se perdi l'accesso alla tua app di autenticazione. Ogni codice può essere usato solo una volta.",
|
||||||
"You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Puoi rigenerare nuovi codici di backup in qualsiasi momento. Questo invaliderà tutti i codici esistenti.",
|
"You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Puoi rigenerare nuovi codici di backup in qualsiasi momento. Questo invaliderà tutti i codici esistenti.",
|
||||||
@@ -531,9 +440,9 @@
|
|||||||
"Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Assicurati di salvare questi codici in un luogo sicuro. I tuoi vecchi codici di backup non sono più validi.",
|
"Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Assicurati di salvare questi codici in un luogo sicuro. I tuoi vecchi codici di backup non sono più validi.",
|
||||||
"Your new backup codes": "I tuoi nuovi codici di backup",
|
"Your new backup codes": "I tuoi nuovi codici di backup",
|
||||||
"I've saved my backup codes": "Ho salvato i miei codici di backup",
|
"I've saved my backup codes": "Ho salvato i miei codici di backup",
|
||||||
"Failed to setup MFA": "Configurazione MFA non riuscita",
|
"Failed to setup MFA": "Impostazione MFA non riuscita",
|
||||||
"Setup & Verify": "Configura e verifica",
|
"Setup & Verify": "Imposta e Verifica",
|
||||||
"Add to authenticator": "Aggiungi all'autenticatore",
|
"Add to authenticator": "Aggiungi ad authenticator",
|
||||||
"1. Scan this QR code with your authenticator app": "1. Scansiona questo codice QR con la tua app di autenticazione",
|
"1. Scan this QR code with your authenticator app": "1. Scansiona questo codice QR con la tua app di autenticazione",
|
||||||
"Can't scan the code?": "Non riesci a scansionare il codice?",
|
"Can't scan the code?": "Non riesci a scansionare il codice?",
|
||||||
"Enter this code manually in your authenticator app:": "Inserisci questo codice manualmente nella tua app di autenticazione:",
|
"Enter this code manually in your authenticator app:": "Inserisci questo codice manualmente nella tua app di autenticazione:",
|
||||||
@@ -547,17 +456,17 @@
|
|||||||
"Print": "Stampa",
|
"Print": "Stampa",
|
||||||
"Two-factor authentication has been set up. Please log in again.": "L'autenticazione a due fattori è stata impostata. Effettua nuovamente l'accesso, per favore.",
|
"Two-factor authentication has been set up. Please log in again.": "L'autenticazione a due fattori è stata impostata. Effettua nuovamente l'accesso, per favore.",
|
||||||
"Two-Factor authentication required": "Autenticazione a due fattori richiesta",
|
"Two-Factor authentication required": "Autenticazione a due fattori richiesta",
|
||||||
"Your workspace requires two-factor authentication for all users": "Il tuo workspace richiede l'autenticazione a due fattori per tutti gli utenti",
|
"Your workspace requires two-factor authentication for all users": "Il tuo spazio di lavoro richiede l'autenticazione a due fattori per tutti gli utenti",
|
||||||
"To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Per continuare ad accedere al tuo spazio di lavoro, devi impostare l'autenticazione a due fattori. Questo aggiunge un ulteriore livello di sicurezza al tuo account.",
|
"To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Per continuare ad accedere al tuo spazio di lavoro, devi impostare l'autenticazione a due fattori. Questo aggiunge un ulteriore livello di sicurezza al tuo account.",
|
||||||
"Set up two-factor authentication": "Configura l'autenticazione a due fattori",
|
"Set up two-factor authentication": "Imposta l'autenticazione a due fattori",
|
||||||
"Cancel and logout": "Annulla e disconnettiti",
|
"Cancel and logout": "Annulla e disconnetti",
|
||||||
"Your workspace requires two-factor authentication. Please set it up to continue.": "Il tuo spazio di lavoro richiede l'autenticazione a due fattori. Impostala per continuare.",
|
"Your workspace requires two-factor authentication. Please set it up to continue.": "Il tuo spazio di lavoro richiede l'autenticazione a due fattori. Impostala per continuare.",
|
||||||
"This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Questo aggiunge un ulteriore livello di sicurezza al tuo account richiedendo un codice di verifica dalla tua app di autenticazione.",
|
"This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Questo aggiunge un ulteriore livello di sicurezza al tuo account richiedendo un codice di verifica dalla tua app di autenticazione.",
|
||||||
"Password is required": "La password è obbligatoria",
|
"Password is required": "La password è richiesta",
|
||||||
"Password must be at least 8 characters": "La password deve contenere almeno 8 caratteri",
|
"Password must be at least 8 characters": "La password deve essere di almeno 8 caratteri",
|
||||||
"Please enter a 6-digit code": "Inserisci un codice di 6 cifre",
|
"Please enter a 6-digit code": "Inserisci un codice a 6 cifre",
|
||||||
"Code must be exactly 6 digits": "Il codice deve essere esattamente di 6 cifre",
|
"Code must be exactly 6 digits": "Il codice deve essere esattamente di 6 cifre",
|
||||||
"Enter the 6-digit code found in your authenticator app": "Inserisci il codice di 6 cifre presente nella tua app di autenticazione",
|
"Enter the 6-digit code found in your authenticator app": "Inserisci il codice a 6 cifre trovato nella tua app di autenticazione",
|
||||||
"Need help authenticating?": "Hai bisogno di aiuto per autenticarti?",
|
"Need help authenticating?": "Hai bisogno di aiuto per autenticarti?",
|
||||||
"MFA QR Code": "Codice QR MFA",
|
"MFA QR Code": "Codice QR MFA",
|
||||||
"Account created successfully. Please log in to set up two-factor authentication.": "Account creato con successo. Effettua l'accesso per impostare l'autenticazione a due fattori.",
|
"Account created successfully. Please log in to set up two-factor authentication.": "Account creato con successo. Effettua l'accesso per impostare l'autenticazione a due fattori.",
|
||||||
@@ -565,7 +474,7 @@
|
|||||||
"Password reset successful. Please log in with your new password to set up two-factor authentication.": "Reimpostazione della password riuscita. Accedi con la tua nuova password per impostare l'autenticazione a due fattori.",
|
"Password reset successful. Please log in with your new password to set up two-factor authentication.": "Reimpostazione della password riuscita. Accedi con la tua nuova password per impostare l'autenticazione a due fattori.",
|
||||||
"Password reset was successful. Please log in with your new password.": "Reimpostazione della password riuscita. Accedi con la tua nuova password.",
|
"Password reset was successful. Please log in with your new password.": "Reimpostazione della password riuscita. Accedi con la tua nuova password.",
|
||||||
"Two-factor authentication": "Autenticazione a due fattori",
|
"Two-factor authentication": "Autenticazione a due fattori",
|
||||||
"Use authenticator app instead": "Usa invece l'app di autenticazione",
|
"Use authenticator app instead": "Usa l'app di autenticazione invece",
|
||||||
"Verify backup code": "Verifica codice di backup",
|
"Verify backup code": "Verifica codice di backup",
|
||||||
"Use backup code": "Usa codice di backup",
|
"Use backup code": "Usa codice di backup",
|
||||||
"Enter one of your backup codes": "Inserisci uno dei tuoi codici di backup",
|
"Enter one of your backup codes": "Inserisci uno dei tuoi codici di backup",
|
||||||
@@ -573,7 +482,7 @@
|
|||||||
"Enter one of your backup codes. Each backup code can only be used once.": "Inserisci uno dei tuoi codici di backup. Ogni codice di backup può essere utilizzato solo una volta.",
|
"Enter one of your backup codes. Each backup code can only be used once.": "Inserisci uno dei tuoi codici di backup. Ogni codice di backup può essere utilizzato solo una volta.",
|
||||||
"Verify": "Verifica",
|
"Verify": "Verifica",
|
||||||
"Trash": "Cestino",
|
"Trash": "Cestino",
|
||||||
"Pages in trash will be permanently deleted after {{count}} days.": "Le pagine nel cestino verranno eliminate definitivamente dopo {{count}} giorni.",
|
"Pages in trash will be permanently deleted after 30 days.": "Le pagine nel cestino verranno eliminate definitivamente dopo 30 giorni.",
|
||||||
"Deleted": "Eliminato",
|
"Deleted": "Eliminato",
|
||||||
"No pages in trash": "Nessuna pagina nel cestino",
|
"No pages in trash": "Nessuna pagina nel cestino",
|
||||||
"Permanently delete page?": "Eliminare definitivamente la pagina?",
|
"Permanently delete page?": "Eliminare definitivamente la pagina?",
|
||||||
@@ -582,470 +491,9 @@
|
|||||||
"Move to trash": "Sposta nel cestino",
|
"Move to trash": "Sposta nel cestino",
|
||||||
"Move this page to trash?": "Spostare questa pagina nel cestino?",
|
"Move this page to trash?": "Spostare questa pagina nel cestino?",
|
||||||
"Restore page": "Ripristina pagina",
|
"Restore page": "Ripristina pagina",
|
||||||
"Permanently delete": "Permanently delete",
|
|
||||||
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> moved this page to Trash {{time}}.",
|
|
||||||
"Page moved to trash": "Pagina spostata nel cestino",
|
"Page moved to trash": "Pagina spostata nel cestino",
|
||||||
"Page restored successfully": "Pagina ripristinata con successo",
|
"Page restored successfully": "Pagina ripristinata con successo",
|
||||||
"Deleted by": "Eliminato da",
|
"Deleted by": "Eliminato da",
|
||||||
"Deleted at": "Eliminato il",
|
"Deleted at": "Eliminato il",
|
||||||
"Preview": "Anteprima",
|
"Preview": "Anteprima"
|
||||||
"Subpages": "Sottopagine",
|
|
||||||
"Failed to load subpages": "Caricamento delle sottopagine non riuscito",
|
|
||||||
"No subpages": "Nessuna sottopagina",
|
|
||||||
"Subpages (Child pages)": "Sottopagine (Pagine figlie)",
|
|
||||||
"List all subpages of the current page": "Elenca tutte le sottopagine della pagina corrente",
|
|
||||||
"Attachments": "Allegati",
|
|
||||||
"All spaces": "Tutti gli spazi",
|
|
||||||
"Unknown": "Sconosciuto",
|
|
||||||
"Find a space": "Trova uno spazio",
|
|
||||||
"Search in all your spaces": "Cerca in tutti i tuoi spazi",
|
|
||||||
"Type": "Tipo",
|
|
||||||
"Enterprise": "Enterprise",
|
|
||||||
"Download attachment": "Scarica allegato",
|
|
||||||
"Allowed email domains": "Domini email consentiti",
|
|
||||||
"Only users with email addresses from these domains can signup via SSO.": "Solo gli utenti con indirizzi email di questi domini possono registrarsi tramite SSO.",
|
|
||||||
"Enter valid domain names separated by comma or space": "Inserisci nomi di dominio validi separati da virgola o spazio",
|
|
||||||
"Enforce two-factor authentication": "Rendi obbligatoria l'autenticazione a due fattori",
|
|
||||||
"Once enforced, all members must enable two-factor authentication to access the workspace.": "Una volta impostata, tutti i membri devono abilitare l'autenticazione a due fattori per accedere all'area di lavoro.",
|
|
||||||
"Toggle MFA enforcement": "Attiva/disattiva obbligatorietà MFA",
|
|
||||||
"Display name": "Nome visualizzato",
|
|
||||||
"Allow signup": "Consenti registrazione",
|
|
||||||
"Enabled": "Abilitato",
|
|
||||||
"Advanced Settings": "Impostazioni avanzate",
|
|
||||||
"Enable TLS/SSL": "Abilita TLS/SSL",
|
|
||||||
"Use secure connection to LDAP server": "Usa una connessione sicura al server LDAP",
|
|
||||||
"Group sync": "Sincronizzazione gruppi",
|
|
||||||
"No SSO providers found.": "Nessun provider SSO trovato.",
|
|
||||||
"Delete SSO provider": "Elimina provider SSO",
|
|
||||||
"Are you sure you want to delete this SSO provider?": "Sei sicuro di voler eliminare questo provider SSO?",
|
|
||||||
"Action": "Azione",
|
|
||||||
"{{ssoProviderType}} configuration": "Configurazione {{ssoProviderType}}",
|
|
||||||
"Icon": "Icona",
|
|
||||||
"Upload image": "Carica immagine",
|
|
||||||
"Remove image": "Rimuovi immagine",
|
|
||||||
"Failed to remove image": "Rimozione immagine fallita",
|
|
||||||
"Image exceeds 10MB limit.": "L'immagine supera il limite di 10MB.",
|
|
||||||
"Image removed successfully": "Immagine rimossa con successo",
|
|
||||||
"API key": "Chiave API",
|
|
||||||
"API keys": "Chiavi API",
|
|
||||||
"API management": "Gestione API",
|
|
||||||
"Custom expiration date": "Data di scadenza personalizzata",
|
|
||||||
"Enter a descriptive token name": "Inserisci un nome descrittivo del token",
|
|
||||||
"Expiration": "Scadenza",
|
|
||||||
"Expired": "Scaduto",
|
|
||||||
"Expires": "Scade",
|
|
||||||
"Last use": "Ultimo utilizzo",
|
|
||||||
"No API keys found": "Nessuna chiave API trovata",
|
|
||||||
"No expiration": "Nessuna scadenza",
|
|
||||||
"Revoked successfully": "Revocata con successo",
|
|
||||||
"Select expiration date": "Seleziona la data di scadenza",
|
|
||||||
"This action cannot be undone. Any applications using this API key will stop working.": "Questa azione non può essere annullata. Qualsiasi applicazione che utilizza questa chiave API smetterà di funzionare.",
|
|
||||||
"Update": "Aggiorna",
|
|
||||||
"Update {{credential}}": "Aggiorna {{credential}}",
|
|
||||||
"Manage API keys for all users in the workspace": "Gestisci le chiavi API per tutti gli utenti nell'area di lavoro",
|
|
||||||
"Restrict API key creation to admins": "Limita la creazione delle chiavi API agli amministratori",
|
|
||||||
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Solo gli amministratori e i proprietari possono creare nuove chiavi API. Le chiavi dei membri esistenti continueranno a funzionare.",
|
|
||||||
"Toggle restrict API keys to admins": "Attiva/disattiva la limitazione delle chiavi API agli amministratori",
|
|
||||||
"API key creation is restricted to admins by your workspace administrator.": "La creazione delle chiavi API è limitata agli amministratori dal tuo amministratore dello spazio di lavoro.",
|
|
||||||
"AI settings": "Impostazioni AI",
|
|
||||||
"AI search": "Ricerca AI",
|
|
||||||
"AI Answer": "Risposta AI",
|
|
||||||
"Ask AI": "Chiedi all'AI",
|
|
||||||
"AI is thinking...": "L'AI sta pensando...",
|
|
||||||
"Thinking": "Sto pensando",
|
|
||||||
"Ask a question...": "Fai una domanda...",
|
|
||||||
"AI Answers": "Risposte AI",
|
|
||||||
"AI-powered search (AI Answers)": "Ricerca con AI (Risposte AI)",
|
|
||||||
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "La ricerca AI utilizza embeddings vettoriali per fornire capacità di ricerca semantica nel contenuto della tua area di lavoro.",
|
|
||||||
"Toggle AI search": "Attiva/disattiva ricerca AI",
|
|
||||||
"Generative AI (Ask AI)": "AI generativa (Chiedi AI)",
|
|
||||||
"Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Abilita la generazione di contenuti con AI nell'editor. Consente agli utenti di generare, migliorare, tradurre e trasformare il testo.",
|
|
||||||
"Toggle generative AI": "Attiva/Disattiva AI generativa",
|
|
||||||
"Upgrade your plan": "Aggiorna il tuo piano",
|
|
||||||
"Available with a paid license": "Disponibile con una licenza a pagamento",
|
|
||||||
"Upgrade your license tier.": "Aggiorna il livello della tua licenza.",
|
|
||||||
"AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "L'IA è disponibile solo nell'edizione Enterprise di Docmost. Contatta sales@docmost.com.",
|
|
||||||
"AI & MCP": "IA e MCP",
|
|
||||||
"AI": "IA",
|
|
||||||
"MCP": "MCP",
|
|
||||||
"Model Context Protocol (MCP)": "Model Context Protocol (MCP)",
|
|
||||||
"Enable the MCP server to allow AI assistants and tools to interact with your workspace content.": "Abilita il server MCP per consentire ad assistenti e strumenti IA di interagire con i contenuti del tuo spazio di lavoro.",
|
|
||||||
"MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "MCP è disponibile solo nell'edizione Enterprise di Docmost. Contatta sales@docmost.com.",
|
|
||||||
"MCP Server URL": "URL del server MCP",
|
|
||||||
"Use your API key for authentication. You can manage API keys in your account settings.": "Usa la tua chiave API per l'autenticazione. Puoi gestire le chiavi API nelle impostazioni del tuo account.",
|
|
||||||
"Supported tools": "Strumenti supportati",
|
|
||||||
"Your workspace has MCP enabled. Use your API key to connect AI assistants.": "Il tuo spazio di lavoro ha MCP abilitato. Usa la tua chiave API per collegare gli assistenti IA.",
|
|
||||||
"MCP server URL:": "URL del server MCP:",
|
|
||||||
"Learn more": "Scopri di più",
|
|
||||||
"Manage API keys for all users in the workspace. View the <anchor>API documentation</anchor> for usage details.": "Gestisci le API key per tutti gli utenti nello spazio di lavoro. Consulta la <anchor>documentazione API</anchor> per i dettagli sull'utilizzo.",
|
|
||||||
"View the <anchor>API documentation</anchor> for usage details.": "Consulta la <anchor>documentazione API</anchor> per i dettagli sull'utilizzo.",
|
|
||||||
"View the <anchor>MCP documentation</anchor>.": "Consulta la <anchor>documentazione MCP</anchor>.",
|
|
||||||
"Sources": "Fonti",
|
|
||||||
"AI Answers not available for attachments": "Risposte AI non disponibili per gli allegati",
|
|
||||||
"No answer available": "Nessuna risposta disponibile",
|
|
||||||
"Background color": "Colore di sfondo",
|
|
||||||
"Highlight color": "Colore evidenziato",
|
|
||||||
"Remove color": "Rimuovi colore",
|
|
||||||
"Notifications": "Notifiche",
|
|
||||||
"No notifications": "Nessuna notifica",
|
|
||||||
"No unread notifications": "Nessuna notifica non letta",
|
|
||||||
"All notifications": "Tutte le notifiche",
|
|
||||||
"Unread only": "Solo non lette",
|
|
||||||
"Mark all as read": "Segna tutto come letto",
|
|
||||||
"Mark as read": "Segna come letto",
|
|
||||||
"More options": "Altre opzioni",
|
|
||||||
"<bold>{{name}}</bold> mentioned you in a comment": "<bold>{{name}}</bold> ti ha menzionato in un commento",
|
|
||||||
"<bold>{{name}}</bold> commented on a page": "<bold>{{name}}</bold> ha commentato una pagina",
|
|
||||||
"<bold>{{name}}</bold> resolved a comment": "<bold>{{name}}</bold> ha risolto un commento",
|
|
||||||
"<bold>{{name}}</bold> mentioned you on a page": "<bold>{{name}}</bold> ti ha menzionato in una pagina",
|
|
||||||
"<bold>{{name}}</bold> gave you edit access to a page": "<bold>{{name}}</bold> ti ha dato accesso in modifica a una pagina",
|
|
||||||
"<bold>{{name}}</bold> gave you view access to a page": "<bold>{{name}}</bold> ti ha dato accesso in visualizzazione a una pagina",
|
|
||||||
"<bold>{{name}}</bold> updated a page": "<bold>{{name}}</bold> ha aggiornato una pagina",
|
|
||||||
"Watch page": "Segui pagina",
|
|
||||||
"Stop watching": "Smetti di seguire",
|
|
||||||
"Watch space": "Segui spazio",
|
|
||||||
"Stop watching space": "Smetti di seguire lo spazio",
|
|
||||||
"Email notifications": "Notifiche email",
|
|
||||||
"Page updates": "Aggiornamenti pagina",
|
|
||||||
"Get notified when pages you watch are updated.": "Ricevi una notifica quando le pagine che segui vengono aggiornate.",
|
|
||||||
"Page mentions": "Menzioni nella pagina",
|
|
||||||
"Get notified when someone mentions you on a page.": "Ricevi una notifica quando qualcuno ti menziona su una pagina.",
|
|
||||||
"Comment mentions": "Menzioni nei commenti",
|
|
||||||
"Get notified when someone mentions you in a comment.": "Ricevi una notifica quando qualcuno ti menziona in un commento.",
|
|
||||||
"New comments": "Nuovi commenti",
|
|
||||||
"Get notified about new comments on threads you participate in.": "Ricevi una notifica sui nuovi commenti nelle discussioni a cui partecipi.",
|
|
||||||
"Resolved comments": "Commenti risolti",
|
|
||||||
"Get notified when your comment is resolved.": "Ricevi una notifica quando il tuo commento viene risolto.",
|
|
||||||
"You are now watching this page": "Ora stai seguendo questa pagina",
|
|
||||||
"You are no longer watching this page": "Non stai più seguendo questa pagina",
|
|
||||||
"You are now watching this space": "Ora stai seguendo questo spazio",
|
|
||||||
"You are no longer watching this space": "Non stai più seguendo questo spazio",
|
|
||||||
"Direct": "Diretto",
|
|
||||||
"Updates": "Aggiornamenti",
|
|
||||||
"Today": "Oggi",
|
|
||||||
"Yesterday": "Ieri",
|
|
||||||
"This week": "Questa settimana",
|
|
||||||
"Older": "Più vecchie",
|
|
||||||
"Restricted page": "Pagina con accesso ristretto",
|
|
||||||
"Restricted pages cannot be shared publicly.": "Le pagine con accesso ristretto non possono essere condivise pubblicamente.",
|
|
||||||
"Restricted by parent": "Limitata dalla pagina genitore",
|
|
||||||
"Restricted": "Limitata",
|
|
||||||
"Open": "Aperta",
|
|
||||||
"Inherits restrictions from ancestor page": "Eredita le restrizioni dalla pagina genitore",
|
|
||||||
"Only people listed below can access this page": "Solo le persone elencate di seguito possono accedere a questa pagina",
|
|
||||||
"Everyone in this space can access": "Chiunque in questo spazio può accedere",
|
|
||||||
"No additional restrictions on this page": "Nessuna restrizione aggiuntiva su questa pagina",
|
|
||||||
"Only specific people can access": "Solo persone specifiche possono accedere",
|
|
||||||
"Use only inherited restrictions": "Usa solo le restrizioni ereditate",
|
|
||||||
"Add restrictions on top of inherited": "Aggiungi restrizioni oltre a quelle ereditate",
|
|
||||||
"Inherited restriction": "Restrizione ereditata",
|
|
||||||
"Access limited by": "Accesso limitato da",
|
|
||||||
"Restrict access to control who can view and edit this page": "Limita l'accesso per controllare chi può visualizzare e modificare questa pagina",
|
|
||||||
"Add additional restrictions specific to this page": "Aggiungi restrizioni aggiuntive specifiche per questa pagina",
|
|
||||||
"Access": "Accesso",
|
|
||||||
"People with access": "Persone con accesso",
|
|
||||||
"Remove all": "Rimuovi tutto",
|
|
||||||
"Remove access": "Rimuovi accesso",
|
|
||||||
"Remove all access": "Rimuovi tutti gli accessi",
|
|
||||||
"Are you sure you want to remove this member's access to the page?": "Sei sicuro di voler rimuovere l'accesso di questo membro alla pagina?",
|
|
||||||
"Are you sure you want to remove all specific access? This will make the page open to everyone in the space.": "Sei sicuro di voler rimuovere tutti gli accessi specifici? Questo renderà la pagina accessibile a tutti nello spazio.",
|
|
||||||
"Trash retention": "Conservazione del cestino",
|
|
||||||
"Pages in trash will be permanently deleted after this period.": "Le pagine nel cestino verranno eliminate definitivamente dopo questo periodo.",
|
|
||||||
"Trash retention updated": "Conservazione del cestino aggiornata",
|
|
||||||
"Failed to update trash retention": "Impossibile aggiornare la conservazione del cestino",
|
|
||||||
"Removed page restriction": "Restrizione della pagina rimossa",
|
|
||||||
"Added page permission": "Permesso sulla pagina aggiunto",
|
|
||||||
"Removed page permission": "Permesso sulla pagina rimosso",
|
|
||||||
"day": "giorno",
|
|
||||||
"days": "giorni",
|
|
||||||
"week": "settimana",
|
|
||||||
"weeks": "settimane",
|
|
||||||
"month": "mese",
|
|
||||||
"months": "mesi",
|
|
||||||
"year": "anno",
|
|
||||||
"years": "anni",
|
|
||||||
"Period": "Periodo",
|
|
||||||
"Fixed date": "Data fissa",
|
|
||||||
"Indefinitely": "A tempo indeterminato",
|
|
||||||
"Days": "Giorni",
|
|
||||||
"Weeks": "Settimane",
|
|
||||||
"Months": "Mesi",
|
|
||||||
"Years": "Anni",
|
|
||||||
"Pick a date": "Scegli una data",
|
|
||||||
"Maximum is {{max}} {{unit}} for this unit": "Il massimo consentito è {{max}} {{unit}} per questa unità",
|
|
||||||
"Never expires. Verifiers can re-verify at any time.": "Non scade mai. I verificatori possono verificare nuovamente in qualsiasi momento.",
|
|
||||||
"Verified": "Verificato",
|
|
||||||
"Review needed": "Revisione necessaria",
|
|
||||||
"Verification expired": "Verifica scaduta",
|
|
||||||
"Draft": "Bozza",
|
|
||||||
"In Approval": "In approvazione",
|
|
||||||
"In approval": "In approvazione",
|
|
||||||
"Approved": "Approvato",
|
|
||||||
"Obsolete": "Obsoleto",
|
|
||||||
"Expiring": "In scadenza",
|
|
||||||
"Set up verification": "Configura la verifica",
|
|
||||||
"Verify page": "Verifica la pagina",
|
|
||||||
"Page verification": "Verifica della pagina",
|
|
||||||
"Add verification": "Aggiungi verifica",
|
|
||||||
"Edit verification": "Modifica verifica",
|
|
||||||
"Search by title": "Cerca per titolo",
|
|
||||||
"Choose how this page should stay accurate.": "Scegli come mantenere accurata questa pagina.",
|
|
||||||
"Recurring verification": "Verifica ricorrente",
|
|
||||||
"Verifiers re-confirm this page on a schedule.": "I verificatori riconfermano questa pagina secondo una pianificazione.",
|
|
||||||
"Re-verify on a schedule (e.g every 30 days )": "Verifica nuovamente secondo una pianificazione (ad es. ogni 30 giorni)",
|
|
||||||
"Page stays editable at all times": "La pagina resta sempre modificabile",
|
|
||||||
"Best for runbooks, FAQs, living documentation": "Ideale per runbook, FAQ e documentazione dinamica",
|
|
||||||
"Approval workflow": "Flusso di approvazione",
|
|
||||||
"Formal document lifecycle with named approvers.": "Ciclo di vita formale del documento con approvatori nominati.",
|
|
||||||
"Draft → In approval → Approved → Obsolete": "Bozza → In approvazione → Approvato → Obsoleto",
|
|
||||||
"Locked once approved, with full history": "Bloccato una volta approvato, con cronologia completa",
|
|
||||||
"Designed for ISO 9001, ISO 13485, and FDA": "Progettato per ISO 9001, ISO 13485 e FDA",
|
|
||||||
"Best for SOPs and controlled documents": "Ideale per SOP e documenti controllati",
|
|
||||||
"Back": "Indietro",
|
|
||||||
"Quality management": "Gestione della qualità",
|
|
||||||
"Recurring": "Ricorrente",
|
|
||||||
"Pages move through draft, approval, and approved stages.": "Le pagine passano attraverso le fasi di bozza, approvazione e approvato.",
|
|
||||||
"Verifiers": "Verificatori",
|
|
||||||
"Add verifier": "Aggiungi verificatore",
|
|
||||||
"I've reviewed this page for accuracy": "Ho controllato l'accuratezza di questa pagina",
|
|
||||||
"Set up": "Configura",
|
|
||||||
"Remove verification": "Rimuovi verifica",
|
|
||||||
"Are you sure you want to remove verification from this page?": "Sei sicuro di voler rimuovere la verifica da questa pagina?",
|
|
||||||
"Assigned verifiers must periodically re-verify this page.": "I verificatori assegnati devono verificare nuovamente questa pagina periodicamente.",
|
|
||||||
"Last verified by {{name}} {{time}} (expired)": "Ultima verifica effettuata da {{name}} {{time}} (scaduta)",
|
|
||||||
"The fixed expiration date has passed.": "La data di scadenza fissa è trascorsa.",
|
|
||||||
"Verified by {{name}} {{time}}": "Verificato da {{name}} {{time}}",
|
|
||||||
"Expires {{date}}": "Scade il {{date}}",
|
|
||||||
"Expired {{date}}": "Scaduto il {{date}}",
|
|
||||||
"Mark as obsolete": "Contrassegna come obsoleto",
|
|
||||||
"Mark obsolete": "Contrassegna come obsoleto",
|
|
||||||
"Returned by {{name}} {{time}}": "Restituito da {{name}} {{time}}",
|
|
||||||
"No approval has been requested yet.": "Non è stata ancora richiesta alcuna approvazione.",
|
|
||||||
"Submitted by {{name}} {{time}}": "Inviato da {{name}} {{time}}",
|
|
||||||
"Someone": "Qualcuno",
|
|
||||||
"Approved by {{name}} {{time}}": "Approvato da {{name}} {{time}}",
|
|
||||||
"This document has been marked as obsolete.": "Questo documento è stato contrassegnato come obsoleto.",
|
|
||||||
"Rejection comment": "Commento di rifiuto",
|
|
||||||
"Reason for returning this document...": "Motivo della restituzione di questo documento...",
|
|
||||||
"Confirm rejection": "Conferma rifiuto",
|
|
||||||
"Submit for approval": "Invia per approvazione",
|
|
||||||
"Reject": "Rifiuta",
|
|
||||||
"Approve": "Approva",
|
|
||||||
"Re-submit for approval": "Invia nuovamente per approvazione",
|
|
||||||
"Verified until": "Verificato fino al",
|
|
||||||
"QMS": "QMS",
|
|
||||||
"Verified pages": "Pagine verificate",
|
|
||||||
"Search pages...": "Cerca pagine...",
|
|
||||||
"Filter by space": "Filtra per spazio",
|
|
||||||
"Filter by type": "Filtra per tipo",
|
|
||||||
"<bold>{{name}}</bold> verified a page": "<bold>{{name}}</bold> ha verificato una pagina",
|
|
||||||
"<bold>{{name}}</bold> submitted a page for your approval": "<bold>{{name}}</bold> ha inviato una pagina per la tua approvazione",
|
|
||||||
"<bold>{{name}}</bold> returned a page for revision": "<bold>{{name}}</bold> ha restituito una pagina per la revisione",
|
|
||||||
"Page verification expires soon": "La verifica della pagina scadrà presto",
|
|
||||||
"Page verification has expired": "La verifica della pagina è scaduta",
|
|
||||||
"Verifying your email": "Verifica della tua email in corso",
|
|
||||||
"Please wait...": "Attendere...",
|
|
||||||
"Verification failed. The link may have expired.": "Verifica non riuscita. Il link potrebbe essere scaduto.",
|
|
||||||
"Check your email": "Controlla la tua email",
|
|
||||||
"We sent a verification link to {{email}}.": "Abbiamo inviato un link di verifica a {{email}}.",
|
|
||||||
"We sent a verification link to your email.": "Abbiamo inviato un link di verifica alla tua email.",
|
|
||||||
"Click the link to verify your email and access your workspace.": "Clicca sul link per verificare la tua email e accedere al tuo workspace.",
|
|
||||||
"Resend verification email": "Invia nuovamente email di verifica",
|
|
||||||
"Verification email sent. Please check your inbox.": "Email di verifica inviata. Controlla la tua casella di posta.",
|
|
||||||
"Failed to resend verification email. Please try again.": "Invio dell'email di verifica non riuscito. Si prega di riprovare.",
|
|
||||||
"We've sent you an email with your associated workspaces.": "Ti abbiamo inviato un'email con i workspace associati.",
|
|
||||||
"Load more": "Carica altro",
|
|
||||||
"Log out of all devices": "Disconnetti da tutti i dispositivi",
|
|
||||||
"Log out of all sessions except this device": "Disconnetti da tutte le sessioni tranne questo dispositivo",
|
|
||||||
"This Device": "Questo dispositivo",
|
|
||||||
"Unknown device": "Dispositivo sconosciuto",
|
|
||||||
"No active sessions": "Nessuna sessione attiva",
|
|
||||||
"Session revoked": "Sessione revocata",
|
|
||||||
"All other sessions revoked": "Tutte le altre sessioni sono state revocate",
|
|
||||||
"Last used": "Ultimo utilizzo",
|
|
||||||
"Created": "Creato",
|
|
||||||
"Rename": "Rinomina",
|
|
||||||
"Publish": "Pubblica",
|
|
||||||
"Security": "Sicurezza",
|
|
||||||
"Enforce SSO": "Rendi obbligatorio SSO",
|
|
||||||
"Once enforced, members will not be able to login with email and password.": "Una volta reso obbligatorio, i membri non potranno accedere con email e password.",
|
|
||||||
"AI-generated content may not be accurate.": "I contenuti generati dall'IA potrebbero non essere accurati.",
|
|
||||||
"AI Chat": "Chat IA",
|
|
||||||
"Analyze for insights": "Analizza per ottenere approfondimenti",
|
|
||||||
"Ask anything...": "Chiedi qualsiasi cosa...",
|
|
||||||
"Chat history": "Cronologia chat",
|
|
||||||
"Chat name": "Nome chat",
|
|
||||||
"Close": "Chiudi",
|
|
||||||
"Docmost AI": "Docmost AI",
|
|
||||||
"Failed to load chat. An error occurred.": "Caricamento della chat non riuscito. Si è verificato un errore.",
|
|
||||||
"Failed to render this message.": "Impossibile visualizzare questo messaggio.",
|
|
||||||
"How can I help you today?": "Come posso aiutarti oggi?",
|
|
||||||
"New chat": "Nuova chat",
|
|
||||||
"No chat history": "Nessuna cronologia chat",
|
|
||||||
"No chats found": "Nessuna chat trovata",
|
|
||||||
"No conversations yet": "Nessuna conversazione al momento",
|
|
||||||
"Open full page": "Apri pagina completa",
|
|
||||||
"Previous 7 days": "Ultimi 7 giorni",
|
|
||||||
"Previous 30 days": "Ultimi 30 giorni",
|
|
||||||
"Search chats...": "Cerca nelle chat...",
|
|
||||||
"Search chats": "Cerca nelle chat",
|
|
||||||
"Ask anything... Use @ to mention pages": "Chiedi qualsiasi cosa... Usa @ per menzionare le pagine",
|
|
||||||
"Ask anything or search your workspace": "Ask anything or search your workspace",
|
|
||||||
"Welcome to {{name}}": "Welcome to {{name}}",
|
|
||||||
"Add files": "Add files",
|
|
||||||
"Mention a page": "Mention a page",
|
|
||||||
"Start a new chat to see it here.": "Avvia una nuova chat per vederla qui.",
|
|
||||||
"Summarize this page": "Riassumi questa pagina",
|
|
||||||
"Toggle AI Chat": "Attiva/disattiva Chat IA",
|
|
||||||
"Translate this page": "Traduci questa pagina",
|
|
||||||
"Try a different search term.": "Prova un termine di ricerca diverso.",
|
|
||||||
"Try again": "Riprova",
|
|
||||||
"Untitled chat": "Chat senza titolo",
|
|
||||||
"What can I help you with?": "Con cosa posso aiutarti?",
|
|
||||||
"Are you sure you want to revoke this {{credential}}": "Sei sicuro di voler revocare questa {{credential}}",
|
|
||||||
"Automatically provision users and groups from your identity provider via SCIM.": "Esegui automaticamente il provisioning di utenti e gruppi dal tuo provider di identità tramite SCIM.",
|
|
||||||
"Configure your identity provider with this URL to provision users and groups.": "Configura il tuo provider di identità con questo URL per eseguire il provisioning di utenti e gruppi.",
|
|
||||||
"Create {{credential}}": "Crea {{credential}}",
|
|
||||||
"{{credential}} created": "{{credential}} creata",
|
|
||||||
"{{credential}} created successfully": "{{credential}} creata con successo",
|
|
||||||
"Created by": "Creata da",
|
|
||||||
"Custom": "Personalizzato",
|
|
||||||
"Enable SCIM": "Abilita SCIM",
|
|
||||||
"Enter a descriptive name": "Inserisci un nome descrittivo",
|
|
||||||
"I've saved my {{credential}}": "Ho salvato la mia {{credential}}",
|
|
||||||
"Important": "Importante",
|
|
||||||
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Assicurati di copiare subito la tua {{credential}}. Non potrai più visualizzarla!",
|
|
||||||
"Never": "Mai",
|
|
||||||
"Revoke {{credential}}": "Revoca {{credential}}",
|
|
||||||
"SCIM endpoint URL": "URL dell'endpoint SCIM",
|
|
||||||
"SCIM provisioning": "Provisioning SCIM",
|
|
||||||
"SCIM takes precedence over SSO group sync while enabled.": "SCIM ha la precedenza sulla sincronizzazione dei gruppi SSO quando è abilitato.",
|
|
||||||
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "Hai raggiunto il numero massimo di {{max}} token SCIM. Elimina un token esistente per crearne uno nuovo.",
|
|
||||||
"SCIM token": "Token SCIM",
|
|
||||||
"SCIM tokens": "Token SCIM",
|
|
||||||
"This action cannot be undone. Your identity provider will stop syncing immediately.": "Questa azione non può essere annullata. Il tuo provider di identità smetterà di sincronizzarsi immediatamente.",
|
|
||||||
"Toggle SCIM provisioning": "Attiva/disattiva il provisioning SCIM",
|
|
||||||
"Token": "Token",
|
|
||||||
"Page menu": "Menu della pagina",
|
|
||||||
"Expand": "Espandi",
|
|
||||||
"Collapse": "Comprimi",
|
|
||||||
"Comment menu": "Menu dei commenti",
|
|
||||||
"Group menu": "Menu del gruppo",
|
|
||||||
"Show hidden breadcrumbs": "Mostra breadcrumb nascosti",
|
|
||||||
"Breadcrumbs": "Breadcrumb",
|
|
||||||
"Page actions": "Azioni della pagina",
|
|
||||||
"Pick emoji": "Scegli emoji",
|
|
||||||
"Template menu": "Menu del modello",
|
|
||||||
"Use": "Use",
|
|
||||||
"Use template": "Use template",
|
|
||||||
"Preview template: {{title}}": "Preview template: {{title}}",
|
|
||||||
"Use a template": "Use a template",
|
|
||||||
"Search templates...": "Search templates...",
|
|
||||||
"Search spaces...": "Search spaces...",
|
|
||||||
"No templates found": "No templates found",
|
|
||||||
"No spaces found": "No spaces found",
|
|
||||||
"Browse all templates": "Browse all templates",
|
|
||||||
"This space": "This space",
|
|
||||||
"All templates": "All templates",
|
|
||||||
"Global": "Global",
|
|
||||||
"New template": "New template",
|
|
||||||
"Edit template": "Edit template",
|
|
||||||
"Are you sure you want to delete this template?": "Are you sure you want to delete this template?",
|
|
||||||
"Template scope updated": "Template scope updated",
|
|
||||||
"Choose which space this template belongs to": "Choose which space this template belongs to",
|
|
||||||
"Scope": "Scope",
|
|
||||||
"Select scope": "Select scope",
|
|
||||||
"Title": "Title",
|
|
||||||
"Saving...": "Saving...",
|
|
||||||
"Saved": "Saved",
|
|
||||||
"Save failed. Retry": "Save failed. Retry",
|
|
||||||
"By {{name}}": "By {{name}}",
|
|
||||||
"Updated {{time}}": "Updated {{time}}",
|
|
||||||
"Choose destination": "Choose destination",
|
|
||||||
"Search pages and spaces...": "Search pages and spaces...",
|
|
||||||
"No results found": "No results found",
|
|
||||||
"You don't have permission to create pages here": "You don't have permission to create pages here",
|
|
||||||
"Chat menu": "Menu della chat",
|
|
||||||
"API key menu": "Menu della chiave API",
|
|
||||||
"Jump to comment selection": "Vai alla selezione dei commenti",
|
|
||||||
"Slash commands": "Comandi slash",
|
|
||||||
"Mention suggestions": "Suggerimenti di menzione",
|
|
||||||
"Link suggestions": "Suggerimenti di link",
|
|
||||||
"Diagram editor": "Editor di diagrammi",
|
|
||||||
"Add comment": "Aggiungi commento",
|
|
||||||
"Find and replace": "Trova e sostituisci",
|
|
||||||
"Main navigation": "Navigazione principale",
|
|
||||||
"Space navigation": "Navigazione dello spazio",
|
|
||||||
"Settings navigation": "Navigazione delle impostazioni",
|
|
||||||
"AI navigation": "Navigazione AI",
|
|
||||||
"Breadcrumb": "Percorso di navigazione",
|
|
||||||
"Synced block": "Synced block",
|
|
||||||
"Create a block that stays in sync across pages.": "Create a block that stays in sync across pages.",
|
|
||||||
"Editing original": "Editing original",
|
|
||||||
"Copy synced block": "Copy synced block",
|
|
||||||
"Unsync": "Unsync",
|
|
||||||
"Delete synced block": "Delete synced block",
|
|
||||||
"Synced to {{count}} other page_one": "Synced to {{count}} other page",
|
|
||||||
"Synced to {{count}} other page_other": "Synced to {{count}} other pages",
|
|
||||||
"ORIGINAL": "ORIGINAL",
|
|
||||||
"THIS PAGE": "THIS PAGE",
|
|
||||||
"No pages": "No pages",
|
|
||||||
"The original synced block no longer exists": "The original synced block no longer exists",
|
|
||||||
"You don't have access to this synced block": "You don't have access to this synced block",
|
|
||||||
"Failed to load this synced block": "Failed to load this synced block",
|
|
||||||
"Fixed editor toolbar": "Fixed editor toolbar",
|
|
||||||
"Show a formatting toolbar above the editor with quick access to common actions.": "Show a formatting toolbar above the editor with quick access to common actions.",
|
|
||||||
"Toggle fixed editor toolbar": "Toggle fixed editor toolbar",
|
|
||||||
"Normal text": "Normal text",
|
|
||||||
"More inline formatting": "More inline formatting",
|
|
||||||
"Subscript": "Subscript",
|
|
||||||
"Superscript": "Superscript",
|
|
||||||
"Inline code": "Inline code",
|
|
||||||
"Insert media": "Insert media",
|
|
||||||
"Mention": "Mention",
|
|
||||||
"Emoji": "Emoji",
|
|
||||||
"Columns": "Columns",
|
|
||||||
"More inserts": "More inserts",
|
|
||||||
"Embeds": "Embeds",
|
|
||||||
"Diagrams": "Diagrams",
|
|
||||||
"Advanced": "Advanced",
|
|
||||||
"Utility": "Utility",
|
|
||||||
"Decrease indent": "Decrease indent",
|
|
||||||
"Increase indent": "Increase indent",
|
|
||||||
"Clear formatting": "Clear formatting",
|
|
||||||
"Code block": "Code block",
|
|
||||||
"Experimental": "Experimental",
|
|
||||||
"Strikethrough": "Strikethrough",
|
|
||||||
"Undo": "Undo",
|
|
||||||
"Redo": "Redo",
|
|
||||||
"Backlinks": "Backlinks",
|
|
||||||
"Last updated by": "Last updated by",
|
|
||||||
"Last updated": "Last updated",
|
|
||||||
"Stats": "Stats",
|
|
||||||
"Word count": "Word count",
|
|
||||||
"Characters": "Characters",
|
|
||||||
"Incoming links": "Incoming links",
|
|
||||||
"Outgoing links": "Outgoing links",
|
|
||||||
"Incoming links ({{count}})": "Incoming links ({{count}})",
|
|
||||||
"Outgoing links ({{count}})": "Outgoing links ({{count}})",
|
|
||||||
"No pages link here yet.": "No pages link here yet.",
|
|
||||||
"This page doesn't link to other pages yet.": "This page doesn't link to other pages yet.",
|
|
||||||
"Verified until {{date}}": "Verified until {{date}}",
|
|
||||||
"Labels": "Labels",
|
|
||||||
"Add label": "Add label",
|
|
||||||
"No labels yet": "No labels yet",
|
|
||||||
"Already added": "Already added",
|
|
||||||
"Invalid label name": "Invalid label name",
|
|
||||||
"No matches": "No matches",
|
|
||||||
"Search or create…": "Search or create…",
|
|
||||||
"Remove label {{name}}": "Remove label {{name}}",
|
|
||||||
"Failed to add label": "Failed to add label",
|
|
||||||
"Failed to remove label": "Failed to remove label",
|
|
||||||
"No pages with this label": "No pages with this label",
|
|
||||||
"Pages tagged with this label will appear here.": "Pages tagged with this label will appear here.",
|
|
||||||
"No pages match your search.": "No pages match your search.",
|
|
||||||
"Updated {{date}}": "Updated {{date}}",
|
|
||||||
"Cell actions": "Cell actions",
|
|
||||||
"Column actions": "Column actions",
|
|
||||||
"Row actions": "Row actions"
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@
|
|||||||
"Add members": "Adicionar membros",
|
"Add members": "Adicionar membros",
|
||||||
"Add to groups": "Adicionar aos grupos",
|
"Add to groups": "Adicionar aos grupos",
|
||||||
"Add space members": "Adicionar membros do espaço",
|
"Add space members": "Adicionar membros do espaço",
|
||||||
"Add to favorites": "Adicionar aos favoritos",
|
|
||||||
"Admin": "Administrador",
|
"Admin": "Administrador",
|
||||||
"Are you sure you want to delete this group? Members will lose access to resources this group has access to.": "Tem certeza de que deseja excluir este grupo? Os membros perderão acesso aos recursos que este grupo possui.",
|
"Are you sure you want to delete this group? Members will lose access to resources this group has access to.": "Tem certeza de que deseja excluir este grupo? Os membros perderão acesso aos recursos que este grupo possui.",
|
||||||
"Are you sure you want to delete this page?": "Tem certeza de que deseja excluir esta página?",
|
"Are you sure you want to delete this page?": "Tem certeza de que deseja excluir esta página?",
|
||||||
@@ -30,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Escolha o idioma da interface.",
|
"Choose your preferred interface language.": "Escolha o idioma da interface.",
|
||||||
"Choose your preferred page width.": "Escolha a largura preferida da página.",
|
"Choose your preferred page width.": "Escolha a largura preferida da página.",
|
||||||
"Confirm": "Confirmar",
|
"Confirm": "Confirmar",
|
||||||
"Copy as Markdown": "Copiar como Markdown",
|
|
||||||
"Copy link": "Copiar link",
|
"Copy link": "Copiar link",
|
||||||
"Create": "Criar",
|
"Create": "Criar",
|
||||||
"Create group": "Criar grupo",
|
"Create group": "Criar grupo",
|
||||||
@@ -55,12 +53,11 @@
|
|||||||
"e.g Space for product team": "ex.: Espaço para a equipe de produto",
|
"e.g Space for product team": "ex.: Espaço para a equipe de produto",
|
||||||
"e.g Space for sales team to collaborate": "ex.: Espaço para a equipe de vendas colaborar",
|
"e.g Space for sales team to collaborate": "ex.: Espaço para a equipe de vendas colaborar",
|
||||||
"Edit": "Editar",
|
"Edit": "Editar",
|
||||||
"Read": "Ler",
|
|
||||||
"Edit group": "Editar grupo",
|
"Edit group": "Editar grupo",
|
||||||
"Email": "Email",
|
"Email": "Email",
|
||||||
"Enter a strong password": "Insira uma senha forte",
|
"Enter a strong password": "Insira uma senha forte",
|
||||||
"Enter valid email addresses separated by comma or space max_50": "Insira endereços de email válidos separados por vírgula ou espaço [máx: 50]",
|
"Enter valid email addresses separated by comma or space max_50": "Insira endereços de email válidos separados por vírgula ou espaço [máx: 50]",
|
||||||
"enter valid emails addresses": "insira endereços de e-mail válidos",
|
"enter valid emails addresses": "insira endereços de email válidos",
|
||||||
"Enter your current password": "Insira sua senha atual",
|
"Enter your current password": "Insira sua senha atual",
|
||||||
"enter your full name": "insira seu nome completo",
|
"enter your full name": "insira seu nome completo",
|
||||||
"Enter your new password": "Insira sua nova senha",
|
"Enter your new password": "Insira sua nova senha",
|
||||||
@@ -71,14 +68,10 @@
|
|||||||
"Export": "Exportar",
|
"Export": "Exportar",
|
||||||
"Failed to create page": "Falha ao criar página",
|
"Failed to create page": "Falha ao criar página",
|
||||||
"Failed to delete page": "Falha ao excluir página",
|
"Failed to delete page": "Falha ao excluir página",
|
||||||
"Failed to restore page": "Failed to restore page",
|
|
||||||
"Failed to fetch recent pages": "Falha ao buscar páginas recentes",
|
"Failed to fetch recent pages": "Falha ao buscar páginas recentes",
|
||||||
"Failed to import pages": "Falha ao importar páginas",
|
"Failed to import pages": "Falha ao importar páginas",
|
||||||
"Failed to load page. An error occurred.": "Falha ao carregar página. Ocorreu um erro.",
|
"Failed to load page. An error occurred.": "Falha ao carregar página. Ocorreu um erro.",
|
||||||
"Failed to update data": "Falha ao atualizar dados",
|
"Failed to update data": "Falha ao atualizar dados",
|
||||||
"Favorite spaces": "Espaços favoritos",
|
|
||||||
"Favorite spaces appear here": "Os espaços favoritos aparecem aqui",
|
|
||||||
"Favorites": "Favoritos",
|
|
||||||
"Full access": "Acesso total",
|
"Full access": "Acesso total",
|
||||||
"Full page width": "Usar largura total da página",
|
"Full page width": "Usar largura total da página",
|
||||||
"Full width": "Largura total",
|
"Full width": "Largura total",
|
||||||
@@ -97,7 +90,6 @@
|
|||||||
"Invite by email": "Convidar por email",
|
"Invite by email": "Convidar por email",
|
||||||
"Invite members": "Convidar membros",
|
"Invite members": "Convidar membros",
|
||||||
"Invite new members": "Convidar novos membros",
|
"Invite new members": "Convidar novos membros",
|
||||||
"Invite People": "Convidar pessoas",
|
|
||||||
"Invited members who are yet to accept their invitation will appear here.": "Membros convidados que ainda não aceitaram o convite aparecerão aqui.",
|
"Invited members who are yet to accept their invitation will appear here.": "Membros convidados que ainda não aceitaram o convite aparecerão aqui.",
|
||||||
"Invited members will be granted access to spaces the groups can access": "Os membros convidados terão acesso aos espaços que os grupos podem acessar",
|
"Invited members will be granted access to spaces the groups can access": "Os membros convidados terão acesso aos espaços que os grupos podem acessar",
|
||||||
"Join the workspace": "Entrar no workspace",
|
"Join the workspace": "Entrar no workspace",
|
||||||
@@ -122,7 +114,6 @@
|
|||||||
"No group found": "Nenhum grupo encontrado",
|
"No group found": "Nenhum grupo encontrado",
|
||||||
"No page history saved yet.": "Nenhum histórico de página salvo ainda.",
|
"No page history saved yet.": "Nenhum histórico de página salvo ainda.",
|
||||||
"No pages yet": "Nenhuma página ainda",
|
"No pages yet": "Nenhuma página ainda",
|
||||||
"No shared pages": "Sem páginas compartilhadas",
|
|
||||||
"No results found...": "Nenhum resultado encontrado...",
|
"No results found...": "Nenhum resultado encontrado...",
|
||||||
"No user found": "Nenhum usuário encontrado",
|
"No user found": "Nenhum usuário encontrado",
|
||||||
"Overview": "Visão geral",
|
"Overview": "Visão geral",
|
||||||
@@ -130,14 +121,11 @@
|
|||||||
"page": "página",
|
"page": "página",
|
||||||
"Page deleted successfully": "Página excluída com sucesso",
|
"Page deleted successfully": "Página excluída com sucesso",
|
||||||
"Page history": "Histórico da página",
|
"Page history": "Histórico da página",
|
||||||
"Select version": "Selecionar versão",
|
|
||||||
"Highlight changes": "Destacar alterações",
|
|
||||||
"Page import is in progress. Please do not close this tab.": "A importação da página está em andamento. Por favor, não feche esta aba.",
|
"Page import is in progress. Please do not close this tab.": "A importação da página está em andamento. Por favor, não feche esta aba.",
|
||||||
"Pages": "Páginas",
|
"Pages": "Páginas",
|
||||||
"pages": "páginas",
|
"pages": "páginas",
|
||||||
"Password": "Senha",
|
"Password": "Senha",
|
||||||
"Password changed successfully": "Senha alterada com sucesso",
|
"Password changed successfully": "Senha alterada com sucesso",
|
||||||
"People": "Pessoas",
|
|
||||||
"Pending": "Pendente",
|
"Pending": "Pendente",
|
||||||
"Please confirm your action": "Por favor, confirme sua ação",
|
"Please confirm your action": "Por favor, confirme sua ação",
|
||||||
"Preferences": "Preferências",
|
"Preferences": "Preferências",
|
||||||
@@ -145,7 +133,6 @@
|
|||||||
"Profile": "Perfil",
|
"Profile": "Perfil",
|
||||||
"Recently updated": "Atualizado recentemente",
|
"Recently updated": "Atualizado recentemente",
|
||||||
"Remove": "Remover",
|
"Remove": "Remover",
|
||||||
"Remove from favorites": "Remover dos favoritos",
|
|
||||||
"Remove group member": "Remover membro do grupo",
|
"Remove group member": "Remover membro do grupo",
|
||||||
"Remove space member": "Remover membro do espaço",
|
"Remove space member": "Remover membro do espaço",
|
||||||
"Restore": "Restaurar",
|
"Restore": "Restaurar",
|
||||||
@@ -156,16 +143,16 @@
|
|||||||
"Search for users": "Buscar usuários",
|
"Search for users": "Buscar usuários",
|
||||||
"Search for users and groups": "Buscar usuários e grupos",
|
"Search for users and groups": "Buscar usuários e grupos",
|
||||||
"Search...": "Buscar...",
|
"Search...": "Buscar...",
|
||||||
"Select language": "Selecione o idioma",
|
"Select language": "Selecionar idioma",
|
||||||
"Select role": "Selecione a função",
|
"Select role": "Selecionar função",
|
||||||
"Select role to assign to all invited members": "Selecione a função a ser atribuída a todos os membros convidados",
|
"Select role to assign to all invited members": "Selecione a função para atribuir a todos os membros convidados",
|
||||||
"Select theme": "Selecione o tema",
|
"Select theme": "Selecionar tema",
|
||||||
"Send invitation": "Enviar convite",
|
"Send invitation": "Enviar convite",
|
||||||
"Invitation sent": "Convite enviado",
|
"Invitation sent": "Convite enviado",
|
||||||
"Settings": "Configurações",
|
"Settings": "Configurações",
|
||||||
"Setup workspace": "Configurar workspace",
|
"Setup workspace": "Configurar workspace",
|
||||||
"Sign In": "Entrar",
|
"Sign In": "Entrar",
|
||||||
"Sign Up": "Cadastrar-se",
|
"Sign Up": "Registrar-se",
|
||||||
"Slug": "Slug",
|
"Slug": "Slug",
|
||||||
"Space": "Espaço",
|
"Space": "Espaço",
|
||||||
"Space description": "Descrição do espaço",
|
"Space description": "Descrição do espaço",
|
||||||
@@ -178,35 +165,34 @@
|
|||||||
"No space found": "Nenhum espaço encontrado",
|
"No space found": "Nenhum espaço encontrado",
|
||||||
"Search for spaces": "Pesquisar espaços",
|
"Search for spaces": "Pesquisar espaços",
|
||||||
"Start typing to search...": "Comece a digitar para buscar...",
|
"Start typing to search...": "Comece a digitar para buscar...",
|
||||||
"Status": "Status",
|
"Status": "Estado",
|
||||||
"Successfully imported": "Importado com sucesso",
|
"Successfully imported": "Importado com sucesso",
|
||||||
"Successfully restored": "Restaurado com sucesso",
|
"Successfully restored": "Restaurado com sucesso",
|
||||||
"System settings": "Configurações do sistema",
|
"System settings": "Configurações do sistema",
|
||||||
"Templates": "Modelos",
|
|
||||||
"Theme": "Tema",
|
"Theme": "Tema",
|
||||||
"To change your email, you have to enter your password and new email.": "Para alterar seu email, você precisa inserir sua senha e o novo email.",
|
"To change your email, you have to enter your password and new email.": "Para alterar seu email, você precisa inserir sua senha e o novo email.",
|
||||||
"Toggle full page width": "Alternar largura total da página",
|
"Toggle full page width": "Alternar para largura total da página",
|
||||||
"Unable to import pages. Please try again.": "Não foi possível importar as páginas. Por favor, tente novamente.",
|
"Unable to import pages. Please try again.": "Não foi possível importar as páginas. Por favor, tente novamente.",
|
||||||
"untitled": "sem título",
|
"untitled": "sem título",
|
||||||
"Untitled": "Sem título",
|
"Untitled": "Sem título",
|
||||||
"Updated successfully": "Atualizado com sucesso",
|
"Updated successfully": "Atualizado com sucesso",
|
||||||
"User": "Usuário",
|
"User": "Usuário",
|
||||||
"Workspace": "Workspace",
|
"Workspace": "Espaço de Trabalho",
|
||||||
"Workspace Name": "Nome do workspace",
|
"Workspace Name": "Nome do Workspace",
|
||||||
"Workspace settings": "Configurações do workspace",
|
"Workspace settings": "Configurações do workspace",
|
||||||
"You can change your password here.": "Você pode alterar sua senha aqui.",
|
"You can change your password here.": "Você pode alterar sua senha aqui.",
|
||||||
"Your Email": "Seu e-mail",
|
"Your Email": "Seu email",
|
||||||
"Your import is complete.": "Sua importação está concluída.",
|
"Your import is complete.": "Sua importação está concluída.",
|
||||||
"Your name": "Seu nome",
|
"Your name": "Seu nome",
|
||||||
"Your Name": "Seu Nome",
|
"Your Name": "Seu Nome",
|
||||||
"Your password": "Sua senha",
|
"Your password": "Sua senha",
|
||||||
"Your password must be a minimum of 8 characters.": "Sua senha deve ter no mínimo 8 caracteres.",
|
"Your password must be a minimum of 8 characters.": "Sua senha deve ter no mínimo 8 caracteres.",
|
||||||
"Sidebar toggle": "Alternar barra lateral",
|
"Sidebar toggle": "Interruptor do painel lateral",
|
||||||
"Comments": "Comentários",
|
"Comments": "Comentários",
|
||||||
"404 page not found": "404 página não encontrada",
|
"404 page not found": "Erro 404: Página não encontrada",
|
||||||
"Sorry, we can't find the page you are looking for.": "Desculpe, não conseguimos encontrar a página que você está procurando.",
|
"Sorry, we can't find the page you are looking for.": "Desculpe, não conseguimos encontrar a página que você está procurando.",
|
||||||
"Take me back to homepage": "Voltar para a página inicial",
|
"Take me back to homepage": "Leve-me de volta para a página inicial",
|
||||||
"Forgot password": "Esqueceu a senha",
|
"Forgot password": "Esqueci a senha",
|
||||||
"Forgot your password?": "Esqueceu sua senha?",
|
"Forgot your password?": "Esqueceu sua senha?",
|
||||||
"A password reset link has been sent to your email. Please check your inbox.": "Um link de redefinição de senha foi enviado para o seu email. Por favor, verifique sua caixa de entrada.",
|
"A password reset link has been sent to your email. Please check your inbox.": "Um link de redefinição de senha foi enviado para o seu email. Por favor, verifique sua caixa de entrada.",
|
||||||
"Send reset link": "Enviar link de recuperação",
|
"Send reset link": "Enviar link de recuperação",
|
||||||
@@ -217,14 +203,9 @@
|
|||||||
"Reply...": "Responder...",
|
"Reply...": "Responder...",
|
||||||
"Error loading comments.": "Erro ao carregar comentários.",
|
"Error loading comments.": "Erro ao carregar comentários.",
|
||||||
"No comments yet.": "Ainda sem comentários.",
|
"No comments yet.": "Ainda sem comentários.",
|
||||||
"No open comments.": "Sem comentários em aberto.",
|
|
||||||
"No resolved comments.": "Sem comentários resolvidos.",
|
|
||||||
"Add a comment...": "Adicione um comentário...",
|
|
||||||
"Edit comment": "Editar comentário",
|
"Edit comment": "Editar comentário",
|
||||||
"Delete comment": "Excluir comentário",
|
"Delete comment": "Excluir comentário",
|
||||||
"Are you sure you want to delete this comment?": "Você tem certeza de que deseja excluir este comentário?",
|
"Are you sure you want to delete this comment?": "Você tem certeza de que deseja excluir este comentário?",
|
||||||
"Delete chat": "Excluir chat",
|
|
||||||
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "Tem certeza de que deseja excluir '{{title}}'? Esta ação não pode ser desfeita.",
|
|
||||||
"Comment created successfully": "Comentário criado com sucesso",
|
"Comment created successfully": "Comentário criado com sucesso",
|
||||||
"Error creating comment": "Erro ao criar comentário",
|
"Error creating comment": "Erro ao criar comentário",
|
||||||
"Comment updated successfully": "Comentário atualizado com sucesso",
|
"Comment updated successfully": "Comentário atualizado com sucesso",
|
||||||
@@ -233,16 +214,17 @@
|
|||||||
"Failed to delete comment": "Falha ao excluir comentário",
|
"Failed to delete comment": "Falha ao excluir comentário",
|
||||||
"Comment resolved successfully": "Comentário resolvido com sucesso",
|
"Comment resolved successfully": "Comentário resolvido com sucesso",
|
||||||
"Comment re-opened successfully": "Comentário reaberto com sucesso",
|
"Comment re-opened successfully": "Comentário reaberto com sucesso",
|
||||||
"Comment unresolved successfully": "Comentário marcado como não resolvido com sucesso",
|
"Comment unresolved successfully": "Comentário não resolvido com sucesso",
|
||||||
"Failed to resolve comment": "Falha ao resolver comentário",
|
"Failed to resolve comment": "Falha ao resolver comentário",
|
||||||
"Resolve comment": "Resolver comentário",
|
"Resolve comment": "Resolver comentário",
|
||||||
"Unresolve comment": "Marcar comentário como não resolvido",
|
"Unresolve comment": "Não resolver comentário",
|
||||||
"Resolve Comment Thread": "Resolver tópico de comentários",
|
"Resolve Comment Thread": "Resolver Fio de Comentários",
|
||||||
"Unresolve Comment Thread": "Marcar tópico de comentários como não resolvido",
|
"Unresolve Comment Thread": "Não resolver Fio de Comentários",
|
||||||
"Are you sure you want to resolve this comment thread? This will mark it as completed.": "Tem certeza de que deseja resolver este fio de comentários? Isso o marcará como concluído.",
|
"Are you sure you want to resolve this comment thread? This will mark it as completed.": "Tem certeza de que deseja resolver este fio de comentários? Isso o marcará como concluído.",
|
||||||
"Are you sure you want to unresolve this comment thread?": "Tem certeza de que deseja não resolver este fio de comentários?",
|
"Are you sure you want to unresolve this comment thread?": "Tem certeza de que deseja não resolver este fio de comentários?",
|
||||||
"Resolved": "Resolvido",
|
"Resolved": "Resolvido",
|
||||||
"No active comments.": "Sem comentários ativos.",
|
"No active comments.": "Sem comentários ativos.",
|
||||||
|
"No resolved comments.": "Sem comentários resolvidos.",
|
||||||
"Revoke invitation": "Cancelar o convite",
|
"Revoke invitation": "Cancelar o convite",
|
||||||
"Revoke": "Anular",
|
"Revoke": "Anular",
|
||||||
"Don't": "Não",
|
"Don't": "Não",
|
||||||
@@ -261,7 +243,7 @@
|
|||||||
"Are you sure you want to delete this space?": "Tem certeza de que deseja excluir este espaço?",
|
"Are you sure you want to delete this space?": "Tem certeza de que deseja excluir este espaço?",
|
||||||
"Delete this space with all its pages and data.": "Excluir este espaço com todas as suas páginas e dados.",
|
"Delete this space with all its pages and data.": "Excluir este espaço com todas as suas páginas e dados.",
|
||||||
"All pages, comments, attachments and permissions in this space will be deleted irreversibly.": "Todas as páginas, comentários, anexos e permissões neste espaço serão excluídos de forma irreversível.",
|
"All pages, comments, attachments and permissions in this space will be deleted irreversibly.": "Todas as páginas, comentários, anexos e permissões neste espaço serão excluídos de forma irreversível.",
|
||||||
"Confirm space name": "Confirmar nome do espaço",
|
"Confirm space name": "Confirme o nome do espaço",
|
||||||
"Type the space name <b>{{spaceName}}</b> to confirm your action.": "Digite o nome do espaço <b>{{spaceName}}</b> para confirmar sua ação.",
|
"Type the space name <b>{{spaceName}}</b> to confirm your action.": "Digite o nome do espaço <b>{{spaceName}}</b> para confirmar sua ação.",
|
||||||
"Format": "Formato",
|
"Format": "Formato",
|
||||||
"Include subpages": "Incluir subpáginas",
|
"Include subpages": "Incluir subpáginas",
|
||||||
@@ -270,7 +252,6 @@
|
|||||||
"Export failed:": "Falha ao exportar:",
|
"Export failed:": "Falha ao exportar:",
|
||||||
"export error": "erro de exportação",
|
"export error": "erro de exportação",
|
||||||
"Export page": "Exportar página",
|
"Export page": "Exportar página",
|
||||||
"Export successful": "Exportação bem-sucedida",
|
|
||||||
"Export space": "Exportar espaço",
|
"Export space": "Exportar espaço",
|
||||||
"Export {{type}}": "Exportar para {{type}}",
|
"Export {{type}}": "Exportar para {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "O arquivo excede o limite de anexos {{limit}}",
|
"File exceeds the {{limit}} attachment limit": "O arquivo excede o limite de anexos {{limit}}",
|
||||||
@@ -287,21 +268,7 @@
|
|||||||
"Add row above": "Adicionar linha acima",
|
"Add row above": "Adicionar linha acima",
|
||||||
"Add row below": "Adicionar linha abaixo",
|
"Add row below": "Adicionar linha abaixo",
|
||||||
"Delete table": "Excluir tabela",
|
"Delete table": "Excluir tabela",
|
||||||
"Add column left": "Add column left",
|
|
||||||
"Add column right": "Add column right",
|
|
||||||
"Clear cell": "Clear cell",
|
|
||||||
"Clear cells": "Clear cells",
|
|
||||||
"Toggle header cell": "Toggle header cell",
|
|
||||||
"Toggle header column": "Toggle header column",
|
|
||||||
"Toggle header row": "Toggle header row",
|
|
||||||
"Move column left": "Move column left",
|
|
||||||
"Move column right": "Move column right",
|
|
||||||
"Move row down": "Move row down",
|
|
||||||
"Move row up": "Move row up",
|
|
||||||
"Sort A → Z": "Sort A → Z",
|
|
||||||
"Sort Z → A": "Sort Z → A",
|
|
||||||
"Info": "Informação",
|
"Info": "Informação",
|
||||||
"Note": "Observação",
|
|
||||||
"Success": "Sucesso",
|
"Success": "Sucesso",
|
||||||
"Warning": "Aviso",
|
"Warning": "Aviso",
|
||||||
"Danger": "Perigo",
|
"Danger": "Perigo",
|
||||||
@@ -312,11 +279,6 @@
|
|||||||
"Save & Exit": "Salvar e Sair",
|
"Save & Exit": "Salvar e Sair",
|
||||||
"Double-click to edit Excalidraw diagram": "Clique duas vezes para editar o diagrama Excalidraw",
|
"Double-click to edit Excalidraw diagram": "Clique duas vezes para editar o diagrama Excalidraw",
|
||||||
"Paste link": "Colar link",
|
"Paste link": "Colar link",
|
||||||
"Paste link or search pages": "Cole o link ou pesquise páginas",
|
|
||||||
"Link to web page": "Link para página da web",
|
|
||||||
"Recents": "Recentes",
|
|
||||||
"Page or URL": "Página ou URL",
|
|
||||||
"Link title": "Título do link",
|
|
||||||
"Edit link": "Editar link",
|
"Edit link": "Editar link",
|
||||||
"Remove link": "Remover link",
|
"Remove link": "Remover link",
|
||||||
"Add link": "Adicionar link",
|
"Add link": "Adicionar link",
|
||||||
@@ -335,7 +297,7 @@
|
|||||||
"Pink": "Rosa",
|
"Pink": "Rosa",
|
||||||
"Gray": "Cinza",
|
"Gray": "Cinza",
|
||||||
"Embed link": "Link embutido",
|
"Embed link": "Link embutido",
|
||||||
"Invalid {{provider}} embed link": "Link de incorporação do {{provider}} inválido",
|
"Invalid {{provider}} embed link": "Link de incorporação {{provider}} inválido",
|
||||||
"Embed {{provider}}": "Incorporar {{provider}}",
|
"Embed {{provider}}": "Incorporar {{provider}}",
|
||||||
"Enter {{provider}} link to embed": "Digite o link do {{provider}} para incorporar",
|
"Enter {{provider}} link to embed": "Digite o link do {{provider}} para incorporar",
|
||||||
"Bold": "Negrito",
|
"Bold": "Negrito",
|
||||||
@@ -362,14 +324,9 @@
|
|||||||
"Create block quote.": "Crie uma citação em bloco.",
|
"Create block quote.": "Crie uma citação em bloco.",
|
||||||
"Insert code snippet.": "Insira um trecho de código.",
|
"Insert code snippet.": "Insira um trecho de código.",
|
||||||
"Insert horizontal rule divider": "Insira um divisor horizontal",
|
"Insert horizontal rule divider": "Insira um divisor horizontal",
|
||||||
"Page break": "Page break",
|
|
||||||
"Insert a page break for printing.": "Insert a page break for printing.",
|
|
||||||
"Upload any image from your device.": "Envie qualquer imagem do seu dispositivo.",
|
"Upload any image from your device.": "Envie qualquer imagem do seu dispositivo.",
|
||||||
"Upload any video from your device.": "Envie qualquer vídeo do seu dispositivo.",
|
"Upload any video from your device.": "Envie qualquer vídeo do seu dispositivo.",
|
||||||
"Upload any audio from your device.": "Envie qualquer áudio do seu dispositivo.",
|
|
||||||
"Upload any file from your device.": "Envie qualquer arquivo do seu dispositivo.",
|
"Upload any file from your device.": "Envie qualquer arquivo do seu dispositivo.",
|
||||||
"Uploading {{name}}": "Enviando {{name}}",
|
|
||||||
"Uploading file": "Enviando arquivo",
|
|
||||||
"Table": "Tabela",
|
"Table": "Tabela",
|
||||||
"Insert a table.": "Insira uma tabela.",
|
"Insert a table.": "Insira uma tabela.",
|
||||||
"Insert collapsible block.": "Insira um bloco colapsável.",
|
"Insert collapsible block.": "Insira um bloco colapsável.",
|
||||||
@@ -377,44 +334,24 @@
|
|||||||
"Divider": "Divisor",
|
"Divider": "Divisor",
|
||||||
"Quote": "Citação",
|
"Quote": "Citação",
|
||||||
"Image": "Imagem",
|
"Image": "Imagem",
|
||||||
"Audio": "Áudio",
|
|
||||||
"Embed PDF": "Incorporar PDF",
|
|
||||||
"Upload and embed a PDF file.": "Envie e incorpore um arquivo PDF.",
|
|
||||||
"Embed as PDF": "Incorporar como PDF",
|
|
||||||
"Failed to load PDF": "Falha ao carregar PDF",
|
|
||||||
"Convert to attachment": "Converter em anexo",
|
|
||||||
"File attachment": "Anexo de arquivo",
|
"File attachment": "Anexo de arquivo",
|
||||||
"Toggle block": "Bloco recolhível",
|
"Toggle block": "Bloco colapsável",
|
||||||
"Callout": "Chamada",
|
"Callout": "Aviso",
|
||||||
"Insert callout notice.": "Insira um aviso.",
|
"Insert callout notice.": "Insira um aviso.",
|
||||||
"Math inline": "Matemática em linha",
|
"Math inline": "Matemática inline",
|
||||||
"Insert inline math equation.": "Insira uma equação matemática inline.",
|
"Insert inline math equation.": "Insira uma equação matemática inline.",
|
||||||
"Math block": "Bloco matemático",
|
"Math block": "Bloco de matemática",
|
||||||
"Insert math equation": "Inserir equação matemática",
|
"Insert math equation": "Insira uma equação matemática",
|
||||||
"Mermaid diagram": "Diagrama Mermaid",
|
"Mermaid diagram": "Diagrama Mermaid",
|
||||||
"Insert mermaid diagram": "Inserir diagrama Mermaid",
|
"Insert mermaid diagram": "Insira um diagrama Mermaid",
|
||||||
"Insert and design Drawio diagrams": "Inserir e criar diagramas Drawio",
|
"Insert and design Drawio diagrams": "Insira e projete diagramas Drawio",
|
||||||
"Insert current date": "Inserir data atual",
|
"Insert current date": "Insira a data atual",
|
||||||
"Draw and sketch excalidraw diagrams": "Desenhar e esboçar diagramas Excalidraw",
|
"Draw and sketch excalidraw diagrams": "Desenhe e esboce diagramas Excalidraw",
|
||||||
"Multiple": "Múltiplo",
|
"Multiple": "Múltiplo",
|
||||||
"Turn into": "Transformar em",
|
|
||||||
"Text align": "Alinhar texto",
|
|
||||||
"This page may have been deleted, moved, or you may not have access.": "Esta página pode ter sido excluída, movida ou você pode não ter acesso a ela.",
|
|
||||||
"Go to homepage": "Ir para a página inicial",
|
|
||||||
"Pages you create will show up here.": "As páginas que você criar aparecerão aqui.",
|
|
||||||
"Heading {{level}}": "Título {{level}}",
|
"Heading {{level}}": "Título {{level}}",
|
||||||
"Toggle title": "Título do bloco recolhível",
|
"Toggle title": "Alternar título",
|
||||||
"Write anything. Enter \"/\" for commands": "Escreva qualquer coisa. Digite \"/\" para ver os comandos",
|
"Write anything. Enter \"/\" for commands": "Escreva qualquer coisa. Digite \"/\" para comandos",
|
||||||
"Write...": "Escreva...",
|
"Names do not match": "Os nomes não coincidem",
|
||||||
"Column count": "Número de colunas",
|
|
||||||
"{{count}} Columns": "{{count}} colunas",
|
|
||||||
"Equal columns": "Colunas iguais",
|
|
||||||
"Left sidebar": "Barra lateral esquerda",
|
|
||||||
"Right sidebar": "Barra lateral direita",
|
|
||||||
"Wide center": "Centro largo",
|
|
||||||
"Left wide": "Largo à esquerda",
|
|
||||||
"Right wide": "Largo à direita",
|
|
||||||
"Names do not match": "Os nomes não correspondem",
|
|
||||||
"Today, {{time}}": "Hoje, {{time}}",
|
"Today, {{time}}": "Hoje, {{time}}",
|
||||||
"Yesterday, {{time}}": "Ontem, {{time}}",
|
"Yesterday, {{time}}": "Ontem, {{time}}",
|
||||||
"Space created successfully": "Espaço criado com sucesso",
|
"Space created successfully": "Espaço criado com sucesso",
|
||||||
@@ -430,71 +367,44 @@
|
|||||||
"Character count: {{characterCount}}": "Contagem de caracteres: {{characterCount}}",
|
"Character count: {{characterCount}}": "Contagem de caracteres: {{characterCount}}",
|
||||||
"New update": "Nova atualização",
|
"New update": "Nova atualização",
|
||||||
"{{latestVersion}} is available": "{{latestVersion}} está disponível",
|
"{{latestVersion}} is available": "{{latestVersion}} está disponível",
|
||||||
"Default page edit mode": "Modo padrão de edição da página",
|
"Default page edit mode": "Modo de edição de página padrão",
|
||||||
"Choose your preferred page edit mode. Avoid accidental edits.": "Escolha o modo de edição de página preferido. Evite edições acidentais.",
|
"Choose your preferred page edit mode. Avoid accidental edits.": "Escolha o modo de edição de página preferido. Evite edições acidentais.",
|
||||||
"Choose {{format}} file": "Escolher arquivo {{format}}",
|
|
||||||
"Reading": "Leitura",
|
"Reading": "Leitura",
|
||||||
"Delete member": "Excluir membro",
|
"Delete member": "Excluir membro",
|
||||||
"Member deleted successfully": "Membro excluído com sucesso",
|
"Member deleted successfully": "Membro removido com sucesso",
|
||||||
"Are you sure you want to delete this workspace member? This action is irreversible.": "Você tem certeza que deseja deletar este membro do workspace? Esta ação é irreversível.",
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "Você tem certeza que deseja deletar este membro do workspace? Esta ação é irreversível.",
|
||||||
"Deactivate member": "Desativar membro",
|
|
||||||
"Activate member": "Ativar membro",
|
|
||||||
"Are you sure you want to deactivate this workspace member? They will no longer be able to access this workspace.": "Tem certeza de que deseja desativar este membro do espaço de trabalho? Ele não poderá mais acessar este espaço de trabalho.",
|
|
||||||
"Are you sure you want to activate this workspace member?": "Tem certeza de que deseja ativar este membro do espaço de trabalho?",
|
|
||||||
"Deactivate": "Desativar",
|
|
||||||
"Activate": "Ativar",
|
|
||||||
"Deactivated": "Desativado",
|
|
||||||
"Move": "Mover",
|
"Move": "Mover",
|
||||||
"Move page": "Mover página",
|
"Move page": "Mover página",
|
||||||
"Move page to a different space.": "Mover página para um espaço diferente.",
|
"Move page to a different space.": "Mover página para um espaço diferente.",
|
||||||
"Real-time editor connection lost. Retrying...": "Conexão do editor em tempo real perdida. Tentando novamente...",
|
"Real-time editor connection lost. Retrying...": "Conexão do editor em tempo real perdida. Tentando novamente...",
|
||||||
"Table of contents": "Sumário",
|
"Table of contents": "Tabela de conteúdos",
|
||||||
"Add headings (H1, H2, H3) to generate a table of contents.": "Adicionar títulos (H1, H2, H3) para gerar uma tabela de conteúdo.",
|
"Add headings (H1, H2, H3) to generate a table of contents.": "Adicionar títulos (H1, H2, H3) para gerar uma tabela de conteúdo.",
|
||||||
"Share": "Compartilhar",
|
"Share": "Compartilhar",
|
||||||
"Public sharing": "Compartilhamento público",
|
"Public sharing": "Compartilhamento público",
|
||||||
"Shared by": "Compartilhado por",
|
"Shared by": "Compartilhado por",
|
||||||
"Shared at": "Compartilhado em",
|
"Shared at": "Compartilhado em",
|
||||||
"Inherits public sharing from": "Herda o compartilhamento público de",
|
"Inherits public sharing from": "Herdado do compartilhamento público de",
|
||||||
"Share to web": "Compartilhar na web",
|
"Share to web": "Compartilhar na web",
|
||||||
"Shared to web": "Compartilhado na web",
|
"Shared to web": "Compartilhado na web",
|
||||||
"Anyone with the link can view this page": "Qualquer pessoa com o link pode visualizar esta página",
|
"Anyone with the link can view this page": "Qualquer um com o link pode ver esta página",
|
||||||
"Make this page publicly accessible": "Tornar esta página acessível publicamente",
|
"Make this page publicly accessible": "Tornar esta página publicamente acessível",
|
||||||
"Include sub-pages": "Incluir subpáginas",
|
"Include sub-pages": "Incluir sub-páginas",
|
||||||
"Make sub-pages public too": "Tornar as subpáginas públicas também",
|
"Make sub-pages public too": "Tornar as sub-páginas públicas também",
|
||||||
"Allow search engines to index page": "Permitir que mecanismos de busca indexem a página",
|
"Allow search engines to index page": "Permitir que mecanismos de busca indexem a página",
|
||||||
"Open page": "Abrir página",
|
"Open page": "Abrir página",
|
||||||
"Page": "Página",
|
"Page": "Página",
|
||||||
"Delete public share link": "Excluir link de compartilhamento público",
|
"Delete public share link": "Excluir o link público compartilhado",
|
||||||
"Delete share": "Excluir compartilhamento",
|
"Delete share": "Excluir compartilhamento",
|
||||||
"Are you sure you want to delete this shared link?": "Tem certeza de que deseja excluir este link compartilhado?",
|
"Are you sure you want to delete this shared link?": "Tem certeza de que deseja excluir este link compartilhado?",
|
||||||
"Publicly shared pages from spaces you are a member of will appear here": "Páginas compartilhadas publicamente dos espaços dos quais você é membro aparecerão aqui",
|
"Publicly shared pages from spaces you are a member of will appear here": "Páginas compartilhadas publicamente de espaços que você é membro aparecerão aqui",
|
||||||
"Share deleted successfully": "Compartilhamento excluído com sucesso",
|
"Share deleted successfully": "Compartilhamento excluído com sucesso",
|
||||||
"Share not found": "Compartilhamento não encontrado",
|
"Share not found": "Compartilhamento não encontrado",
|
||||||
"Failed to share page": "Falha ao compartilhar a página",
|
"Failed to share page": "Falha ao compartilhar página",
|
||||||
"Disable public sharing": "Desativar compartilhamento público",
|
|
||||||
"Prevent members from sharing pages publicly.": "Impedir que os membros compartilhem páginas publicamente.",
|
|
||||||
"Toggle public sharing": "Alternar compartilhamento público",
|
|
||||||
"Toggle space public sharing": "Alternar compartilhamento público do espaço",
|
|
||||||
"Allow viewers to comment": "Permitir que os visualizadores comentem",
|
|
||||||
"Allow viewers to add comments on pages in this space.": "Permitir que os visualizadores adicionem comentários em páginas deste espaço.",
|
|
||||||
"Toggle viewer comments": "Ativar/desativar comentários de visualizadores",
|
|
||||||
"Public sharing is disabled at the workspace level": "O compartilhamento público está desativado no nível do espaço de trabalho",
|
|
||||||
"Prevent pages in this space from being shared publicly.": "Impedir que as páginas neste espaço sejam compartilhadas publicamente.",
|
|
||||||
"Page permissions": "Permissões da página},{",
|
|
||||||
"Control who can view and edit individual pages. Available with an enterprise license.": "Controle quem pode visualizar e editar páginas individuais. Disponível com licença empresarial.",
|
|
||||||
"Enable public sharing": "Ativar compartilhamento público",
|
|
||||||
"Are you sure you want to enable public sharing? Members will be able to share pages publicly.": "Tem certeza de que deseja ativar o compartilhamento público? Os membros poderão compartilhar páginas publicamente.",
|
|
||||||
"Are you sure you want to disable public sharing? All existing shared links in this workspace will be deleted.": "Tem certeza de que deseja desativar o compartilhamento público? Todos os links compartilhados existentes neste espaço de trabalho serão excluídos.",
|
|
||||||
"Are you sure you want to enable public sharing for this space?": "Tem certeza de que deseja ativar o compartilhamento público para este espaço?",
|
|
||||||
"Are you sure you want to disable public sharing? All existing shared links in this space will be deleted.": "Tem certeza de que deseja desativar o compartilhamento público? Todos os links compartilhados existentes neste espaço serão excluídos.",
|
|
||||||
"Public sharing is disabled": "Compartilhamento público está desativado",
|
|
||||||
"Public sharing has been disabled at the workspace level.": "O compartilhamento público foi desativado no nível do espaço de trabalho.",
|
|
||||||
"Public sharing has been disabled for this space.": "O compartilhamento público foi desativado para este espaço.",
|
|
||||||
"Copy page": "Copiar página",
|
"Copy page": "Copiar página",
|
||||||
"Copy page to a different space.": "Copiar página para um espaço diferente.",
|
"Copy page to a different space.": "Copiar página para um espaço diferente.",
|
||||||
"Page copied successfully": "Página copiada com sucesso",
|
"Page copied successfully": "Página copiada com sucesso",
|
||||||
"Page duplicated successfully": "Página duplicada com sucesso",
|
"Page duplicated successfully": "Página duplicada com sucesso",
|
||||||
"Find": "Localizar",
|
"Find": "Encontrar",
|
||||||
"Not found": "Não encontrado",
|
"Not found": "Não encontrado",
|
||||||
"Previous Match (Shift+Enter)": "Correspondência anterior (Shift+Enter)",
|
"Previous Match (Shift+Enter)": "Correspondência anterior (Shift+Enter)",
|
||||||
"Next match (Enter)": "Próxima correspondência (Enter)",
|
"Next match (Enter)": "Próxima correspondência (Enter)",
|
||||||
@@ -504,16 +414,15 @@
|
|||||||
"Replace (Enter)": "Substituir (Enter)",
|
"Replace (Enter)": "Substituir (Enter)",
|
||||||
"Replace all (Ctrl+Alt+Enter)": "Substituir tudo (Ctrl+Alt+Enter)",
|
"Replace all (Ctrl+Alt+Enter)": "Substituir tudo (Ctrl+Alt+Enter)",
|
||||||
"Replace all": "Substituir tudo",
|
"Replace all": "Substituir tudo",
|
||||||
"View all": "Ver tudo",
|
|
||||||
"View all spaces": "Ver todos os espaços",
|
"View all spaces": "Ver todos os espaços",
|
||||||
"Error": "Erro",
|
"Error": "Erro",
|
||||||
"Failed to disable MFA": "Falha ao desativar a MFA",
|
"Failed to disable MFA": "Falha ao desativar a MFA",
|
||||||
"Disable two-factor authentication": "Desativar autenticação de dois fatores",
|
"Disable two-factor authentication": "Desativar autenticação de dois fatores",
|
||||||
"Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Desativar a autenticação de dois fatores tornará sua conta menos segura. Você só precisará de sua senha para entrar.",
|
"Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Desativar a autenticação de dois fatores tornará sua conta menos segura. Você só precisará de sua senha para entrar.",
|
||||||
"Please enter your password to disable two-factor authentication:": "Por favor, insira sua senha para desativar a autenticação de dois fatores:",
|
"Please enter your password to disable two-factor authentication:": "Por favor, insira sua senha para desativar a autenticação de dois fatores:",
|
||||||
"Two-factor authentication has been enabled": "A autenticação de dois fatores foi ativada",
|
"Two-factor authentication has been enabled": "Autenticação de dois fatores foi ativada",
|
||||||
"Two-factor authentication has been disabled": "A autenticação de dois fatores foi desativada",
|
"Two-factor authentication has been disabled": "Autenticação de dois fatores foi desativada",
|
||||||
"2-step verification": "Verificação em 2 etapas",
|
"2-step verification": "Verificação em duas etapas",
|
||||||
"Protect your account with an additional verification layer when signing in.": "Proteja sua conta com uma camada adicional de verificação ao entrar.",
|
"Protect your account with an additional verification layer when signing in.": "Proteja sua conta com uma camada adicional de verificação ao entrar.",
|
||||||
"Two-factor authentication is active on your account.": "Autenticação de dois fatores está ativa na sua conta.",
|
"Two-factor authentication is active on your account.": "Autenticação de dois fatores está ativa na sua conta.",
|
||||||
"Add 2FA method": "Adicionar método de 2FA",
|
"Add 2FA method": "Adicionar método de 2FA",
|
||||||
@@ -521,43 +430,43 @@
|
|||||||
"Disable": "Desativar",
|
"Disable": "Desativar",
|
||||||
"Invalid verification code": "Código de verificação inválido",
|
"Invalid verification code": "Código de verificação inválido",
|
||||||
"New backup codes have been generated": "Novos códigos de backup foram gerados",
|
"New backup codes have been generated": "Novos códigos de backup foram gerados",
|
||||||
"Failed to regenerate backup codes": "Falha ao regenerar os códigos de backup",
|
"Failed to regenerate backup codes": "Falha ao regenerar códigos de backup",
|
||||||
"About backup codes": "Sobre os códigos de backup",
|
"About backup codes": "Sobre códigos de backup",
|
||||||
"Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Códigos de backup podem ser usados para acessar sua conta se perder acesso ao aplicativo autenticador. Cada código só pode ser usado uma vez.",
|
"Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Códigos de backup podem ser usados para acessar sua conta se perder acesso ao aplicativo autenticador. Cada código só pode ser usado uma vez.",
|
||||||
"You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Você pode regenerar novos códigos de backup a qualquer momento. Isso invalidará todos os códigos existentes.",
|
"You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Você pode regenerar novos códigos de backup a qualquer momento. Isso invalidará todos os códigos existentes.",
|
||||||
"Confirm password": "Confirmar senha",
|
"Confirm password": "Confirmar senha",
|
||||||
"Generate new backup codes": "Gerar novos códigos de backup",
|
"Generate new backup codes": "Gerar novos códigos de backup",
|
||||||
"Save your new backup codes": "Salve seus novos códigos de backup",
|
"Save your new backup codes": "Salvar seus novos códigos de backup",
|
||||||
"Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Certifique-se de salvar esses códigos em um local seguro. Seus códigos de backup antigos não são mais válidos.",
|
"Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Certifique-se de salvar esses códigos em um local seguro. Seus códigos de backup antigos não são mais válidos.",
|
||||||
"Your new backup codes": "Seus novos códigos de backup",
|
"Your new backup codes": "Seus novos códigos de backup",
|
||||||
"I've saved my backup codes": "Salvei meus códigos de backup",
|
"I've saved my backup codes": "Eu salvei meus códigos de backup",
|
||||||
"Failed to setup MFA": "Falha ao configurar a MFA",
|
"Failed to setup MFA": "Falha ao configurar a MFA",
|
||||||
"Setup & Verify": "Configurar e verificar",
|
"Setup & Verify": "Configurar & Verificar",
|
||||||
"Add to authenticator": "Adicionar ao autenticador",
|
"Add to authenticator": "Adicionar ao autenticador",
|
||||||
"1. Scan this QR code with your authenticator app": "1. Escaneie este código QR com seu aplicativo autenticador",
|
"1. Scan this QR code with your authenticator app": "1. Escaneie este código QR com seu aplicativo autenticador",
|
||||||
"Can't scan the code?": "Não consegue escanear o código?",
|
"Can't scan the code?": "Não consegue escanear o código?",
|
||||||
"Enter this code manually in your authenticator app:": "Digite este código manualmente em seu aplicativo autenticador:",
|
"Enter this code manually in your authenticator app:": "Digite este código manualmente em seu aplicativo autenticador:",
|
||||||
"2. Enter the 6-digit code from your authenticator": "2. Insira o código de 6 dígitos do seu autenticador",
|
"2. Enter the 6-digit code from your authenticator": "2. Digite o código de 6 dígitos do seu autenticador",
|
||||||
"Verify and enable": "Verificar e ativar",
|
"Verify and enable": "Verificar e ativar",
|
||||||
"Failed to generate QR code. Please try again.": "Falha ao gerar código QR. Por favor, tente novamente.",
|
"Failed to generate QR code. Please try again.": "Falha ao gerar código QR. Por favor, tente novamente.",
|
||||||
"Backup": "Backup",
|
"Backup": "Backup",
|
||||||
"Save codes": "Salvar códigos",
|
"Save codes": "Salvar códigos",
|
||||||
"Save your backup codes": "Salve seus códigos de backup",
|
"Save your backup codes": "Salvar seus códigos de backup",
|
||||||
"These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Esses códigos podem ser usados para acessar sua conta se você perder o acesso ao aplicativo autenticador. Cada código só pode ser usado uma vez.",
|
"These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Esses códigos podem ser usados para acessar sua conta se você perder o acesso ao aplicativo autenticador. Cada código só pode ser usado uma vez.",
|
||||||
"Print": "Imprimir",
|
"Print": "Imprimir",
|
||||||
"Two-factor authentication has been set up. Please log in again.": "A autenticação de dois fatores foi configurada. Por favor, faça login novamente.",
|
"Two-factor authentication has been set up. Please log in again.": "A autenticação de dois fatores foi configurada. Por favor, faça login novamente.",
|
||||||
"Two-Factor authentication required": "Autenticação de dois fatores obrigatória",
|
"Two-Factor authentication required": "Autenticação de dois fatores necessária",
|
||||||
"Your workspace requires two-factor authentication for all users": "Seu workspace exige autenticação de dois fatores para todos os usuários",
|
"Your workspace requires two-factor authentication for all users": "Seu espaço de trabalho requer autenticação de dois fatores para todos os usuários",
|
||||||
"To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Para continuar acessando seu espaço de trabalho, você deve configurar a autenticação de dois fatores. Isso adiciona uma camada extra de segurança à sua conta.",
|
"To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Para continuar acessando seu espaço de trabalho, você deve configurar a autenticação de dois fatores. Isso adiciona uma camada extra de segurança à sua conta.",
|
||||||
"Set up two-factor authentication": "Configurar autenticação de dois fatores",
|
"Set up two-factor authentication": "Configurar autenticação de dois fatores",
|
||||||
"Cancel and logout": "Cancelar e sair",
|
"Cancel and logout": "Cancelar e sair",
|
||||||
"Your workspace requires two-factor authentication. Please set it up to continue.": "Seu espaço de trabalho requer autenticação de dois fatores. Por favor, configure para continuar.",
|
"Your workspace requires two-factor authentication. Please set it up to continue.": "Seu espaço de trabalho requer autenticação de dois fatores. Por favor, configure para continuar.",
|
||||||
"This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Isso adiciona uma camada extra de segurança à sua conta, exigindo um código de verificação de seu aplicativo autenticador.",
|
"This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Isso adiciona uma camada extra de segurança à sua conta, exigindo um código de verificação de seu aplicativo autenticador.",
|
||||||
"Password is required": "A senha é obrigatória",
|
"Password is required": "Senha é necessária",
|
||||||
"Password must be at least 8 characters": "A senha deve ter pelo menos 8 caracteres",
|
"Password must be at least 8 characters": "A senha deve ter pelo menos 8 caracteres",
|
||||||
"Please enter a 6-digit code": "Insira um código de 6 dígitos",
|
"Please enter a 6-digit code": "Por favor, insira um código de 6 dígitos",
|
||||||
"Code must be exactly 6 digits": "O código deve ter exatamente 6 dígitos",
|
"Code must be exactly 6 digits": "O código deve ter exatamente 6 dígitos",
|
||||||
"Enter the 6-digit code found in your authenticator app": "Insira o código de 6 dígitos encontrado no seu aplicativo autenticador",
|
"Enter the 6-digit code found in your authenticator app": "Insira o código de 6 dígitos encontrado em seu aplicativo autenticador",
|
||||||
"Need help authenticating?": "Precisa de ajuda para autenticar?",
|
"Need help authenticating?": "Precisa de ajuda para autenticar?",
|
||||||
"MFA QR Code": "Código QR de MFA",
|
"MFA QR Code": "Código QR de MFA",
|
||||||
"Account created successfully. Please log in to set up two-factor authentication.": "Conta criada com sucesso. Por favor, faça login para configurar a autenticação de dois fatores.",
|
"Account created successfully. Please log in to set up two-factor authentication.": "Conta criada com sucesso. Por favor, faça login para configurar a autenticação de dois fatores.",
|
||||||
@@ -565,487 +474,26 @@
|
|||||||
"Password reset successful. Please log in with your new password to set up two-factor authentication.": "Redefinição de senha bem-sucedida. Por favor, faça login com sua nova senha para configurar a autenticação de dois fatores.",
|
"Password reset successful. Please log in with your new password to set up two-factor authentication.": "Redefinição de senha bem-sucedida. Por favor, faça login com sua nova senha para configurar a autenticação de dois fatores.",
|
||||||
"Password reset was successful. Please log in with your new password.": "Redefinição de senha foi bem-sucedida. Por favor, faça login com sua nova senha.",
|
"Password reset was successful. Please log in with your new password.": "Redefinição de senha foi bem-sucedida. Por favor, faça login com sua nova senha.",
|
||||||
"Two-factor authentication": "Autenticação de dois fatores",
|
"Two-factor authentication": "Autenticação de dois fatores",
|
||||||
"Use authenticator app instead": "Usar aplicativo autenticador em vez disso",
|
"Use authenticator app instead": "Use o aplicativo autenticador em vez disso",
|
||||||
"Verify backup code": "Verificar código de backup",
|
"Verify backup code": "Verificar código de backup",
|
||||||
"Use backup code": "Usar código de backup",
|
"Use backup code": "Usar código de backup",
|
||||||
"Enter one of your backup codes": "Insira um dos seus códigos de backup",
|
"Enter one of your backup codes": "Digite um de seus códigos de backup",
|
||||||
"Backup code": "Código de backup",
|
"Backup code": "Código de backup",
|
||||||
"Enter one of your backup codes. Each backup code can only be used once.": "Digite um de seus códigos de backup. Cada código de backup só pode ser usado uma vez.",
|
"Enter one of your backup codes. Each backup code can only be used once.": "Digite um de seus códigos de backup. Cada código de backup só pode ser usado uma vez.",
|
||||||
"Verify": "Verificar",
|
"Verify": "Verificar",
|
||||||
"Trash": "Lixeira",
|
"Trash": "Lixeira",
|
||||||
"Pages in trash will be permanently deleted after {{count}} days.": "{count, plural, one {A página na lixeira será excluída permanentemente após # dia.} other {As páginas na lixeira serão excluídas permanentemente após # dias.}}",
|
"Pages in trash will be permanently deleted after 30 days.": "Páginas na lixeira serão excluídas permanentemente após 30 dias.",
|
||||||
"Deleted": "Excluído",
|
"Deleted": "Excluído",
|
||||||
"No pages in trash": "Nenhuma página na lixeira",
|
"No pages in trash": "Sem páginas na lixeira",
|
||||||
"Permanently delete page?": "Excluir página permanentemente?",
|
"Permanently delete page?": "Excluir página permanentemente?",
|
||||||
"Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "Tem certeza de que deseja excluir permanentemente '{{title}}'? Esta ação não pode ser desfeita.",
|
"Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "Tem certeza de que deseja excluir permanentemente '{{title}}'? Esta ação não pode ser desfeita.",
|
||||||
"Restore '{{title}}' and its sub-pages?": "Restaurar '{{title}}' e suas subpáginas?",
|
"Restore '{{title}}' and its sub-pages?": "Restaurar '{{title}}' e suas subpáginas?",
|
||||||
"Move to trash": "Mover para a lixeira",
|
"Move to trash": "Mover para a lixeira",
|
||||||
"Move this page to trash?": "Mover esta página para a lixeira?",
|
"Move this page to trash?": "Mover esta página para a lixeira?",
|
||||||
"Restore page": "Restaurar página",
|
"Restore page": "Restaurar página",
|
||||||
"Permanently delete": "Permanently delete",
|
|
||||||
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> moved this page to Trash {{time}}.",
|
|
||||||
"Page moved to trash": "Página movida para a lixeira",
|
"Page moved to trash": "Página movida para a lixeira",
|
||||||
"Page restored successfully": "Página restaurada com sucesso",
|
"Page restored successfully": "Página restaurada com sucesso",
|
||||||
"Deleted by": "Excluído por",
|
"Deleted by": "Excluído por",
|
||||||
"Deleted at": "Excluído em",
|
"Deleted at": "Excluído em",
|
||||||
"Preview": "Visualização",
|
"Preview": "Visualização"
|
||||||
"Subpages": "Subpáginas",
|
|
||||||
"Failed to load subpages": "Falha ao carregar as subpáginas",
|
|
||||||
"No subpages": "Nenhuma subpágina",
|
|
||||||
"Subpages (Child pages)": "Subpáginas (páginas filhas)",
|
|
||||||
"List all subpages of the current page": "Listar todas as subpáginas da página atual",
|
|
||||||
"Attachments": "Anexos",
|
|
||||||
"All spaces": "Todos os espaços",
|
|
||||||
"Unknown": "Desconhecido",
|
|
||||||
"Find a space": "Encontrar um espaço",
|
|
||||||
"Search in all your spaces": "Pesquisar em todos os seus espaços",
|
|
||||||
"Type": "Tipo",
|
|
||||||
"Enterprise": "Enterprise",
|
|
||||||
"Download attachment": "Baixar anexo",
|
|
||||||
"Allowed email domains": "Domínios de e-mail permitidos",
|
|
||||||
"Only users with email addresses from these domains can signup via SSO.": "Somente usuários com endereços de e-mail desses domínios podem se cadastrar via SSO.",
|
|
||||||
"Enter valid domain names separated by comma or space": "Insira nomes de domínio válidos separados por vírgula ou espaço",
|
|
||||||
"Enforce two-factor authentication": "Exigir autenticação de dois fatores",
|
|
||||||
"Once enforced, all members must enable two-factor authentication to access the workspace.": "Uma vez imposto, todos os membros devem habilitar a autenticação de dois fatores para acessar o espaço de trabalho.",
|
|
||||||
"Toggle MFA enforcement": "Alternar exigência de MFA",
|
|
||||||
"Display name": "Nome de exibição",
|
|
||||||
"Allow signup": "Permitir cadastro",
|
|
||||||
"Enabled": "Ativado",
|
|
||||||
"Advanced Settings": "Configurações avançadas",
|
|
||||||
"Enable TLS/SSL": "Ativar TLS/SSL",
|
|
||||||
"Use secure connection to LDAP server": "Usar conexão segura com o servidor LDAP",
|
|
||||||
"Group sync": "Sincronização de grupos",
|
|
||||||
"No SSO providers found.": "Nenhum provedor de SSO encontrado.",
|
|
||||||
"Delete SSO provider": "Excluir provedor de SSO",
|
|
||||||
"Are you sure you want to delete this SSO provider?": "Tem certeza de que deseja excluir este provedor de SSO?",
|
|
||||||
"Action": "Ação",
|
|
||||||
"{{ssoProviderType}} configuration": "Configuração de {{ssoProviderType}}",
|
|
||||||
"Icon": "Ícone",
|
|
||||||
"Upload image": "Enviar imagem",
|
|
||||||
"Remove image": "Remover imagem",
|
|
||||||
"Failed to remove image": "Falha ao remover imagem",
|
|
||||||
"Image exceeds 10MB limit.": "A imagem excede o limite de 10MB.",
|
|
||||||
"Image removed successfully": "Imagem removida com sucesso",
|
|
||||||
"API key": "Chave API",
|
|
||||||
"API keys": "Chaves API",
|
|
||||||
"API management": "Gestão de API",
|
|
||||||
"Custom expiration date": "Data de expiração personalizada",
|
|
||||||
"Enter a descriptive token name": "Insira um nome descritivo para o token",
|
|
||||||
"Expiration": "Expiração",
|
|
||||||
"Expired": "Expirado",
|
|
||||||
"Expires": "Expira",
|
|
||||||
"Last use": "Último uso",
|
|
||||||
"No API keys found": "Nenhuma chave API encontrada",
|
|
||||||
"No expiration": "Sem expiração",
|
|
||||||
"Revoked successfully": "Revogada com sucesso",
|
|
||||||
"Select expiration date": "Selecionar data de expiração",
|
|
||||||
"This action cannot be undone. Any applications using this API key will stop working.": "Esta ação não pode ser desfeita. Qualquer aplicação usando esta chave API deixará de funcionar.",
|
|
||||||
"Update": "Atualizar",
|
|
||||||
"Update {{credential}}": "Atualizar {{credential}}",
|
|
||||||
"Manage API keys for all users in the workspace": "Gerenciar chaves API para todos os usuários no espaço de trabalho",
|
|
||||||
"Restrict API key creation to admins": "Restringir a criação de chave de API aos administradores",
|
|
||||||
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Somente administradores e proprietários podem criar novas chaves de API. As chaves de membros já existentes continuarão funcionando.",
|
|
||||||
"Toggle restrict API keys to admins": "Alternar restrição de chaves de API para administradores",
|
|
||||||
"API key creation is restricted to admins by your workspace administrator.": "A criação de chaves de API foi restringida aos administradores pelo administrador do seu workspace.",
|
|
||||||
"AI settings": "Configurações de IA",
|
|
||||||
"AI search": "Pesquisa IA",
|
|
||||||
"AI Answer": "Resposta de IA",
|
|
||||||
"Ask AI": "Pergunte à IA",
|
|
||||||
"AI is thinking...": "IA está pensando...",
|
|
||||||
"Thinking": "Pensando",
|
|
||||||
"Ask a question...": "Faça uma pergunta...",
|
|
||||||
"AI Answers": "Respostas de IA",
|
|
||||||
"AI-powered search (AI Answers)": "Pesquisa com IA (Respostas de IA)",
|
|
||||||
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "A pesquisa IA usa vetores de incorporação para fornecer capacidades de pesquisa semântica em todo o conteúdo do seu espaço de trabalho.",
|
|
||||||
"Toggle AI search": "Alternar pesquisa de IA",
|
|
||||||
"Generative AI (Ask AI)": "IA generativa (Perguntar à IA)",
|
|
||||||
"Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Habilitar geração de conteúdo com IA no editor. Permite aos usuários gerar, melhorar, traduzir e transformar texto.",
|
|
||||||
"Toggle generative AI": "Alternar IA generativa",
|
|
||||||
"Upgrade your plan": "Faça upgrade do seu plano",
|
|
||||||
"Available with a paid license": "Disponível com uma licença paga",
|
|
||||||
"Upgrade your license tier.": "Faça upgrade do seu nível de licença.",
|
|
||||||
"AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "A IA está disponível apenas na edição empresarial do Docmost. Contate sales@docmost.com.",
|
|
||||||
"AI & MCP": "IA e MCP",
|
|
||||||
"AI": "IA",
|
|
||||||
"MCP": "MCP",
|
|
||||||
"Model Context Protocol (MCP)": "Protocolo de Contexto de Modelo (MCP)",
|
|
||||||
"Enable the MCP server to allow AI assistants and tools to interact with your workspace content.": "Ative o servidor MCP para permitir que assistentes de IA e ferramentas interajam com o conteúdo do seu espaço de trabalho.",
|
|
||||||
"MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "O MCP está disponível apenas na edição empresarial do Docmost. Contate sales@docmost.com.",
|
|
||||||
"MCP Server URL": "URL do servidor MCP",
|
|
||||||
"Use your API key for authentication. You can manage API keys in your account settings.": "Use sua chave de API para autenticação. Você pode gerenciar chaves de API nas configurações da sua conta.",
|
|
||||||
"Supported tools": "Ferramentas compatíveis",
|
|
||||||
"Your workspace has MCP enabled. Use your API key to connect AI assistants.": "Seu espaço de trabalho tem MCP habilitado. Use sua chave de API para conectar assistentes de IA.",
|
|
||||||
"MCP server URL:": "URL do servidor MCP:",
|
|
||||||
"Learn more": "Saiba mais",
|
|
||||||
"Manage API keys for all users in the workspace. View the <anchor>API documentation</anchor> for usage details.": "Gerencie as chaves de API de todos os usuários do workspace. Veja a <anchor>documentação da API</anchor> para detalhes de uso.",
|
|
||||||
"View the <anchor>API documentation</anchor> for usage details.": "Veja a <anchor>documentação da API</anchor> para detalhes de uso.",
|
|
||||||
"View the <anchor>MCP documentation</anchor>.": "Veja a <anchor>documentação MCP</anchor>.",
|
|
||||||
"Sources": "Fontes",
|
|
||||||
"AI Answers not available for attachments": "Respostas de IA não disponíveis para anexos",
|
|
||||||
"No answer available": "Nenhuma resposta disponível",
|
|
||||||
"Background color": "Cor de fundo",
|
|
||||||
"Highlight color": "Cor de destaque",
|
|
||||||
"Remove color": "Remover cor",
|
|
||||||
"Notifications": "Notificações",
|
|
||||||
"No notifications": "Sem notificações",
|
|
||||||
"No unread notifications": "Sem notificações não lidas",
|
|
||||||
"All notifications": "Todas as notificações",
|
|
||||||
"Unread only": "Somente não lidas",
|
|
||||||
"Mark all as read": "Marcar todas como lidas",
|
|
||||||
"Mark as read": "Marcar como lida",
|
|
||||||
"More options": "Mais opções",
|
|
||||||
"<bold>{{name}}</bold> mentioned you in a comment": "<bold>{{name}}</bold> mencionou você em um comentário",
|
|
||||||
"<bold>{{name}}</bold> commented on a page": "<bold>{{name}}</bold> comentou em uma página",
|
|
||||||
"<bold>{{name}}</bold> resolved a comment": "<bold>{{name}}</bold> resolveu um comentário",
|
|
||||||
"<bold>{{name}}</bold> mentioned you on a page": "<bold>{{name}}</bold> mencionou você em uma página",
|
|
||||||
"<bold>{{name}}</bold> gave you edit access to a page": "<bold>{{name}}</bold> concedeu a você acesso de edição a uma página",
|
|
||||||
"<bold>{{name}}</bold> gave you view access to a page": "<bold>{{name}}</bold> concedeu a você acesso de visualização a uma página",
|
|
||||||
"<bold>{{name}}</bold> updated a page": "<bold>{{name}}</bold> atualizou uma página",
|
|
||||||
"Watch page": "Acompanhar página",
|
|
||||||
"Stop watching": "Parar de acompanhar",
|
|
||||||
"Watch space": "Acompanhar espaço",
|
|
||||||
"Stop watching space": "Parar de acompanhar espaço",
|
|
||||||
"Email notifications": "Notificações por e-mail",
|
|
||||||
"Page updates": "Atualizações da página",
|
|
||||||
"Get notified when pages you watch are updated.": "Receba notificações quando as páginas que você observa forem atualizadas.",
|
|
||||||
"Page mentions": "Menções na página",
|
|
||||||
"Get notified when someone mentions you on a page.": "Receba notificações quando alguém mencionar você em uma página.",
|
|
||||||
"Comment mentions": "Menções em comentários",
|
|
||||||
"Get notified when someone mentions you in a comment.": "Receba notificações quando alguém mencionar você em um comentário.",
|
|
||||||
"New comments": "Novos comentários",
|
|
||||||
"Get notified about new comments on threads you participate in.": "Receba notificações sobre novos comentários nas discussões em que você participa.",
|
|
||||||
"Resolved comments": "Comentários resolvidos",
|
|
||||||
"Get notified when your comment is resolved.": "Receba notificações quando seu comentário for resolvido.",
|
|
||||||
"You are now watching this page": "Agora você está observando esta página",
|
|
||||||
"You are no longer watching this page": "Você não está mais observando esta página",
|
|
||||||
"You are now watching this space": "Agora você está acompanhando este espaço",
|
|
||||||
"You are no longer watching this space": "Você não está mais acompanhando este espaço",
|
|
||||||
"Direct": "Direto",
|
|
||||||
"Updates": "Atualizações",
|
|
||||||
"Today": "Hoje",
|
|
||||||
"Yesterday": "Ontem",
|
|
||||||
"This week": "Esta semana",
|
|
||||||
"Older": "Mais antigo",
|
|
||||||
"Restricted page": "Página restrita",
|
|
||||||
"Restricted pages cannot be shared publicly.": "Páginas restritas não podem ser compartilhadas publicamente.",
|
|
||||||
"Restricted by parent": "Restrita pela página pai",
|
|
||||||
"Restricted": "Restrito",
|
|
||||||
"Open": "Aberto",
|
|
||||||
"Inherits restrictions from ancestor page": "Herda restrições da página ancestral",
|
|
||||||
"Only people listed below can access this page": "Apenas as pessoas listadas abaixo podem acessar esta página",
|
|
||||||
"Everyone in this space can access": "Todos neste espaço podem acessar",
|
|
||||||
"No additional restrictions on this page": "Sem restrições adicionais nesta página",
|
|
||||||
"Only specific people can access": "Apenas pessoas específicas podem acessar",
|
|
||||||
"Use only inherited restrictions": "Usar apenas restrições herdadas",
|
|
||||||
"Add restrictions on top of inherited": "Adicionar restrições além das herdadas",
|
|
||||||
"Inherited restriction": "Restrição herdada",
|
|
||||||
"Access limited by": "Acesso limitado por",
|
|
||||||
"Restrict access to control who can view and edit this page": "Restringir o acesso para controlar quem pode visualizar e editar esta página",
|
|
||||||
"Add additional restrictions specific to this page": "Adicionar restrições adicionais específicas para esta página",
|
|
||||||
"Access": "Acesso",
|
|
||||||
"People with access": "Pessoas com acesso",
|
|
||||||
"Remove all": "Remover tudo",
|
|
||||||
"Remove access": "Remover acesso",
|
|
||||||
"Remove all access": "Remover todo o acesso",
|
|
||||||
"Are you sure you want to remove this member's access to the page?": "Tem certeza de que deseja remover o acesso deste membro à página?",
|
|
||||||
"Are you sure you want to remove all specific access? This will make the page open to everyone in the space.": "Tem certeza de que deseja remover todo o acesso específico? Isso fará com que a página fique aberta para todos no espaço.",
|
|
||||||
"Trash retention": "Retenção da lixeira",
|
|
||||||
"Pages in trash will be permanently deleted after this period.": "As páginas na lixeira serão excluídas permanentemente após este período.",
|
|
||||||
"Trash retention updated": "Retenção da lixeira atualizada",
|
|
||||||
"Failed to update trash retention": "Falha ao atualizar a retenção da lixeira",
|
|
||||||
"Removed page restriction": "Restrição de página removida",
|
|
||||||
"Added page permission": "Permissão de página adicionada",
|
|
||||||
"Removed page permission": "Permissão de página removida",
|
|
||||||
"day": "dia",
|
|
||||||
"days": "dias",
|
|
||||||
"week": "semana",
|
|
||||||
"weeks": "semanas",
|
|
||||||
"month": "mês",
|
|
||||||
"months": "meses",
|
|
||||||
"year": "ano",
|
|
||||||
"years": "anos",
|
|
||||||
"Period": "Período",
|
|
||||||
"Fixed date": "Data fixa",
|
|
||||||
"Indefinitely": "Indefinidamente",
|
|
||||||
"Days": "Dias",
|
|
||||||
"Weeks": "Semanas",
|
|
||||||
"Months": "Meses",
|
|
||||||
"Years": "Anos",
|
|
||||||
"Pick a date": "Escolha uma data",
|
|
||||||
"Maximum is {{max}} {{unit}} for this unit": "O máximo é {{max}} {{unit}} para esta unidade",
|
|
||||||
"Never expires. Verifiers can re-verify at any time.": "Nunca expira. Os verificadores podem verificar novamente a qualquer momento.",
|
|
||||||
"Verified": "Verificado",
|
|
||||||
"Review needed": "Revisão necessária",
|
|
||||||
"Verification expired": "A verificação expirou",
|
|
||||||
"Draft": "Rascunho",
|
|
||||||
"In Approval": "Em aprovação",
|
|
||||||
"In approval": "Em aprovação",
|
|
||||||
"Approved": "Aprovado",
|
|
||||||
"Obsolete": "Obsoleto",
|
|
||||||
"Expiring": "Expirando",
|
|
||||||
"Set up verification": "Configurar verificação",
|
|
||||||
"Verify page": "Verificar página",
|
|
||||||
"Page verification": "Verificação da página",
|
|
||||||
"Add verification": "Adicionar verificação",
|
|
||||||
"Edit verification": "Editar verificação",
|
|
||||||
"Search by title": "Pesquisar por título",
|
|
||||||
"Choose how this page should stay accurate.": "Escolha como esta página deve permanecer precisa.",
|
|
||||||
"Recurring verification": "Verificação recorrente",
|
|
||||||
"Verifiers re-confirm this page on a schedule.": "Os verificadores confirmam novamente esta página em uma programação definida.",
|
|
||||||
"Re-verify on a schedule (e.g every 30 days )": "Verificar novamente em uma programação definida (ex.: a cada 30 dias)",
|
|
||||||
"Page stays editable at all times": "A página permanece editável o tempo todo",
|
|
||||||
"Best for runbooks, FAQs, living documentation": "Ideal para runbooks, FAQs e documentação viva",
|
|
||||||
"Approval workflow": "Fluxo de aprovação",
|
|
||||||
"Formal document lifecycle with named approvers.": "Ciclo de vida formal do documento com aprovadores nomeados.",
|
|
||||||
"Draft → In approval → Approved → Obsolete": "Rascunho → Em aprovação → Aprovado → Obsoleto",
|
|
||||||
"Locked once approved, with full history": "Bloqueado após a aprovação, com histórico completo",
|
|
||||||
"Designed for ISO 9001, ISO 13485, and FDA": "Desenvolvido para ISO 9001, ISO 13485 e FDA",
|
|
||||||
"Best for SOPs and controlled documents": "Ideal para POPs e documentos controlados",
|
|
||||||
"Back": "Voltar",
|
|
||||||
"Quality management": "Gestão da qualidade",
|
|
||||||
"Recurring": "Recorrente",
|
|
||||||
"Pages move through draft, approval, and approved stages.": "As páginas passam pelos estágios de rascunho, aprovação e aprovado.",
|
|
||||||
"Verifiers": "Verificadores",
|
|
||||||
"Add verifier": "Adicionar verificador",
|
|
||||||
"I've reviewed this page for accuracy": "Revisei esta página quanto à precisão",
|
|
||||||
"Set up": "Configurar",
|
|
||||||
"Remove verification": "Remover verificação",
|
|
||||||
"Are you sure you want to remove verification from this page?": "Tem certeza de que deseja remover a verificação desta página?",
|
|
||||||
"Assigned verifiers must periodically re-verify this page.": "Os verificadores atribuídos devem verificar novamente esta página periodicamente.",
|
|
||||||
"Last verified by {{name}} {{time}} (expired)": "Verificado pela última vez por {{name}} {{time}} (expirado)",
|
|
||||||
"The fixed expiration date has passed.": "A data fixa de expiração já passou.",
|
|
||||||
"Verified by {{name}} {{time}}": "Verificado por {{name}} {{time}}",
|
|
||||||
"Expires {{date}}": "Expira em {{date}}",
|
|
||||||
"Expired {{date}}": "Expirou em {{date}}",
|
|
||||||
"Mark as obsolete": "Marcar como obsoleto",
|
|
||||||
"Mark obsolete": "Marcar como obsoleto",
|
|
||||||
"Returned by {{name}} {{time}}": "Devolvido por {{name}} {{time}}",
|
|
||||||
"No approval has been requested yet.": "Nenhuma aprovação foi solicitada ainda.",
|
|
||||||
"Submitted by {{name}} {{time}}": "Enviado por {{name}} {{time}}",
|
|
||||||
"Someone": "Alguém",
|
|
||||||
"Approved by {{name}} {{time}}": "Aprovado por {{name}} {{time}}",
|
|
||||||
"This document has been marked as obsolete.": "Este documento foi marcado como obsoleto.",
|
|
||||||
"Rejection comment": "Comentário de rejeição",
|
|
||||||
"Reason for returning this document...": "Motivo para devolver este documento...",
|
|
||||||
"Confirm rejection": "Confirmar rejeição",
|
|
||||||
"Submit for approval": "Enviar para aprovação",
|
|
||||||
"Reject": "Rejeitar",
|
|
||||||
"Approve": "Aprovar",
|
|
||||||
"Re-submit for approval": "Reenviar para aprovação",
|
|
||||||
"Verified until": "Verificado até",
|
|
||||||
"QMS": "SGQ",
|
|
||||||
"Verified pages": "Páginas verificadas",
|
|
||||||
"Search pages...": "Pesquisar páginas...",
|
|
||||||
"Filter by space": "Filtrar por espaço",
|
|
||||||
"Filter by type": "Filtrar por tipo",
|
|
||||||
"<bold>{{name}}</bold> verified a page": "<bold>{{name}}</bold> verificou uma página",
|
|
||||||
"<bold>{{name}}</bold> submitted a page for your approval": "<bold>{{name}}</bold> enviou uma página para sua aprovação",
|
|
||||||
"<bold>{{name}}</bold> returned a page for revision": "<bold>{{name}}</bold> devolveu uma página para revisão",
|
|
||||||
"Page verification expires soon": "A verificação da página expirará em breve",
|
|
||||||
"Page verification has expired": "A verificação da página expirou",
|
|
||||||
"Verifying your email": "Verificando seu e-mail",
|
|
||||||
"Please wait...": "Por favor, aguarde...",
|
|
||||||
"Verification failed. The link may have expired.": "Falha na verificação. O link pode ter expirado.",
|
|
||||||
"Check your email": "Verifique seu e-mail",
|
|
||||||
"We sent a verification link to {{email}}.": "Enviamos um link de verificação para {{email}}.",
|
|
||||||
"We sent a verification link to your email.": "Enviamos um link de verificação para seu e-mail.",
|
|
||||||
"Click the link to verify your email and access your workspace.": "Clique no link para verificar seu e-mail e acessar seu workspace.",
|
|
||||||
"Resend verification email": "Reenviar e-mail de verificação",
|
|
||||||
"Verification email sent. Please check your inbox.": "E-mail de verificação enviado. Por favor, verifique sua caixa de entrada.",
|
|
||||||
"Failed to resend verification email. Please try again.": "Falha ao reenviar o e-mail de verificação. Por favor, tente novamente.",
|
|
||||||
"We've sent you an email with your associated workspaces.": "Enviamos um e-mail para você com seus workspaces associados.",
|
|
||||||
"Load more": "Carregar mais",
|
|
||||||
"Log out of all devices": "Sair de todos os dispositivos",
|
|
||||||
"Log out of all sessions except this device": "Sair de todas as sessões, exceto deste dispositivo",
|
|
||||||
"This Device": "Este dispositivo",
|
|
||||||
"Unknown device": "Dispositivo desconhecido",
|
|
||||||
"No active sessions": "Nenhuma sessão ativa",
|
|
||||||
"Session revoked": "Sessão revogada",
|
|
||||||
"All other sessions revoked": "Todas as outras sessões foram revogadas",
|
|
||||||
"Last used": "Último uso",
|
|
||||||
"Created": "Criado",
|
|
||||||
"Rename": "Renomear",
|
|
||||||
"Publish": "Publicar",
|
|
||||||
"Security": "Segurança",
|
|
||||||
"Enforce SSO": "Exigir SSO",
|
|
||||||
"Once enforced, members will not be able to login with email and password.": "Depois de exigido, os membros não poderão fazer login com e-mail e senha.",
|
|
||||||
"AI-generated content may not be accurate.": "O conteúdo gerado por IA pode não ser preciso.",
|
|
||||||
"AI Chat": "Chat com IA",
|
|
||||||
"Analyze for insights": "Analisar para obter insights",
|
|
||||||
"Ask anything...": "Pergunte qualquer coisa...",
|
|
||||||
"Chat history": "Histórico de chats",
|
|
||||||
"Chat name": "Nome do chat",
|
|
||||||
"Close": "Fechar",
|
|
||||||
"Docmost AI": "Docmost AI",
|
|
||||||
"Failed to load chat. An error occurred.": "Falha ao carregar o chat. Ocorreu um erro.",
|
|
||||||
"Failed to render this message.": "Falha ao renderizar esta mensagem.",
|
|
||||||
"How can I help you today?": "Como posso ajudar você hoje?",
|
|
||||||
"New chat": "Novo chat",
|
|
||||||
"No chat history": "Nenhum histórico de chat",
|
|
||||||
"No chats found": "Nenhum chat encontrado",
|
|
||||||
"No conversations yet": "Ainda não há conversas",
|
|
||||||
"Open full page": "Abrir página inteira",
|
|
||||||
"Previous 7 days": "Últimos 7 dias",
|
|
||||||
"Previous 30 days": "Últimos 30 dias",
|
|
||||||
"Search chats...": "Pesquisar chats...",
|
|
||||||
"Search chats": "Pesquisar chats",
|
|
||||||
"Ask anything... Use @ to mention pages": "Pergunte qualquer coisa... Use @ para mencionar páginas",
|
|
||||||
"Ask anything or search your workspace": "Ask anything or search your workspace",
|
|
||||||
"Welcome to {{name}}": "Welcome to {{name}}",
|
|
||||||
"Add files": "Add files",
|
|
||||||
"Mention a page": "Mention a page",
|
|
||||||
"Start a new chat to see it here.": "Inicie um novo chat para vê-lo aqui.",
|
|
||||||
"Summarize this page": "Resumir esta página",
|
|
||||||
"Toggle AI Chat": "Alternar chat com IA",
|
|
||||||
"Translate this page": "Traduzir esta página",
|
|
||||||
"Try a different search term.": "Tente um termo de pesquisa diferente.",
|
|
||||||
"Try again": "Tentar novamente",
|
|
||||||
"Untitled chat": "Chat sem título",
|
|
||||||
"What can I help you with?": "Com o que posso ajudar você?",
|
|
||||||
"Are you sure you want to revoke this {{credential}}": "Tem certeza de que deseja revogar esta {{credential}}",
|
|
||||||
"Automatically provision users and groups from your identity provider via SCIM.": "Provisione automaticamente usuários e grupos do seu provedor de identidade via SCIM.",
|
|
||||||
"Configure your identity provider with this URL to provision users and groups.": "Configure seu provedor de identidade com esta URL para provisionar usuários e grupos.",
|
|
||||||
"Create {{credential}}": "Criar {{credential}}",
|
|
||||||
"{{credential}} created": "{{credential}} criada",
|
|
||||||
"{{credential}} created successfully": "{{credential}} criada com sucesso",
|
|
||||||
"Created by": "Criado por",
|
|
||||||
"Custom": "Personalizado",
|
|
||||||
"Enable SCIM": "Ativar SCIM",
|
|
||||||
"Enter a descriptive name": "Insira um nome descritivo",
|
|
||||||
"I've saved my {{credential}}": "Salvei minha {{credential}}",
|
|
||||||
"Important": "Importante",
|
|
||||||
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Copie sua {{credential}} agora. Você não poderá vê-la novamente!",
|
|
||||||
"Never": "Nunca",
|
|
||||||
"Revoke {{credential}}": "Revogar {{credential}}",
|
|
||||||
"SCIM endpoint URL": "URL do endpoint SCIM",
|
|
||||||
"SCIM provisioning": "Provisionamento SCIM",
|
|
||||||
"SCIM takes precedence over SSO group sync while enabled.": "O SCIM tem precedência sobre a sincronização de grupos por SSO enquanto estiver ativado.",
|
|
||||||
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "Você atingiu o máximo de {{max}} tokens SCIM. Exclua um token existente para criar um novo.",
|
|
||||||
"SCIM token": "Token SCIM",
|
|
||||||
"SCIM tokens": "Tokens SCIM",
|
|
||||||
"This action cannot be undone. Your identity provider will stop syncing immediately.": "Esta ação não pode ser desfeita. Seu provedor de identidade deixará de sincronizar imediatamente.",
|
|
||||||
"Toggle SCIM provisioning": "Alternar provisionamento SCIM",
|
|
||||||
"Token": "Token",
|
|
||||||
"Page menu": "Menu da página",
|
|
||||||
"Expand": "Expandir",
|
|
||||||
"Collapse": "Recolher",
|
|
||||||
"Comment menu": "Menu de comentários",
|
|
||||||
"Group menu": "Menu do grupo",
|
|
||||||
"Show hidden breadcrumbs": "Mostrar breadcrumbs ocultos",
|
|
||||||
"Breadcrumbs": "Trilhas de navegação",
|
|
||||||
"Page actions": "Ações da página",
|
|
||||||
"Pick emoji": "Escolher emoji",
|
|
||||||
"Template menu": "Menu do modelo",
|
|
||||||
"Use": "Use",
|
|
||||||
"Use template": "Use template",
|
|
||||||
"Preview template: {{title}}": "Preview template: {{title}}",
|
|
||||||
"Use a template": "Use a template",
|
|
||||||
"Search templates...": "Search templates...",
|
|
||||||
"Search spaces...": "Search spaces...",
|
|
||||||
"No templates found": "No templates found",
|
|
||||||
"No spaces found": "No spaces found",
|
|
||||||
"Browse all templates": "Browse all templates",
|
|
||||||
"This space": "This space",
|
|
||||||
"All templates": "All templates",
|
|
||||||
"Global": "Global",
|
|
||||||
"New template": "New template",
|
|
||||||
"Edit template": "Edit template",
|
|
||||||
"Are you sure you want to delete this template?": "Are you sure you want to delete this template?",
|
|
||||||
"Template scope updated": "Template scope updated",
|
|
||||||
"Choose which space this template belongs to": "Choose which space this template belongs to",
|
|
||||||
"Scope": "Scope",
|
|
||||||
"Select scope": "Select scope",
|
|
||||||
"Title": "Title",
|
|
||||||
"Saving...": "Saving...",
|
|
||||||
"Saved": "Saved",
|
|
||||||
"Save failed. Retry": "Save failed. Retry",
|
|
||||||
"By {{name}}": "By {{name}}",
|
|
||||||
"Updated {{time}}": "Updated {{time}}",
|
|
||||||
"Choose destination": "Choose destination",
|
|
||||||
"Search pages and spaces...": "Search pages and spaces...",
|
|
||||||
"No results found": "No results found",
|
|
||||||
"You don't have permission to create pages here": "You don't have permission to create pages here",
|
|
||||||
"Chat menu": "Menu do chat",
|
|
||||||
"API key menu": "Menu da chave de API",
|
|
||||||
"Jump to comment selection": "Ir para a seleção de comentários",
|
|
||||||
"Slash commands": "Comandos de barra",
|
|
||||||
"Mention suggestions": "Sugestões de menção",
|
|
||||||
"Link suggestions": "Sugestões de links",
|
|
||||||
"Diagram editor": "Editor de diagramas",
|
|
||||||
"Add comment": "Adicionar comentário",
|
|
||||||
"Find and replace": "Localizar e substituir",
|
|
||||||
"Main navigation": "Navegação principal",
|
|
||||||
"Space navigation": "Navegação do espaço",
|
|
||||||
"Settings navigation": "Navegação de configurações",
|
|
||||||
"AI navigation": "Navegação de IA",
|
|
||||||
"Breadcrumb": "Trilha de navegação",
|
|
||||||
"Synced block": "Synced block",
|
|
||||||
"Create a block that stays in sync across pages.": "Create a block that stays in sync across pages.",
|
|
||||||
"Editing original": "Editing original",
|
|
||||||
"Copy synced block": "Copy synced block",
|
|
||||||
"Unsync": "Unsync",
|
|
||||||
"Delete synced block": "Delete synced block",
|
|
||||||
"Synced to {{count}} other page_one": "Synced to {{count}} other page",
|
|
||||||
"Synced to {{count}} other page_other": "Synced to {{count}} other pages",
|
|
||||||
"ORIGINAL": "ORIGINAL",
|
|
||||||
"THIS PAGE": "THIS PAGE",
|
|
||||||
"No pages": "No pages",
|
|
||||||
"The original synced block no longer exists": "The original synced block no longer exists",
|
|
||||||
"You don't have access to this synced block": "You don't have access to this synced block",
|
|
||||||
"Failed to load this synced block": "Failed to load this synced block",
|
|
||||||
"Fixed editor toolbar": "Fixed editor toolbar",
|
|
||||||
"Show a formatting toolbar above the editor with quick access to common actions.": "Show a formatting toolbar above the editor with quick access to common actions.",
|
|
||||||
"Toggle fixed editor toolbar": "Toggle fixed editor toolbar",
|
|
||||||
"Normal text": "Normal text",
|
|
||||||
"More inline formatting": "More inline formatting",
|
|
||||||
"Subscript": "Subscript",
|
|
||||||
"Superscript": "Superscript",
|
|
||||||
"Inline code": "Inline code",
|
|
||||||
"Insert media": "Insert media",
|
|
||||||
"Mention": "Mention",
|
|
||||||
"Emoji": "Emoji",
|
|
||||||
"Columns": "Columns",
|
|
||||||
"More inserts": "More inserts",
|
|
||||||
"Embeds": "Embeds",
|
|
||||||
"Diagrams": "Diagrams",
|
|
||||||
"Advanced": "Advanced",
|
|
||||||
"Utility": "Utility",
|
|
||||||
"Decrease indent": "Decrease indent",
|
|
||||||
"Increase indent": "Increase indent",
|
|
||||||
"Clear formatting": "Clear formatting",
|
|
||||||
"Code block": "Code block",
|
|
||||||
"Experimental": "Experimental",
|
|
||||||
"Strikethrough": "Strikethrough",
|
|
||||||
"Undo": "Undo",
|
|
||||||
"Redo": "Redo",
|
|
||||||
"Backlinks": "Backlinks",
|
|
||||||
"Last updated by": "Last updated by",
|
|
||||||
"Last updated": "Last updated",
|
|
||||||
"Stats": "Stats",
|
|
||||||
"Word count": "Word count",
|
|
||||||
"Characters": "Characters",
|
|
||||||
"Incoming links": "Incoming links",
|
|
||||||
"Outgoing links": "Outgoing links",
|
|
||||||
"Incoming links ({{count}})": "Incoming links ({{count}})",
|
|
||||||
"Outgoing links ({{count}})": "Outgoing links ({{count}})",
|
|
||||||
"No pages link here yet.": "No pages link here yet.",
|
|
||||||
"This page doesn't link to other pages yet.": "This page doesn't link to other pages yet.",
|
|
||||||
"Verified until {{date}}": "Verified until {{date}}",
|
|
||||||
"Labels": "Labels",
|
|
||||||
"Add label": "Add label",
|
|
||||||
"No labels yet": "No labels yet",
|
|
||||||
"Already added": "Already added",
|
|
||||||
"Invalid label name": "Invalid label name",
|
|
||||||
"No matches": "No matches",
|
|
||||||
"Search or create…": "Search or create…",
|
|
||||||
"Remove label {{name}}": "Remove label {{name}}",
|
|
||||||
"Failed to add label": "Failed to add label",
|
|
||||||
"Failed to remove label": "Failed to remove label",
|
|
||||||
"No pages with this label": "No pages with this label",
|
|
||||||
"Pages tagged with this label will appear here.": "Pages tagged with this label will appear here.",
|
|
||||||
"No pages match your search.": "No pages match your search.",
|
|
||||||
"Updated {{date}}": "Updated {{date}}",
|
|
||||||
"Cell actions": "Cell actions",
|
|
||||||
"Column actions": "Column actions",
|
|
||||||
"Row actions": "Row actions"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,10 @@
|
|||||||
"Add members": "Добавить участников",
|
"Add members": "Добавить участников",
|
||||||
"Add to groups": "Добавить в группы",
|
"Add to groups": "Добавить в группы",
|
||||||
"Add space members": "Добавить участников пространства",
|
"Add space members": "Добавить участников пространства",
|
||||||
"Add to favorites": "Добавить в избранное",
|
|
||||||
"Admin": "Администратор",
|
"Admin": "Администратор",
|
||||||
"Are you sure you want to delete this group? Members will lose access to resources this group has access to.": "Вы уверены, что хотите удалить эту группу? Участники потеряют доступ к материалам, к которым у этой группы есть доступ.",
|
"Are you sure you want to delete this group? Members will lose access to resources this group has access to.": "Вы уверены, что хотите удалить эту группу? Участники потеряют доступ к материалам, к которым у этой группы есть доступ.",
|
||||||
"Are you sure you want to delete this page?": "Вы уверены, что хотите удалить эту страницу?",
|
"Are you sure you want to delete this page?": "Вы уверены, что хотите удалить эту страницу?",
|
||||||
"Are you sure you want to remove this user from the group? The user will lose access to resources this group has access to.": "Вы уверены, что хотите удалить этого пользователя из группы? Пользователь потеряет доступ к материалам, к которым есть доступ у этой группы.",
|
"Are you sure you want to remove this user from the group? The user will lose access to resources this group has access to.": "Вы уверены, что хотите удалить этого пользователя из группы? Пользователь потеряет доступ к материалам, к которым у этой группы есть доступ.",
|
||||||
"Are you sure you want to remove this user from the space? The user will lose all access to this space.": "Вы уверены, что хотите удалить этого пользователя из пространства? Пользователь потеряет весь доступ к этому пространству.",
|
"Are you sure you want to remove this user from the space? The user will lose all access to this space.": "Вы уверены, что хотите удалить этого пользователя из пространства? Пользователь потеряет весь доступ к этому пространству.",
|
||||||
"Are you sure you want to restore this version? Any changes not versioned will be lost.": "Вы уверены, что хотите восстановить эту версию? Все не зафиксированные изменения будут потеряны.",
|
"Are you sure you want to restore this version? Any changes not versioned will be lost.": "Вы уверены, что хотите восстановить эту версию? Все не зафиксированные изменения будут потеряны.",
|
||||||
"Can become members of groups and spaces in workspace": "Могут становиться участниками групп и пространств в рабочей области",
|
"Can become members of groups and spaces in workspace": "Могут становиться участниками групп и пространств в рабочей области",
|
||||||
@@ -30,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Выберите предпочитаемый язык интерфейса.",
|
"Choose your preferred interface language.": "Выберите предпочитаемый язык интерфейса.",
|
||||||
"Choose your preferred page width.": "Выберите предпочитаемую ширину страницы.",
|
"Choose your preferred page width.": "Выберите предпочитаемую ширину страницы.",
|
||||||
"Confirm": "Подтвердить",
|
"Confirm": "Подтвердить",
|
||||||
"Copy as Markdown": "Копировать как Markdown",
|
|
||||||
"Copy link": "Копировать ссылку",
|
"Copy link": "Копировать ссылку",
|
||||||
"Create": "Создать",
|
"Create": "Создать",
|
||||||
"Create group": "Создать группу",
|
"Create group": "Создать группу",
|
||||||
@@ -47,20 +45,19 @@
|
|||||||
"Details": "Подробности",
|
"Details": "Подробности",
|
||||||
"e.g ACME": "например, ACME",
|
"e.g ACME": "например, ACME",
|
||||||
"e.g ACME Inc": "например, ACME Inc",
|
"e.g ACME Inc": "например, ACME Inc",
|
||||||
"e.g Developers": "например, Developers",
|
"e.g Developers": "например, Разработчики",
|
||||||
"e.g Group for developers": "например, Группа для разработчиков",
|
"e.g Group for developers": "например, Группа для разработчиков",
|
||||||
"e.g product": "например, product",
|
"e.g product": "например, продукт",
|
||||||
"e.g Product Team": "например, Команда продукта",
|
"e.g Product Team": "например, Продуктовая команда",
|
||||||
"e.g Sales": "например, Sales",
|
"e.g Sales": "например, Продажи",
|
||||||
"e.g Space for product team": "например, Пространство для команды продукта",
|
"e.g Space for product team": "например, Пространство для продуктовой команды",
|
||||||
"e.g Space for sales team to collaborate": "например, Пространство для совместной работы команды продаж",
|
"e.g Space for sales team to collaborate": "например, Пространство для совместной работы команды продаж",
|
||||||
"Edit": "Редактировать",
|
"Edit": "Редактировать",
|
||||||
"Read": "Чтение",
|
|
||||||
"Edit group": "Редактировать группу",
|
"Edit group": "Редактировать группу",
|
||||||
"Email": "Электронная почта",
|
"Email": "Электронная почта",
|
||||||
"Enter a strong password": "Введите надёжный пароль",
|
"Enter a strong password": "Введите надёжный пароль",
|
||||||
"Enter valid email addresses separated by comma or space max_50": "Введите действительные адреса электронной почты, разделенные запятой или пробелом [макс: 50]",
|
"Enter valid email addresses separated by comma or space max_50": "Введите действительные адреса электронной почты, разделенные запятой или пробелом [макс: 50]",
|
||||||
"enter valid emails addresses": "введите корректные адреса электронной почты",
|
"enter valid emails addresses": "введите действительные адреса электронной почты",
|
||||||
"Enter your current password": "Введите ваш текущий пароль",
|
"Enter your current password": "Введите ваш текущий пароль",
|
||||||
"enter your full name": "введите ваше полное имя",
|
"enter your full name": "введите ваше полное имя",
|
||||||
"Enter your new password": "Введите ваш новый пароль",
|
"Enter your new password": "Введите ваш новый пароль",
|
||||||
@@ -71,14 +68,10 @@
|
|||||||
"Export": "Экспорт",
|
"Export": "Экспорт",
|
||||||
"Failed to create page": "Не удалось создать страницу",
|
"Failed to create page": "Не удалось создать страницу",
|
||||||
"Failed to delete page": "Не удалось удалить страницу",
|
"Failed to delete page": "Не удалось удалить страницу",
|
||||||
"Failed to restore page": "Failed to restore page",
|
|
||||||
"Failed to fetch recent pages": "Не удалось получить недавние страницы",
|
"Failed to fetch recent pages": "Не удалось получить недавние страницы",
|
||||||
"Failed to import pages": "Не удалось импортировать страницы",
|
"Failed to import pages": "Не удалось импортировать страницы",
|
||||||
"Failed to load page. An error occurred.": "Не удалось загрузить страницу. Произошла ошибка.",
|
"Failed to load page. An error occurred.": "Не удалось загрузить страницу. Произошла ошибка.",
|
||||||
"Failed to update data": "Не удалось обновить данные",
|
"Failed to update data": "Не удалось обновить данные",
|
||||||
"Favorite spaces": "Избранные пространства",
|
|
||||||
"Favorite spaces appear here": "Здесь отображаются избранные пространства",
|
|
||||||
"Favorites": "Избранное",
|
|
||||||
"Full access": "Полный доступ",
|
"Full access": "Полный доступ",
|
||||||
"Full page width": "Ширина на всю страницу",
|
"Full page width": "Ширина на всю страницу",
|
||||||
"Full width": "Во всю ширину",
|
"Full width": "Во всю ширину",
|
||||||
@@ -92,12 +85,11 @@
|
|||||||
"Import pages": "Импорт страниц",
|
"Import pages": "Импорт страниц",
|
||||||
"Import pages & space settings": "Импорт страниц и настройки пространства",
|
"Import pages & space settings": "Импорт страниц и настройки пространства",
|
||||||
"Importing pages": "Импортирование страниц",
|
"Importing pages": "Импортирование страниц",
|
||||||
"invalid invitation link": "недействительная ссылка-приглашение",
|
"invalid invitation link": "ссылка на приглашение недействительна",
|
||||||
"Invitation signup": "Регистрация по приглашению",
|
"Invitation signup": "Регистрация по приглашению",
|
||||||
"Invite by email": "Пригласить по электронной почте",
|
"Invite by email": "Пригласить по электронной почте",
|
||||||
"Invite members": "Пригласить участников",
|
"Invite members": "Пригласить участников",
|
||||||
"Invite new members": "Пригласить новых участников",
|
"Invite new members": "Пригласить новых участников",
|
||||||
"Invite People": "Пригласить людей",
|
|
||||||
"Invited members who are yet to accept their invitation will appear here.": "Приглашённые участники, которые ещё не приняли приглашение, появятся здесь.",
|
"Invited members who are yet to accept their invitation will appear here.": "Приглашённые участники, которые ещё не приняли приглашение, появятся здесь.",
|
||||||
"Invited members will be granted access to spaces the groups can access": "Приглашённые участники получат доступ к пространствам, доступ к которым есть у группы",
|
"Invited members will be granted access to spaces the groups can access": "Приглашённые участники получат доступ к пространствам, доступ к которым есть у группы",
|
||||||
"Join the workspace": "Присоединиться к рабочей области",
|
"Join the workspace": "Присоединиться к рабочей области",
|
||||||
@@ -122,7 +114,6 @@
|
|||||||
"No group found": "Группа не найдена",
|
"No group found": "Группа не найдена",
|
||||||
"No page history saved yet.": "История страниц ещё не сохранена.",
|
"No page history saved yet.": "История страниц ещё не сохранена.",
|
||||||
"No pages yet": "Страниц пока нет",
|
"No pages yet": "Страниц пока нет",
|
||||||
"No shared pages": "Нет общих страниц",
|
|
||||||
"No results found...": "Результаты не найдены...",
|
"No results found...": "Результаты не найдены...",
|
||||||
"No user found": "Пользователь не найден",
|
"No user found": "Пользователь не найден",
|
||||||
"Overview": "Обзор",
|
"Overview": "Обзор",
|
||||||
@@ -130,14 +121,11 @@
|
|||||||
"page": "страница",
|
"page": "страница",
|
||||||
"Page deleted successfully": "Страница успешно удалена",
|
"Page deleted successfully": "Страница успешно удалена",
|
||||||
"Page history": "История страницы",
|
"Page history": "История страницы",
|
||||||
"Select version": "Выбрать версию",
|
|
||||||
"Highlight changes": "Выделить изменения",
|
|
||||||
"Page import is in progress. Please do not close this tab.": "Импорт страницы в процессе. Пожалуйста, не закрывайте эту вкладку.",
|
"Page import is in progress. Please do not close this tab.": "Импорт страницы в процессе. Пожалуйста, не закрывайте эту вкладку.",
|
||||||
"Pages": "Страницы",
|
"Pages": "Страницы",
|
||||||
"pages": "страницы",
|
"pages": "страницы",
|
||||||
"Password": "Пароль",
|
"Password": "Пароль",
|
||||||
"Password changed successfully": "Пароль успешно изменён",
|
"Password changed successfully": "Пароль успешно изменён",
|
||||||
"People": "Люди",
|
|
||||||
"Pending": "В ожидании",
|
"Pending": "В ожидании",
|
||||||
"Please confirm your action": "Пожалуйста, подтвердите ваше действие",
|
"Please confirm your action": "Пожалуйста, подтвердите ваше действие",
|
||||||
"Preferences": "Настройки",
|
"Preferences": "Настройки",
|
||||||
@@ -145,7 +133,6 @@
|
|||||||
"Profile": "Профиль",
|
"Profile": "Профиль",
|
||||||
"Recently updated": "Обновлено недавно",
|
"Recently updated": "Обновлено недавно",
|
||||||
"Remove": "Удалить",
|
"Remove": "Удалить",
|
||||||
"Remove from favorites": "Удалить из избранного",
|
|
||||||
"Remove group member": "Удалить участника группы",
|
"Remove group member": "Удалить участника группы",
|
||||||
"Remove space member": "Удалить участника пространства",
|
"Remove space member": "Удалить участника пространства",
|
||||||
"Restore": "Восстановить",
|
"Restore": "Восстановить",
|
||||||
@@ -158,50 +145,49 @@
|
|||||||
"Search...": "Поиск...",
|
"Search...": "Поиск...",
|
||||||
"Select language": "Выберите язык",
|
"Select language": "Выберите язык",
|
||||||
"Select role": "Выберите роль",
|
"Select role": "Выберите роль",
|
||||||
"Select role to assign to all invited members": "Выберите роль, которую нужно назначить всем приглашённым участникам",
|
"Select role to assign to all invited members": "Выберите роль для всех приглашённых участников",
|
||||||
"Select theme": "Выберите тему",
|
"Select theme": "Выберите тему",
|
||||||
"Send invitation": "Отправить приглашение",
|
"Send invitation": "Отправить приглашение",
|
||||||
"Invitation sent": "Приглашение отправлено",
|
"Invitation sent": "Приглашение отправлено",
|
||||||
"Settings": "Настройки",
|
"Settings": "Настройки",
|
||||||
"Setup workspace": "Настроить рабочее пространство",
|
"Setup workspace": "Настроить рабочую область",
|
||||||
"Sign In": "Войти",
|
"Sign In": "Вход",
|
||||||
"Sign Up": "Зарегистрироваться",
|
"Sign Up": "Регистрация",
|
||||||
"Slug": "Слаг",
|
"Slug": "Slug",
|
||||||
"Space": "Пространство",
|
"Space": "Пространство",
|
||||||
"Space description": "Описание пространства",
|
"Space description": "Описание пространства",
|
||||||
"Space menu": "Меню пространства",
|
"Space menu": "Меню пространства",
|
||||||
"Space name": "Название пространства",
|
"Space name": "Название пространства",
|
||||||
"Space settings": "Настройки пространства",
|
"Space settings": "Настройки пространства",
|
||||||
"Space slug": "Слаг пространства",
|
"Space slug": "Slug пространства",
|
||||||
"Spaces": "Пространства",
|
"Spaces": "Пространства",
|
||||||
"Spaces you belong to": "Пространства, в которых вы состоите",
|
"Spaces you belong to": "Пространства, к которым вы принадлежите",
|
||||||
"No space found": "Пространство не найдено",
|
"No space found": "Пространства не найдены",
|
||||||
"Search for spaces": "Поиск пространств",
|
"Search for spaces": "Поиск пространств",
|
||||||
"Start typing to search...": "Начните вводить для поиска...",
|
"Start typing to search...": "Начните вводить для поиска...",
|
||||||
"Status": "Статус",
|
"Status": "Статус",
|
||||||
"Successfully imported": "Успешно импортировано",
|
"Successfully imported": "Успешно импортировано",
|
||||||
"Successfully restored": "Успешно восстановлено",
|
"Successfully restored": "Успешно восстановлено",
|
||||||
"System settings": "Системные настройки",
|
"System settings": "Системные настройки",
|
||||||
"Templates": "Шаблоны",
|
|
||||||
"Theme": "Тема",
|
"Theme": "Тема",
|
||||||
"To change your email, you have to enter your password and new email.": "Чтобы изменить электронную почту, вам нужно ввести пароль и новый адрес.",
|
"To change your email, you have to enter your password and new email.": "Чтобы изменить электронную почту, вам нужно ввести пароль и новый адрес.",
|
||||||
"Toggle full page width": "Переключить полную ширину страницы",
|
"Toggle full page width": "Переключить ширину на всю страницу",
|
||||||
"Unable to import pages. Please try again.": "Не удалось импортировать страницы. Пожалуйста, попробуйте ещё раз.",
|
"Unable to import pages. Please try again.": "Не удалось импортировать страницы. Пожалуйста, попробуйте ещё раз.",
|
||||||
"untitled": "без названия",
|
"untitled": "без названия",
|
||||||
"Untitled": "Без названия",
|
"Untitled": "Без названия",
|
||||||
"Updated successfully": "Успешно обновлено",
|
"Updated successfully": "Обновлено успешно",
|
||||||
"User": "Пользователь",
|
"User": "Пользователь",
|
||||||
"Workspace": "Рабочее пространство",
|
"Workspace": "Рабочая область",
|
||||||
"Workspace Name": "Название рабочего пространства",
|
"Workspace Name": "Имя рабочей области",
|
||||||
"Workspace settings": "Настройки рабочего пространства",
|
"Workspace settings": "Настройки рабочей области",
|
||||||
"You can change your password here.": "Вы можете изменить свой пароль здесь.",
|
"You can change your password here.": "Вы можете изменить свой пароль здесь.",
|
||||||
"Your Email": "Ваш адрес электронной почты",
|
"Your Email": "Ваш адрес электронной почты",
|
||||||
"Your import is complete.": "Ваш импорт завершен.",
|
"Your import is complete.": "Ваш импорт завершен.",
|
||||||
"Your name": "Ваше имя",
|
"Your name": "Ваше имя",
|
||||||
"Your Name": "Ваше имя",
|
"Your Name": "Ваше Имя",
|
||||||
"Your password": "Ваш пароль",
|
"Your password": "Ваш пароль",
|
||||||
"Your password must be a minimum of 8 characters.": "Ваш пароль должен содержать минимум 8 символов.",
|
"Your password must be a minimum of 8 characters.": "Ваш пароль должен содержать минимум 8 символов.",
|
||||||
"Sidebar toggle": "Переключатель боковой панели",
|
"Sidebar toggle": "Переключить боковую панель",
|
||||||
"Comments": "Комментарии",
|
"Comments": "Комментарии",
|
||||||
"404 page not found": "404 страница не найдена",
|
"404 page not found": "404 страница не найдена",
|
||||||
"Sorry, we can't find the page you are looking for.": "К сожалению, мы не можем найти страницу, которую вы ищете.",
|
"Sorry, we can't find the page you are looking for.": "К сожалению, мы не можем найти страницу, которую вы ищете.",
|
||||||
@@ -217,14 +203,9 @@
|
|||||||
"Reply...": "Ответить...",
|
"Reply...": "Ответить...",
|
||||||
"Error loading comments.": "Ошибка при загрузке комментариев.",
|
"Error loading comments.": "Ошибка при загрузке комментариев.",
|
||||||
"No comments yet.": "Комментариев пока нет.",
|
"No comments yet.": "Комментариев пока нет.",
|
||||||
"No open comments.": "Нет открытых комментариев.",
|
|
||||||
"No resolved comments.": "Нет решённых комментариев.",
|
|
||||||
"Add a comment...": "Добавить комментарий...",
|
|
||||||
"Edit comment": "Редактировать комментарий",
|
"Edit comment": "Редактировать комментарий",
|
||||||
"Delete comment": "Удалить комментарий",
|
"Delete comment": "Удалить комментарий",
|
||||||
"Are you sure you want to delete this comment?": "Вы уверены, что хотите удалить этот комментарий?",
|
"Are you sure you want to delete this comment?": "Вы уверены, что хотите удалить этот комментарий?",
|
||||||
"Delete chat": "Удалить чат",
|
|
||||||
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "Вы уверены, что хотите удалить '{{title}}'? Это действие нельзя отменить.",
|
|
||||||
"Comment created successfully": "Комментарий успешно создан",
|
"Comment created successfully": "Комментарий успешно создан",
|
||||||
"Error creating comment": "Ошибка при создании комментария",
|
"Error creating comment": "Ошибка при создании комментария",
|
||||||
"Comment updated successfully": "Комментарий успешно обновлён",
|
"Comment updated successfully": "Комментарий успешно обновлён",
|
||||||
@@ -232,17 +213,18 @@
|
|||||||
"Comment deleted successfully": "Комментарий успешно удалён",
|
"Comment deleted successfully": "Комментарий успешно удалён",
|
||||||
"Failed to delete comment": "Не удалось удалить комментарий",
|
"Failed to delete comment": "Не удалось удалить комментарий",
|
||||||
"Comment resolved successfully": "Комментарий успешно разрешён",
|
"Comment resolved successfully": "Комментарий успешно разрешён",
|
||||||
"Comment re-opened successfully": "Комментарий успешно открыт повторно",
|
"Comment re-opened successfully": "Комментарий успешно открыт заново",
|
||||||
"Comment unresolved successfully": "Комментарий успешно переведён в нерешённые",
|
"Comment unresolved successfully": "Комментарий успешно размечен как нерешённый",
|
||||||
"Failed to resolve comment": "Не удалось разрешить комментарий",
|
"Failed to resolve comment": "Не удалось разрешить комментарий",
|
||||||
"Resolve comment": "Решить комментарий",
|
"Resolve comment": "Разрешить комментарий",
|
||||||
"Unresolve comment": "Снять статус решённого с комментария",
|
"Unresolve comment": "Отметить комментарий как нерешённый",
|
||||||
"Resolve Comment Thread": "Решить ветку комментариев",
|
"Resolve Comment Thread": "Закрыть цепочку комментариев",
|
||||||
"Unresolve Comment Thread": "Снять статус решённой с ветки комментариев",
|
"Unresolve Comment Thread": "Отметить цепочку комментариев как нерешённую",
|
||||||
"Are you sure you want to resolve this comment thread? This will mark it as completed.": "Вы уверены, что хотите закрыть эту цепочку комментариев? Это пометит её как завершённую.",
|
"Are you sure you want to resolve this comment thread? This will mark it as completed.": "Вы уверены, что хотите закрыть эту цепочку комментариев? Это пометит её как завершённую.",
|
||||||
"Are you sure you want to unresolve this comment thread?": "Вы уверены, что хотите отметить эту цепочку комментариев как нерешённую?",
|
"Are you sure you want to unresolve this comment thread?": "Вы уверены, что хотите отметить эту цепочку комментариев как нерешённую?",
|
||||||
"Resolved": "Решено",
|
"Resolved": "Решено",
|
||||||
"No active comments.": "Нет активных комментариев.",
|
"No active comments.": "Нет активных комментариев.",
|
||||||
|
"No resolved comments.": "Нет решённых комментариев.",
|
||||||
"Revoke invitation": "Отозвать приглашение",
|
"Revoke invitation": "Отозвать приглашение",
|
||||||
"Revoke": "Отозвать",
|
"Revoke": "Отозвать",
|
||||||
"Don't": "Нет",
|
"Don't": "Нет",
|
||||||
@@ -270,7 +252,6 @@
|
|||||||
"Export failed:": "Экспортирование не удалось:",
|
"Export failed:": "Экспортирование не удалось:",
|
||||||
"export error": "ошибка экспорта",
|
"export error": "ошибка экспорта",
|
||||||
"Export page": "Экспорт страницы",
|
"Export page": "Экспорт страницы",
|
||||||
"Export successful": "Экспорт выполнен успешно",
|
|
||||||
"Export space": "Экспорт пространства",
|
"Export space": "Экспорт пространства",
|
||||||
"Export {{type}}": "Экспорт {{type}}",
|
"Export {{type}}": "Экспорт {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "Файл превышает лимит вложений {{limit}}",
|
"File exceeds the {{limit}} attachment limit": "Файл превышает лимит вложений {{limit}}",
|
||||||
@@ -287,21 +268,7 @@
|
|||||||
"Add row above": "Добавить строку выше",
|
"Add row above": "Добавить строку выше",
|
||||||
"Add row below": "Добавить строку ниже",
|
"Add row below": "Добавить строку ниже",
|
||||||
"Delete table": "Удалить таблицу",
|
"Delete table": "Удалить таблицу",
|
||||||
"Add column left": "Add column left",
|
|
||||||
"Add column right": "Add column right",
|
|
||||||
"Clear cell": "Clear cell",
|
|
||||||
"Clear cells": "Clear cells",
|
|
||||||
"Toggle header cell": "Toggle header cell",
|
|
||||||
"Toggle header column": "Toggle header column",
|
|
||||||
"Toggle header row": "Toggle header row",
|
|
||||||
"Move column left": "Move column left",
|
|
||||||
"Move column right": "Move column right",
|
|
||||||
"Move row down": "Move row down",
|
|
||||||
"Move row up": "Move row up",
|
|
||||||
"Sort A → Z": "Sort A → Z",
|
|
||||||
"Sort Z → A": "Sort Z → A",
|
|
||||||
"Info": "Информация",
|
"Info": "Информация",
|
||||||
"Note": "Примечание",
|
|
||||||
"Success": "Успешно",
|
"Success": "Успешно",
|
||||||
"Warning": "Предупреждение",
|
"Warning": "Предупреждение",
|
||||||
"Danger": "Важно",
|
"Danger": "Важно",
|
||||||
@@ -312,11 +279,6 @@
|
|||||||
"Save & Exit": "Сохранить и выйти",
|
"Save & Exit": "Сохранить и выйти",
|
||||||
"Double-click to edit Excalidraw diagram": "Кликните дважды для редактирования диаграммы Excalidraw",
|
"Double-click to edit Excalidraw diagram": "Кликните дважды для редактирования диаграммы Excalidraw",
|
||||||
"Paste link": "Вставить ссылку",
|
"Paste link": "Вставить ссылку",
|
||||||
"Paste link or search pages": "Вставьте ссылку или найдите страницы",
|
|
||||||
"Link to web page": "Ссылка на веб-страницу",
|
|
||||||
"Recents": "Недавние",
|
|
||||||
"Page or URL": "Страница или URL",
|
|
||||||
"Link title": "Заголовок ссылки",
|
|
||||||
"Edit link": "Редактировать ссылку",
|
"Edit link": "Редактировать ссылку",
|
||||||
"Remove link": "Удалить ссылку",
|
"Remove link": "Удалить ссылку",
|
||||||
"Add link": "Добавить ссылку",
|
"Add link": "Добавить ссылку",
|
||||||
@@ -335,7 +297,7 @@
|
|||||||
"Pink": "Розовый",
|
"Pink": "Розовый",
|
||||||
"Gray": "Серый",
|
"Gray": "Серый",
|
||||||
"Embed link": "Встроенная ссылка",
|
"Embed link": "Встроенная ссылка",
|
||||||
"Invalid {{provider}} embed link": "Недействительная ссылка для встраивания {{provider}}",
|
"Invalid {{provider}} embed link": "Неверная ссылка для встраивания {{provider}}",
|
||||||
"Embed {{provider}}": "Встроить {{provider}}",
|
"Embed {{provider}}": "Встроить {{provider}}",
|
||||||
"Enter {{provider}} link to embed": "Введите ссылку для встраивания {{provider}}",
|
"Enter {{provider}} link to embed": "Введите ссылку для встраивания {{provider}}",
|
||||||
"Bold": "Жирный",
|
"Bold": "Жирный",
|
||||||
@@ -362,14 +324,9 @@
|
|||||||
"Create block quote.": "Создать блок цитирования.",
|
"Create block quote.": "Создать блок цитирования.",
|
||||||
"Insert code snippet.": "Вставить фрагмент кода.",
|
"Insert code snippet.": "Вставить фрагмент кода.",
|
||||||
"Insert horizontal rule divider": "Вставить горизонтальный разделитель",
|
"Insert horizontal rule divider": "Вставить горизонтальный разделитель",
|
||||||
"Page break": "Page break",
|
|
||||||
"Insert a page break for printing.": "Insert a page break for printing.",
|
|
||||||
"Upload any image from your device.": "Загрузить любое изображение с вашего устройства.",
|
"Upload any image from your device.": "Загрузить любое изображение с вашего устройства.",
|
||||||
"Upload any video from your device.": "Загрузить любое видео с вашего устройства.",
|
"Upload any video from your device.": "Загрузить любое видео с вашего устройства.",
|
||||||
"Upload any audio from your device.": "Загрузите любой аудиофайл с вашего устройства.",
|
|
||||||
"Upload any file from your device.": "Загрузить любой файл с вашего устройства.",
|
"Upload any file from your device.": "Загрузить любой файл с вашего устройства.",
|
||||||
"Uploading {{name}}": "Загрузка {{name}}",
|
|
||||||
"Uploading file": "Загрузка файла",
|
|
||||||
"Table": "Таблица",
|
"Table": "Таблица",
|
||||||
"Insert a table.": "Вставить таблицу.",
|
"Insert a table.": "Вставить таблицу.",
|
||||||
"Insert collapsible block.": "Вставить сворачиваемый блок.",
|
"Insert collapsible block.": "Вставить сворачиваемый блок.",
|
||||||
@@ -377,43 +334,23 @@
|
|||||||
"Divider": "Разделитель",
|
"Divider": "Разделитель",
|
||||||
"Quote": "Цитата",
|
"Quote": "Цитата",
|
||||||
"Image": "Изображение",
|
"Image": "Изображение",
|
||||||
"Audio": "Аудио",
|
"File attachment": "Прикрепленный файл",
|
||||||
"Embed PDF": "Встроить PDF",
|
|
||||||
"Upload and embed a PDF file.": "Загрузите и встроите PDF-файл.",
|
|
||||||
"Embed as PDF": "Встроить как PDF",
|
|
||||||
"Failed to load PDF": "Не удалось загрузить PDF",
|
|
||||||
"Convert to attachment": "Преобразовать в вложение",
|
|
||||||
"File attachment": "Вложение файла",
|
|
||||||
"Toggle block": "Сворачиваемый блок",
|
"Toggle block": "Сворачиваемый блок",
|
||||||
"Callout": "Выноска",
|
"Callout": "Выноска",
|
||||||
"Insert callout notice.": "Вставить выноску с сообщением.",
|
"Insert callout notice.": "Вставить выноску с сообщением.",
|
||||||
"Math inline": "Строчная формула",
|
"Math inline": "Формула",
|
||||||
"Insert inline math equation.": "Вставить математическое выражение в строку.",
|
"Insert inline math equation.": "Вставить математическое выражение в строку.",
|
||||||
"Math block": "Блок формулы",
|
"Math block": "Блок формул",
|
||||||
"Insert math equation": "Вставить математическую формулу",
|
"Insert math equation": "Вставить математическое выражение",
|
||||||
"Mermaid diagram": "Диаграмма Mermaid",
|
"Mermaid diagram": "Диаграмма Mermaid",
|
||||||
"Insert mermaid diagram": "Вставить диаграмму Mermaid",
|
"Insert mermaid diagram": "Вставить диаграмму Mermaid",
|
||||||
"Insert and design Drawio diagrams": "Вставляйте и редактируйте диаграммы Drawio",
|
"Insert and design Drawio diagrams": "Вставить и рисовать диаграммы Draw.io",
|
||||||
"Insert current date": "Вставить текущую дату",
|
"Insert current date": "Вставить текущую дату",
|
||||||
"Draw and sketch excalidraw diagrams": "Рисуйте и создавайте диаграммы Excalidraw",
|
"Draw and sketch excalidraw diagrams": "Вставить и рисовать диаграммы Excalidraw",
|
||||||
"Multiple": "Несколько",
|
"Multiple": "Несколько",
|
||||||
"Turn into": "Преобразовать в",
|
|
||||||
"Text align": "Выравнивание текста",
|
|
||||||
"This page may have been deleted, moved, or you may not have access.": "Эта страница могла быть удалена, перемещена, или у вас может отсутствовать доступ к ней.",
|
|
||||||
"Go to homepage": "Вернуться на главную",
|
|
||||||
"Pages you create will show up here.": "Созданные вами страницы появятся здесь.",
|
|
||||||
"Heading {{level}}": "Заголовок {{level}}",
|
"Heading {{level}}": "Заголовок {{level}}",
|
||||||
"Toggle title": "Заголовок сворачиваемого блока",
|
"Toggle title": "Переключить заголовок",
|
||||||
"Write anything. Enter \"/\" for commands": "Пишите что угодно. Введите \"/\" для команд",
|
"Write anything. Enter \"/\" for commands": "Начните писать. Введите \"/\" для списка команд",
|
||||||
"Write...": "Напишите...",
|
|
||||||
"Column count": "Количество столбцов",
|
|
||||||
"{{count}} Columns": "{count, plural, one{# столбец} few{# столбца} many{# столбцов} other{# столбца}}",
|
|
||||||
"Equal columns": "Равные столбцы",
|
|
||||||
"Left sidebar": "Левая боковая панель",
|
|
||||||
"Right sidebar": "Правая боковая панель",
|
|
||||||
"Wide center": "Широкий по центру",
|
|
||||||
"Left wide": "Широкий слева",
|
|
||||||
"Right wide": "Широкий справа",
|
|
||||||
"Names do not match": "Названия не совпадают",
|
"Names do not match": "Названия не совпадают",
|
||||||
"Today, {{time}}": "Сегодня, {{time}}",
|
"Today, {{time}}": "Сегодня, {{time}}",
|
||||||
"Yesterday, {{time}}": "Вчера, {{time}}",
|
"Yesterday, {{time}}": "Вчера, {{time}}",
|
||||||
@@ -421,75 +358,48 @@
|
|||||||
"Space updated successfully": "Пространство успешно обновлено",
|
"Space updated successfully": "Пространство успешно обновлено",
|
||||||
"Space deleted successfully": "Пространство успешно удалено",
|
"Space deleted successfully": "Пространство успешно удалено",
|
||||||
"Members added successfully": "Участники успешно добавлены",
|
"Members added successfully": "Участники успешно добавлены",
|
||||||
"Member removed successfully": "Участник успешно удалён",
|
"Member removed successfully": "Участник успешно удален",
|
||||||
"Member role updated successfully": "Роль участника успешно обновлена",
|
"Member role updated successfully": "Роль участника успешно обновлена",
|
||||||
"Created by: <b>{{creatorName}}</b>": "Создано: <b>{{creatorName}}</b>",
|
"Created by: <b>{{creatorName}}</b>": "Автор: <b>{{creatorName}}</b>",
|
||||||
"Created at: {{time}}": "Создано: {{time}}",
|
"Created at: {{time}}": "Дата создания: {{time}}",
|
||||||
"Edited by {{name}} {{time}}": "Изменено: {{name}} {{time}}",
|
"Edited by {{name}} {{time}}": "Изменено {{name}} {{time}}",
|
||||||
"Word count: {{wordCount}}": "Количество слов: {{wordCount}}",
|
"Word count: {{wordCount}}": "Количество слов: {{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Количество символов: {{characterCount}}",
|
"Character count: {{characterCount}}": "Количество символов: {{characterCount}}",
|
||||||
"New update": "Новое обновление",
|
"New update": "Новое обновление",
|
||||||
"{{latestVersion}} is available": "Доступна версия {{latestVersion}}",
|
"{{latestVersion}} is available": "Доступна новая версия {{latestVersion}}",
|
||||||
"Default page edit mode": "Режим редактирования страницы по умолчанию",
|
"Default page edit mode": "Режим редактирования страницы по умолчанию",
|
||||||
"Choose your preferred page edit mode. Avoid accidental edits.": "Выберите предпочитаемый режим редактирования страницы. Избегайте случайных изменений.",
|
"Choose your preferred page edit mode. Avoid accidental edits.": "Выберите предпочитаемый режим редактирования страницы. Избегайте случайных изменений.",
|
||||||
"Choose {{format}} file": "Выберите файл {{format}}",
|
|
||||||
"Reading": "Чтение",
|
"Reading": "Чтение",
|
||||||
"Delete member": "Удалить участника",
|
"Delete member": "Удалить участника",
|
||||||
"Member deleted successfully": "Участник успешно удалён",
|
"Member deleted successfully": "Участник успешно удален",
|
||||||
"Are you sure you want to delete this workspace member? This action is irreversible.": "Вы уверены, что хотите удалить этого участника рабочей области? Это действие необратимо.",
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "Вы уверены, что хотите удалить этого участника рабочей области? Это действие необратимо.",
|
||||||
"Deactivate member": "Деактивировать участника",
|
|
||||||
"Activate member": "Активировать участника",
|
|
||||||
"Are you sure you want to deactivate this workspace member? They will no longer be able to access this workspace.": "Вы уверены, что хотите деактивировать этого участника рабочего пространства? Они больше не смогут получить доступ к этому рабочему пространству.",
|
|
||||||
"Are you sure you want to activate this workspace member?": "Вы уверены, что хотите активировать этого участника рабочего пространства?",
|
|
||||||
"Deactivate": "Деактивировать",
|
|
||||||
"Activate": "Активировать",
|
|
||||||
"Deactivated": "Деактивирован",
|
|
||||||
"Move": "Переместить",
|
"Move": "Переместить",
|
||||||
"Move page": "Переместить страницу",
|
"Move page": "Переместить страницу",
|
||||||
"Move page to a different space.": "Переместите страницу в другое пространство.",
|
"Move page to a different space.": "Переместите страницу в другое пространство.",
|
||||||
"Real-time editor connection lost. Retrying...": "Соединение с редактором в реальном времени потеряно. Повторная попытка...",
|
"Real-time editor connection lost. Retrying...": "Соединение с редактором в реальном времени потеряно. Повторная попытка...",
|
||||||
"Table of contents": "Оглавление",
|
"Table of contents": "Содержание",
|
||||||
"Add headings (H1, H2, H3) to generate a table of contents.": "Добавьте заголовки (H1, H2, H3), чтобы создать оглавление.",
|
"Add headings (H1, H2, H3) to generate a table of contents.": "Добавьте заголовки (H1, H2, H3), чтобы создать оглавление.",
|
||||||
"Share": "Поделиться",
|
"Share": "Поделиться",
|
||||||
"Public sharing": "Публичный доступ",
|
"Public sharing": "Общий доступ",
|
||||||
"Shared by": "Поделился",
|
"Shared by": "Поделился",
|
||||||
"Shared at": "Дата публикации",
|
"Shared at": "Поделился в",
|
||||||
"Inherits public sharing from": "Наследует публичный доступ от",
|
"Inherits public sharing from": "Наследует общий доступ от",
|
||||||
"Share to web": "Опубликовать в интернете",
|
"Share to web": "Поделиться в интернете",
|
||||||
"Shared to web": "Опубликовано в интернете",
|
"Shared to web": "Размещено в интернете",
|
||||||
"Anyone with the link can view this page": "Любой, у кого есть ссылка, может просматривать эту страницу",
|
"Anyone with the link can view this page": "Любой, у кого есть ссылка, может просмотреть эту страницу",
|
||||||
"Make this page publicly accessible": "Сделать эту страницу общедоступной",
|
"Make this page publicly accessible": "Сделать эту страницу общедоступной",
|
||||||
"Include sub-pages": "Включить подстраницы",
|
"Include sub-pages": "Включить подстраницы",
|
||||||
"Make sub-pages public too": "Сделать подстраницы тоже общедоступными",
|
"Make sub-pages public too": "Сделать подстраницы также общедоступными",
|
||||||
"Allow search engines to index page": "Разрешить поисковым системам индексировать страницу",
|
"Allow search engines to index page": "Разрешить поисковым системам индексировать страницу",
|
||||||
"Open page": "Открыть страницу",
|
"Open page": "Открыть страницу",
|
||||||
"Page": "Страница",
|
"Page": "Страница",
|
||||||
"Delete public share link": "Удалить публичную ссылку",
|
"Delete public share link": "Удалить ссылку на общий доступ",
|
||||||
"Delete share": "Удалить общий доступ",
|
"Delete share": "Удалить общий доступ",
|
||||||
"Are you sure you want to delete this shared link?": "Вы уверены, что хотите удалить эту ссылку общего доступа?",
|
"Are you sure you want to delete this shared link?": "Вы уверены, что хотите удалить эту ссылку общего доступа?",
|
||||||
"Publicly shared pages from spaces you are a member of will appear here": "Здесь будут отображаться публично опубликованные страницы из пространств, участником которых вы являетесь",
|
"Publicly shared pages from spaces you are a member of will appear here": "Общие страницы из пространств, участником которых вы являетесь, появятся здесь",
|
||||||
"Share deleted successfully": "Общий доступ успешно удалён",
|
"Share deleted successfully": "Общий доступ успешно удален",
|
||||||
"Share not found": "Общий доступ не найден",
|
"Share not found": "Общий доступ не найден",
|
||||||
"Failed to share page": "Не удалось предоставить доступ к странице",
|
"Failed to share page": "Не удалось поделиться страницей",
|
||||||
"Disable public sharing": "Отключить общий доступ",
|
|
||||||
"Prevent members from sharing pages publicly.": "Запретить участникам делиться страницами публично.",
|
|
||||||
"Toggle public sharing": "Переключить общий доступ",
|
|
||||||
"Toggle space public sharing": "Переключить общий доступ для пространства",
|
|
||||||
"Allow viewers to comment": "Разрешить зрителям комментировать",
|
|
||||||
"Allow viewers to add comments on pages in this space.": "Разрешить зрителям добавлять комментарии на страницах в этом пространстве.",
|
|
||||||
"Toggle viewer comments": "Переключить комментарии зрителей",
|
|
||||||
"Public sharing is disabled at the workspace level": "Общий доступ отключен на уровне рабочего пространства",
|
|
||||||
"Prevent pages in this space from being shared publicly.": "Запретить делиться страницами в этом пространстве публично.",
|
|
||||||
"Page permissions": "Права доступа к странице},{",
|
|
||||||
"Control who can view and edit individual pages. Available with an enterprise license.": "Контролируйте, кто может просматривать и редактировать отдельные страницы. Доступно при наличии лицензии Enterprise.",
|
|
||||||
"Enable public sharing": "Включить общий доступ",
|
|
||||||
"Are you sure you want to enable public sharing? Members will be able to share pages publicly.": "Вы уверены, что хотите включить общий доступ? Участники смогут делиться страницами публично.",
|
|
||||||
"Are you sure you want to disable public sharing? All existing shared links in this workspace will be deleted.": "Вы уверены, что хотите отключить общий доступ? Все существующие ссылки в этом рабочем пространстве будут удалены.",
|
|
||||||
"Are you sure you want to enable public sharing for this space?": "Вы уверены, что хотите включить общий доступ для этого пространства?",
|
|
||||||
"Are you sure you want to disable public sharing? All existing shared links in this space will be deleted.": "Вы уверены, что хотите отключить общий доступ? Все существующие ссылки в этом пространстве будут удалены.",
|
|
||||||
"Public sharing is disabled": "Общий доступ отключен",
|
|
||||||
"Public sharing has been disabled at the workspace level.": "Общий доступ был отключен на уровне рабочего пространства.",
|
|
||||||
"Public sharing has been disabled for this space.": "Общий доступ был отключен для этого пространства.",
|
|
||||||
"Copy page": "Копировать страницу",
|
"Copy page": "Копировать страницу",
|
||||||
"Copy page to a different space.": "Копировать страницу в другое пространство.",
|
"Copy page to a different space.": "Копировать страницу в другое пространство.",
|
||||||
"Page copied successfully": "Страница успешно скопирована",
|
"Page copied successfully": "Страница успешно скопирована",
|
||||||
@@ -504,10 +414,9 @@
|
|||||||
"Replace (Enter)": "Заменить (Enter)",
|
"Replace (Enter)": "Заменить (Enter)",
|
||||||
"Replace all (Ctrl+Alt+Enter)": "Заменить все (Ctrl+Alt+Enter)",
|
"Replace all (Ctrl+Alt+Enter)": "Заменить все (Ctrl+Alt+Enter)",
|
||||||
"Replace all": "Заменить все",
|
"Replace all": "Заменить все",
|
||||||
"View all": "Просмотреть все",
|
|
||||||
"View all spaces": "Просмотреть все пространства",
|
"View all spaces": "Просмотреть все пространства",
|
||||||
"Error": "Ошибка",
|
"Error": "Ошибка",
|
||||||
"Failed to disable MFA": "Не удалось отключить MFA",
|
"Failed to disable MFA": "Не удалось отключить двухфакторную аутентификацию",
|
||||||
"Disable two-factor authentication": "Отключить двухфакторную аутентификацию",
|
"Disable two-factor authentication": "Отключить двухфакторную аутентификацию",
|
||||||
"Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Отключение двухфакторной аутентификации сделает вашу учетную запись менее безопасной. Для входа потребуется только пароль.",
|
"Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Отключение двухфакторной аутентификации сделает вашу учетную запись менее безопасной. Для входа потребуется только пароль.",
|
||||||
"Please enter your password to disable two-factor authentication:": "Пожалуйста, введите ваш пароль, чтобы отключить двухфакторную аутентификацию:",
|
"Please enter your password to disable two-factor authentication:": "Пожалуйста, введите ваш пароль, чтобы отключить двухфакторную аутентификацию:",
|
||||||
@@ -519,61 +428,61 @@
|
|||||||
"Add 2FA method": "Добавить метод 2FA",
|
"Add 2FA method": "Добавить метод 2FA",
|
||||||
"Backup codes": "Резервные коды",
|
"Backup codes": "Резервные коды",
|
||||||
"Disable": "Отключить",
|
"Disable": "Отключить",
|
||||||
"Invalid verification code": "Недействительный код подтверждения",
|
"Invalid verification code": "Неверный код проверки",
|
||||||
"New backup codes have been generated": "Новые резервные коды сгенерированы",
|
"New backup codes have been generated": "Созданы новые резервные коды",
|
||||||
"Failed to regenerate backup codes": "Не удалось заново сгенерировать резервные коды",
|
"Failed to regenerate backup codes": "Не удалось создать новые резервные коды",
|
||||||
"About backup codes": "О резервных кодах",
|
"About backup codes": "О резервных кодах",
|
||||||
"Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Резервные коды можно использовать для доступа к вашей учетной записи, если вы потеряли доступ к приложению-аутентификатору. Каждый код можно использовать только один раз.",
|
"Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Резервные коды можно использовать для доступа к вашей учетной записи, если вы потеряли доступ к приложению-аутентификатору. Каждый код можно использовать только один раз.",
|
||||||
"You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Вы можете создать новые резервные коды в любое время. Это аннулирует все существующие коды.",
|
"You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Вы можете создать новые резервные коды в любое время. Это аннулирует все существующие коды.",
|
||||||
"Confirm password": "Подтвердите пароль",
|
"Confirm password": "Подтвердите пароль",
|
||||||
"Generate new backup codes": "Сгенерировать новые резервные коды",
|
"Generate new backup codes": "Создать новые резервные коды",
|
||||||
"Save your new backup codes": "Сохраните ваши новые резервные коды",
|
"Save your new backup codes": "Сохраните ваши новые резервные коды",
|
||||||
"Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Убедитесь, что сохранили эти коды в безопасном месте. Ваши старые резервные коды больше недействительны.",
|
"Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Убедитесь, что сохранили эти коды в безопасном месте. Ваши старые резервные коды больше недействительны.",
|
||||||
"Your new backup codes": "Ваши новые резервные коды",
|
"Your new backup codes": "Ваши новые резервные коды",
|
||||||
"I've saved my backup codes": "Я сохранил резервные коды",
|
"I've saved my backup codes": "Я сохранил(а) свои резервные коды",
|
||||||
"Failed to setup MFA": "Не удалось настроить MFA",
|
"Failed to setup MFA": "Не удалось настроить многофакторную аутентификацию",
|
||||||
"Setup & Verify": "Настроить и подтвердить",
|
"Setup & Verify": "Настроить и проверить",
|
||||||
"Add to authenticator": "Добавить в приложение-аутентификатор",
|
"Add to authenticator": "Добавить в аутентификатор",
|
||||||
"1. Scan this QR code with your authenticator app": "1. Отсканируйте этот QR-код с помощью приложения-аутентификатора",
|
"1. Scan this QR code with your authenticator app": "1. Отсканируйте этот QR-код с помощью вашего приложения-аутентификатора",
|
||||||
"Can't scan the code?": "Не удается сканировать код?",
|
"Can't scan the code?": "Не удается сканировать код?",
|
||||||
"Enter this code manually in your authenticator app:": "Введите этот код вручную в приложении-аутентификаторе:",
|
"Enter this code manually in your authenticator app:": "Введите этот код вручную в приложении-аутентификаторе:",
|
||||||
"2. Enter the 6-digit code from your authenticator": "2. Введите 6-значный код из приложения-аутентификатора",
|
"2. Enter the 6-digit code from your authenticator": "2. Введите 6-значный код из вашего аутентификатора",
|
||||||
"Verify and enable": "Подтвердить и включить",
|
"Verify and enable": "Проверить и включить",
|
||||||
"Failed to generate QR code. Please try again.": "Не удалось создать QR-код. Пожалуйста, попробуйте снова.",
|
"Failed to generate QR code. Please try again.": "Не удалось создать QR-код. Пожалуйста, попробуйте снова.",
|
||||||
"Backup": "Резервный",
|
"Backup": "Резервное копирование",
|
||||||
"Save codes": "Сохранить коды",
|
"Save codes": "Сохранить коды",
|
||||||
"Save your backup codes": "Сохраните резервные коды",
|
"Save your backup codes": "Сохраните ваши резервные коды",
|
||||||
"These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Эти коды можно использовать для доступа к вашей учетной записи, если вы потеряли доступ к приложению-аутентификатору. Каждый код можно использовать только один раз.",
|
"These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Эти коды можно использовать для доступа к вашей учетной записи, если вы потеряли доступ к приложению-аутентификатору. Каждый код можно использовать только один раз.",
|
||||||
"Print": "Печать",
|
"Print": "Печать",
|
||||||
"Two-factor authentication has been set up. Please log in again.": "Двухфакторная аутентификация настроена. Пожалуйста, войдите снова.",
|
"Two-factor authentication has been set up. Please log in again.": "Двухфакторная аутентификация настроена. Пожалуйста, войдите снова.",
|
||||||
"Two-Factor authentication required": "Требуется двухфакторная аутентификация",
|
"Two-Factor authentication required": "Требуется двухфакторная аутентификация",
|
||||||
"Your workspace requires two-factor authentication for all users": "Ваше рабочее пространство требует двухфакторную аутентификацию для всех пользователей",
|
"Your workspace requires two-factor authentication for all users": "Ваше рабочее пространство требует двухфакторной аутентификации для всех пользователей",
|
||||||
"To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Чтобы продолжать доступ к вашему рабочему пространству, вы должны настроить двухфакторную аутентификацию. Это добавляет дополнительный уровень безопасности к вашей учетной записи.",
|
"To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Чтобы продолжать доступ к вашему рабочему пространству, вы должны настроить двухфакторную аутентификацию. Это добавляет дополнительный уровень безопасности к вашей учетной записи.",
|
||||||
"Set up two-factor authentication": "Настроить двухфакторную аутентификацию",
|
"Set up two-factor authentication": "Настройте двухфакторную аутентификацию",
|
||||||
"Cancel and logout": "Отменить и выйти",
|
"Cancel and logout": "Отменить и выйти",
|
||||||
"Your workspace requires two-factor authentication. Please set it up to continue.": "Ваше рабочее пространство требует двухфакторной аутентификации. Пожалуйста, настройте её, чтобы продолжить.",
|
"Your workspace requires two-factor authentication. Please set it up to continue.": "Ваше рабочее пространство требует двухфакторной аутентификации. Пожалуйста, настройте её, чтобы продолжить.",
|
||||||
"This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Это добавляет дополнительный уровень безопасности к вашей учетной записи, требуя код проверки из вашего приложения-аутентификатора.",
|
"This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Это добавляет дополнительный уровень безопасности к вашей учетной записи, требуя код проверки из вашего приложения-аутентификатора.",
|
||||||
"Password is required": "Требуется пароль",
|
"Password is required": "Требуется пароль",
|
||||||
"Password must be at least 8 characters": "Пароль должен содержать не менее 8 символов",
|
"Password must be at least 8 characters": "Пароль должен содержать как минимум 8 символов",
|
||||||
"Please enter a 6-digit code": "Пожалуйста, введите 6-значный код",
|
"Please enter a 6-digit code": "Пожалуйста, введите 6-значный код",
|
||||||
"Code must be exactly 6 digits": "Код должен состоять ровно из 6 цифр",
|
"Code must be exactly 6 digits": "Код должен содержать ровно 6 цифр",
|
||||||
"Enter the 6-digit code found in your authenticator app": "Введите 6-значный код из вашего приложения-аутентификатора",
|
"Enter the 6-digit code found in your authenticator app": "Введите 6-значный код из вашего приложения-аутентификатора",
|
||||||
"Need help authenticating?": "Нужна помощь с аутентификацией?",
|
"Need help authenticating?": "Нужна помощь с аутентификацией?",
|
||||||
"MFA QR Code": "QR-код MFA",
|
"MFA QR Code": "QR-код двухфакторной аутентификации",
|
||||||
"Account created successfully. Please log in to set up two-factor authentication.": "Учетная запись успешно создана. Пожалуйста, войдите, чтобы настроить двухфакторную аутентификацию.",
|
"Account created successfully. Please log in to set up two-factor authentication.": "Учетная запись успешно создана. Пожалуйста, войдите, чтобы настроить двухфакторную аутентификацию.",
|
||||||
"Password reset successful. Please log in with your new password and complete two-factor authentication.": "Сброс пароля выполнен успешно. Пожалуйста, войдите с вашим новым паролем и завершите настройку двухфакторной аутентификации.",
|
"Password reset successful. Please log in with your new password and complete two-factor authentication.": "Сброс пароля выполнен успешно. Пожалуйста, войдите с вашим новым паролем и завершите настройку двухфакторной аутентификации.",
|
||||||
"Password reset successful. Please log in with your new password to set up two-factor authentication.": "Сброс пароля выполнен успешно. Пожалуйста, войдите с вашим новым паролем, чтобы настроить двухфакторную аутентификацию.",
|
"Password reset successful. Please log in with your new password to set up two-factor authentication.": "Сброс пароля выполнен успешно. Пожалуйста, войдите с вашим новым паролем, чтобы настроить двухфакторную аутентификацию.",
|
||||||
"Password reset was successful. Please log in with your new password.": "Сброс пароля выполнен успешно. Пожалуйста, войдите с вашим новым паролем.",
|
"Password reset was successful. Please log in with your new password.": "Сброс пароля выполнен успешно. Пожалуйста, войдите с вашим новым паролем.",
|
||||||
"Two-factor authentication": "Двухфакторная аутентификация",
|
"Two-factor authentication": "Двухфакторная аутентификация",
|
||||||
"Use authenticator app instead": "Использовать приложение-аутентификатор вместо этого",
|
"Use authenticator app instead": "Используйте приложение-аутентификатор вместо этого",
|
||||||
"Verify backup code": "Подтвердить резервный код",
|
"Verify backup code": "Проверка резервного кода",
|
||||||
"Use backup code": "Использовать резервный код",
|
"Use backup code": "Использовать резервный код",
|
||||||
"Enter one of your backup codes": "Введите один из ваших резервных кодов",
|
"Enter one of your backup codes": "Введите один из ваших резервных кодов",
|
||||||
"Backup code": "Резервный код",
|
"Backup code": "Резервный код",
|
||||||
"Enter one of your backup codes. Each backup code can only be used once.": "Введите один из ваших резервных кодов. Каждый резервный код можно использовать только один раз.",
|
"Enter one of your backup codes. Each backup code can only be used once.": "Введите один из ваших резервных кодов. Каждый резервный код можно использовать только один раз.",
|
||||||
"Verify": "Подтвердить",
|
"Verify": "Проверить",
|
||||||
"Trash": "Корзина",
|
"Trash": "Корзина",
|
||||||
"Pages in trash will be permanently deleted after {{count}} days.": "{count, plural, one {Страница в корзине будет окончательно удалена через # день.} few {Страницы в корзине будут окончательно удалены через # дня.} many {Страницы в корзине будут окончательно удалены через # дней.} other {Страницы в корзине будут окончательно удалены через # дней.}}",
|
"Pages in trash will be permanently deleted after 30 days.": "Страницы в корзине будут окончательно удалены через 30 дней.",
|
||||||
"Deleted": "Удалено",
|
"Deleted": "Удалено",
|
||||||
"No pages in trash": "В корзине нет страниц",
|
"No pages in trash": "В корзине нет страниц",
|
||||||
"Permanently delete page?": "Удалить страницу окончательно?",
|
"Permanently delete page?": "Удалить страницу окончательно?",
|
||||||
@@ -582,470 +491,9 @@
|
|||||||
"Move to trash": "Переместить в корзину",
|
"Move to trash": "Переместить в корзину",
|
||||||
"Move this page to trash?": "Переместить эту страницу в корзину?",
|
"Move this page to trash?": "Переместить эту страницу в корзину?",
|
||||||
"Restore page": "Восстановить страницу",
|
"Restore page": "Восстановить страницу",
|
||||||
"Permanently delete": "Permanently delete",
|
|
||||||
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> moved this page to Trash {{time}}.",
|
|
||||||
"Page moved to trash": "Страница перемещена в корзину",
|
"Page moved to trash": "Страница перемещена в корзину",
|
||||||
"Page restored successfully": "Страница успешно восстановлена",
|
"Page restored successfully": "Страница успешно восстановлена",
|
||||||
"Deleted by": "Удалено пользователем",
|
"Deleted by": "Удалено пользователем",
|
||||||
"Deleted at": "Удалено",
|
"Deleted at": "Удалено в",
|
||||||
"Preview": "Предпросмотр",
|
"Preview": "Предпросмотр"
|
||||||
"Subpages": "Подстраницы",
|
|
||||||
"Failed to load subpages": "Не удалось загрузить подстраницы",
|
|
||||||
"No subpages": "Нет подстраниц",
|
|
||||||
"Subpages (Child pages)": "Подстраницы (дочерние страницы)",
|
|
||||||
"List all subpages of the current page": "Показать все подстраницы текущей страницы",
|
|
||||||
"Attachments": "Вложения",
|
|
||||||
"All spaces": "Все пространства",
|
|
||||||
"Unknown": "Неизвестно",
|
|
||||||
"Find a space": "Найти пространство",
|
|
||||||
"Search in all your spaces": "Искать во всех ваших пространствах",
|
|
||||||
"Type": "Тип",
|
|
||||||
"Enterprise": "Корпоративный",
|
|
||||||
"Download attachment": "Скачать вложение",
|
|
||||||
"Allowed email domains": "Разрешённые домены электронной почты",
|
|
||||||
"Only users with email addresses from these domains can signup via SSO.": "Только пользователи с адресами электронной почты из этих доменов могут зарегистрироваться через SSO.",
|
|
||||||
"Enter valid domain names separated by comma or space": "Введите корректные доменные имена, разделённые запятой или пробелом",
|
|
||||||
"Enforce two-factor authentication": "Обязательная двухфакторная аутентификация",
|
|
||||||
"Once enforced, all members must enable two-factor authentication to access the workspace.": "После введения обязательности все участники должны будут включить двухфакторную аутентификацию для доступа к рабочему пространству.",
|
|
||||||
"Toggle MFA enforcement": "Переключить обязательность MFA",
|
|
||||||
"Display name": "Отображаемое имя",
|
|
||||||
"Allow signup": "Разрешить регистрацию",
|
|
||||||
"Enabled": "Включено",
|
|
||||||
"Advanced Settings": "Расширенные настройки",
|
|
||||||
"Enable TLS/SSL": "Включить TLS/SSL",
|
|
||||||
"Use secure connection to LDAP server": "Использовать защищённое подключение к серверу LDAP",
|
|
||||||
"Group sync": "Синхронизация групп",
|
|
||||||
"No SSO providers found.": "Поставщики SSO не найдены.",
|
|
||||||
"Delete SSO provider": "Удалить провайдера SSO",
|
|
||||||
"Are you sure you want to delete this SSO provider?": "Вы уверены, что хотите удалить этого поставщика SSO?",
|
|
||||||
"Action": "Действие",
|
|
||||||
"{{ssoProviderType}} configuration": "Конфигурация {{ssoProviderType}}",
|
|
||||||
"Icon": "Иконка",
|
|
||||||
"Upload image": "Загрузить изображение",
|
|
||||||
"Remove image": "Удалить изображение",
|
|
||||||
"Failed to remove image": "Не удалось удалить изображение",
|
|
||||||
"Image exceeds 10MB limit.": "Изображение превышает предел 10MB.",
|
|
||||||
"Image removed successfully": "Изображение успешно удалено",
|
|
||||||
"API key": "API ключ",
|
|
||||||
"API keys": "API ключи",
|
|
||||||
"API management": "Управление API",
|
|
||||||
"Custom expiration date": "Пользовательская дата срока действия",
|
|
||||||
"Enter a descriptive token name": "Введите понятное имя токена",
|
|
||||||
"Expiration": "Срок действия",
|
|
||||||
"Expired": "Истек",
|
|
||||||
"Expires": "Истекает",
|
|
||||||
"Last use": "Последнее использование",
|
|
||||||
"No API keys found": "API ключи не найдены",
|
|
||||||
"No expiration": "Не истекает",
|
|
||||||
"Revoked successfully": "Отозван успешно",
|
|
||||||
"Select expiration date": "Выберете срок действия",
|
|
||||||
"This action cannot be undone. Any applications using this API key will stop working.": "Это действие необратимо. Любые приложения, использующие этот API ключ, перестанут работать.",
|
|
||||||
"Update": "Обновить",
|
|
||||||
"Update {{credential}}": "Обновить {{credential}}",
|
|
||||||
"Manage API keys for all users in the workspace": "Управлять API ключами для всех пользователей в рабочей области",
|
|
||||||
"Restrict API key creation to admins": "Ограничить создание API-ключей только администраторами.",
|
|
||||||
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Только администраторы и владельцы могут создавать новые API-ключи. Существующие ключи участников продолжат работать.",
|
|
||||||
"Toggle restrict API keys to admins": "Переключить ограничение создания API-ключей только для администраторов",
|
|
||||||
"API key creation is restricted to admins by your workspace administrator.": "Создание API-ключей ограничено администраторами вашего рабочего пространства.",
|
|
||||||
"AI settings": "Настройки ИИ",
|
|
||||||
"AI search": "Поиск ИИ",
|
|
||||||
"AI Answer": "Ответ ИИ",
|
|
||||||
"Ask AI": "Спросить ИИ",
|
|
||||||
"AI is thinking...": "ИИ обрабатывает запрос...",
|
|
||||||
"Thinking": "Думаю",
|
|
||||||
"Ask a question...": "Задайте вопрос...",
|
|
||||||
"AI Answers": "Ответы ИИ",
|
|
||||||
"AI-powered search (AI Answers)": "Поиск на базе ИИ (Ответы ИИ)",
|
|
||||||
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "Поиск ИИ использует векторные встраивания для обеспечения семантического поиска по содержимому вашего рабочего пространства.",
|
|
||||||
"Toggle AI search": "Переключить поиск ИИ",
|
|
||||||
"Generative AI (Ask AI)": "Генеративный ИИ (Спросить ИИ)",
|
|
||||||
"Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Включите создание контента на базе ИИ в редакторе. Позволяет пользователям генерировать, улучшать, переводить и преобразовывать текст.",
|
|
||||||
"Toggle generative AI": "Переключить генеративный ИИ",
|
|
||||||
"Upgrade your plan": "Обновите свой тарифный план",
|
|
||||||
"Available with a paid license": "Доступно с платной лицензией",
|
|
||||||
"Upgrade your license tier.": "Обновите уровень вашей лицензии.",
|
|
||||||
"AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "ИИ доступен только в корпоративной версии Docmost. Свяжитесь по адресу sales@docmost.com.",
|
|
||||||
"AI & MCP": "ИИ и MCP",
|
|
||||||
"AI": "ИИ",
|
|
||||||
"MCP": "MCP",
|
|
||||||
"Model Context Protocol (MCP)": "Протокол контекста модели (MCP)",
|
|
||||||
"Enable the MCP server to allow AI assistants and tools to interact with your workspace content.": "Включите сервер MCP, чтобы ИИ-ассистенты и инструменты могли взаимодействовать с содержимым вашего рабочего пространства.",
|
|
||||||
"MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "MCP доступен только в корпоративной версии Docmost. Свяжитесь по адресу sales@docmost.com.",
|
|
||||||
"MCP Server URL": "URL сервера MCP",
|
|
||||||
"Use your API key for authentication. You can manage API keys in your account settings.": "Используйте ваш API-ключ для аутентификации. Управлять API-ключами можно в настройках аккаунта.",
|
|
||||||
"Supported tools": "Поддерживаемые инструменты",
|
|
||||||
"Your workspace has MCP enabled. Use your API key to connect AI assistants.": "В вашем рабочем пространстве включён MCP. Используйте свой API-ключ для подключения ИИ-ассистентов.",
|
|
||||||
"MCP server URL:": "URL сервера MCP:",
|
|
||||||
"Learn more": "Подробнее",
|
|
||||||
"Manage API keys for all users in the workspace. View the <anchor>API documentation</anchor> for usage details.": "Управляйте API-ключами для всех пользователей в рабочем пространстве. Смотрите <anchor>документацию по API</anchor> для получения информации об использовании.",
|
|
||||||
"View the <anchor>API documentation</anchor> for usage details.": "Смотрите <anchor>документацию по API</anchor> для получения информации об использовании.",
|
|
||||||
"View the <anchor>MCP documentation</anchor>.": "Смотрите <anchor>документацию по MCP</anchor>.",
|
|
||||||
"Sources": "Источники",
|
|
||||||
"AI Answers not available for attachments": "Ответы ИИ недоступны для вложений",
|
|
||||||
"No answer available": "Ответ недоступен",
|
|
||||||
"Background color": "Цвет фона",
|
|
||||||
"Highlight color": "Цвет выделения",
|
|
||||||
"Remove color": "Удалить цвет",
|
|
||||||
"Notifications": "Уведомления",
|
|
||||||
"No notifications": "Нет уведомлений",
|
|
||||||
"No unread notifications": "Нет непрочитанных уведомлений",
|
|
||||||
"All notifications": "Все уведомления",
|
|
||||||
"Unread only": "Только непрочитанные",
|
|
||||||
"Mark all as read": "Отметить все как прочитанные",
|
|
||||||
"Mark as read": "Отметить как прочитанное",
|
|
||||||
"More options": "Больше возможностей",
|
|
||||||
"<bold>{{name}}</bold> mentioned you in a comment": "<bold>{{name}}</bold> упомянул вас в комментарии",
|
|
||||||
"<bold>{{name}}</bold> commented on a page": "<bold>{{name}}</bold> оставил комментарий на странице",
|
|
||||||
"<bold>{{name}}</bold> resolved a comment": "<bold>{{name}}</bold> отметил(а) комментарий как решённый",
|
|
||||||
"<bold>{{name}}</bold> mentioned you on a page": "<bold>{{name}}</bold> упомянул(а) вас на странице",
|
|
||||||
"<bold>{{name}}</bold> gave you edit access to a page": "<bold>{{name}}</bold> предоставил(а) вам доступ на редактирование страницы",
|
|
||||||
"<bold>{{name}}</bold> gave you view access to a page": "<bold>{{name}}</bold> предоставил(а) вам доступ на просмотр страницы",
|
|
||||||
"<bold>{{name}}</bold> updated a page": "<bold>{{name}}</bold> обновил(а) страницу",
|
|
||||||
"Watch page": "Следить за страницей",
|
|
||||||
"Stop watching": "Перестать следить",
|
|
||||||
"Watch space": "Следить за пространством",
|
|
||||||
"Stop watching space": "Перестать следить за пространством",
|
|
||||||
"Email notifications": "Уведомления на email",
|
|
||||||
"Page updates": "Обновления страницы",
|
|
||||||
"Get notified when pages you watch are updated.": "Получайте уведомления, когда отслеживаемые вами страницы обновляются.",
|
|
||||||
"Page mentions": "Упоминания на странице",
|
|
||||||
"Get notified when someone mentions you on a page.": "Получайте уведомления, когда кто-то упоминает вас на странице.",
|
|
||||||
"Comment mentions": "Упоминания в комментариях",
|
|
||||||
"Get notified when someone mentions you in a comment.": "Получайте уведомления, когда кто-то упоминает вас в комментарии.",
|
|
||||||
"New comments": "Новые комментарии",
|
|
||||||
"Get notified about new comments on threads you participate in.": "Получайте уведомления о новых комментариях в цепочках, в которых вы участвуете.",
|
|
||||||
"Resolved comments": "Разрешённые комментарии",
|
|
||||||
"Get notified when your comment is resolved.": "Получайте уведомление, когда ваш комментарий разрешён.",
|
|
||||||
"You are now watching this page": "Вы теперь следите за этой страницей",
|
|
||||||
"You are no longer watching this page": "Вы больше не следите за этой страницей",
|
|
||||||
"You are now watching this space": "Теперь вы следите за этим пространством",
|
|
||||||
"You are no longer watching this space": "Вы больше не следите за этим пространством",
|
|
||||||
"Direct": "Прямые",
|
|
||||||
"Updates": "Обновления",
|
|
||||||
"Today": "Сегодня",
|
|
||||||
"Yesterday": "Вчера",
|
|
||||||
"This week": "На этой неделе",
|
|
||||||
"Older": "Старше",
|
|
||||||
"Restricted page": "Страница с ограниченным доступом",
|
|
||||||
"Restricted pages cannot be shared publicly.": "Страницы с ограниченным доступом нельзя сделать общедоступными.",
|
|
||||||
"Restricted by parent": "Ограничено родительской страницей",
|
|
||||||
"Restricted": "Ограничено",
|
|
||||||
"Open": "Открыто",
|
|
||||||
"Inherits restrictions from ancestor page": "Наследует ограничения от родительской страницы",
|
|
||||||
"Only people listed below can access this page": "Доступ к этой странице имеют только перечисленные ниже пользователи",
|
|
||||||
"Everyone in this space can access": "Доступ имеют все участники этого пространства",
|
|
||||||
"No additional restrictions on this page": "На этой странице нет дополнительных ограничений",
|
|
||||||
"Only specific people can access": "Доступ имеют только определённые пользователи",
|
|
||||||
"Use only inherited restrictions": "Использовать только унаследованные ограничения",
|
|
||||||
"Add restrictions on top of inherited": "Добавить ограничения поверх унаследованных",
|
|
||||||
"Inherited restriction": "Унаследованное ограничение",
|
|
||||||
"Access limited by": "Доступ ограничен",
|
|
||||||
"Restrict access to control who can view and edit this page": "Ограничьте доступ, чтобы контролировать, кто может просматривать и редактировать эту страницу",
|
|
||||||
"Add additional restrictions specific to this page": "Добавить дополнительные ограничения, применимые только к этой странице",
|
|
||||||
"Access": "Доступ",
|
|
||||||
"People with access": "Пользователи с доступом",
|
|
||||||
"Remove all": "Удалить всё",
|
|
||||||
"Remove access": "Удалить доступ",
|
|
||||||
"Remove all access": "Удалить весь доступ",
|
|
||||||
"Are you sure you want to remove this member's access to the page?": "Вы уверены, что хотите удалить доступ этого участника к странице?",
|
|
||||||
"Are you sure you want to remove all specific access? This will make the page open to everyone in the space.": "Вы уверены, что хотите удалить все специальные права доступа? Это сделает страницу доступной всем участникам пространства.",
|
|
||||||
"Trash retention": "Срок хранения корзины",
|
|
||||||
"Pages in trash will be permanently deleted after this period.": "Страницы в корзине будут окончательно удалены по истечении этого срока.",
|
|
||||||
"Trash retention updated": "Срок хранения корзины обновлён",
|
|
||||||
"Failed to update trash retention": "Не удалось обновить срок хранения корзины",
|
|
||||||
"Removed page restriction": "Ограничение доступа к странице удалено",
|
|
||||||
"Added page permission": "Добавлено разрешение доступа к странице",
|
|
||||||
"Removed page permission": "Удалено разрешение доступа к странице",
|
|
||||||
"day": "день",
|
|
||||||
"days": "дни",
|
|
||||||
"week": "неделя",
|
|
||||||
"weeks": "недели",
|
|
||||||
"month": "месяц",
|
|
||||||
"months": "месяцы",
|
|
||||||
"year": "год",
|
|
||||||
"years": "годы",
|
|
||||||
"Period": "Период",
|
|
||||||
"Fixed date": "Фиксированная дата",
|
|
||||||
"Indefinitely": "Бессрочно",
|
|
||||||
"Days": "Дни",
|
|
||||||
"Weeks": "Недели",
|
|
||||||
"Months": "Месяцы",
|
|
||||||
"Years": "Годы",
|
|
||||||
"Pick a date": "Выберите дату",
|
|
||||||
"Maximum is {{max}} {{unit}} for this unit": "Максимум для этой единицы измерения — {{max}} {{unit}}",
|
|
||||||
"Never expires. Verifiers can re-verify at any time.": "Срок действия не истекает. Проверяющие могут повторно подтверждать в любое время.",
|
|
||||||
"Verified": "Проверено",
|
|
||||||
"Review needed": "Требуется проверка",
|
|
||||||
"Verification expired": "Срок проверки истёк",
|
|
||||||
"Draft": "Черновик",
|
|
||||||
"In Approval": "На утверждении",
|
|
||||||
"In approval": "На утверждении",
|
|
||||||
"Approved": "Утверждено",
|
|
||||||
"Obsolete": "Устарело",
|
|
||||||
"Expiring": "Истекает",
|
|
||||||
"Set up verification": "Настроить проверку",
|
|
||||||
"Verify page": "Проверить страницу",
|
|
||||||
"Page verification": "Проверка страницы",
|
|
||||||
"Add verification": "Добавить проверку",
|
|
||||||
"Edit verification": "Изменить проверку",
|
|
||||||
"Search by title": "Поиск по заголовку",
|
|
||||||
"Choose how this page should stay accurate.": "Выберите, как поддерживать актуальность этой страницы.",
|
|
||||||
"Recurring verification": "Регулярная проверка",
|
|
||||||
"Verifiers re-confirm this page on a schedule.": "Проверяющие повторно подтверждают эту страницу по расписанию.",
|
|
||||||
"Re-verify on a schedule (e.g every 30 days )": "Повторно проверять по расписанию (например, каждые 30 дней)",
|
|
||||||
"Page stays editable at all times": "Страница остаётся редактируемой в любое время",
|
|
||||||
"Best for runbooks, FAQs, living documentation": "Лучше всего подходит для инструкций, FAQ и живой документации",
|
|
||||||
"Approval workflow": "Процесс утверждения",
|
|
||||||
"Formal document lifecycle with named approvers.": "Формальный жизненный цикл документа с назначенными утверждающими.",
|
|
||||||
"Draft → In approval → Approved → Obsolete": "Черновик → На утверждении → Утверждено → Устарело",
|
|
||||||
"Locked once approved, with full history": "После утверждения блокируется, с полной историей",
|
|
||||||
"Designed for ISO 9001, ISO 13485, and FDA": "Разработано для ISO 9001, ISO 13485 и FDA",
|
|
||||||
"Best for SOPs and controlled documents": "Лучше всего подходит для СОП и контролируемых документов",
|
|
||||||
"Back": "Назад",
|
|
||||||
"Quality management": "Управление качеством",
|
|
||||||
"Recurring": "Регулярно",
|
|
||||||
"Pages move through draft, approval, and approved stages.": "Страницы проходят стадии черновика, утверждения и утверждённого состояния.",
|
|
||||||
"Verifiers": "Проверяющие",
|
|
||||||
"Add verifier": "Добавить проверяющего",
|
|
||||||
"I've reviewed this page for accuracy": "Я проверил(а) эту страницу на точность",
|
|
||||||
"Set up": "Настроить",
|
|
||||||
"Remove verification": "Удалить проверку",
|
|
||||||
"Are you sure you want to remove verification from this page?": "Вы уверены, что хотите удалить проверку с этой страницы?",
|
|
||||||
"Assigned verifiers must periodically re-verify this page.": "Назначенные проверяющие должны периодически повторно проверять эту страницу.",
|
|
||||||
"Last verified by {{name}} {{time}} (expired)": "Последняя проверка: {{name}}, {{time}} (срок истёк)",
|
|
||||||
"The fixed expiration date has passed.": "Фиксированная дата истечения срока уже прошла.",
|
|
||||||
"Verified by {{name}} {{time}}": "Проверено: {{name}}, {{time}}",
|
|
||||||
"Expires {{date}}": "Истекает {{date}}",
|
|
||||||
"Expired {{date}}": "Срок истёк {{date}}",
|
|
||||||
"Mark as obsolete": "Отметить как устаревшее",
|
|
||||||
"Mark obsolete": "Отметить как устаревшее",
|
|
||||||
"Returned by {{name}} {{time}}": "Возвращено: {{name}}, {{time}}",
|
|
||||||
"No approval has been requested yet.": "Запрос на утверждение ещё не отправлен.",
|
|
||||||
"Submitted by {{name}} {{time}}": "Отправлено: {{name}}, {{time}}",
|
|
||||||
"Someone": "Кто-то",
|
|
||||||
"Approved by {{name}} {{time}}": "Утверждено: {{name}}, {{time}}",
|
|
||||||
"This document has been marked as obsolete.": "Этот документ был отмечен как устаревший.",
|
|
||||||
"Rejection comment": "Комментарий к отклонению",
|
|
||||||
"Reason for returning this document...": "Причина возврата этого документа...",
|
|
||||||
"Confirm rejection": "Подтвердить отклонение",
|
|
||||||
"Submit for approval": "Отправить на утверждение",
|
|
||||||
"Reject": "Отклонить",
|
|
||||||
"Approve": "Утвердить",
|
|
||||||
"Re-submit for approval": "Повторно отправить на утверждение",
|
|
||||||
"Verified until": "Проверено до",
|
|
||||||
"QMS": "QMS",
|
|
||||||
"Verified pages": "Проверенные страницы",
|
|
||||||
"Search pages...": "Поиск страниц...",
|
|
||||||
"Filter by space": "Фильтр по пространству",
|
|
||||||
"Filter by type": "Фильтр по типу",
|
|
||||||
"<bold>{{name}}</bold> verified a page": "<bold>{{name}}</bold> проверил(а) страницу",
|
|
||||||
"<bold>{{name}}</bold> submitted a page for your approval": "<bold>{{name}}</bold> отправил(а) страницу вам на утверждение",
|
|
||||||
"<bold>{{name}}</bold> returned a page for revision": "<bold>{{name}}</bold> вернул(а) страницу на доработку",
|
|
||||||
"Page verification expires soon": "Срок проверки страницы скоро истекает",
|
|
||||||
"Page verification has expired": "Срок проверки страницы истёк",
|
|
||||||
"Verifying your email": "Подтверждение вашего адреса электронной почты",
|
|
||||||
"Please wait...": "Пожалуйста, подождите...",
|
|
||||||
"Verification failed. The link may have expired.": "Ошибка проверки. Ссылка могла устареть.",
|
|
||||||
"Check your email": "Проверьте вашу электронную почту",
|
|
||||||
"We sent a verification link to {{email}}.": "Мы отправили ссылку для подтверждения на {{email}}.",
|
|
||||||
"We sent a verification link to your email.": "Мы отправили ссылку для подтверждения на вашу электронную почту.",
|
|
||||||
"Click the link to verify your email and access your workspace.": "Перейдите по ссылке, чтобы подтвердить электронную почту и получить доступ к рабочему пространству.",
|
|
||||||
"Resend verification email": "Отправить письмо для подтверждения повторно",
|
|
||||||
"Verification email sent. Please check your inbox.": "Письмо для подтверждения отправлено. Пожалуйста, проверьте ваш почтовый ящик.",
|
|
||||||
"Failed to resend verification email. Please try again.": "Не удалось отправить письмо для подтверждения. Пожалуйста, попробуйте снова.",
|
|
||||||
"We've sent you an email with your associated workspaces.": "Мы отправили вам электронное письмо с привязанными рабочими пространствами.",
|
|
||||||
"Load more": "Загрузить ещё",
|
|
||||||
"Log out of all devices": "Выйти на всех устройствах",
|
|
||||||
"Log out of all sessions except this device": "Выйти из всех сеансов, кроме этого устройства",
|
|
||||||
"This Device": "Это устройство",
|
|
||||||
"Unknown device": "Неизвестное устройство",
|
|
||||||
"No active sessions": "Нет активных сеансов",
|
|
||||||
"Session revoked": "Сеанс отозван",
|
|
||||||
"All other sessions revoked": "Все остальные сеансы отозваны",
|
|
||||||
"Last used": "Последнее использование",
|
|
||||||
"Created": "Создано",
|
|
||||||
"Rename": "Переименовать",
|
|
||||||
"Publish": "Опубликовать",
|
|
||||||
"Security": "Безопасность",
|
|
||||||
"Enforce SSO": "Сделать SSO обязательным",
|
|
||||||
"Once enforced, members will not be able to login with email and password.": "После включения участники не смогут входить с помощью электронной почты и пароля.",
|
|
||||||
"AI-generated content may not be accurate.": "Контент, созданный ИИ, может быть неточным.",
|
|
||||||
"AI Chat": "Чат с ИИ",
|
|
||||||
"Analyze for insights": "Проанализировать и получить выводы",
|
|
||||||
"Ask anything...": "Спросите что угодно...",
|
|
||||||
"Chat history": "История чатов",
|
|
||||||
"Chat name": "Название чата",
|
|
||||||
"Close": "Закрыть",
|
|
||||||
"Docmost AI": "Docmost AI",
|
|
||||||
"Failed to load chat. An error occurred.": "Не удалось загрузить чат. Произошла ошибка.",
|
|
||||||
"Failed to render this message.": "Не удалось отобразить это сообщение.",
|
|
||||||
"How can I help you today?": "Чем я могу помочь вам сегодня?",
|
|
||||||
"New chat": "Новый чат",
|
|
||||||
"No chat history": "Нет истории чатов",
|
|
||||||
"No chats found": "Чаты не найдены",
|
|
||||||
"No conversations yet": "Пока нет разговоров",
|
|
||||||
"Open full page": "Открыть полную страницу",
|
|
||||||
"Previous 7 days": "Предыдущие 7 дней",
|
|
||||||
"Previous 30 days": "Предыдущие 30 дней",
|
|
||||||
"Search chats...": "Поиск чатов...",
|
|
||||||
"Search chats": "Поиск чатов",
|
|
||||||
"Ask anything... Use @ to mention pages": "Спросите что угодно... Используйте @, чтобы упомянуть страницы",
|
|
||||||
"Ask anything or search your workspace": "Ask anything or search your workspace",
|
|
||||||
"Welcome to {{name}}": "Добро пожаловать в {{name}}",
|
|
||||||
"Add files": "Добавить файлы",
|
|
||||||
"Mention a page": "Упомянуть страницу",
|
|
||||||
"Start a new chat to see it here.": "Начните новый чат, чтобы увидеть его здесь.",
|
|
||||||
"Summarize this page": "Суммировать эту страницу",
|
|
||||||
"Toggle AI Chat": "Переключить чат с ИИ",
|
|
||||||
"Translate this page": "Перевести эту страницу",
|
|
||||||
"Try a different search term.": "Попробуйте другой поисковый запрос.",
|
|
||||||
"Try again": "Попробовать снова",
|
|
||||||
"Untitled chat": "Чат без названия",
|
|
||||||
"What can I help you with?": "Чем я могу вам помочь?",
|
|
||||||
"Are you sure you want to revoke this {{credential}}": "Вы уверены, что хотите отозвать этот {{credential}}",
|
|
||||||
"Automatically provision users and groups from your identity provider via SCIM.": "Автоматически предоставляйте доступ пользователям и группам из вашего провайдера удостоверений через SCIM.",
|
|
||||||
"Configure your identity provider with this URL to provision users and groups.": "Настройте ваш провайдер удостоверений с этим URL для предоставления доступа пользователям и группам.",
|
|
||||||
"Create {{credential}}": "Создать {{credential}}",
|
|
||||||
"{{credential}} created": "{{credential}} создан",
|
|
||||||
"{{credential}} created successfully": "{{credential}} успешно создан",
|
|
||||||
"Created by": "Создан",
|
|
||||||
"Custom": "Пользовательский",
|
|
||||||
"Enable SCIM": "Включить SCIM",
|
|
||||||
"Enter a descriptive name": "Введите понятное имя",
|
|
||||||
"I've saved my {{credential}}": "Я сохранил свой {{credential}}",
|
|
||||||
"Important": "Важно",
|
|
||||||
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Обязательно скопируйте ваш {{credential}} сейчас. Позже вы не сможете увидеть его снова!",
|
|
||||||
"Never": "Никогда",
|
|
||||||
"Revoke {{credential}}": "Отозвать {{credential}}",
|
|
||||||
"SCIM endpoint URL": "URL конечной точки SCIM",
|
|
||||||
"SCIM provisioning": "SCIM-подготовка учетных записей",
|
|
||||||
"SCIM takes precedence over SSO group sync while enabled.": "Пока SCIM включен, он имеет приоритет над синхронизацией групп через SSO.",
|
|
||||||
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "Вы достигли максимального количества токенов SCIM: {{max}}. Удалите существующий токен, чтобы создать новый.",
|
|
||||||
"SCIM token": "Токен SCIM",
|
|
||||||
"SCIM tokens": "Токены SCIM",
|
|
||||||
"This action cannot be undone. Your identity provider will stop syncing immediately.": "Это действие нельзя отменить. Ваш провайдер удостоверений немедленно прекратит синхронизацию.",
|
|
||||||
"Toggle SCIM provisioning": "Переключить подготовку учетных записей SCIM",
|
|
||||||
"Token": "Токен",
|
|
||||||
"Page menu": "Меню страницы",
|
|
||||||
"Expand": "Развернуть",
|
|
||||||
"Collapse": "Свернуть",
|
|
||||||
"Comment menu": "Меню комментария",
|
|
||||||
"Group menu": "Меню группы",
|
|
||||||
"Show hidden breadcrumbs": "Показать скрытые хлебные крошки",
|
|
||||||
"Breadcrumbs": "Хлебные крошки",
|
|
||||||
"Page actions": "Действия со страницей",
|
|
||||||
"Pick emoji": "Выбрать эмодзи",
|
|
||||||
"Template menu": "Меню шаблона",
|
|
||||||
"Use": "Use",
|
|
||||||
"Use template": "Use template",
|
|
||||||
"Preview template: {{title}}": "Preview template: {{title}}",
|
|
||||||
"Use a template": "Use a template",
|
|
||||||
"Search templates...": "Search templates...",
|
|
||||||
"Search spaces...": "Search spaces...",
|
|
||||||
"No templates found": "No templates found",
|
|
||||||
"No spaces found": "No spaces found",
|
|
||||||
"Browse all templates": "Browse all templates",
|
|
||||||
"This space": "This space",
|
|
||||||
"All templates": "All templates",
|
|
||||||
"Global": "Global",
|
|
||||||
"New template": "New template",
|
|
||||||
"Edit template": "Edit template",
|
|
||||||
"Are you sure you want to delete this template?": "Are you sure you want to delete this template?",
|
|
||||||
"Template scope updated": "Template scope updated",
|
|
||||||
"Choose which space this template belongs to": "Choose which space this template belongs to",
|
|
||||||
"Scope": "Scope",
|
|
||||||
"Select scope": "Select scope",
|
|
||||||
"Title": "Title",
|
|
||||||
"Saving...": "Saving...",
|
|
||||||
"Saved": "Saved",
|
|
||||||
"Save failed. Retry": "Save failed. Retry",
|
|
||||||
"By {{name}}": "By {{name}}",
|
|
||||||
"Updated {{time}}": "Updated {{time}}",
|
|
||||||
"Choose destination": "Choose destination",
|
|
||||||
"Search pages and spaces...": "Search pages and spaces...",
|
|
||||||
"No results found": "No results found",
|
|
||||||
"You don't have permission to create pages here": "You don't have permission to create pages here",
|
|
||||||
"Chat menu": "Меню чата",
|
|
||||||
"API key menu": "Меню API-ключа",
|
|
||||||
"Jump to comment selection": "Перейти к выбору комментария",
|
|
||||||
"Slash commands": "Команды со слешем",
|
|
||||||
"Mention suggestions": "Подсказки упоминаний",
|
|
||||||
"Link suggestions": "Подсказки ссылок",
|
|
||||||
"Diagram editor": "Редактор диаграмм",
|
|
||||||
"Add comment": "Добавить комментарий",
|
|
||||||
"Find and replace": "Найти и заменить",
|
|
||||||
"Main navigation": "Основная навигация",
|
|
||||||
"Space navigation": "Навигация по пространству",
|
|
||||||
"Settings navigation": "Навигация по настройкам",
|
|
||||||
"AI navigation": "Навигация ИИ",
|
|
||||||
"Breadcrumb": "Хлебная крошка",
|
|
||||||
"Synced block": "Синхронизированный блок",
|
|
||||||
"Create a block that stays in sync across pages.": "Создайте блок, который будет синхронизироваться между страницами.",
|
|
||||||
"Editing original": "Редактирование оригинала",
|
|
||||||
"Copy synced block": "Скопировать синхронизированный блок",
|
|
||||||
"Unsync": "Не синхронизировать",
|
|
||||||
"Delete synced block": "Удалить синхронизированный блок",
|
|
||||||
"Synced to {{count}} other page_one": "Синхронизировано с {{count}} с другой страницей",
|
|
||||||
"Synced to {{count}} other page_other": "Синхронизировано с {{count}} с другими страницами",
|
|
||||||
"ORIGINAL": "ОРИГИНАЛ",
|
|
||||||
"THIS PAGE": "ЭТА СТРАНИЦА",
|
|
||||||
"No pages": "Нет страниц",
|
|
||||||
"The original synced block no longer exists": "Исходный синхронизированный блок больше не существует",
|
|
||||||
"You don't have access to this synced block": "У вас нет доступа к этому синхронизированному блоку",
|
|
||||||
"Failed to load this synced block": "Не удалось загрузить этот синхронизированный блок",
|
|
||||||
"Fixed editor toolbar": "Fixed editor toolbar",
|
|
||||||
"Show a formatting toolbar above the editor with quick access to common actions.": "Show a formatting toolbar above the editor with quick access to common actions.",
|
|
||||||
"Toggle fixed editor toolbar": "Toggle fixed editor toolbar",
|
|
||||||
"Normal text": "Normal text",
|
|
||||||
"More inline formatting": "More inline formatting",
|
|
||||||
"Subscript": "Подстрочный",
|
|
||||||
"Superscript": "Надстрочный",
|
|
||||||
"Inline code": "Встроенный код",
|
|
||||||
"Insert media": "Вставить медиа",
|
|
||||||
"Mention": "Упоминание",
|
|
||||||
"Emoji": "Эмодзи",
|
|
||||||
"Columns": "Столбцы",
|
|
||||||
"More inserts": "More inserts",
|
|
||||||
"Embeds": "Embeds",
|
|
||||||
"Diagrams": "Диаграммы",
|
|
||||||
"Advanced": "Дополнительно",
|
|
||||||
"Utility": "Utility",
|
|
||||||
"Decrease indent": "Decrease indent",
|
|
||||||
"Increase indent": "Increase indent",
|
|
||||||
"Clear formatting": "Очистить форматирование",
|
|
||||||
"Code block": "Блок кода",
|
|
||||||
"Experimental": "Experimental",
|
|
||||||
"Strikethrough": "Зачеркивание",
|
|
||||||
"Undo": "Отменить",
|
|
||||||
"Redo": "Повторить",
|
|
||||||
"Backlinks": "Обратные ссылки",
|
|
||||||
"Last updated by": "Последний изменивший",
|
|
||||||
"Last updated": "Последнее обновление",
|
|
||||||
"Stats": "Статистика",
|
|
||||||
"Word count": "Количество слов",
|
|
||||||
"Characters": "Символы",
|
|
||||||
"Incoming links": "Incoming links",
|
|
||||||
"Outgoing links": "Outgoing links",
|
|
||||||
"Incoming links ({{count}})": "Incoming links ({{count}})",
|
|
||||||
"Outgoing links ({{count}})": "Outgoing links ({{count}})",
|
|
||||||
"No pages link here yet.": "На эту страницу пока что нет ссылок.",
|
|
||||||
"This page doesn't link to other pages yet.": "Эта страница пока не содержит ссылок на другие страницы.",
|
|
||||||
"Verified until {{date}}": "Подтверждено до: {{date}}",
|
|
||||||
"Labels": "Labels",
|
|
||||||
"Add label": "Add label",
|
|
||||||
"No labels yet": "No labels yet",
|
|
||||||
"Already added": "Already added",
|
|
||||||
"Invalid label name": "Invalid label name",
|
|
||||||
"No matches": "No matches",
|
|
||||||
"Search or create…": "Search or create…",
|
|
||||||
"Remove label {{name}}": "Remove label {{name}}",
|
|
||||||
"Failed to add label": "Failed to add label",
|
|
||||||
"Failed to remove label": "Failed to remove label",
|
|
||||||
"No pages with this label": "No pages with this label",
|
|
||||||
"Pages tagged with this label will appear here.": "Pages tagged with this label will appear here.",
|
|
||||||
"No pages match your search.": "No pages match your search.",
|
|
||||||
"Updated {{date}}": "Updated {{date}}",
|
|
||||||
"Cell actions": "Cell actions",
|
|
||||||
"Column actions": "Column actions",
|
|
||||||
"Row actions": "Row actions"
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@
|
|||||||
"Add members": "添加成员",
|
"Add members": "添加成员",
|
||||||
"Add to groups": "添加到群组",
|
"Add to groups": "添加到群组",
|
||||||
"Add space members": "添加空间成员",
|
"Add space members": "添加空间成员",
|
||||||
"Add to favorites": "添加到收藏",
|
|
||||||
"Admin": "管理员",
|
"Admin": "管理员",
|
||||||
"Are you sure you want to delete this group? Members will lose access to resources this group has access to.": "您确定要删除这个群组吗?成员将失去对该群组可访问资源的访问权限。",
|
"Are you sure you want to delete this group? Members will lose access to resources this group has access to.": "您确定要删除这个群组吗?成员将失去对该群组可访问资源的访问权限。",
|
||||||
"Are you sure you want to delete this page?": "您确定要删除这个页面吗?",
|
"Are you sure you want to delete this page?": "您确定要删除这个页面吗?",
|
||||||
@@ -30,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "选择您喜欢的界面语言。",
|
"Choose your preferred interface language.": "选择您喜欢的界面语言。",
|
||||||
"Choose your preferred page width.": "选择您喜欢的页面宽度。",
|
"Choose your preferred page width.": "选择您喜欢的页面宽度。",
|
||||||
"Confirm": "确认",
|
"Confirm": "确认",
|
||||||
"Copy as Markdown": "复制为Markdown",
|
|
||||||
"Copy link": "复制链接",
|
"Copy link": "复制链接",
|
||||||
"Create": "创建",
|
"Create": "创建",
|
||||||
"Create group": "创建群组",
|
"Create group": "创建群组",
|
||||||
@@ -45,24 +43,23 @@
|
|||||||
"Are you sure you want to delete this page? This will delete its children and page history. This action is irreversible.": "您确定要删除这个页面吗?这将删除其子页面和页面历史记录。此操作不可逆。",
|
"Are you sure you want to delete this page? This will delete its children and page history. This action is irreversible.": "您确定要删除这个页面吗?这将删除其子页面和页面历史记录。此操作不可逆。",
|
||||||
"Description": "描述",
|
"Description": "描述",
|
||||||
"Details": "详情",
|
"Details": "详情",
|
||||||
"e.g ACME": "例如 ACME",
|
"e.g ACME": "例如:ACME",
|
||||||
"e.g ACME Inc": "例如 ACME Inc",
|
"e.g ACME Inc": "例如:ACME Inc",
|
||||||
"e.g Developers": "例如 Developers",
|
"e.g Developers": "例如:开发人员",
|
||||||
"e.g Group for developers": "例如 开发者小组",
|
"e.g Group for developers": "例如:开发人员群组",
|
||||||
"e.g product": "例如 product",
|
"e.g product": "例如:product",
|
||||||
"e.g Product Team": "例如 产品团队",
|
"e.g Product Team": "例如:产品团队",
|
||||||
"e.g Sales": "例如 销售",
|
"e.g Sales": "例如:销售",
|
||||||
"e.g Space for product team": "例如 产品团队空间",
|
"e.g Space for product team": "例如:产品团队的空间",
|
||||||
"e.g Space for sales team to collaborate": "例如 供销售团队协作的空间",
|
"e.g Space for sales team to collaborate": "例如:销售团队协作的空间",
|
||||||
"Edit": "编辑",
|
"Edit": "编辑",
|
||||||
"Read": "读取",
|
|
||||||
"Edit group": "编辑群组",
|
"Edit group": "编辑群组",
|
||||||
"Email": "电子邮箱",
|
"Email": "电子邮箱",
|
||||||
"Enter a strong password": "输入一个强密码",
|
"Enter a strong password": "输入一个强密码",
|
||||||
"Enter valid email addresses separated by comma or space max_50": "输入有效的电子邮箱地址,用逗号或空格分隔 [最多:50个]",
|
"Enter valid email addresses separated by comma or space max_50": "输入有效的电子邮箱地址,用逗号或空格分隔 [最多:50个]",
|
||||||
"enter valid emails addresses": "请输入有效的电子邮箱地址",
|
"enter valid emails addresses": "输入有效的电子邮箱地址",
|
||||||
"Enter your current password": "输入您的当前密码",
|
"Enter your current password": "输入您的当前密码",
|
||||||
"enter your full name": "请输入您的全名",
|
"enter your full name": "输入您的全名",
|
||||||
"Enter your new password": "输入您的新密码",
|
"Enter your new password": "输入您的新密码",
|
||||||
"Enter your new preferred email": "输入您新的首选电子邮箱",
|
"Enter your new preferred email": "输入您新的首选电子邮箱",
|
||||||
"Enter your password": "输入您的密码",
|
"Enter your password": "输入您的密码",
|
||||||
@@ -71,14 +68,10 @@
|
|||||||
"Export": "导出",
|
"Export": "导出",
|
||||||
"Failed to create page": "创建页面失败",
|
"Failed to create page": "创建页面失败",
|
||||||
"Failed to delete page": "删除页面失败",
|
"Failed to delete page": "删除页面失败",
|
||||||
"Failed to restore page": "Failed to restore page",
|
|
||||||
"Failed to fetch recent pages": "获取最近页面失败",
|
"Failed to fetch recent pages": "获取最近页面失败",
|
||||||
"Failed to import pages": "导入页面失败",
|
"Failed to import pages": "导入页面失败",
|
||||||
"Failed to load page. An error occurred.": "页面加载失败。发生了一个错误。",
|
"Failed to load page. An error occurred.": "页面加载失败。发生了一个错误。",
|
||||||
"Failed to update data": "数据更新失败",
|
"Failed to update data": "数据更新失败",
|
||||||
"Favorite spaces": "收藏的空间",
|
|
||||||
"Favorite spaces appear here": "收藏的空间会显示在这里",
|
|
||||||
"Favorites": "收藏",
|
|
||||||
"Full access": "完全访问",
|
"Full access": "完全访问",
|
||||||
"Full page width": "全页宽度",
|
"Full page width": "全页宽度",
|
||||||
"Full width": "全宽",
|
"Full width": "全宽",
|
||||||
@@ -97,7 +90,6 @@
|
|||||||
"Invite by email": "通过电子邮箱邀请",
|
"Invite by email": "通过电子邮箱邀请",
|
||||||
"Invite members": "邀请成员",
|
"Invite members": "邀请成员",
|
||||||
"Invite new members": "邀请新成员",
|
"Invite new members": "邀请新成员",
|
||||||
"Invite People": "邀请成员",
|
|
||||||
"Invited members who are yet to accept their invitation will appear here.": "尚未接受邀请的成员将显示在这里。",
|
"Invited members who are yet to accept their invitation will appear here.": "尚未接受邀请的成员将显示在这里。",
|
||||||
"Invited members will be granted access to spaces the groups can access": "被邀请的成员将被授予访问群组可以访问的空间的权限",
|
"Invited members will be granted access to spaces the groups can access": "被邀请的成员将被授予访问群组可以访问的空间的权限",
|
||||||
"Join the workspace": "加入工作空间",
|
"Join the workspace": "加入工作空间",
|
||||||
@@ -122,7 +114,6 @@
|
|||||||
"No group found": "未找到群组",
|
"No group found": "未找到群组",
|
||||||
"No page history saved yet.": "尚未保存页面历史。",
|
"No page history saved yet.": "尚未保存页面历史。",
|
||||||
"No pages yet": "暂无页面",
|
"No pages yet": "暂无页面",
|
||||||
"No shared pages": "没有共享页面",
|
|
||||||
"No results found...": "未找到结果...",
|
"No results found...": "未找到结果...",
|
||||||
"No user found": "未找到用户",
|
"No user found": "未找到用户",
|
||||||
"Overview": "概览",
|
"Overview": "概览",
|
||||||
@@ -130,14 +121,11 @@
|
|||||||
"page": "个页面",
|
"page": "个页面",
|
||||||
"Page deleted successfully": "页面已成功删除",
|
"Page deleted successfully": "页面已成功删除",
|
||||||
"Page history": "页面历史",
|
"Page history": "页面历史",
|
||||||
"Select version": "选择版本",
|
|
||||||
"Highlight changes": "突出显示更改",
|
|
||||||
"Page import is in progress. Please do not close this tab.": "页面导入正在进行中。请不要关闭此标签页。",
|
"Page import is in progress. Please do not close this tab.": "页面导入正在进行中。请不要关闭此标签页。",
|
||||||
"Pages": "页面",
|
"Pages": "页面",
|
||||||
"pages": "个页面",
|
"pages": "个页面",
|
||||||
"Password": "密码",
|
"Password": "密码",
|
||||||
"Password changed successfully": "密码更改成功",
|
"Password changed successfully": "密码更改成功",
|
||||||
"People": "人员",
|
|
||||||
"Pending": "待定",
|
"Pending": "待定",
|
||||||
"Please confirm your action": "请确认您的操作",
|
"Please confirm your action": "请确认您的操作",
|
||||||
"Preferences": "偏好设置",
|
"Preferences": "偏好设置",
|
||||||
@@ -145,7 +133,6 @@
|
|||||||
"Profile": "个人资料",
|
"Profile": "个人资料",
|
||||||
"Recently updated": "最近更新",
|
"Recently updated": "最近更新",
|
||||||
"Remove": "移除",
|
"Remove": "移除",
|
||||||
"Remove from favorites": "从收藏中移除",
|
|
||||||
"Remove group member": "移除群组成员",
|
"Remove group member": "移除群组成员",
|
||||||
"Remove space member": "移除空间成员",
|
"Remove space member": "移除空间成员",
|
||||||
"Restore": "恢复",
|
"Restore": "恢复",
|
||||||
@@ -158,54 +145,53 @@
|
|||||||
"Search...": "搜索...",
|
"Search...": "搜索...",
|
||||||
"Select language": "选择语言",
|
"Select language": "选择语言",
|
||||||
"Select role": "选择角色",
|
"Select role": "选择角色",
|
||||||
"Select role to assign to all invited members": "选择要分配给所有受邀成员的角色",
|
"Select role to assign to all invited members": "选择要分配给所有被邀请成员的角色",
|
||||||
"Select theme": "选择主题",
|
"Select theme": "选择主题",
|
||||||
"Send invitation": "发送邀请",
|
"Send invitation": "发送邀请",
|
||||||
"Invitation sent": "邀请已发送",
|
"Invitation sent": "邀请邮件已发送",
|
||||||
"Settings": "设置",
|
"Settings": "设置",
|
||||||
"Setup workspace": "设置工作区",
|
"Setup workspace": "设置工作空间",
|
||||||
"Sign In": "登录",
|
"Sign In": "登录",
|
||||||
"Sign Up": "注册",
|
"Sign Up": "注册",
|
||||||
"Slug": "标识符",
|
"Slug": "短链接",
|
||||||
"Space": "空间",
|
"Space": "空间",
|
||||||
"Space description": "空间描述",
|
"Space description": "空间描述",
|
||||||
"Space menu": "空间菜单",
|
"Space menu": "空间菜单",
|
||||||
"Space name": "空间名称",
|
"Space name": "空间名称",
|
||||||
"Space settings": "空间设置",
|
"Space settings": "空间设置",
|
||||||
"Space slug": "空间标识符",
|
"Space slug": "空间短链接",
|
||||||
"Spaces": "空间",
|
"Spaces": "空间",
|
||||||
"Spaces you belong to": "您所属的空间",
|
"Spaces you belong to": "您所属的空间",
|
||||||
"No space found": "未找到空间",
|
"No space found": "未找到空间",
|
||||||
"Search for spaces": "搜索空间",
|
"Search for spaces": "搜索空间",
|
||||||
"Start typing to search...": "开始输入以搜索...",
|
"Start typing to search...": "开始输入以搜索...",
|
||||||
"Status": "状态",
|
"Status": "状态",
|
||||||
"Successfully imported": "导入成功",
|
"Successfully imported": "成功导入",
|
||||||
"Successfully restored": "恢复成功",
|
"Successfully restored": "恢复成功",
|
||||||
"System settings": "系统设置",
|
"System settings": "系统设置",
|
||||||
"Templates": "模板",
|
|
||||||
"Theme": "主题",
|
"Theme": "主题",
|
||||||
"To change your email, you have to enter your password and new email.": "要更改您的电子邮箱,您需要输入密码和新的电子邮箱地址。",
|
"To change your email, you have to enter your password and new email.": "要更改您的电子邮箱,您需要输入密码和新的电子邮箱地址。",
|
||||||
"Toggle full page width": "切换整页宽度",
|
"Toggle full page width": "切换全页宽度",
|
||||||
"Unable to import pages. Please try again.": "无法导入页面。请重试。",
|
"Unable to import pages. Please try again.": "无法导入页面。请重试。",
|
||||||
"untitled": "未命名",
|
"untitled": "无标题",
|
||||||
"Untitled": "未命名",
|
"Untitled": "无标题",
|
||||||
"Updated successfully": "更新成功",
|
"Updated successfully": "更新成功",
|
||||||
"User": "用户",
|
"User": "用户",
|
||||||
"Workspace": "工作区",
|
"Workspace": "工作区",
|
||||||
"Workspace Name": "工作区名称",
|
"Workspace Name": "工作空间名称",
|
||||||
"Workspace settings": "工作区设置",
|
"Workspace settings": "工作区设置",
|
||||||
"You can change your password here.": "您可以在这里更改密码。",
|
"You can change your password here.": "您可以在这里更改密码。",
|
||||||
"Your Email": "您的邮箱",
|
"Your Email": "您的电子邮箱",
|
||||||
"Your import is complete.": "导入已完成。",
|
"Your import is complete.": "导入已完成。",
|
||||||
"Your name": "您的姓名",
|
"Your name": "您的姓名",
|
||||||
"Your Name": "您的姓名",
|
"Your Name": "您的姓名",
|
||||||
"Your password": "您的密码",
|
"Your password": "您的密码",
|
||||||
"Your password must be a minimum of 8 characters.": "您的密码必须至少包含8个字符。",
|
"Your password must be a minimum of 8 characters.": "您的密码必须至少包含8个字符。",
|
||||||
"Sidebar toggle": "侧边栏切换",
|
"Sidebar toggle": "切换侧边栏",
|
||||||
"Comments": "评论",
|
"Comments": "评论",
|
||||||
"404 page not found": "404 页面未找到",
|
"404 page not found": "404 页面未找到",
|
||||||
"Sorry, we can't find the page you are looking for.": "抱歉,我们无法找到你所需要的页面",
|
"Sorry, we can't find the page you are looking for.": "抱歉,我们无法找到你所需要的页面",
|
||||||
"Take me back to homepage": "返回首页",
|
"Take me back to homepage": "回到主页",
|
||||||
"Forgot password": "忘记密码",
|
"Forgot password": "忘记密码",
|
||||||
"Forgot your password?": "忘记密码了吗?",
|
"Forgot your password?": "忘记密码了吗?",
|
||||||
"A password reset link has been sent to your email. Please check your inbox.": "密码重置链接已经发送到您的邮箱,请检查收件箱",
|
"A password reset link has been sent to your email. Please check your inbox.": "密码重置链接已经发送到您的邮箱,请检查收件箱",
|
||||||
@@ -217,14 +203,9 @@
|
|||||||
"Reply...": "回复...",
|
"Reply...": "回复...",
|
||||||
"Error loading comments.": "加载评论时出错",
|
"Error loading comments.": "加载评论时出错",
|
||||||
"No comments yet.": "目前还没有评论",
|
"No comments yet.": "目前还没有评论",
|
||||||
"No open comments.": "没有未解决的评论。",
|
|
||||||
"No resolved comments.": "没有已解决的评论。",
|
|
||||||
"Add a comment...": "添加评论...",
|
|
||||||
"Edit comment": "编辑评论",
|
"Edit comment": "编辑评论",
|
||||||
"Delete comment": "删除评论",
|
"Delete comment": "删除评论",
|
||||||
"Are you sure you want to delete this comment?": "你确定要删除这条评论吗?",
|
"Are you sure you want to delete this comment?": "你确定要删除这条评论吗?",
|
||||||
"Delete chat": "删除聊天",
|
|
||||||
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "您确定要删除「{{title}}」吗?此操作无法撤销。",
|
|
||||||
"Comment created successfully": "成功创建评论",
|
"Comment created successfully": "成功创建评论",
|
||||||
"Error creating comment": "创建评论时出错",
|
"Error creating comment": "创建评论时出错",
|
||||||
"Comment updated successfully": "评论更新成功",
|
"Comment updated successfully": "评论更新成功",
|
||||||
@@ -232,8 +213,8 @@
|
|||||||
"Comment deleted successfully": "成功删除评论",
|
"Comment deleted successfully": "成功删除评论",
|
||||||
"Failed to delete comment": "删除评论失败",
|
"Failed to delete comment": "删除评论失败",
|
||||||
"Comment resolved successfully": "成功标记评论为解决",
|
"Comment resolved successfully": "成功标记评论为解决",
|
||||||
"Comment re-opened successfully": "评论重新打开成功",
|
"Comment re-opened successfully": "成功重新打开评论",
|
||||||
"Comment unresolved successfully": "评论已成功取消解决",
|
"Comment unresolved successfully": "成功标记评论为未解决",
|
||||||
"Failed to resolve comment": "标记评论为解决失败",
|
"Failed to resolve comment": "标记评论为解决失败",
|
||||||
"Resolve comment": "解决评论",
|
"Resolve comment": "解决评论",
|
||||||
"Unresolve comment": "取消解决评论",
|
"Unresolve comment": "取消解决评论",
|
||||||
@@ -243,6 +224,7 @@
|
|||||||
"Are you sure you want to unresolve this comment thread?": "确定要取消解决此评论线程吗?",
|
"Are you sure you want to unresolve this comment thread?": "确定要取消解决此评论线程吗?",
|
||||||
"Resolved": "已解决",
|
"Resolved": "已解决",
|
||||||
"No active comments.": "没有活跃的评论。",
|
"No active comments.": "没有活跃的评论。",
|
||||||
|
"No resolved comments.": "没有已解决的评论。",
|
||||||
"Revoke invitation": "撤回邀请",
|
"Revoke invitation": "撤回邀请",
|
||||||
"Revoke": "撤销",
|
"Revoke": "撤销",
|
||||||
"Don't": "不要",
|
"Don't": "不要",
|
||||||
@@ -253,7 +235,7 @@
|
|||||||
"Copy": "复制",
|
"Copy": "复制",
|
||||||
"Copy to space": "复制到空间",
|
"Copy to space": "复制到空间",
|
||||||
"Copied": "已复制",
|
"Copied": "已复制",
|
||||||
"Duplicate": "复制",
|
"Duplicate": "重复",
|
||||||
"Select a user": "选择一个用户",
|
"Select a user": "选择一个用户",
|
||||||
"Select a group": "选择一个组",
|
"Select a group": "选择一个组",
|
||||||
"Export all pages and attachments in this space.": "导出当前空间的所有页面和附件",
|
"Export all pages and attachments in this space.": "导出当前空间的所有页面和附件",
|
||||||
@@ -270,7 +252,6 @@
|
|||||||
"Export failed:": "导出失败:",
|
"Export failed:": "导出失败:",
|
||||||
"export error": "导出出错",
|
"export error": "导出出错",
|
||||||
"Export page": "导出页面",
|
"Export page": "导出页面",
|
||||||
"Export successful": "导出成功",
|
|
||||||
"Export space": "导出空间",
|
"Export space": "导出空间",
|
||||||
"Export {{type}}": "导出为 {{type}}",
|
"Export {{type}}": "导出为 {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "文件超出了 {{limit}} 类型附件限制",
|
"File exceeds the {{limit}} attachment limit": "文件超出了 {{limit}} 类型附件限制",
|
||||||
@@ -287,21 +268,7 @@
|
|||||||
"Add row above": "在上方添加行",
|
"Add row above": "在上方添加行",
|
||||||
"Add row below": "在下方插入行",
|
"Add row below": "在下方插入行",
|
||||||
"Delete table": "删除表格",
|
"Delete table": "删除表格",
|
||||||
"Add column left": "Add column left",
|
|
||||||
"Add column right": "Add column right",
|
|
||||||
"Clear cell": "Clear cell",
|
|
||||||
"Clear cells": "Clear cells",
|
|
||||||
"Toggle header cell": "Toggle header cell",
|
|
||||||
"Toggle header column": "Toggle header column",
|
|
||||||
"Toggle header row": "Toggle header row",
|
|
||||||
"Move column left": "Move column left",
|
|
||||||
"Move column right": "Move column right",
|
|
||||||
"Move row down": "Move row down",
|
|
||||||
"Move row up": "Move row up",
|
|
||||||
"Sort A → Z": "Sort A → Z",
|
|
||||||
"Sort Z → A": "Sort Z → A",
|
|
||||||
"Info": "信息",
|
"Info": "信息",
|
||||||
"Note": "注意",
|
|
||||||
"Success": "成功",
|
"Success": "成功",
|
||||||
"Warning": "警告",
|
"Warning": "警告",
|
||||||
"Danger": "危险",
|
"Danger": "危险",
|
||||||
@@ -312,11 +279,6 @@
|
|||||||
"Save & Exit": "保存并退出",
|
"Save & Exit": "保存并退出",
|
||||||
"Double-click to edit Excalidraw diagram": "双击以编辑 Excalidraw 图表",
|
"Double-click to edit Excalidraw diagram": "双击以编辑 Excalidraw 图表",
|
||||||
"Paste link": "粘贴链接",
|
"Paste link": "粘贴链接",
|
||||||
"Paste link or search pages": "粘贴链接或搜索页面",
|
|
||||||
"Link to web page": "链接到网页",
|
|
||||||
"Recents": "最近使用",
|
|
||||||
"Page or URL": "页面或网址",
|
|
||||||
"Link title": "链接标题",
|
|
||||||
"Edit link": "编辑链接",
|
"Edit link": "编辑链接",
|
||||||
"Remove link": "移除链接",
|
"Remove link": "移除链接",
|
||||||
"Add link": "添加链接",
|
"Add link": "添加链接",
|
||||||
@@ -362,30 +324,19 @@
|
|||||||
"Create block quote.": "创建引用块",
|
"Create block quote.": "创建引用块",
|
||||||
"Insert code snippet.": "插入代码片段",
|
"Insert code snippet.": "插入代码片段",
|
||||||
"Insert horizontal rule divider": "插入水平分割线",
|
"Insert horizontal rule divider": "插入水平分割线",
|
||||||
"Page break": "Page break",
|
|
||||||
"Insert a page break for printing.": "Insert a page break for printing.",
|
|
||||||
"Upload any image from your device.": "从设备上传任何图像",
|
"Upload any image from your device.": "从设备上传任何图像",
|
||||||
"Upload any video from your device.": "从设备上传任何视频",
|
"Upload any video from your device.": "从设备上传任何视频",
|
||||||
"Upload any audio from your device.": "从您的设备上传任意音频文件。",
|
|
||||||
"Upload any file from your device.": "从设备上传任何文件",
|
"Upload any file from your device.": "从设备上传任何文件",
|
||||||
"Uploading {{name}}": "正在上传{{name}}",
|
|
||||||
"Uploading file": "正在上传文件",
|
|
||||||
"Table": "表格",
|
"Table": "表格",
|
||||||
"Insert a table.": "插入一个表格",
|
"Insert a table.": "插入一个表格",
|
||||||
"Insert collapsible block.": "插入一个折叠块",
|
"Insert collapsible block.": "插入一个折叠块",
|
||||||
"Video": "视频",
|
"Video": "视频",
|
||||||
"Divider": "分割线",
|
"Divider": "分割线",
|
||||||
"Quote": "引用",
|
"Quote": "引用",
|
||||||
"Image": "图片",
|
"Image": "图像",
|
||||||
"Audio": "音频",
|
|
||||||
"Embed PDF": "嵌入 PDF",
|
|
||||||
"Upload and embed a PDF file.": "上传并嵌入 PDF 文件。",
|
|
||||||
"Embed as PDF": "作为 PDF 嵌入",
|
|
||||||
"Failed to load PDF": "加载 PDF 失败",
|
|
||||||
"Convert to attachment": "转换为附件",
|
|
||||||
"File attachment": "文件附件",
|
"File attachment": "文件附件",
|
||||||
"Toggle block": "折叠块",
|
"Toggle block": "切换块",
|
||||||
"Callout": "提示块",
|
"Callout": "标注块",
|
||||||
"Insert callout notice.": "插入标注提示块",
|
"Insert callout notice.": "插入标注提示块",
|
||||||
"Math inline": "行内公式",
|
"Math inline": "行内公式",
|
||||||
"Insert inline math equation.": "插入行内公式",
|
"Insert inline math equation.": "插入行内公式",
|
||||||
@@ -393,57 +344,35 @@
|
|||||||
"Insert math equation": "插入数学公式",
|
"Insert math equation": "插入数学公式",
|
||||||
"Mermaid diagram": "Mermaid 图表",
|
"Mermaid diagram": "Mermaid 图表",
|
||||||
"Insert mermaid diagram": "插入 Mermaid 图表",
|
"Insert mermaid diagram": "插入 Mermaid 图表",
|
||||||
"Insert and design Drawio diagrams": "插入并设计 Drawio 图表",
|
"Insert and design Drawio diagrams": "插入并设计 Draw.io 图表",
|
||||||
"Insert current date": "插入当前日期",
|
"Insert current date": "插入当前日期",
|
||||||
"Draw and sketch excalidraw diagrams": "绘制和草绘 Excalidraw 图表",
|
"Draw and sketch excalidraw diagrams": "绘制 Excalidraw 图表",
|
||||||
"Multiple": "多个",
|
"Multiple": "多个",
|
||||||
"Turn into": "变成",
|
"Heading {{level}}": "{{level}} 级标题",
|
||||||
"Text align": "文本对齐",
|
"Toggle title": "切换标题",
|
||||||
"This page may have been deleted, moved, or you may not have access.": "此页面可能已被删除、移动,或者您可能无权访问。{",
|
"Write anything. Enter \"/\" for commands": "开始编写内容,输入 \"/\" 以使用指令",
|
||||||
"Go to homepage": "前往首页",
|
|
||||||
"Pages you create will show up here.": "您创建的页面将显示在此处。",
|
|
||||||
"Heading {{level}}": "标题 {{level}}",
|
|
||||||
"Toggle title": "折叠标题",
|
|
||||||
"Write anything. Enter \"/\" for commands": "输入任意内容。输入“/”查看命令",
|
|
||||||
"Write...": "写点内容...",
|
|
||||||
"Column count": "列数",
|
|
||||||
"{{count}} Columns": "{{count}} 列",
|
|
||||||
"Equal columns": "等宽列",
|
|
||||||
"Left sidebar": "左侧边栏",
|
|
||||||
"Right sidebar": "右侧边栏",
|
|
||||||
"Wide center": "中间加宽",
|
|
||||||
"Left wide": "左侧加宽",
|
|
||||||
"Right wide": "右侧加宽",
|
|
||||||
"Names do not match": "名称不匹配",
|
"Names do not match": "名称不匹配",
|
||||||
"Today, {{time}}": "今天,{{time}}",
|
"Today, {{time}}": "今天,{{time}}",
|
||||||
"Yesterday, {{time}}": "昨天,{{time}}",
|
"Yesterday, {{time}}": "昨天,{{time}}",
|
||||||
"Space created successfully": "空间创建成功",
|
"Space created successfully": "空间创建成功",
|
||||||
"Space updated successfully": "空间更新成功",
|
"Space updated successfully": "空间更新成功",
|
||||||
"Space deleted successfully": "空间删除成功",
|
"Space deleted successfully": "空间已成功删除",
|
||||||
"Members added successfully": "成员添加成功",
|
"Members added successfully": "成员添加成功",
|
||||||
"Member removed successfully": "成员移除成功",
|
"Member removed successfully": "成员移除成功",
|
||||||
"Member role updated successfully": "成员角色更新成功",
|
"Member role updated successfully": "成员角色更新成功",
|
||||||
"Created by: <b>{{creatorName}}</b>": "创建者:<b>{{creatorName}}</b>",
|
"Created by: <b>{{creatorName}}</b>": "创建者:<b>{{creatorName}}</b>",
|
||||||
"Created at: {{time}}": "创建于:{{time}}",
|
"Created at: {{time}}": "创建于:{{time}}",
|
||||||
"Edited by {{name}} {{time}}": "由 {{name}} 编辑于 {{time}}",
|
"Edited by {{name}} {{time}}": "由{{name}} 编辑于 {{time}}",
|
||||||
"Word count: {{wordCount}}": "字数:{{wordCount}}",
|
"Word count: {{wordCount}}": "字数:{{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "字符数:{{characterCount}}",
|
"Character count: {{characterCount}}": "字符数:{{characterCount}}",
|
||||||
"New update": "新更新",
|
"New update": "新更新",
|
||||||
"{{latestVersion}} is available": "{{latestVersion}} 可用",
|
"{{latestVersion}} is available": "{{latestVersion}} 已经可以使用",
|
||||||
"Default page edit mode": "默认页面编辑模式",
|
"Default page edit mode": "默认页面编辑模式",
|
||||||
"Choose your preferred page edit mode. Avoid accidental edits.": "选择您偏好的页面编辑模式。避免意外编辑。",
|
"Choose your preferred page edit mode. Avoid accidental edits.": "选择您偏好的页面编辑模式。避免意外编辑。",
|
||||||
"Choose {{format}} file": "选择 {{format}} 文件",
|
|
||||||
"Reading": "阅读",
|
"Reading": "阅读",
|
||||||
"Delete member": "删除成员",
|
"Delete member": "删除成员",
|
||||||
"Member deleted successfully": "成员删除成功",
|
"Member deleted successfully": "成员删除成功",
|
||||||
"Are you sure you want to delete this workspace member? This action is irreversible.": "您确定要删除此工作区成员吗?此操作不可逆。",
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "您确定要删除此工作区成员吗?此操作不可逆。",
|
||||||
"Deactivate member": "停用成员",
|
|
||||||
"Activate member": "激活成员",
|
|
||||||
"Are you sure you want to deactivate this workspace member? They will no longer be able to access this workspace.": "您确定要停用此工作区成员吗?该成员将无法再访问此工作区。",
|
|
||||||
"Are you sure you want to activate this workspace member?": "您确定要激活此工作区成员吗?",
|
|
||||||
"Deactivate": "停用",
|
|
||||||
"Activate": "激活",
|
|
||||||
"Deactivated": "已停用",
|
|
||||||
"Move": "移动",
|
"Move": "移动",
|
||||||
"Move page": "移动页面",
|
"Move page": "移动页面",
|
||||||
"Move page to a different space.": "将页面移动到不同的空间。",
|
"Move page to a different space.": "将页面移动到不同的空间。",
|
||||||
@@ -453,599 +382,118 @@
|
|||||||
"Share": "分享",
|
"Share": "分享",
|
||||||
"Public sharing": "公开分享",
|
"Public sharing": "公开分享",
|
||||||
"Shared by": "分享者",
|
"Shared by": "分享者",
|
||||||
"Shared at": "分享于",
|
"Shared at": "分享时间",
|
||||||
"Inherits public sharing from": "继承公开分享自",
|
"Inherits public sharing from": "继承自的公开分享",
|
||||||
"Share to web": "分享到网页",
|
"Share to web": "分享到网页",
|
||||||
"Shared to web": "已分享到网页",
|
"Shared to web": "已分享到网页",
|
||||||
"Anyone with the link can view this page": "任何拥有链接的人都可以查看此页面",
|
"Anyone with the link can view this page": "任何有链接的人都可以查看此页面",
|
||||||
"Make this page publicly accessible": "将此页面设为公开可访问",
|
"Make this page publicly accessible": "使此页面可公开访问",
|
||||||
"Include sub-pages": "包含子页面",
|
"Include sub-pages": "包括子页面",
|
||||||
"Make sub-pages public too": "同时将子页面设为公开",
|
"Make sub-pages public too": "将子页面也设为公开",
|
||||||
"Allow search engines to index page": "允许搜索引擎索引页面",
|
"Allow search engines to index page": "允许搜索引擎索引页面",
|
||||||
"Open page": "打开页面",
|
"Open page": "打开页面",
|
||||||
"Page": "页面",
|
"Page": "页面",
|
||||||
"Delete public share link": "删除公开分享链接",
|
"Delete public share link": "删除公开分享链接",
|
||||||
"Delete share": "删除分享",
|
"Delete share": "删除分享",
|
||||||
"Are you sure you want to delete this shared link?": "您确定要删除此分享链接吗?",
|
"Are you sure you want to delete this shared link?": "您确定要删除此分享链接吗?",
|
||||||
"Publicly shared pages from spaces you are a member of will appear here": "您所属空间中公开分享的页面将显示在这里",
|
"Publicly shared pages from spaces you are a member of will appear here": "您所在空间的公开共享页面会显示在此处",
|
||||||
"Share deleted successfully": "分享删除成功",
|
"Share deleted successfully": "分享已成功删除",
|
||||||
"Share not found": "未找到分享",
|
"Share not found": "未找到分享",
|
||||||
"Failed to share page": "页面分享失败",
|
"Failed to share page": "页面分享失败",
|
||||||
"Disable public sharing": "禁用公开分享",
|
|
||||||
"Prevent members from sharing pages publicly.": "阻止成员公开分享页面。",
|
|
||||||
"Toggle public sharing": "切换公开分享",
|
|
||||||
"Toggle space public sharing": "切换空间公开分享",
|
|
||||||
"Allow viewers to comment": "允许观众评论",
|
|
||||||
"Allow viewers to add comments on pages in this space.": "允许观众在此空间的页面上添加评论。",
|
|
||||||
"Toggle viewer comments": "切换观众评论",
|
|
||||||
"Public sharing is disabled at the workspace level": "公开分享在工作区级别被禁用",
|
|
||||||
"Prevent pages in this space from being shared publicly.": "阻止此空间中的页面被公开分享。",
|
|
||||||
"Page permissions": "页面权限},{",
|
|
||||||
"Control who can view and edit individual pages. Available with an enterprise license.": "控制谁可以查看和编辑单个页面。此功能在企业版许可下可用。",
|
|
||||||
"Enable public sharing": "启用公开分享",
|
|
||||||
"Are you sure you want to enable public sharing? Members will be able to share pages publicly.": "您确定要启用公开分享吗?成员将能够公开分享页面。",
|
|
||||||
"Are you sure you want to disable public sharing? All existing shared links in this workspace will be deleted.": "您确定要禁用公开分享吗?此工作区中的所有现有共享链接都将被删除。",
|
|
||||||
"Are you sure you want to enable public sharing for this space?": "您确定要为此空间启用公开分享吗?",
|
|
||||||
"Are you sure you want to disable public sharing? All existing shared links in this space will be deleted.": "您确定要禁用公开分享吗?此空间中的所有现有共享链接都将被删除。",
|
|
||||||
"Public sharing is disabled": "公开分享已被禁用",
|
|
||||||
"Public sharing has been disabled at the workspace level.": "公开分享已在工作区级别被禁用。",
|
|
||||||
"Public sharing has been disabled for this space.": "此空间的公开分享已被禁用。",
|
|
||||||
"Copy page": "复制页面",
|
"Copy page": "复制页面",
|
||||||
"Copy page to a different space.": "将页面复制到不同的空间。",
|
"Copy page to a different space.": "将页面复制到不同的空间。",
|
||||||
"Page copied successfully": "页面复制成功",
|
"Page copied successfully": "页面复制成功",
|
||||||
"Page duplicated successfully": "页面副本创建成功",
|
"Page duplicated successfully": "页面复制成功",
|
||||||
"Find": "查找",
|
"Find": "查找",
|
||||||
"Not found": "未找到",
|
"Not found": "未找到",
|
||||||
"Previous Match (Shift+Enter)": "上一个匹配项(Shift+Enter)",
|
"Previous Match (Shift+Enter)": "上一个匹配 (Shift+Enter)",
|
||||||
"Next match (Enter)": "下一个匹配项(Enter)",
|
"Next match (Enter)": "下一个匹配 (Enter)",
|
||||||
"Match case (Alt+C)": "区分大小写(Alt+C)",
|
"Match case (Alt+C)": "区分大小写 (Alt+C)",
|
||||||
"Replace": "替换",
|
"Replace": "替换",
|
||||||
"Close (Escape)": "关闭(Escape)",
|
"Close (Escape)": "关闭 (Escape)",
|
||||||
"Replace (Enter)": "替换(Enter)",
|
"Replace (Enter)": "替换 (Enter)",
|
||||||
"Replace all (Ctrl+Alt+Enter)": "全部替换(Ctrl+Alt+Enter)",
|
"Replace all (Ctrl+Alt+Enter)": "全部替换 (Ctrl+Alt+Enter)",
|
||||||
"Replace all": "全部替换",
|
"Replace all": "全部替换",
|
||||||
"View all": "查看全部",
|
|
||||||
"View all spaces": "查看所有空间",
|
"View all spaces": "查看所有空间",
|
||||||
"Error": "错误",
|
"Error": "错误",
|
||||||
"Failed to disable MFA": "禁用 MFA 失败",
|
"Failed to disable MFA": "停用 MFA 失败",
|
||||||
"Disable two-factor authentication": "禁用双重身份验证",
|
"Disable two-factor authentication": "停用双因素认证",
|
||||||
"Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "停用双因素认证会降低账户安全性。您只需密码即可登录。",
|
"Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "停用双因素认证会降低账户安全性。您只需密码即可登录。",
|
||||||
"Please enter your password to disable two-factor authentication:": "请输入您的密码以停用双因素认证:",
|
"Please enter your password to disable two-factor authentication:": "请输入您的密码以停用双因素认证:",
|
||||||
"Two-factor authentication has been enabled": "双重身份验证已启用",
|
"Two-factor authentication has been enabled": "双因素认证已启用",
|
||||||
"Two-factor authentication has been disabled": "双重身份验证已禁用",
|
"Two-factor authentication has been disabled": "双因素认证已停用",
|
||||||
"2-step verification": "两步验证",
|
"2-step verification": "两步验证",
|
||||||
"Protect your account with an additional verification layer when signing in.": "通过额外的验证层保护您的账户安全。",
|
"Protect your account with an additional verification layer when signing in.": "通过额外的验证层保护您的账户安全。",
|
||||||
"Two-factor authentication is active on your account.": "您的账户已激活双因素认证。",
|
"Two-factor authentication is active on your account.": "您的账户已激活双因素认证。",
|
||||||
"Add 2FA method": "添加 2FA 方式",
|
"Add 2FA method": "添加 2FA 方法",
|
||||||
"Backup codes": "备用代码",
|
"Backup codes": "备份代码",
|
||||||
"Disable": "禁用",
|
"Disable": "停用",
|
||||||
"Invalid verification code": "无效的验证码",
|
"Invalid verification code": "无效的验证码",
|
||||||
"New backup codes have been generated": "新的备用代码已生成",
|
"New backup codes have been generated": "已生成新的备份代码",
|
||||||
"Failed to regenerate backup codes": "重新生成备用代码失败",
|
"Failed to regenerate backup codes": "重新生成备份代码失败",
|
||||||
"About backup codes": "关于备用代码",
|
"About backup codes": "关于备份代码",
|
||||||
"Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "如果您无法访问身份验证器应用,可使用备份代码访问账户。每个代码仅可使用一次。",
|
"Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "如果您无法访问身份验证器应用,可使用备份代码访问账户。每个代码仅可使用一次。",
|
||||||
"You can regenerate new backup codes at any time. This will invalidate all existing codes.": "您可以随时重新生成新的备份代码。这将使所有现有代码失效。",
|
"You can regenerate new backup codes at any time. This will invalidate all existing codes.": "您可以随时重新生成新的备份代码。这将使所有现有代码失效。",
|
||||||
"Confirm password": "确认密码",
|
"Confirm password": "确认密码",
|
||||||
"Generate new backup codes": "生成新的备用代码",
|
"Generate new backup codes": "生成新的备份代码",
|
||||||
"Save your new backup codes": "保存您的新备用代码",
|
"Save your new backup codes": "保存您的新备份代码",
|
||||||
"Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "请确保将这些代码保存在安全的地方。您的旧备份代码不再有效。",
|
"Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "请确保将这些代码保存在安全的地方。您的旧备份代码不再有效。",
|
||||||
"Your new backup codes": "您的新备用代码",
|
"Your new backup codes": "您的新备份代码",
|
||||||
"I've saved my backup codes": "我已保存备用代码",
|
"I've saved my backup codes": "我已经保存了我的备份代码",
|
||||||
"Failed to setup MFA": "设置 MFA 失败",
|
"Failed to setup MFA": "设置 MFA 失败",
|
||||||
"Setup & Verify": "设置并验证",
|
"Setup & Verify": "设置并验证",
|
||||||
"Add to authenticator": "添加到身份验证器",
|
"Add to authenticator": "添加到身份验证器",
|
||||||
"1. Scan this QR code with your authenticator app": "1. 使用您的身份验证器应用扫描此二维码",
|
"1. Scan this QR code with your authenticator app": "1. 用身份验证器应用扫描此二维码",
|
||||||
"Can't scan the code?": "无法扫描代码?",
|
"Can't scan the code?": "无法扫描代码?",
|
||||||
"Enter this code manually in your authenticator app:": "在您的身份验证器应用中手动输入此代码:",
|
"Enter this code manually in your authenticator app:": "在您的身份验证器应用中手动输入此代码:",
|
||||||
"2. Enter the 6-digit code from your authenticator": "2. 输入您的身份验证器中的 6 位代码",
|
"2. Enter the 6-digit code from your authenticator": "2. 输入来自身份验证器的6位代码",
|
||||||
"Verify and enable": "验证并启用",
|
"Verify and enable": "验证并启用",
|
||||||
"Failed to generate QR code. Please try again.": "生成二维码失败。请重试。",
|
"Failed to generate QR code. Please try again.": "生成二维码失败。请重试。",
|
||||||
"Backup": "备用",
|
"Backup": "备份",
|
||||||
"Save codes": "保存代码",
|
"Save codes": "保存代码",
|
||||||
"Save your backup codes": "保存您的备用代码",
|
"Save your backup codes": "保存您的备份代码",
|
||||||
"These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "如果无法访问身份验证器应用,可以使用这些代码访问账户。每个代码仅可使用一次。",
|
"These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "如果无法访问身份验证器应用,可以使用这些代码访问账户。每个代码仅可使用一次。",
|
||||||
"Print": "打印",
|
"Print": "打印",
|
||||||
"Two-factor authentication has been set up. Please log in again.": "双因素认证已设置。请重新登录。",
|
"Two-factor authentication has been set up. Please log in again.": "双因素认证已设置。请重新登录。",
|
||||||
"Two-Factor authentication required": "需要双重身份验证",
|
"Two-Factor authentication required": "需要双因素认证",
|
||||||
"Your workspace requires two-factor authentication for all users": "您的工作区要求所有用户启用双重身份验证",
|
"Your workspace requires two-factor authentication for all users": "您的工作区要求所有用户启用双因素认证",
|
||||||
"To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "要继续访问工作区,必须设置双因素认证。此操作为您的账户添加一层额外的安全保障。",
|
"To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "要继续访问工作区,必须设置双因素认证。此操作为您的账户添加一层额外的安全保障。",
|
||||||
"Set up two-factor authentication": "设置双重身份验证",
|
"Set up two-factor authentication": "设置双因素认证",
|
||||||
"Cancel and logout": "取消并退出登录",
|
"Cancel and logout": "取消并退出登录",
|
||||||
"Your workspace requires two-factor authentication. Please set it up to continue.": "您的工作区需要双因素认证。请设置以继续。",
|
"Your workspace requires two-factor authentication. Please set it up to continue.": "您的工作区需要双因素认证。请设置以继续。",
|
||||||
"This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "通过要求您的身份验证器应用提供验证码,此操作为您的账户增加了一层额外的安全保障。",
|
"This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "通过要求您的身份验证器应用提供验证码,此操作为您的账户增加了一层额外的安全保障。",
|
||||||
"Password is required": "密码为必填项",
|
"Password is required": "需要密码",
|
||||||
"Password must be at least 8 characters": "密码长度至少为 8 个字符",
|
"Password must be at least 8 characters": "密码必须至少包含8个字符",
|
||||||
"Please enter a 6-digit code": "请输入 6 位代码",
|
"Please enter a 6-digit code": "请输入6位代码",
|
||||||
"Code must be exactly 6 digits": "代码必须正好为 6 位",
|
"Code must be exactly 6 digits": "代码必须正好是6位",
|
||||||
"Enter the 6-digit code found in your authenticator app": "输入您身份验证器应用中的 6 位代码",
|
"Enter the 6-digit code found in your authenticator app": "输入在您的身份验证器应用中找到的6位代码",
|
||||||
"Need help authenticating?": "需要帮助进行身份验证吗?",
|
"Need help authenticating?": "需要帮助进行身份验证吗?",
|
||||||
"MFA QR Code": "MFA 二维码",
|
"MFA QR Code": "MFA二维码",
|
||||||
"Account created successfully. Please log in to set up two-factor authentication.": "账户创建成功。请登录以设置双因素认证。",
|
"Account created successfully. Please log in to set up two-factor authentication.": "账户创建成功。请登录以设置双因素认证。",
|
||||||
"Password reset successful. Please log in with your new password and complete two-factor authentication.": "密码重置成功。请使用新密码登录并完成双因素认证。",
|
"Password reset successful. Please log in with your new password and complete two-factor authentication.": "密码重置成功。请使用新密码登录并完成双因素认证。",
|
||||||
"Password reset successful. Please log in with your new password to set up two-factor authentication.": "密码重置成功。请使用新密码登录以设置双因素认证。",
|
"Password reset successful. Please log in with your new password to set up two-factor authentication.": "密码重置成功。请使用新密码登录以设置双因素认证。",
|
||||||
"Password reset was successful. Please log in with your new password.": "密码重置成功。请使用新密码登录。",
|
"Password reset was successful. Please log in with your new password.": "密码重置成功。请使用新密码登录。",
|
||||||
"Two-factor authentication": "双重身份验证",
|
"Two-factor authentication": "双因素认证",
|
||||||
"Use authenticator app instead": "改用身份验证器应用",
|
"Use authenticator app instead": "改用身份验证器应用",
|
||||||
"Verify backup code": "验证备用代码",
|
"Verify backup code": "验证备份代码",
|
||||||
"Use backup code": "使用备用代码",
|
"Use backup code": "使用备份代码",
|
||||||
"Enter one of your backup codes": "输入您的一个备用代码",
|
"Enter one of your backup codes": "输入您的一个备份代码",
|
||||||
"Backup code": "备用代码",
|
"Backup code": "备份代码",
|
||||||
"Enter one of your backup codes. Each backup code can only be used once.": "输入您的一个备份代码。每个备份代码只能使用一次。",
|
"Enter one of your backup codes. Each backup code can only be used once.": "输入您的一个备份代码。每个备份代码只能使用一次。",
|
||||||
"Verify": "验证",
|
"Verify": "验证",
|
||||||
"Trash": "回收站",
|
"Trash": "垃圾箱",
|
||||||
"Pages in trash will be permanently deleted after {{count}} days.": "垃圾箱中的页面将在{{count}}天后被永久删除。",
|
"Pages in trash will be permanently deleted after 30 days.": "垃圾箱中的页面将在30天后被永久删除。",
|
||||||
"Deleted": "已删除",
|
"Deleted": "已删除",
|
||||||
"No pages in trash": "回收站中没有页面",
|
"No pages in trash": "垃圾箱中没有页面",
|
||||||
"Permanently delete page?": "永久删除页面?",
|
"Permanently delete page?": "永久删除页面?",
|
||||||
"Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "确定要永久删除“{{title}}”吗?此操作无法撤销。",
|
"Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "确定要永久删除“{{title}}”吗?此操作无法撤销。",
|
||||||
"Restore '{{title}}' and its sub-pages?": "恢复“{{title}}”及其子页面?",
|
"Restore '{{title}}' and its sub-pages?": "恢复“{{title}}”及其子页面?",
|
||||||
"Move to trash": "移至回收站",
|
"Move to trash": "移至垃圾箱",
|
||||||
"Move this page to trash?": "将此页面移至垃圾箱?",
|
"Move this page to trash?": "将此页面移至垃圾箱?",
|
||||||
"Restore page": "恢复页面",
|
"Restore page": "恢复页面",
|
||||||
"Permanently delete": "Permanently delete",
|
"Page moved to trash": "页面已移至垃圾箱",
|
||||||
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> moved this page to Trash {{time}}.",
|
|
||||||
"Page moved to trash": "页面已移至回收站",
|
|
||||||
"Page restored successfully": "页面恢复成功",
|
"Page restored successfully": "页面恢复成功",
|
||||||
"Deleted by": "删除者",
|
"Deleted by": "删除人",
|
||||||
"Deleted at": "删除于",
|
"Deleted at": "删除时间",
|
||||||
"Preview": "预览",
|
"Preview": "预览"
|
||||||
"Subpages": "子页面",
|
|
||||||
"Failed to load subpages": "加载子页面失败",
|
|
||||||
"No subpages": "没有子页面",
|
|
||||||
"Subpages (Child pages)": "子页面(下级页面)",
|
|
||||||
"List all subpages of the current page": "列出当前页面的所有子页面",
|
|
||||||
"Attachments": "附件",
|
|
||||||
"All spaces": "所有空间",
|
|
||||||
"Unknown": "未知",
|
|
||||||
"Find a space": "查找空间",
|
|
||||||
"Search in all your spaces": "在您的所有空间中搜索",
|
|
||||||
"Type": "类型",
|
|
||||||
"Enterprise": "企业版",
|
|
||||||
"Download attachment": "下载附件",
|
|
||||||
"Allowed email domains": "允许的邮箱域名",
|
|
||||||
"Only users with email addresses from these domains can signup via SSO.": "只有使用这些域名邮箱地址的用户才能通过 SSO 注册。",
|
|
||||||
"Enter valid domain names separated by comma or space": "请输入有效的域名,并用逗号或空格分隔",
|
|
||||||
"Enforce two-factor authentication": "强制启用双重身份验证",
|
|
||||||
"Once enforced, all members must enable two-factor authentication to access the workspace.": "一旦实施,所有成员必须启用双因素认证才能访问工作区。",
|
|
||||||
"Toggle MFA enforcement": "切换 MFA 强制执行",
|
|
||||||
"Display name": "显示名称",
|
|
||||||
"Allow signup": "允许注册",
|
|
||||||
"Enabled": "已启用",
|
|
||||||
"Advanced Settings": "高级设置",
|
|
||||||
"Enable TLS/SSL": "启用 TLS/SSL",
|
|
||||||
"Use secure connection to LDAP server": "使用安全连接访问 LDAP 服务器",
|
|
||||||
"Group sync": "群组同步",
|
|
||||||
"No SSO providers found.": "未找到SSO提供商。",
|
|
||||||
"Delete SSO provider": "删除 SSO 提供商",
|
|
||||||
"Are you sure you want to delete this SSO provider?": "您确定要删除此SSO提供商吗?",
|
|
||||||
"Action": "操作",
|
|
||||||
"{{ssoProviderType}} configuration": "{{ssoProviderType}} 配置",
|
|
||||||
"Icon": "图标",
|
|
||||||
"Upload image": "上传图片",
|
|
||||||
"Remove image": "删除图片",
|
|
||||||
"Failed to remove image": "无法删除图片",
|
|
||||||
"Image exceeds 10MB limit.": "图片超过10MB限制。",
|
|
||||||
"Image removed successfully": "图片删除成功",
|
|
||||||
"API key": "API密钥",
|
|
||||||
"API keys": "API密钥",
|
|
||||||
"API management": "API管理",
|
|
||||||
"Custom expiration date": "自定义到期日期",
|
|
||||||
"Enter a descriptive token name": "输入描述性令牌名称",
|
|
||||||
"Expiration": "到期",
|
|
||||||
"Expired": "已过期",
|
|
||||||
"Expires": "到期",
|
|
||||||
"Last use": "上次使用",
|
|
||||||
"No API keys found": "找不到API密钥",
|
|
||||||
"No expiration": "无到期",
|
|
||||||
"Revoked successfully": "撤销成功",
|
|
||||||
"Select expiration date": "选择到期日期",
|
|
||||||
"This action cannot be undone. Any applications using this API key will stop working.": "此操作无法撤销。使用此API密钥的任何应用程序将停止工作。",
|
|
||||||
"Update": "更新",
|
|
||||||
"Update {{credential}}": "更新{{credential}}",
|
|
||||||
"Manage API keys for all users in the workspace": "管理工作空间中所有用户的API密钥",
|
|
||||||
"Restrict API key creation to admins": "仅限管理员创建 API 密钥。",
|
|
||||||
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "只有管理员和所有者可以创建新的 API 密钥。现有成员密钥将继续有效。",
|
|
||||||
"Toggle restrict API keys to admins": "切换仅限管理员创建 API 密钥",
|
|
||||||
"API key creation is restricted to admins by your workspace administrator.": "API 密钥的创建已被您的工作区管理员限制为仅管理员可用。",
|
|
||||||
"AI settings": "AI设置",
|
|
||||||
"AI search": "AI搜索",
|
|
||||||
"AI Answer": "AI回答",
|
|
||||||
"Ask AI": "询问AI",
|
|
||||||
"AI is thinking...": "AI正在思考...",
|
|
||||||
"Thinking": "思考中",
|
|
||||||
"Ask a question...": "提问...",
|
|
||||||
"AI Answers": "AI答案",
|
|
||||||
"AI-powered search (AI Answers)": "AI驱动的搜索 (AI答案)",
|
|
||||||
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI搜索使用向量嵌入提供跨工作空间内容的语义搜索功能。",
|
|
||||||
"Toggle AI search": "切换AI搜索",
|
|
||||||
"Generative AI (Ask AI)": "生成型AI (询问AI)",
|
|
||||||
"Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "在编辑器中启用AI驱动的内容生成。允许用户生成、改进、翻译和转换文本。",
|
|
||||||
"Toggle generative AI": "切换生成型AI",
|
|
||||||
"Upgrade your plan": "升级您的方案",
|
|
||||||
"Available with a paid license": "需付费许可才可用",
|
|
||||||
"Upgrade your license tier.": "升级您的许可等级。",
|
|
||||||
"AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "AI 仅在 Docmost 企业版中提供。请联系 sales@docmost.com。",
|
|
||||||
"AI & MCP": "AI 与 MCP",
|
|
||||||
"AI": "AI",
|
|
||||||
"MCP": "MCP",
|
|
||||||
"Model Context Protocol (MCP)": "模型上下文协议(MCP)",
|
|
||||||
"Enable the MCP server to allow AI assistants and tools to interact with your workspace content.": "启用 MCP 服务器以允许 AI 助手和工具与您的工作区内容交互。",
|
|
||||||
"MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "MCP 仅在 Docmost 企业版中提供。请联系 sales@docmost.com。",
|
|
||||||
"MCP Server URL": "MCP 服务器 URL",
|
|
||||||
"Use your API key for authentication. You can manage API keys in your account settings.": "使用您的 API 密钥进行身份验证。您可以在账户设置中管理 API 密钥。",
|
|
||||||
"Supported tools": "支持的工具",
|
|
||||||
"Your workspace has MCP enabled. Use your API key to connect AI assistants.": "您的工作区已启用 MCP。使用您的 API 密钥连接 AI 助手。",
|
|
||||||
"MCP server URL:": "MCP 服务器 URL:",
|
|
||||||
"Learn more": "了解更多",
|
|
||||||
"Manage API keys for all users in the workspace. View the <anchor>API documentation</anchor> for usage details.": "为工作区内所有用户管理 API 密钥。有关使用详情,请查阅<anchor>API 文档</anchor>。",
|
|
||||||
"View the <anchor>API documentation</anchor> for usage details.": "有关使用详情,请查阅<anchor>API 文档</anchor>。",
|
|
||||||
"View the <anchor>MCP documentation</anchor>.": "查看<anchor>MCP 文档</anchor>。",
|
|
||||||
"Sources": "来源",
|
|
||||||
"AI Answers not available for attachments": "AI答案不适用于附件",
|
|
||||||
"No answer available": "无可用答案",
|
|
||||||
"Background color": "背景颜色",
|
|
||||||
"Highlight color": "突出显示颜色",
|
|
||||||
"Remove color": "移除颜色",
|
|
||||||
"Notifications": "通知",
|
|
||||||
"No notifications": "没有通知",
|
|
||||||
"No unread notifications": "没有未读通知",
|
|
||||||
"All notifications": "所有通知",
|
|
||||||
"Unread only": "仅未读",
|
|
||||||
"Mark all as read": "标记所有为已读",
|
|
||||||
"Mark as read": "标记为已读",
|
|
||||||
"More options": "更多选项",
|
|
||||||
"<bold>{{name}}</bold> mentioned you in a comment": "<bold>{{name}}</bold>在评论中提到你",
|
|
||||||
"<bold>{{name}}</bold> commented on a page": "<bold>{{name}}</bold>在页面上评论了",
|
|
||||||
"<bold>{{name}}</bold> resolved a comment": "<bold>{{name}}</bold> 解决了一条评论",
|
|
||||||
"<bold>{{name}}</bold> mentioned you on a page": "<bold>{{name}}</bold> 在某个页面中提到了您",
|
|
||||||
"<bold>{{name}}</bold> gave you edit access to a page": "<bold>{{name}}</bold> 授予您某个页面的编辑权限",
|
|
||||||
"<bold>{{name}}</bold> gave you view access to a page": "<bold>{{name}}</bold> 授予您某个页面的查看权限",
|
|
||||||
"<bold>{{name}}</bold> updated a page": "<bold>{{name}}</bold> 更新了某个页面",
|
|
||||||
"Watch page": "关注页面",
|
|
||||||
"Stop watching": "取消关注",
|
|
||||||
"Watch space": "关注空间",
|
|
||||||
"Stop watching space": "取消关注空间",
|
|
||||||
"Email notifications": "邮件通知",
|
|
||||||
"Page updates": "页面更新",
|
|
||||||
"Get notified when pages you watch are updated.": "当你关注的页面有更新时收到通知。",
|
|
||||||
"Page mentions": "页面提及",
|
|
||||||
"Get notified when someone mentions you on a page.": "当有人在页面上提到你时收到通知。",
|
|
||||||
"Comment mentions": "评论提及",
|
|
||||||
"Get notified when someone mentions you in a comment.": "当有人在评论中提到你时收到通知。",
|
|
||||||
"New comments": "新评论",
|
|
||||||
"Get notified about new comments on threads you participate in.": "当你参与的讨论有新评论时收到通知。",
|
|
||||||
"Resolved comments": "已解决的评论",
|
|
||||||
"Get notified when your comment is resolved.": "当你的评论被解决时收到通知。",
|
|
||||||
"You are now watching this page": "你现在正在关注此页面",
|
|
||||||
"You are no longer watching this page": "你已取消关注此页面",
|
|
||||||
"You are now watching this space": "您现在正在关注此空间",
|
|
||||||
"You are no longer watching this space": "您已不再关注此空间",
|
|
||||||
"Direct": "直接",
|
|
||||||
"Updates": "更新",
|
|
||||||
"Today": "今天",
|
|
||||||
"Yesterday": "昨天",
|
|
||||||
"This week": "本周",
|
|
||||||
"Older": "较早",
|
|
||||||
"Restricted page": "受限页面",
|
|
||||||
"Restricted pages cannot be shared publicly.": "受限页面不能公开共享。",
|
|
||||||
"Restricted by parent": "受父页面限制",
|
|
||||||
"Restricted": "受限",
|
|
||||||
"Open": "公开",
|
|
||||||
"Inherits restrictions from ancestor page": "继承自上级页面的限制",
|
|
||||||
"Only people listed below can access this page": "只有下面列出的人可以访问此页面",
|
|
||||||
"Everyone in this space can access": "此空间中的所有人均可访问",
|
|
||||||
"No additional restrictions on this page": "此页面无额外限制",
|
|
||||||
"Only specific people can access": "仅特定人员可访问",
|
|
||||||
"Use only inherited restrictions": "仅使用继承的限制",
|
|
||||||
"Add restrictions on top of inherited": "在继承的限制之上添加限制",
|
|
||||||
"Inherited restriction": "继承的限制",
|
|
||||||
"Access limited by": "访问受限于",
|
|
||||||
"Restrict access to control who can view and edit this page": "限制访问以控制谁可以查看和编辑此页面",
|
|
||||||
"Add additional restrictions specific to this page": "为此页面添加额外的特定限制",
|
|
||||||
"Access": "访问",
|
|
||||||
"People with access": "有访问权限的人员",
|
|
||||||
"Remove all": "全部移除",
|
|
||||||
"Remove access": "移除访问权限",
|
|
||||||
"Remove all access": "移除所有访问权限",
|
|
||||||
"Are you sure you want to remove this member's access to the page?": "您确定要移除此成员对该页面的访问权限吗?",
|
|
||||||
"Are you sure you want to remove all specific access? This will make the page open to everyone in the space.": "您确定要删除所有特定访问权限吗?这将使该页面对该空间中的所有人开放。",
|
|
||||||
"Trash retention": "垃圾箱保留期",
|
|
||||||
"Pages in trash will be permanently deleted after this period.": "该期限结束后,垃圾箱中的页面将被永久删除。",
|
|
||||||
"Trash retention updated": "垃圾箱保留期已更新",
|
|
||||||
"Failed to update trash retention": "更新垃圾箱保留期失败",
|
|
||||||
"Removed page restriction": "已移除页面限制",
|
|
||||||
"Added page permission": "已添加页面权限",
|
|
||||||
"Removed page permission": "已移除页面权限",
|
|
||||||
"day": "天",
|
|
||||||
"days": "天",
|
|
||||||
"week": "周",
|
|
||||||
"weeks": "周",
|
|
||||||
"month": "个月",
|
|
||||||
"months": "个月",
|
|
||||||
"year": "年",
|
|
||||||
"years": "年",
|
|
||||||
"Period": "周期",
|
|
||||||
"Fixed date": "固定日期",
|
|
||||||
"Indefinitely": "无限期",
|
|
||||||
"Days": "天",
|
|
||||||
"Weeks": "周",
|
|
||||||
"Months": "个月",
|
|
||||||
"Years": "年",
|
|
||||||
"Pick a date": "选择日期",
|
|
||||||
"Maximum is {{max}} {{unit}} for this unit": "此单位的最大值为 {{max}} {{unit}}",
|
|
||||||
"Never expires. Verifiers can re-verify at any time.": "永不过期。验证者可随时重新验证。",
|
|
||||||
"Verified": "已验证",
|
|
||||||
"Review needed": "需要审核",
|
|
||||||
"Verification expired": "验证已过期",
|
|
||||||
"Draft": "草稿",
|
|
||||||
"In Approval": "审批中",
|
|
||||||
"In approval": "审批中",
|
|
||||||
"Approved": "已批准",
|
|
||||||
"Obsolete": "已作废",
|
|
||||||
"Expiring": "即将过期",
|
|
||||||
"Set up verification": "设置验证",
|
|
||||||
"Verify page": "验证页面",
|
|
||||||
"Page verification": "页面验证",
|
|
||||||
"Add verification": "添加验证",
|
|
||||||
"Edit verification": "编辑验证",
|
|
||||||
"Search by title": "按标题搜索",
|
|
||||||
"Choose how this page should stay accurate.": "选择此页面保持准确的方式。",
|
|
||||||
"Recurring verification": "定期验证",
|
|
||||||
"Verifiers re-confirm this page on a schedule.": "验证者按计划重新确认此页面。",
|
|
||||||
"Re-verify on a schedule (e.g every 30 days )": "按计划重新验证(例如每 30 天一次)",
|
|
||||||
"Page stays editable at all times": "页面始终可编辑",
|
|
||||||
"Best for runbooks, FAQs, living documentation": "最适合运行手册、常见问题和动态文档",
|
|
||||||
"Approval workflow": "审批工作流",
|
|
||||||
"Formal document lifecycle with named approvers.": "具有指定审批人的正式文档生命周期。",
|
|
||||||
"Draft → In approval → Approved → Obsolete": "草稿 → 审批中 → 已批准 → 已作废",
|
|
||||||
"Locked once approved, with full history": "批准后锁定,并保留完整历史记录",
|
|
||||||
"Designed for ISO 9001, ISO 13485, and FDA": "专为 ISO 9001、ISO 13485 和 FDA 设计",
|
|
||||||
"Best for SOPs and controlled documents": "最适合 SOP 和受控文档",
|
|
||||||
"Back": "返回",
|
|
||||||
"Quality management": "质量管理",
|
|
||||||
"Recurring": "定期",
|
|
||||||
"Pages move through draft, approval, and approved stages.": "页面会经历草稿、审批中和已批准阶段。",
|
|
||||||
"Verifiers": "验证者",
|
|
||||||
"Add verifier": "添加验证者",
|
|
||||||
"I've reviewed this page for accuracy": "我已审核此页面的准确性",
|
|
||||||
"Set up": "设置",
|
|
||||||
"Remove verification": "移除验证",
|
|
||||||
"Are you sure you want to remove verification from this page?": "确定要移除此页面的验证吗?",
|
|
||||||
"Assigned verifiers must periodically re-verify this page.": "指定的验证者必须定期重新验证此页面。",
|
|
||||||
"Last verified by {{name}} {{time}} (expired)": "最后由 {{name}} 于 {{time}} 验证(已过期)",
|
|
||||||
"The fixed expiration date has passed.": "固定到期日已过。",
|
|
||||||
"Verified by {{name}} {{time}}": "由 {{name}} 于 {{time}} 验证",
|
|
||||||
"Expires {{date}}": "于 {{date}} 到期",
|
|
||||||
"Expired {{date}}": "已于 {{date}} 过期",
|
|
||||||
"Mark as obsolete": "标记为作废",
|
|
||||||
"Mark obsolete": "标记作废",
|
|
||||||
"Returned by {{name}} {{time}}": "由 {{name}} 于 {{time}} 退回",
|
|
||||||
"No approval has been requested yet.": "尚未请求审批。",
|
|
||||||
"Submitted by {{name}} {{time}}": "由 {{name}} 于 {{time}} 提交",
|
|
||||||
"Someone": "某人",
|
|
||||||
"Approved by {{name}} {{time}}": "由 {{name}} 于 {{time}} 批准",
|
|
||||||
"This document has been marked as obsolete.": "此文档已被标记为作废。",
|
|
||||||
"Rejection comment": "退回意见",
|
|
||||||
"Reason for returning this document...": "退回此文档的原因...",
|
|
||||||
"Confirm rejection": "确认退回",
|
|
||||||
"Submit for approval": "提交审批",
|
|
||||||
"Reject": "退回",
|
|
||||||
"Approve": "批准",
|
|
||||||
"Re-submit for approval": "重新提交审批",
|
|
||||||
"Verified until": "验证有效期至",
|
|
||||||
"QMS": "QMS",
|
|
||||||
"Verified pages": "已验证页面",
|
|
||||||
"Search pages...": "搜索页面...",
|
|
||||||
"Filter by space": "按空间筛选",
|
|
||||||
"Filter by type": "按类型筛选",
|
|
||||||
"<bold>{{name}}</bold> verified a page": "<bold>{{name}}</bold> 验证了一个页面",
|
|
||||||
"<bold>{{name}}</bold> submitted a page for your approval": "<bold>{{name}}</bold> 提交了一个页面供您审批",
|
|
||||||
"<bold>{{name}}</bold> returned a page for revision": "<bold>{{name}}</bold> 退回了一个页面以供修改",
|
|
||||||
"Page verification expires soon": "页面验证即将过期",
|
|
||||||
"Page verification has expired": "页面验证已过期",
|
|
||||||
"Verifying your email": "正在验证您的邮箱",
|
|
||||||
"Please wait...": "请稍候……",
|
|
||||||
"Verification failed. The link may have expired.": "验证失败。该链接可能已过期。",
|
|
||||||
"Check your email": "检查您的邮箱",
|
|
||||||
"We sent a verification link to {{email}}.": "我们已向{{email}}发送了一封验证邮件。",
|
|
||||||
"We sent a verification link to your email.": "我们已向您的邮箱发送了一封验证邮件。",
|
|
||||||
"Click the link to verify your email and access your workspace.": "请点击链接以验证邮箱并访问您的工作区。",
|
|
||||||
"Resend verification email": "重新发送验证邮件",
|
|
||||||
"Verification email sent. Please check your inbox.": "验证邮件已发送。请检查您的收件箱。",
|
|
||||||
"Failed to resend verification email. Please try again.": "重新发送验证邮件失败。请重试。",
|
|
||||||
"We've sent you an email with your associated workspaces.": "我们已向您发送包含关联工作区的邮件。",
|
|
||||||
"Load more": "加载更多",
|
|
||||||
"Log out of all devices": "退出所有设备登录",
|
|
||||||
"Log out of all sessions except this device": "退出除当前设备外的所有会话",
|
|
||||||
"This Device": "此设备",
|
|
||||||
"Unknown device": "未知设备",
|
|
||||||
"No active sessions": "没有活动会话",
|
|
||||||
"Session revoked": "会话已撤销",
|
|
||||||
"All other sessions revoked": "所有其他会话已撤销",
|
|
||||||
"Last used": "上次使用",
|
|
||||||
"Created": "创建时间",
|
|
||||||
"Rename": "重命名",
|
|
||||||
"Publish": "发布",
|
|
||||||
"Security": "安全",
|
|
||||||
"Enforce SSO": "强制使用 SSO",
|
|
||||||
"Once enforced, members will not be able to login with email and password.": "启用后,成员将无法使用邮箱和密码登录。",
|
|
||||||
"AI-generated content may not be accurate.": "AI 生成的内容可能并不准确。",
|
|
||||||
"AI Chat": "AI 聊天",
|
|
||||||
"Analyze for insights": "分析并获取洞察",
|
|
||||||
"Ask anything...": "随便问点什么...",
|
|
||||||
"Chat history": "聊天记录",
|
|
||||||
"Chat name": "聊天名称",
|
|
||||||
"Close": "关闭",
|
|
||||||
"Docmost AI": "Docmost AI",
|
|
||||||
"Failed to load chat. An error occurred.": "加载聊天失败。发生错误。",
|
|
||||||
"Failed to render this message.": "渲染此消息失败。",
|
|
||||||
"How can I help you today?": "今天我可以如何帮助您?",
|
|
||||||
"New chat": "新聊天",
|
|
||||||
"No chat history": "没有聊天记录",
|
|
||||||
"No chats found": "未找到聊天",
|
|
||||||
"No conversations yet": "暂无对话",
|
|
||||||
"Open full page": "打开完整页面",
|
|
||||||
"Previous 7 days": "前 7 天",
|
|
||||||
"Previous 30 days": "前 30 天",
|
|
||||||
"Search chats...": "搜索聊天...",
|
|
||||||
"Search chats": "搜索聊天",
|
|
||||||
"Ask anything... Use @ to mention pages": "询问任何内容……使用 @ 提及页面",
|
|
||||||
"Ask anything or search your workspace": "Ask anything or search your workspace",
|
|
||||||
"Welcome to {{name}}": "Welcome to {{name}}",
|
|
||||||
"Add files": "Add files",
|
|
||||||
"Mention a page": "Mention a page",
|
|
||||||
"Start a new chat to see it here.": "开始新的聊天后会显示在这里。",
|
|
||||||
"Summarize this page": "总结此页面",
|
|
||||||
"Toggle AI Chat": "切换 AI 聊天",
|
|
||||||
"Translate this page": "翻译此页面",
|
|
||||||
"Try a different search term.": "请尝试其他搜索词。",
|
|
||||||
"Try again": "重试",
|
|
||||||
"Untitled chat": "未命名聊天",
|
|
||||||
"What can I help you with?": "我能帮您做什么?",
|
|
||||||
"Are you sure you want to revoke this {{credential}}": "确定要撤销此{{credential}}吗",
|
|
||||||
"Automatically provision users and groups from your identity provider via SCIM.": "通过 SCIM 从您的身份提供商自动预配用户和群组。",
|
|
||||||
"Configure your identity provider with this URL to provision users and groups.": "使用此 URL 配置您的身份提供商以预配用户和群组。",
|
|
||||||
"Create {{credential}}": "创建{{credential}}",
|
|
||||||
"{{credential}} created": "已创建{{credential}}",
|
|
||||||
"{{credential}} created successfully": "已成功创建{{credential}}",
|
|
||||||
"Created by": "创建者",
|
|
||||||
"Custom": "自定义",
|
|
||||||
"Enable SCIM": "启用 SCIM",
|
|
||||||
"Enter a descriptive name": "输入描述性名称",
|
|
||||||
"I've saved my {{credential}}": "我已保存我的{{credential}}",
|
|
||||||
"Important": "重要",
|
|
||||||
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "请务必立即复制您的{{credential}}。之后您将无法再次查看!",
|
|
||||||
"Never": "从不",
|
|
||||||
"Revoke {{credential}}": "撤销{{credential}}",
|
|
||||||
"SCIM endpoint URL": "SCIM 端点 URL",
|
|
||||||
"SCIM provisioning": "SCIM 预配",
|
|
||||||
"SCIM takes precedence over SSO group sync while enabled.": "启用后,SCIM 的优先级高于 SSO 群组同步。",
|
|
||||||
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "您已达到 {{max}} 个 SCIM 令牌的上限。请删除一个现有令牌以创建新令牌。",
|
|
||||||
"SCIM token": "SCIM 令牌",
|
|
||||||
"SCIM tokens": "SCIM 令牌",
|
|
||||||
"This action cannot be undone. Your identity provider will stop syncing immediately.": "此操作无法撤销。您的身份提供商将立即停止同步。",
|
|
||||||
"Toggle SCIM provisioning": "切换 SCIM 预配",
|
|
||||||
"Token": "令牌",
|
|
||||||
"Page menu": "页面菜单",
|
|
||||||
"Expand": "展开",
|
|
||||||
"Collapse": "折叠",
|
|
||||||
"Comment menu": "评论菜单",
|
|
||||||
"Group menu": "群组菜单",
|
|
||||||
"Show hidden breadcrumbs": "显示隐藏的面包屑",
|
|
||||||
"Breadcrumbs": "面包屑",
|
|
||||||
"Page actions": "页面操作",
|
|
||||||
"Pick emoji": "选择表情符号",
|
|
||||||
"Template menu": "模板菜单",
|
|
||||||
"Use": "Use",
|
|
||||||
"Use template": "Use template",
|
|
||||||
"Preview template: {{title}}": "Preview template: {{title}}",
|
|
||||||
"Use a template": "Use a template",
|
|
||||||
"Search templates...": "Search templates...",
|
|
||||||
"Search spaces...": "Search spaces...",
|
|
||||||
"No templates found": "No templates found",
|
|
||||||
"No spaces found": "No spaces found",
|
|
||||||
"Browse all templates": "Browse all templates",
|
|
||||||
"This space": "This space",
|
|
||||||
"All templates": "All templates",
|
|
||||||
"Global": "Global",
|
|
||||||
"New template": "New template",
|
|
||||||
"Edit template": "Edit template",
|
|
||||||
"Are you sure you want to delete this template?": "Are you sure you want to delete this template?",
|
|
||||||
"Template scope updated": "Template scope updated",
|
|
||||||
"Choose which space this template belongs to": "Choose which space this template belongs to",
|
|
||||||
"Scope": "Scope",
|
|
||||||
"Select scope": "Select scope",
|
|
||||||
"Title": "Title",
|
|
||||||
"Saving...": "Saving...",
|
|
||||||
"Saved": "Saved",
|
|
||||||
"Save failed. Retry": "Save failed. Retry",
|
|
||||||
"By {{name}}": "By {{name}}",
|
|
||||||
"Updated {{time}}": "Updated {{time}}",
|
|
||||||
"Choose destination": "Choose destination",
|
|
||||||
"Search pages and spaces...": "Search pages and spaces...",
|
|
||||||
"No results found": "No results found",
|
|
||||||
"You don't have permission to create pages here": "You don't have permission to create pages here",
|
|
||||||
"Chat menu": "聊天菜单",
|
|
||||||
"API key menu": "API 密钥菜单",
|
|
||||||
"Jump to comment selection": "跳转到评论选择",
|
|
||||||
"Slash commands": "斜杠命令",
|
|
||||||
"Mention suggestions": "提及建议",
|
|
||||||
"Link suggestions": "链接建议",
|
|
||||||
"Diagram editor": "图表编辑器",
|
|
||||||
"Add comment": "添加评论",
|
|
||||||
"Find and replace": "查找和替换",
|
|
||||||
"Main navigation": "主导航",
|
|
||||||
"Space navigation": "空间导航",
|
|
||||||
"Settings navigation": "设置导航",
|
|
||||||
"AI navigation": "AI 导航",
|
|
||||||
"Breadcrumb": "面包屑",
|
|
||||||
"Synced block": "Synced block",
|
|
||||||
"Create a block that stays in sync across pages.": "Create a block that stays in sync across pages.",
|
|
||||||
"Editing original": "Editing original",
|
|
||||||
"Copy synced block": "Copy synced block",
|
|
||||||
"Unsync": "Unsync",
|
|
||||||
"Delete synced block": "Delete synced block",
|
|
||||||
"Synced to {{count}} other page_one": "Synced to {{count}} other page",
|
|
||||||
"Synced to {{count}} other page_other": "Synced to {{count}} other pages",
|
|
||||||
"ORIGINAL": "ORIGINAL",
|
|
||||||
"THIS PAGE": "THIS PAGE",
|
|
||||||
"No pages": "No pages",
|
|
||||||
"The original synced block no longer exists": "The original synced block no longer exists",
|
|
||||||
"You don't have access to this synced block": "You don't have access to this synced block",
|
|
||||||
"Failed to load this synced block": "Failed to load this synced block",
|
|
||||||
"Fixed editor toolbar": "Fixed editor toolbar",
|
|
||||||
"Show a formatting toolbar above the editor with quick access to common actions.": "Show a formatting toolbar above the editor with quick access to common actions.",
|
|
||||||
"Toggle fixed editor toolbar": "Toggle fixed editor toolbar",
|
|
||||||
"Normal text": "Normal text",
|
|
||||||
"More inline formatting": "More inline formatting",
|
|
||||||
"Subscript": "Subscript",
|
|
||||||
"Superscript": "Superscript",
|
|
||||||
"Inline code": "Inline code",
|
|
||||||
"Insert media": "Insert media",
|
|
||||||
"Mention": "Mention",
|
|
||||||
"Emoji": "Emoji",
|
|
||||||
"Columns": "Columns",
|
|
||||||
"More inserts": "More inserts",
|
|
||||||
"Embeds": "Embeds",
|
|
||||||
"Diagrams": "Diagrams",
|
|
||||||
"Advanced": "Advanced",
|
|
||||||
"Utility": "Utility",
|
|
||||||
"Decrease indent": "Decrease indent",
|
|
||||||
"Increase indent": "Increase indent",
|
|
||||||
"Clear formatting": "Clear formatting",
|
|
||||||
"Code block": "Code block",
|
|
||||||
"Experimental": "Experimental",
|
|
||||||
"Strikethrough": "Strikethrough",
|
|
||||||
"Undo": "Undo",
|
|
||||||
"Redo": "Redo",
|
|
||||||
"Backlinks": "Backlinks",
|
|
||||||
"Last updated by": "Last updated by",
|
|
||||||
"Last updated": "Last updated",
|
|
||||||
"Stats": "Stats",
|
|
||||||
"Word count": "Word count",
|
|
||||||
"Characters": "Characters",
|
|
||||||
"Incoming links": "Incoming links",
|
|
||||||
"Outgoing links": "Outgoing links",
|
|
||||||
"Incoming links ({{count}})": "Incoming links ({{count}})",
|
|
||||||
"Outgoing links ({{count}})": "Outgoing links ({{count}})",
|
|
||||||
"No pages link here yet.": "No pages link here yet.",
|
|
||||||
"This page doesn't link to other pages yet.": "This page doesn't link to other pages yet.",
|
|
||||||
"Verified until {{date}}": "Verified until {{date}}",
|
|
||||||
"Labels": "Labels",
|
|
||||||
"Add label": "Add label",
|
|
||||||
"No labels yet": "No labels yet",
|
|
||||||
"Already added": "Already added",
|
|
||||||
"Invalid label name": "Invalid label name",
|
|
||||||
"No matches": "No matches",
|
|
||||||
"Search or create…": "Search or create…",
|
|
||||||
"Remove label {{name}}": "Remove label {{name}}",
|
|
||||||
"Failed to add label": "Failed to add label",
|
|
||||||
"Failed to remove label": "Failed to remove label",
|
|
||||||
"No pages with this label": "No pages with this label",
|
|
||||||
"Pages tagged with this label will appear here.": "Pages tagged with this label will appear here.",
|
|
||||||
"No pages match your search.": "No pages match your search.",
|
|
||||||
"Updated {{date}}": "Updated {{date}}",
|
|
||||||
"Cell actions": "Cell actions",
|
|
||||||
"Column actions": "Column actions",
|
|
||||||
"Row actions": "Row actions"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Docmost",
|
|
||||||
"short_name": "Docmost",
|
|
||||||
"start_url": "/",
|
|
||||||
"display": "standalone",
|
|
||||||
"background_color": "#222",
|
|
||||||
"theme_color": "#222",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "icons/favicon-16x16.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "16x16"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "icons/favicon-32x32.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "32x32"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "icons/app-icon-192x192.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "180x180 192x192"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "icons/app-icon-512x512.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
+8
-28
@@ -14,6 +14,7 @@ import AccountPreferences from "@/pages/settings/account/account-preferences.tsx
|
|||||||
import SpaceHome from "@/pages/space/space-home.tsx";
|
import SpaceHome from "@/pages/space/space-home.tsx";
|
||||||
import PageRedirect from "@/pages/page/page-redirect.tsx";
|
import PageRedirect from "@/pages/page/page-redirect.tsx";
|
||||||
import Layout from "@/components/layouts/global/layout.tsx";
|
import Layout from "@/components/layouts/global/layout.tsx";
|
||||||
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import InviteSignup from "@/pages/auth/invite-signup.tsx";
|
import InviteSignup from "@/pages/auth/invite-signup.tsx";
|
||||||
import ForgotPassword from "@/pages/auth/forgot-password.tsx";
|
import ForgotPassword from "@/pages/auth/forgot-password.tsx";
|
||||||
import PasswordReset from "./pages/auth/password-reset";
|
import PasswordReset from "./pages/auth/password-reset";
|
||||||
@@ -26,7 +27,6 @@ import Security from "@/ee/security/pages/security.tsx";
|
|||||||
import License from "@/ee/licence/pages/license.tsx";
|
import License from "@/ee/licence/pages/license.tsx";
|
||||||
import { useRedirectToCloudSelect } from "@/ee/hooks/use-redirect-to-cloud-select.tsx";
|
import { useRedirectToCloudSelect } from "@/ee/hooks/use-redirect-to-cloud-select.tsx";
|
||||||
import SharedPage from "@/pages/share/shared-page.tsx";
|
import SharedPage from "@/pages/share/shared-page.tsx";
|
||||||
import PdfRenderPage from "@/ee/pdf-export/pdf-render-page.tsx";
|
|
||||||
import Shares from "@/pages/settings/shares/shares.tsx";
|
import Shares from "@/pages/settings/shares/shares.tsx";
|
||||||
import ShareLayout from "@/features/share/components/share-layout.tsx";
|
import ShareLayout from "@/features/share/components/share-layout.tsx";
|
||||||
import ShareRedirect from "@/pages/share/share-redirect.tsx";
|
import ShareRedirect from "@/pages/share/share-redirect.tsx";
|
||||||
@@ -35,16 +35,6 @@ import SpacesPage from "@/pages/spaces/spaces.tsx";
|
|||||||
import { MfaChallengePage } from "@/ee/mfa/pages/mfa-challenge-page";
|
import { MfaChallengePage } from "@/ee/mfa/pages/mfa-challenge-page";
|
||||||
import { MfaSetupRequiredPage } from "@/ee/mfa/pages/mfa-setup-required-page";
|
import { MfaSetupRequiredPage } from "@/ee/mfa/pages/mfa-setup-required-page";
|
||||||
import SpaceTrash from "@/pages/space/space-trash.tsx";
|
import SpaceTrash from "@/pages/space/space-trash.tsx";
|
||||||
import UserApiKeys from "@/ee/api-key/pages/user-api-keys";
|
|
||||||
import WorkspaceApiKeys from "@/ee/api-key/pages/workspace-api-keys";
|
|
||||||
import AiSettings from "@/ee/ai/pages/ai-settings.tsx";
|
|
||||||
import AuditLogs from "@/ee/audit/pages/audit-logs.tsx";
|
|
||||||
import VerifiedPages from "@/ee/page-verification/pages/verified-pages.tsx";
|
|
||||||
import TemplateList from "@/ee/template/pages/template-list";
|
|
||||||
import TemplateEditor from "@/ee/template/pages/template-editor";
|
|
||||||
import FavoritesPage from "@/pages/favorites/favorites-page";
|
|
||||||
import AiChat from "@/ee/ai-chat/pages/ai-chat.tsx";
|
|
||||||
import VerifyEmail from "@/ee/pages/verify-email.tsx";
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -70,7 +60,6 @@ export default function App() {
|
|||||||
<>
|
<>
|
||||||
<Route path={"/create"} element={<CreateWorkspace />} />
|
<Route path={"/create"} element={<CreateWorkspace />} />
|
||||||
<Route path={"/select"} element={<CloudLogin />} />
|
<Route path={"/select"} element={<CloudLogin />} />
|
||||||
<Route path={"/verify-email"} element={<VerifyEmail />} />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -82,26 +71,23 @@ export default function App() {
|
|||||||
<Route path={"/share/p/:pageSlug"} element={<SharedPage />} />
|
<Route path={"/share/p/:pageSlug"} element={<SharedPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={"/pdf-render/:pageId"} element={<PdfRenderPage />} />
|
|
||||||
<Route path={"/share/:shareId"} element={<ShareRedirect />} />
|
<Route path={"/share/:shareId"} element={<ShareRedirect />} />
|
||||||
<Route path={"/p/:pageSlug"} element={<PageRedirect />} />
|
<Route path={"/p/:pageSlug"} element={<PageRedirect />} />
|
||||||
|
|
||||||
<Route element={<Layout />}>
|
<Route element={<Layout />}>
|
||||||
<Route path={"/home"} element={<Home />} />
|
<Route path={"/home"} element={<Home />} />
|
||||||
<Route path={"/ai"} element={<AiChat />} />
|
|
||||||
<Route path={"/ai/chat/:chatId"} element={<AiChat />} />
|
|
||||||
<Route path={"/spaces"} element={<SpacesPage />} />
|
<Route path={"/spaces"} element={<SpacesPage />} />
|
||||||
<Route path={"/favorites"} element={<FavoritesPage />} />
|
|
||||||
<Route path={"/templates"} element={<TemplateList />} />
|
|
||||||
<Route
|
|
||||||
path={"/templates/:templateId"}
|
|
||||||
element={<TemplateEditor />}
|
|
||||||
/>
|
|
||||||
<Route path={"/s/:spaceSlug"} element={<SpaceHome />} />
|
<Route path={"/s/:spaceSlug"} element={<SpaceHome />} />
|
||||||
<Route path={"/s/:spaceSlug/trash"} element={<SpaceTrash />} />
|
<Route path={"/s/:spaceSlug/trash"} element={<SpaceTrash />} />
|
||||||
<Route
|
<Route
|
||||||
path={"/s/:spaceSlug/p/:pageSlug"}
|
path={"/s/:spaceSlug/p/:pageSlug"}
|
||||||
element={<Page />}
|
element={
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={<>{t("Failed to load page. An error occurred.")}</>}
|
||||||
|
>
|
||||||
|
<Page />
|
||||||
|
</ErrorBoundary>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route path={"/settings"}>
|
<Route path={"/settings"}>
|
||||||
@@ -110,19 +96,13 @@ export default function App() {
|
|||||||
path={"account/preferences"}
|
path={"account/preferences"}
|
||||||
element={<AccountPreferences />}
|
element={<AccountPreferences />}
|
||||||
/>
|
/>
|
||||||
<Route path={"account/api-keys"} element={<UserApiKeys />} />
|
|
||||||
<Route path={"workspace"} element={<WorkspaceSettings />} />
|
<Route path={"workspace"} element={<WorkspaceSettings />} />
|
||||||
<Route path={"members"} element={<WorkspaceMembers />} />
|
<Route path={"members"} element={<WorkspaceMembers />} />
|
||||||
<Route path={"api-keys"} element={<WorkspaceApiKeys />} />
|
|
||||||
<Route path={"groups"} element={<Groups />} />
|
<Route path={"groups"} element={<Groups />} />
|
||||||
<Route path={"groups/:groupId"} element={<GroupInfo />} />
|
<Route path={"groups/:groupId"} element={<GroupInfo />} />
|
||||||
<Route path={"spaces"} element={<Spaces />} />
|
<Route path={"spaces"} element={<Spaces />} />
|
||||||
<Route path={"sharing"} element={<Shares />} />
|
<Route path={"sharing"} element={<Shares />} />
|
||||||
<Route path={"security"} element={<Security />} />
|
<Route path={"security"} element={<Security />} />
|
||||||
<Route path={"ai"} element={<AiSettings />} />
|
|
||||||
<Route path={"ai/mcp"} element={<AiSettings />} />
|
|
||||||
<Route path={"audit"} element={<AuditLogs />} />
|
|
||||||
<Route path={"verifications"} element={<VerifiedPages />} />
|
|
||||||
{!isCloud() && <Route path={"license"} element={<License />} />}
|
{!isCloud() && <Route path={"license"} element={<License />} />}
|
||||||
{isCloud() && <Route path={"billing"} element={<Billing />} />}
|
{isCloud() && <Route path={"billing"} element={<Billing />} />}
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@@ -1,175 +0,0 @@
|
|||||||
import React, { useRef } from "react";
|
|
||||||
import { Menu, Box, Loader } from "@mantine/core";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { IconTrash, IconUpload } from "@tabler/icons-react";
|
|
||||||
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
|
||||||
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
|
|
||||||
import { notifications } from "@mantine/notifications";
|
|
||||||
|
|
||||||
interface AvatarUploaderProps {
|
|
||||||
currentImageUrl?: string | null;
|
|
||||||
fallbackName?: string;
|
|
||||||
radius?: string | number;
|
|
||||||
size?: string | number;
|
|
||||||
variant?: string;
|
|
||||||
type: AvatarIconType;
|
|
||||||
onUpload: (file: File) => Promise<void>;
|
|
||||||
onRemove: () => Promise<void>;
|
|
||||||
isLoading?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AvatarUploader({
|
|
||||||
currentImageUrl,
|
|
||||||
fallbackName,
|
|
||||||
radius,
|
|
||||||
variant,
|
|
||||||
size,
|
|
||||||
type,
|
|
||||||
onUpload,
|
|
||||||
onRemove,
|
|
||||||
isLoading = false,
|
|
||||||
disabled = false,
|
|
||||||
}: AvatarUploaderProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const handleFileInputChange = async (
|
|
||||||
event: React.ChangeEvent<HTMLInputElement>,
|
|
||||||
) => {
|
|
||||||
const file = event.target.files?.[0];
|
|
||||||
if (!file || disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate file size (max 10MB)
|
|
||||||
const maxSizeInBytes = 10 * 1024 * 1024;
|
|
||||||
if (file.size > maxSizeInBytes) {
|
|
||||||
notifications.show({
|
|
||||||
message: t("Image exceeds 10MB limit."),
|
|
||||||
color: "red",
|
|
||||||
});
|
|
||||||
// Reset the input
|
|
||||||
if (fileInputRef.current) {
|
|
||||||
fileInputRef.current.value = "";
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await onUpload(file);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
notifications.show({
|
|
||||||
message: t("Failed to upload image"),
|
|
||||||
color: "red",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the input so the same file can be selected again
|
|
||||||
if (fileInputRef.current) {
|
|
||||||
fileInputRef.current.value = "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUploadClick = () => {
|
|
||||||
if (fileInputRef.current) {
|
|
||||||
fileInputRef.current.click();
|
|
||||||
} else {
|
|
||||||
console.error("File input ref is null!");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const ariaLabel = {
|
|
||||||
[AvatarIconType.AVATAR]: t("Change avatar"),
|
|
||||||
[AvatarIconType.SPACE_ICON]: t("Change space icon"),
|
|
||||||
[AvatarIconType.WORKSPACE_ICON]: t("Change workspace icon"),
|
|
||||||
}[type];
|
|
||||||
|
|
||||||
const handleRemove = async () => {
|
|
||||||
if (disabled) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await onRemove();
|
|
||||||
notifications.show({
|
|
||||||
message: t("Image removed successfully"),
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
notifications.show({
|
|
||||||
message: t("Failed to remove image"),
|
|
||||||
color: "red",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
ref={fileInputRef}
|
|
||||||
onChange={handleFileInputChange}
|
|
||||||
accept="image/png,image/jpeg,image/jpg"
|
|
||||||
aria-label={ariaLabel}
|
|
||||||
tabIndex={-1}
|
|
||||||
style={{ display: "none" }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Menu shadow="md" width={200} withArrow disabled={disabled || isLoading}>
|
|
||||||
<Menu.Target>
|
|
||||||
<Box style={{ position: "relative", display: "inline-block" }}>
|
|
||||||
<CustomAvatar
|
|
||||||
component="button"
|
|
||||||
size={size}
|
|
||||||
avatarUrl={currentImageUrl}
|
|
||||||
name={fallbackName}
|
|
||||||
aria-label={ariaLabel}
|
|
||||||
aria-haspopup="menu"
|
|
||||||
style={{
|
|
||||||
cursor: disabled || isLoading ? "default" : "pointer",
|
|
||||||
opacity: isLoading ? 0.6 : 1,
|
|
||||||
}}
|
|
||||||
radius={radius}
|
|
||||||
variant={variant}
|
|
||||||
type={type}
|
|
||||||
/>
|
|
||||||
{isLoading && (
|
|
||||||
<Box
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
|
||||||
zIndex: 200,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Loader size="sm" />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Menu.Target>
|
|
||||||
|
|
||||||
<Menu.Dropdown>
|
|
||||||
<Menu.Item
|
|
||||||
leftSection={<IconUpload size={16} />}
|
|
||||||
disabled={isLoading || disabled}
|
|
||||||
onClick={handleUploadClick}
|
|
||||||
>
|
|
||||||
{t("Upload image")}
|
|
||||||
</Menu.Item>
|
|
||||||
|
|
||||||
{currentImageUrl && (
|
|
||||||
<Menu.Item
|
|
||||||
leftSection={<IconTrash size={16} />}
|
|
||||||
color="red"
|
|
||||||
onClick={handleRemove}
|
|
||||||
disabled={isLoading || disabled}
|
|
||||||
>
|
|
||||||
{t("Remove image")}
|
|
||||||
</Menu.Item>
|
|
||||||
)}
|
|
||||||
</Menu.Dropdown>
|
|
||||||
</Menu>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// Source: https://github.com/mantinedev/mantine/blob/master/packages/@mantine/core/src/components/CopyButton/CopyButton.tsx - MIT
|
|
||||||
// modified to use the polyfilled clipboard api
|
|
||||||
import React from "react";
|
|
||||||
import { useClipboard } from "@/hooks/use-clipboard";
|
|
||||||
import { useProps } from "@mantine/core";
|
|
||||||
|
|
||||||
interface CopyButtonProps {
|
|
||||||
/** Children callback, provides current status and copy function as an argument */
|
|
||||||
children: (payload: { copied: boolean; copy: () => void }) => React.ReactNode;
|
|
||||||
|
|
||||||
/** Value that is copied to the clipboard when the button is clicked */
|
|
||||||
value: string;
|
|
||||||
|
|
||||||
/** Copied status timeout in ms @default `1000` */
|
|
||||||
timeout?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultProps = {
|
|
||||||
timeout: 1000,
|
|
||||||
} satisfies Partial<CopyButtonProps>;
|
|
||||||
|
|
||||||
export function CopyButton(props: CopyButtonProps) {
|
|
||||||
const { children, timeout, value, ...others } = useProps(
|
|
||||||
"CopyButton",
|
|
||||||
defaultProps,
|
|
||||||
props,
|
|
||||||
);
|
|
||||||
const clipboard = useClipboard({ timeout });
|
|
||||||
const copy = () => clipboard.copy(value);
|
|
||||||
return <>{children({ copy, copied: clipboard.copied, ...others })}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
CopyButton.displayName = "@mantine/core/CopyButton";
|
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
import { ActionIcon, MantineColor, MantineSize, Tooltip } from "@mantine/core";
|
import { ActionIcon, CopyButton, Tooltip } from "@mantine/core";
|
||||||
import { CopyButton } from "@/components/common/copy-button";
|
|
||||||
import { IconCheck, IconCopy } from "@tabler/icons-react";
|
import { IconCheck, IconCopy } from "@tabler/icons-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface CopyProps {
|
interface CopyProps {
|
||||||
text: string;
|
text: string;
|
||||||
size?: MantineSize;
|
|
||||||
color?: MantineColor;
|
|
||||||
}
|
}
|
||||||
export default function CopyTextButton({ text, size }: CopyProps) {
|
export default function CopyTextButton({ text }: CopyProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -24,8 +21,6 @@ export default function CopyTextButton({ text, size }: CopyProps) {
|
|||||||
color={copied ? "teal" : "gray"}
|
color={copied ? "teal" : "gray"}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={copy}
|
onClick={copy}
|
||||||
size={size}
|
|
||||||
aria-label={copied ? t("Copied") : t("Copy")}
|
|
||||||
>
|
>
|
||||||
{copied ? <IconCheck size={16} /> : <IconCopy size={16} />}
|
{copied ? <IconCheck size={16} /> : <IconCopy size={16} />}
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
|||||||
@@ -30,11 +30,9 @@ export default function ExportModal({
|
|||||||
const [format, setFormat] = useState<ExportFormat>(ExportFormat.Markdown);
|
const [format, setFormat] = useState<ExportFormat>(ExportFormat.Markdown);
|
||||||
const [includeChildren, setIncludeChildren] = useState<boolean>(false);
|
const [includeChildren, setIncludeChildren] = useState<boolean>(false);
|
||||||
const [includeAttachments, setIncludeAttachments] = useState<boolean>(false);
|
const [includeAttachments, setIncludeAttachments] = useState<boolean>(false);
|
||||||
const [isExporting, setIsExporting] = useState<boolean>(false);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleExport = async () => {
|
const handleExport = async () => {
|
||||||
setIsExporting(true);
|
|
||||||
try {
|
try {
|
||||||
if (type === "page") {
|
if (type === "page") {
|
||||||
await exportPage({
|
await exportPage({
|
||||||
@@ -47,9 +45,6 @@ export default function ExportModal({
|
|||||||
if (type === "space") {
|
if (type === "space") {
|
||||||
await exportSpace({ spaceId: id, format, includeAttachments });
|
await exportSpace({ spaceId: id, format, includeAttachments });
|
||||||
}
|
}
|
||||||
notifications.show({
|
|
||||||
message: t("Export successful"),
|
|
||||||
});
|
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
@@ -57,8 +52,6 @@ export default function ExportModal({
|
|||||||
color: "red",
|
color: "red",
|
||||||
});
|
});
|
||||||
console.error("export error", err);
|
console.error("export error", err);
|
||||||
} finally {
|
|
||||||
setIsExporting(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,7 +136,7 @@ export default function ExportModal({
|
|||||||
<Button onClick={onClose} variant="default">
|
<Button onClick={onClose} variant="default">
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleExport} loading={isExporting}>{t("Export")}</Button>
|
<Button onClick={handleExport}>{t("Export")}</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
interface NoTableResultsProps {
|
interface NoTableResultsProps {
|
||||||
colSpan: number;
|
colSpan: number;
|
||||||
text?: string;
|
|
||||||
}
|
}
|
||||||
export default function NoTableResults({ colSpan, text }: NoTableResultsProps) {
|
export default function NoTableResults({ colSpan }: NoTableResultsProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Td colSpan={colSpan}>
|
<Table.Td colSpan={colSpan}>
|
||||||
<Text fw={500} c="dimmed" ta="center">
|
<Text fw={500} c="dimmed" ta="center">
|
||||||
{text || t("No results found...")}
|
{t("No results found...")}
|
||||||
</Text>
|
</Text>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import { Button, Group } from "@mantine/core";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export interface PagePaginationProps {
|
export interface PagePaginationProps {
|
||||||
|
currentPage: number;
|
||||||
hasPrevPage: boolean;
|
hasPrevPage: boolean;
|
||||||
hasNextPage: boolean;
|
hasNextPage: boolean;
|
||||||
onPrev: () => void;
|
onPageChange: (newPage: number) => void;
|
||||||
onNext: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Paginate({
|
export default function Paginate({
|
||||||
|
currentPage,
|
||||||
hasPrevPage,
|
hasPrevPage,
|
||||||
hasNextPage,
|
hasNextPage,
|
||||||
onPrev,
|
onPageChange,
|
||||||
onNext,
|
|
||||||
}: PagePaginationProps) {
|
}: PagePaginationProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ export default function Paginate({
|
|||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="compact-sm"
|
size="compact-sm"
|
||||||
onClick={onPrev}
|
onClick={() => onPageChange(currentPage - 1)}
|
||||||
disabled={!hasPrevPage}
|
disabled={!hasPrevPage}
|
||||||
>
|
>
|
||||||
{t("Prev")}
|
{t("Prev")}
|
||||||
@@ -34,7 +34,7 @@ export default function Paginate({
|
|||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="compact-sm"
|
size="compact-sm"
|
||||||
onClick={onNext}
|
onClick={() => onPageChange(currentPage + 1)}
|
||||||
disabled={!hasNextPage}
|
disabled={!hasNextPage}
|
||||||
>
|
>
|
||||||
{t("Next")}
|
{t("Next")}
|
||||||
|
|||||||
@@ -4,43 +4,38 @@ import {
|
|||||||
UnstyledButton,
|
UnstyledButton,
|
||||||
Badge,
|
Badge,
|
||||||
Table,
|
Table,
|
||||||
ThemeIcon,
|
ActionIcon,
|
||||||
Button,
|
} from '@mantine/core';
|
||||||
} from "@mantine/core";
|
import {Link} from 'react-router-dom';
|
||||||
import { Link } from "react-router-dom";
|
import PageListSkeleton from '@/components/ui/page-list-skeleton.tsx';
|
||||||
import PageListSkeleton from "@/components/ui/page-list-skeleton.tsx";
|
import { buildPageUrl } from '@/features/page/page.utils.ts';
|
||||||
import { buildPageUrl } from "@/features/page/page.utils.ts";
|
import { formattedDate } from '@/lib/time.ts';
|
||||||
import { formattedDate } from "@/lib/time.ts";
|
import { useRecentChangesQuery } from '@/features/page/queries/page-query.ts';
|
||||||
import { useRecentChangesQuery } from "@/features/page/queries/page-query.ts";
|
import { IconFileDescription } from '@tabler/icons-react';
|
||||||
import { IconFileDescription, IconFiles } from "@tabler/icons-react";
|
import { getSpaceUrl } from '@/lib/config.ts';
|
||||||
import { EmptyState } from "@/components/ui/empty-state.tsx";
|
|
||||||
import { getSpaceUrl } from "@/lib/config.ts";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { getInitialsColor } from "@/lib/get-initials-color.ts";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
spaceId?: string;
|
spaceId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RecentChanges({ spaceId }: Props) {
|
export default function RecentChanges({spaceId}: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data, isLoading, isError, hasNextPage, fetchNextPage, isFetchingNextPage } = useRecentChangesQuery(spaceId);
|
const {data: pages, isLoading, isError} = useRecentChangesQuery(spaceId);
|
||||||
const pages = data?.pages.flatMap((p) => p.items) ?? [];
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <PageListSkeleton />;
|
return <PageListSkeleton/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return <Text>{t("Failed to fetch recent pages")}</Text>;
|
return <Text>{t("Failed to fetch recent pages")}</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pages.length > 0 ? (
|
return pages && pages.items.length > 0 ? (
|
||||||
<>
|
|
||||||
<Table.ScrollContainer minWidth={500}>
|
<Table.ScrollContainer minWidth={500}>
|
||||||
<Table highlightOnHover verticalSpacing="sm">
|
<Table highlightOnHover verticalSpacing="sm">
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{pages.map((page) => (
|
{pages.items.map((page) => (
|
||||||
<Table.Tr key={page.id}>
|
<Table.Tr key={page.id}>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<UnstyledButton
|
<UnstyledButton
|
||||||
@@ -49,9 +44,9 @@ export default function RecentChanges({ spaceId }: Props) {
|
|||||||
>
|
>
|
||||||
<Group wrap="nowrap">
|
<Group wrap="nowrap">
|
||||||
{page.icon || (
|
{page.icon || (
|
||||||
<ThemeIcon variant="transparent" color="gray" size={18}>
|
<ActionIcon variant='transparent' color='gray' size={18}>
|
||||||
<IconFileDescription size={18} />
|
<IconFileDescription size={18}/>
|
||||||
</ThemeIcon>
|
</ActionIcon>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Text fw={500} size="md" lineClamp={1}>
|
<Text fw={500} size="md" lineClamp={1}>
|
||||||
@@ -63,23 +58,18 @@ export default function RecentChanges({ spaceId }: Props) {
|
|||||||
{!spaceId && (
|
{!spaceId && (
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Badge
|
<Badge
|
||||||
color={getInitialsColor(page?.space.name)}
|
color="blue"
|
||||||
variant="light"
|
variant="light"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={getSpaceUrl(page?.space.slug)}
|
to={getSpaceUrl(page?.space.slug)}
|
||||||
style={{ cursor: "pointer" }}
|
style={{cursor: 'pointer'}}
|
||||||
>
|
>
|
||||||
{page?.space.name}
|
{page?.space.name}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
)}
|
)}
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Text
|
<Text c="dimmed" style={{whiteSpace: 'nowrap'}} size="xs" fw={500}>
|
||||||
c="dimmed"
|
|
||||||
style={{ whiteSpace: "nowrap" }}
|
|
||||||
size="xs"
|
|
||||||
fw={500}
|
|
||||||
>
|
|
||||||
{formattedDate(page.updatedAt)}
|
{formattedDate(page.updatedAt)}
|
||||||
</Text>
|
</Text>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
@@ -88,24 +78,9 @@ export default function RecentChanges({ spaceId }: Props) {
|
|||||||
</Table.Tbody>
|
</Table.Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Table.ScrollContainer>
|
</Table.ScrollContainer>
|
||||||
{hasNextPage && (
|
|
||||||
<Button
|
|
||||||
variant="subtle"
|
|
||||||
fullWidth
|
|
||||||
mt="sm"
|
|
||||||
mb="xl"
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
loading={isFetchingNextPage}
|
|
||||||
>
|
|
||||||
{t("Load more")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<EmptyState
|
<Text size="md" ta="center">
|
||||||
icon={IconFiles}
|
{t("No pages yet")}
|
||||||
title={t("No pages yet")}
|
</Text>
|
||||||
description={t("Pages you create will show up here.")}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,12 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
export interface SearchInputProps {
|
export interface SearchInputProps {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
ariaLabel?: string;
|
|
||||||
debounceDelay?: number;
|
debounceDelay?: number;
|
||||||
onSearch: (value: string) => void;
|
onSearch: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SearchInput({
|
export function SearchInput({
|
||||||
placeholder,
|
placeholder,
|
||||||
ariaLabel,
|
|
||||||
debounceDelay = 500,
|
debounceDelay = 500,
|
||||||
onSearch,
|
onSearch,
|
||||||
}: SearchInputProps) {
|
}: SearchInputProps) {
|
||||||
@@ -30,7 +28,6 @@ export function SearchInput({
|
|||||||
<TextInput
|
<TextInput
|
||||||
size="sm"
|
size="sm"
|
||||||
placeholder={placeholder || t("Search...")}
|
placeholder={placeholder || t("Search...")}
|
||||||
aria-label={ariaLabel || placeholder || t("Search")}
|
|
||||||
leftSection={<IconSearch size={16} />}
|
leftSection={<IconSearch size={16} />}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.currentTarget.value)}
|
onChange={(e) => setValue(e.currentTarget.value)}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import { rem } from "@mantine/core";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
size?: number | string;
|
|
||||||
stroke?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function IconColumns4({ size = 24, stroke = 2 }: Props) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width={rem(size)}
|
|
||||||
height={rem(size)}
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth={stroke}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
>
|
|
||||||
<path d="M3 4a1 1 0 0 1 1 -1h16a1 1 0 0 1 1 1v16a1 1 0 0 1 -1 1h-16a1 1 0 0 1 -1 -1v-16" />
|
|
||||||
<path d="M7.5 3v18" />
|
|
||||||
<path d="M12 3v18" />
|
|
||||||
<path d="M16.5 3v18" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { rem } from "@mantine/core";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
size?: number | string;
|
|
||||||
stroke?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function IconColumns5({ size = 24, stroke = 2 }: Props) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width={rem(size)}
|
|
||||||
height={rem(size)}
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth={stroke}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
>
|
|
||||||
<path d="M3 4a1 1 0 0 1 1 -1h16a1 1 0 0 1 1 1v16a1 1 0 0 1 -1 1h-16a1 1 0 0 1 -1 -1v-16" />
|
|
||||||
<path d="M6.6 3v18" />
|
|
||||||
<path d="M10.2 3v18" />
|
|
||||||
<path d="M13.8 3v18" />
|
|
||||||
<path d="M17.4 3v18" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { ThemeIcon } from "@mantine/core";
|
import { ActionIcon, rem } from "@mantine/core";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { IconUsersGroup } from "@tabler/icons-react";
|
import { IconUsersGroup } from "@tabler/icons-react";
|
||||||
|
|
||||||
export function IconGroupCircle() {
|
export function IconGroupCircle() {
|
||||||
return (
|
return (
|
||||||
<ThemeIcon variant="light" size="lg" color="gray" radius="xl">
|
<ActionIcon variant="light" size="lg" color="gray" radius="xl">
|
||||||
<IconUsersGroup stroke={1.5} />
|
<IconUsersGroup stroke={1.5} />
|
||||||
</ThemeIcon>
|
</ActionIcon>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,19 +7,6 @@
|
|||||||
padding-right: var(--mantine-spacing-md);
|
padding-right: var(--mantine-spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brandIcon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
display: block;
|
display: block;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@@ -29,9 +16,6 @@
|
|||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
|
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
|
||||||
font-size: var(--mantine-font-size-sm);
|
font-size: var(--mantine-font-size-sm);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
user-select: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
@mixin hover {
|
@mixin hover {
|
||||||
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
||||||
|
|||||||
@@ -1,18 +1,8 @@
|
|||||||
import {
|
import { Badge, Group, Text, Tooltip } from "@mantine/core";
|
||||||
ActionIcon,
|
|
||||||
Badge,
|
|
||||||
Box,
|
|
||||||
Group,
|
|
||||||
Text,
|
|
||||||
Tooltip,
|
|
||||||
UnstyledButton,
|
|
||||||
} from "@mantine/core";
|
|
||||||
import classes from "./app-header.module.css";
|
import classes from "./app-header.module.css";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import TopMenu from "@/components/layouts/global/top-menu.tsx";
|
import TopMenu from "@/components/layouts/global/top-menu.tsx";
|
||||||
import { Link, useLocation } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { IconSparkles } from "@tabler/icons-react";
|
|
||||||
import useToggleAside from "@/hooks/use-toggle-aside.tsx";
|
|
||||||
import APP_ROUTE from "@/lib/app-route.ts";
|
import APP_ROUTE from "@/lib/app-route.ts";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import {
|
import {
|
||||||
@@ -24,20 +14,8 @@ import SidebarToggle from "@/components/ui/sidebar-toggle-button.tsx";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import useTrial from "@/ee/hooks/use-trial.tsx";
|
import useTrial from "@/ee/hooks/use-trial.tsx";
|
||||||
import { isCloud } from "@/lib/config.ts";
|
import { isCloud } from "@/lib/config.ts";
|
||||||
import {
|
|
||||||
SearchControl,
|
|
||||||
SearchMobileControl,
|
|
||||||
} from "@/features/search/components/search-control.tsx";
|
|
||||||
import {
|
|
||||||
searchSpotlight,
|
|
||||||
shareSearchSpotlight,
|
|
||||||
} from "@/features/search/constants.ts";
|
|
||||||
import { NotificationPopover } from "@/features/notification/components/notification-popover.tsx";
|
|
||||||
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
|
||||||
|
|
||||||
const links = [
|
const links = [{ link: APP_ROUTE.HOME, label: "Home" }];
|
||||||
{ link: APP_ROUTE.HOME, label: "Home" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function AppHeader() {
|
export function AppHeader() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -47,12 +25,10 @@ export function AppHeader() {
|
|||||||
const [desktopOpened] = useAtom(desktopSidebarAtom);
|
const [desktopOpened] = useAtom(desktopSidebarAtom);
|
||||||
const toggleDesktop = useToggleSidebar(desktopSidebarAtom);
|
const toggleDesktop = useToggleSidebar(desktopSidebarAtom);
|
||||||
const { isTrial, trialDaysLeft } = useTrial();
|
const { isTrial, trialDaysLeft } = useTrial();
|
||||||
const location = useLocation();
|
|
||||||
const toggleAside = useToggleAside();
|
|
||||||
const [workspace] = useAtom(workspaceAtom);
|
|
||||||
const aiChatEnabled = workspace?.settings?.ai?.chat === true;
|
|
||||||
|
|
||||||
const isPageRoute = location.pathname.includes("/p/");
|
const isHomeRoute = location.pathname.startsWith("/home");
|
||||||
|
const isSpacesRoute = location.pathname === "/spaces";
|
||||||
|
const hideSidebar = isHomeRoute || isSpacesRoute;
|
||||||
|
|
||||||
const items = links.map((link) => (
|
const items = links.map((link) => (
|
||||||
<Link key={link.label} to={link.link} className={classes.link}>
|
<Link key={link.label} to={link.link} className={classes.link}>
|
||||||
@@ -64,6 +40,8 @@ export function AppHeader() {
|
|||||||
<>
|
<>
|
||||||
<Group h="100%" px="md" justify="space-between" wrap={"nowrap"}>
|
<Group h="100%" px="md" justify="space-between" wrap={"nowrap"}>
|
||||||
<Group wrap="nowrap">
|
<Group wrap="nowrap">
|
||||||
|
{!hideSidebar && (
|
||||||
|
<>
|
||||||
<Tooltip label={t("Sidebar toggle")}>
|
<Tooltip label={t("Sidebar toggle")}>
|
||||||
<SidebarToggle
|
<SidebarToggle
|
||||||
aria-label={t("Sidebar toggle")}
|
aria-label={t("Sidebar toggle")}
|
||||||
@@ -83,85 +61,25 @@ export function AppHeader() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Link to="/home" className={classes.brand} aria-label="Docmost">
|
|
||||||
<Box hiddenFrom="sm" className={classes.brandIcon}>
|
|
||||||
<img
|
|
||||||
src="/icons/favicon-32x32.png"
|
|
||||||
alt="Docmost"
|
|
||||||
width={22}
|
|
||||||
height={22}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Text
|
<Text
|
||||||
size="lg"
|
size="lg"
|
||||||
fw={600}
|
fw={600}
|
||||||
style={{ userSelect: "none" }}
|
style={{ cursor: "pointer", userSelect: "none" }}
|
||||||
visibleFrom="sm"
|
component={Link}
|
||||||
|
to="/home"
|
||||||
>
|
>
|
||||||
Docmost
|
Docmost
|
||||||
</Text>
|
</Text>
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Group ml={50} gap={5} className={classes.links} visibleFrom="sm">
|
<Group ml={50} gap={5} className={classes.links} visibleFrom="sm">
|
||||||
{items}
|
{items}
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<div>
|
|
||||||
<Group visibleFrom="sm">
|
|
||||||
<SearchControl onClick={searchSpotlight.open} />
|
|
||||||
</Group>
|
|
||||||
<Group hiddenFrom="sm">
|
|
||||||
<SearchMobileControl onSearch={searchSpotlight.open} />
|
|
||||||
</Group>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Group px={"xl"} wrap="nowrap">
|
<Group px={"xl"} wrap="nowrap">
|
||||||
{aiChatEnabled && (
|
|
||||||
<>
|
|
||||||
<UnstyledButton
|
|
||||||
component={Link}
|
|
||||||
to="/ai"
|
|
||||||
className={classes.link}
|
|
||||||
visibleFrom="sm"
|
|
||||||
onClick={(e: React.MouseEvent) => {
|
|
||||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isPageRoute) {
|
|
||||||
e.preventDefault();
|
|
||||||
toggleAside("chat");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("AI Chat")}
|
|
||||||
</UnstyledButton>
|
|
||||||
<Tooltip label={t("AI Chat")} openDelay={250} withArrow>
|
|
||||||
<ActionIcon
|
|
||||||
component={Link}
|
|
||||||
to="/ai"
|
|
||||||
variant="subtle"
|
|
||||||
color="dark"
|
|
||||||
size="sm"
|
|
||||||
hiddenFrom="sm"
|
|
||||||
aria-label={t("AI Chat")}
|
|
||||||
onClick={(e: React.MouseEvent) => {
|
|
||||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isPageRoute) {
|
|
||||||
e.preventDefault();
|
|
||||||
toggleAside("chat");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconSparkles size={20} stroke={2} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<NotificationPopover />
|
|
||||||
{isCloud() && isTrial && trialDaysLeft !== 0 && (
|
{isCloud() && isTrial && trialDaysLeft !== 0 && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="light"
|
variant="light"
|
||||||
|
|||||||
@@ -27,3 +27,5 @@
|
|||||||
background: light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-5))
|
background: light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-5))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { TableOfContents } from "@/features/editor/components/table-of-contents/table-of-contents.tsx";
|
import { TableOfContents } from "@/features/editor/components/table-of-contents/table-of-contents.tsx";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { pageEditorAtom } from "@/features/editor/atoms/editor-atoms.ts";
|
import { pageEditorAtom } from "@/features/editor/atoms/editor-atoms.ts";
|
||||||
import AsideChatPanel from "@/ee/ai-chat/components/aside-chat-panel";
|
|
||||||
import { PageDetailsAside } from "@/features/page-details/components/page-details-aside.tsx";
|
|
||||||
|
|
||||||
export default function Aside() {
|
export default function Aside() {
|
||||||
const [{ tab }] = useAtom(asideStateAtom);
|
const [{ tab }] = useAtom(asideStateAtom);
|
||||||
@@ -27,31 +25,21 @@ export default function Aside() {
|
|||||||
component = <TableOfContents editor={pageEditor} />;
|
component = <TableOfContents editor={pageEditor} />;
|
||||||
title = "Table of contents";
|
title = "Table of contents";
|
||||||
break;
|
break;
|
||||||
case "chat":
|
|
||||||
component = <AsideChatPanel />;
|
|
||||||
title = "AI Chat";
|
|
||||||
break;
|
|
||||||
case "details":
|
|
||||||
component = <PageDetailsAside />;
|
|
||||||
title = "Details";
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
component = null;
|
component = null;
|
||||||
title = null;
|
title = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box p="md" style={{ height: "100%", display: "flex", flexDirection: "column" }}>
|
<Box p="md">
|
||||||
{component && (
|
{component && (
|
||||||
<>
|
<>
|
||||||
{tab !== "chat" && (
|
|
||||||
<Text mb="md" fw={500}>
|
<Text mb="md" fw={500}>
|
||||||
{t(title)}
|
{t(title)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
|
||||||
|
|
||||||
{tab === "comments" || tab === "chat" ? (
|
{tab === "comments" ? (
|
||||||
component
|
<CommentListWithTabs />
|
||||||
) : (
|
) : (
|
||||||
<ScrollArea
|
<ScrollArea
|
||||||
style={{ height: "85vh" }}
|
style={{ height: "85vh" }}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { AppShell, Container } from "@mantine/core";
|
import { AppShell, Container } from "@mantine/core";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import SettingsSidebar from "@/components/settings/settings-sidebar.tsx";
|
import SettingsSidebar from "@/components/settings/settings-sidebar.tsx";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import {
|
import {
|
||||||
@@ -11,25 +10,22 @@ import {
|
|||||||
sidebarWidthAtom,
|
sidebarWidthAtom,
|
||||||
} from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
|
} from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
|
||||||
import { SpaceSidebar } from "@/features/space/components/sidebar/space-sidebar.tsx";
|
import { SpaceSidebar } from "@/features/space/components/sidebar/space-sidebar.tsx";
|
||||||
import AiChatSidebar from "@/ee/ai-chat/components/ai-chat-sidebar.tsx";
|
|
||||||
import { AppHeader } from "@/components/layouts/global/app-header.tsx";
|
import { AppHeader } from "@/components/layouts/global/app-header.tsx";
|
||||||
import Aside from "@/components/layouts/global/aside.tsx";
|
import Aside from "@/components/layouts/global/aside.tsx";
|
||||||
import classes from "./app-shell.module.css";
|
import classes from "./app-shell.module.css";
|
||||||
import { useTrialEndAction } from "@/ee/hooks/use-trial-end-action.tsx";
|
import { useTrialEndAction } from "@/ee/hooks/use-trial-end-action.tsx";
|
||||||
import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts";
|
import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts";
|
||||||
import GlobalSidebar from "@/components/layouts/global/global-sidebar.tsx";
|
|
||||||
|
|
||||||
export default function GlobalAppShell({
|
export default function GlobalAppShell({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
|
||||||
useTrialEndAction();
|
useTrialEndAction();
|
||||||
const [mobileOpened] = useAtom(mobileSidebarAtom);
|
const [mobileOpened] = useAtom(mobileSidebarAtom);
|
||||||
const toggleMobile = useToggleSidebar(mobileSidebarAtom);
|
const toggleMobile = useToggleSidebar(mobileSidebarAtom);
|
||||||
const [desktopOpened] = useAtom(desktopSidebarAtom);
|
const [desktopOpened] = useAtom(desktopSidebarAtom);
|
||||||
const [{ isAsideOpen, tab: asideTab }] = useAtom(asideStateAtom);
|
const [{ isAsideOpen }] = useAtom(asideStateAtom);
|
||||||
const [sidebarWidth, setSidebarWidth] = useAtom(sidebarWidthAtom);
|
const [sidebarWidth, setSidebarWidth] = useAtom(sidebarWidthAtom);
|
||||||
const [isResizing, setIsResizing] = useState(false);
|
const [isResizing, setIsResizing] = useState(false);
|
||||||
const sidebarRef = useRef(null);
|
const sidebarRef = useRef(null);
|
||||||
@@ -76,21 +72,24 @@ export default function GlobalAppShell({
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isSettingsRoute = location.pathname.startsWith("/settings");
|
const isSettingsRoute = location.pathname.startsWith("/settings");
|
||||||
const isSpaceRoute = location.pathname.startsWith("/s/");
|
const isSpaceRoute = location.pathname.startsWith("/s/");
|
||||||
const isAiRoute = location.pathname.startsWith("/ai");
|
const isHomeRoute = location.pathname.startsWith("/home");
|
||||||
|
const isSpacesRoute = location.pathname === "/spaces";
|
||||||
const isPageRoute = location.pathname.includes("/p/");
|
const isPageRoute = location.pathname.includes("/p/");
|
||||||
const showGlobalSidebar = !isSpaceRoute && !isSettingsRoute && !isAiRoute;
|
const hideSidebar = isHomeRoute || isSpacesRoute;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
header={{ height: 45 }}
|
header={{ height: 45 }}
|
||||||
navbar={{
|
navbar={
|
||||||
|
!hideSidebar && {
|
||||||
width: isSpaceRoute ? sidebarWidth : 300,
|
width: isSpaceRoute ? sidebarWidth : 300,
|
||||||
breakpoint: "sm",
|
breakpoint: "sm",
|
||||||
collapsed: {
|
collapsed: {
|
||||||
mobile: !mobileOpened,
|
mobile: !mobileOpened,
|
||||||
desktop: !desktopOpened,
|
desktop: !desktopOpened,
|
||||||
},
|
},
|
||||||
}}
|
}
|
||||||
|
}
|
||||||
aside={
|
aside={
|
||||||
isPageRoute && {
|
isPageRoute && {
|
||||||
width: 350,
|
width: 350,
|
||||||
@@ -103,55 +102,27 @@ export default function GlobalAppShell({
|
|||||||
<AppShell.Header px="md" className={classes.header}>
|
<AppShell.Header px="md" className={classes.header}>
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
</AppShell.Header>
|
</AppShell.Header>
|
||||||
|
{!hideSidebar && (
|
||||||
<AppShell.Navbar
|
<AppShell.Navbar
|
||||||
className={classes.navbar}
|
className={classes.navbar}
|
||||||
withBorder={false}
|
withBorder={false}
|
||||||
ref={sidebarRef}
|
ref={sidebarRef}
|
||||||
aria-label={
|
|
||||||
isSpaceRoute
|
|
||||||
? t("Space navigation")
|
|
||||||
: isSettingsRoute
|
|
||||||
? t("Settings navigation")
|
|
||||||
: isAiRoute
|
|
||||||
? t("AI navigation")
|
|
||||||
: t("Main navigation")
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{isSpaceRoute && (
|
|
||||||
<div className={classes.resizeHandle} onMouseDown={startResizing} />
|
<div className={classes.resizeHandle} onMouseDown={startResizing} />
|
||||||
)}
|
|
||||||
{isSpaceRoute && <SpaceSidebar />}
|
{isSpaceRoute && <SpaceSidebar />}
|
||||||
{isSettingsRoute && <SettingsSidebar />}
|
{isSettingsRoute && <SettingsSidebar />}
|
||||||
{isAiRoute && <AiChatSidebar />}
|
|
||||||
{showGlobalSidebar && <GlobalSidebar />}
|
|
||||||
</AppShell.Navbar>
|
</AppShell.Navbar>
|
||||||
<AppShell.Main id="main-content">
|
)}
|
||||||
|
<AppShell.Main>
|
||||||
{isSettingsRoute ? (
|
{isSettingsRoute ? (
|
||||||
<Container size={900} pb={80}>
|
<Container size={850}>{children}</Container>
|
||||||
{children}
|
|
||||||
</Container>
|
|
||||||
) : (
|
) : (
|
||||||
children
|
children
|
||||||
)}
|
)}
|
||||||
</AppShell.Main>
|
</AppShell.Main>
|
||||||
|
|
||||||
{isPageRoute && (
|
{isPageRoute && (
|
||||||
<AppShell.Aside
|
<AppShell.Aside className={classes.aside} p="md" withBorder={false}>
|
||||||
className={classes.aside}
|
|
||||||
p="md"
|
|
||||||
withBorder={false}
|
|
||||||
aria-label={
|
|
||||||
asideTab === "comments"
|
|
||||||
? t("Comments")
|
|
||||||
: asideTab === "toc"
|
|
||||||
? t("Table of contents")
|
|
||||||
: asideTab === "chat"
|
|
||||||
? t("AI Chat")
|
|
||||||
: asideTab === "details"
|
|
||||||
? t("Details")
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Aside />
|
<Aside />
|
||||||
</AppShell.Aside>
|
</AppShell.Aside>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
.navbar {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
padding: var(--mantine-spacing-md);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
padding-bottom: var(--mantine-spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
padding-left: var(--mantine-spacing-xs);
|
|
||||||
min-height: 30px;
|
|
||||||
border-radius: var(--mantine-radius-sm);
|
|
||||||
font-weight: 500;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
background-color: light-dark(
|
|
||||||
var(--mantine-color-gray-1),
|
|
||||||
var(--mantine-color-dark-6)
|
|
||||||
);
|
|
||||||
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-active] {
|
|
||||||
&,
|
|
||||||
& :hover {
|
|
||||||
background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6));
|
|
||||||
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkIcon {
|
|
||||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
|
||||||
margin-right: var(--mantine-spacing-sm);
|
|
||||||
width: rem(16px);
|
|
||||||
height: rem(16px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sectionHeader {
|
|
||||||
padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm);
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
color: var(--mantine-color-dimmed);
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spacer {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottomSection {
|
|
||||||
padding-top: var(--mantine-spacing-xs);
|
|
||||||
border-top: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
}
|
|
||||||
|
|
||||||
.spaceItem {
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--mantine-spacing-sm);
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
padding-left: var(--mantine-spacing-xs);
|
|
||||||
min-height: 30px;
|
|
||||||
border-radius: var(--mantine-radius-sm);
|
|
||||||
font-weight: 500;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
background-color: light-dark(
|
|
||||||
var(--mantine-color-gray-1),
|
|
||||||
var(--mantine-color-dark-6)
|
|
||||||
);
|
|
||||||
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { ScrollArea, Text, Divider, Modal, UnstyledButton } from "@mantine/core";
|
|
||||||
import {
|
|
||||||
IconHome,
|
|
||||||
IconClock,
|
|
||||||
IconStar,
|
|
||||||
IconLayoutGrid,
|
|
||||||
IconSettings,
|
|
||||||
IconUserPlus,
|
|
||||||
} from "@tabler/icons-react";
|
|
||||||
import { Link, useLocation } from "react-router-dom";
|
|
||||||
import classes from "./global-sidebar.module.css";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom";
|
|
||||||
import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar";
|
|
||||||
import { useFavoritesQuery } from "@/features/favorite/queries/favorite-query";
|
|
||||||
import { getSpaceUrl } from "@/lib/config";
|
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
|
||||||
import { WorkspaceInviteForm } from "@/features/workspace/components/members/components/workspace-invite-form";
|
|
||||||
import { CustomAvatar } from "@/components/ui/custom-avatar";
|
|
||||||
import { AvatarIconType } from "@/features/attachments/types/attachment.types";
|
|
||||||
|
|
||||||
const mainNavItems = [
|
|
||||||
{ label: "Home", icon: IconHome, path: "/home" },
|
|
||||||
{ label: "Favorites", icon: IconStar, path: "/favorites" },
|
|
||||||
{ label: "Spaces", icon: IconLayoutGrid, path: "/spaces" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function GlobalSidebar() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const location = useLocation();
|
|
||||||
const [active, setActive] = useState(location.pathname);
|
|
||||||
const [mobileSidebarOpened] = useAtom(mobileSidebarAtom);
|
|
||||||
const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom);
|
|
||||||
const { data: favoriteSpacesData, isPending: isFavoritesPending } = useFavoritesQuery("space");
|
|
||||||
const favoriteSpaces = favoriteSpacesData?.pages.flatMap((p) => p.items) ?? [];
|
|
||||||
const sortedFavoriteSpaces = [...favoriteSpaces]
|
|
||||||
.filter((fav) => fav.space)
|
|
||||||
.sort((a, b) => {
|
|
||||||
const cmp = (a.space!.name ?? "").localeCompare(b.space!.name ?? "", undefined, { sensitivity: "base" });
|
|
||||||
return cmp !== 0 ? cmp : a.id.localeCompare(b.id);
|
|
||||||
});
|
|
||||||
const [inviteOpened, { open: openInvite, close: closeInvite }] =
|
|
||||||
useDisclosure(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setActive(location.pathname);
|
|
||||||
}, [location.pathname]);
|
|
||||||
|
|
||||||
const handleNavClick = () => {
|
|
||||||
if (mobileSidebarOpened) {
|
|
||||||
toggleMobileSidebar();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.navbar}>
|
|
||||||
<ScrollArea w="100%" style={{ flex: 1 }}>
|
|
||||||
<div className={classes.section}>
|
|
||||||
{mainNavItems.map((item) => (
|
|
||||||
<Link
|
|
||||||
key={item.label}
|
|
||||||
className={classes.link}
|
|
||||||
data-active={active === item.path || undefined}
|
|
||||||
to={item.path}
|
|
||||||
onClick={handleNavClick}
|
|
||||||
>
|
|
||||||
<item.icon className={classes.linkIcon} stroke={2} />
|
|
||||||
<span>{t(item.label)}</span>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider my="xs" />
|
|
||||||
<div className={classes.section}>
|
|
||||||
<Text className={classes.sectionHeader}>{t("Favorite spaces")}</Text>
|
|
||||||
{!isFavoritesPending && sortedFavoriteSpaces.length === 0 ? (
|
|
||||||
<Text size="xs" c="dimmed" pl="xs" py={4}>
|
|
||||||
{t("Favorite spaces appear here")}
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{sortedFavoriteSpaces.slice(0, 10).map((fav) => (
|
|
||||||
<Link
|
|
||||||
key={fav.id}
|
|
||||||
className={classes.spaceItem}
|
|
||||||
to={getSpaceUrl(fav.space!.slug)}
|
|
||||||
onClick={handleNavClick}
|
|
||||||
>
|
|
||||||
<CustomAvatar
|
|
||||||
name={fav.space!.name}
|
|
||||||
avatarUrl={fav.space!.logo}
|
|
||||||
type={AvatarIconType.SPACE_ICON}
|
|
||||||
color="initials"
|
|
||||||
variant="filled"
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
<Text size="sm" fw={500} lineClamp={1}>
|
|
||||||
{fav.space!.name}
|
|
||||||
</Text>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
{sortedFavoriteSpaces.length > 10 && (
|
|
||||||
<Link
|
|
||||||
className={classes.spaceItem}
|
|
||||||
to="/spaces"
|
|
||||||
onClick={handleNavClick}
|
|
||||||
>
|
|
||||||
<Text size="xs" c="dimmed">
|
|
||||||
{t("View all")}
|
|
||||||
</Text>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</ScrollArea>
|
|
||||||
|
|
||||||
<div className={classes.bottomSection}>
|
|
||||||
<UnstyledButton
|
|
||||||
className={classes.link}
|
|
||||||
onClick={openInvite}
|
|
||||||
>
|
|
||||||
<IconUserPlus className={classes.linkIcon} stroke={2} />
|
|
||||||
<span>{t("Invite People")}</span>
|
|
||||||
</UnstyledButton>
|
|
||||||
<Link
|
|
||||||
className={classes.link}
|
|
||||||
data-active={active.startsWith("/settings") || undefined}
|
|
||||||
to="/settings/account/profile"
|
|
||||||
onClick={handleNavClick}
|
|
||||||
>
|
|
||||||
<IconSettings className={classes.linkIcon} stroke={2} />
|
|
||||||
<span>{t("Settings")}</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Modal
|
|
||||||
size="550"
|
|
||||||
opened={inviteOpened}
|
|
||||||
onClose={closeInvite}
|
|
||||||
title={t("Invite new members")}
|
|
||||||
centered
|
|
||||||
>
|
|
||||||
<Divider size="xs" mb="xs" />
|
|
||||||
<ScrollArea h="80%">
|
|
||||||
<WorkspaceInviteForm onClose={closeInvite} />
|
|
||||||
</ScrollArea>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ export const desktopSidebarAtom = atomWithWebStorage<boolean>(
|
|||||||
|
|
||||||
export const desktopAsideAtom = atom<boolean>(false);
|
export const desktopAsideAtom = atom<boolean>(false);
|
||||||
|
|
||||||
// Valid `tab` values: "" | "comments" | "toc" | "chat" | "details"
|
|
||||||
type AsideStateType = {
|
type AsideStateType = {
|
||||||
tab: string;
|
tab: string;
|
||||||
isAsideOpen: boolean;
|
isAsideOpen: boolean;
|
||||||
|
|||||||
@@ -1,23 +1,16 @@
|
|||||||
import { UserProvider } from "@/features/user/user-provider.tsx";
|
import { UserProvider } from "@/features/user/user-provider.tsx";
|
||||||
import { Outlet, useParams } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
import GlobalAppShell from "@/components/layouts/global/global-app-shell.tsx";
|
import GlobalAppShell from "@/components/layouts/global/global-app-shell.tsx";
|
||||||
import { PosthogUser } from "@/ee/components/posthog-user.tsx";
|
import { PosthogUser } from "@/ee/components/posthog-user.tsx";
|
||||||
import { isCloud } from "@/lib/config.ts";
|
import { isCloud } from "@/lib/config.ts";
|
||||||
import { SearchSpotlight } from "@/features/search/components/search-spotlight.tsx";
|
|
||||||
import React from "react";
|
|
||||||
import { useGetSpaceBySlugQuery } from "@/features/space/queries/space-query.ts";
|
|
||||||
|
|
||||||
export default function Layout() {
|
export default function Layout() {
|
||||||
const { spaceSlug } = useParams();
|
|
||||||
const { data: space } = useGetSpaceBySlugQuery(spaceSlug);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserProvider>
|
<UserProvider>
|
||||||
<GlobalAppShell>
|
<GlobalAppShell>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</GlobalAppShell>
|
</GlobalAppShell>
|
||||||
{isCloud() && <PosthogUser />}
|
{isCloud() && <PosthogUser />}
|
||||||
<SearchSpotlight spaceId={space?.id} />
|
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
Group,
|
Group,
|
||||||
Menu,
|
Menu,
|
||||||
Text,
|
|
||||||
UnstyledButton,
|
UnstyledButton,
|
||||||
|
Text,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
IconBrush,
|
IconBrush,
|
||||||
IconCheck,
|
IconCheck,
|
||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
|
IconChevronRight,
|
||||||
IconDeviceDesktop,
|
IconDeviceDesktop,
|
||||||
IconLogout,
|
IconLogout,
|
||||||
IconMoon,
|
IconMoon,
|
||||||
@@ -25,7 +26,6 @@ import APP_ROUTE from "@/lib/app-route.ts";
|
|||||||
import useAuth from "@/features/auth/hooks/use-auth.ts";
|
import useAuth from "@/features/auth/hooks/use-auth.ts";
|
||||||
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
|
|
||||||
|
|
||||||
export default function TopMenu() {
|
export default function TopMenu() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -50,7 +50,6 @@ export default function TopMenu() {
|
|||||||
name={workspace?.name}
|
name={workspace?.name}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
size="sm"
|
size="sm"
|
||||||
type={AvatarIconType.WORKSPACE_ICON}
|
|
||||||
/>
|
/>
|
||||||
<Text fw={500} size="sm" lh={1} mr={3} lineClamp={1}>
|
<Text fw={500} size="sm" lh={1} mr={3} lineClamp={1}>
|
||||||
{workspace?.name}
|
{workspace?.name}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default function AppVersion() {
|
|||||||
href="https://github.com/docmost/docmost/releases"
|
href="https://github.com/docmost/docmost/releases"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{appVersion?.currentVersion && <>v{appVersion?.currentVersion}</>}
|
v{APP_VERSION}
|
||||||
</Text>
|
</Text>
|
||||||
</Indicator>
|
</Indicator>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -10,13 +10,9 @@ import { getWorkspaceMembers } from "@/features/workspace/services/workspace-ser
|
|||||||
import { getLicenseInfo } from "@/ee/licence/services/license-service.ts";
|
import { getLicenseInfo } from "@/ee/licence/services/license-service.ts";
|
||||||
import { getSsoProviders } from "@/ee/security/services/security-service.ts";
|
import { getSsoProviders } from "@/ee/security/services/security-service.ts";
|
||||||
import { getShares } from "@/features/share/services/share-service.ts";
|
import { getShares } from "@/features/share/services/share-service.ts";
|
||||||
import { getApiKeys } from "@/ee/api-key";
|
|
||||||
import { getAuditLogs } from "@/ee/audit/services/audit-service";
|
|
||||||
import { getVerificationList } from "@/ee/page-verification/services/page-verification-service";
|
|
||||||
import { getScimTokens } from "@/ee/scim/services/scim-token-service";
|
|
||||||
|
|
||||||
export const prefetchWorkspaceMembers = () => {
|
export const prefetchWorkspaceMembers = () => {
|
||||||
const params: QueryParams = { limit: 100, query: "" };
|
const params = { limit: 100, page: 1, query: "" } as QueryParams;
|
||||||
queryClient.prefetchQuery({
|
queryClient.prefetchQuery({
|
||||||
queryKey: ["workspaceMembers", params],
|
queryKey: ["workspaceMembers", params],
|
||||||
queryFn: () => getWorkspaceMembers(params),
|
queryFn: () => getWorkspaceMembers(params),
|
||||||
@@ -25,15 +21,15 @@ export const prefetchWorkspaceMembers = () => {
|
|||||||
|
|
||||||
export const prefetchSpaces = () => {
|
export const prefetchSpaces = () => {
|
||||||
queryClient.prefetchQuery({
|
queryClient.prefetchQuery({
|
||||||
queryKey: ["spaces", {}],
|
queryKey: ["spaces", { page: 1 }],
|
||||||
queryFn: () => getSpaces({}),
|
queryFn: () => getSpaces({ page: 1 }),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const prefetchGroups = () => {
|
export const prefetchGroups = () => {
|
||||||
queryClient.prefetchQuery({
|
queryClient.prefetchQuery({
|
||||||
queryKey: ["groups", {}],
|
queryKey: ["groups", { page: 1 }],
|
||||||
queryFn: () => getGroups({}),
|
queryFn: () => getGroups({ page: 1 }),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,44 +61,7 @@ export const prefetchSsoProviders = () => {
|
|||||||
|
|
||||||
export const prefetchShares = () => {
|
export const prefetchShares = () => {
|
||||||
queryClient.prefetchQuery({
|
queryClient.prefetchQuery({
|
||||||
queryKey: ["share-list", {}],
|
queryKey: ["share-list", { page: 1 }],
|
||||||
queryFn: () => getShares({}),
|
queryFn: () => getShares({ page: 1, limit: 100 }),
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const prefetchApiKeys = () => {
|
|
||||||
queryClient.prefetchQuery({
|
|
||||||
queryKey: ["api-key-list", {}],
|
|
||||||
queryFn: () => getApiKeys({}),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const prefetchApiKeyManagement = () => {
|
|
||||||
queryClient.prefetchQuery({
|
|
||||||
queryKey: ["api-key-list", { adminView: true }],
|
|
||||||
queryFn: () => getApiKeys({ adminView: true }),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const prefetchAuditLogs = () => {
|
|
||||||
const params = { limit: 50 };
|
|
||||||
queryClient.prefetchQuery({
|
|
||||||
queryKey: ["audit-logs", params],
|
|
||||||
queryFn: () => getAuditLogs(params),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const prefetchVerifiedPages = () => {
|
|
||||||
const params = { limit: 50 };
|
|
||||||
queryClient.prefetchQuery({
|
|
||||||
queryKey: ["verification-list", params],
|
|
||||||
queryFn: () => getVerificationList(params),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const prefetchScimTokens = () => {
|
|
||||||
queryClient.prefetchQuery({
|
|
||||||
queryKey: ["scim-token-list", { cursor: undefined }],
|
|
||||||
queryFn: () => getScimTokens({}),
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,51 +12,43 @@ import {
|
|||||||
IconLock,
|
IconLock,
|
||||||
IconKey,
|
IconKey,
|
||||||
IconWorld,
|
IconWorld,
|
||||||
IconSparkles,
|
|
||||||
IconHistory,
|
|
||||||
IconShieldCheck,
|
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { Link, useLocation } from "react-router-dom";
|
import { Link, useLocation } from "react-router-dom";
|
||||||
import classes from "./settings.module.css";
|
import classes from "./settings.module.css";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { isCloud } from "@/lib/config.ts";
|
import { isCloud } from "@/lib/config.ts";
|
||||||
import useUserRole from "@/hooks/use-user-role.tsx";
|
import useUserRole from "@/hooks/use-user-role.tsx";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai/index";
|
||||||
import { entitlementAtom } from "@/ee/entitlement/entitlement-atom";
|
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||||
import { Feature } from "@/ee/features";
|
|
||||||
import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label";
|
|
||||||
import {
|
import {
|
||||||
prefetchApiKeyManagement,
|
|
||||||
prefetchApiKeys,
|
|
||||||
prefetchBilling,
|
prefetchBilling,
|
||||||
prefetchGroups,
|
prefetchGroups,
|
||||||
prefetchLicense,
|
prefetchLicense,
|
||||||
prefetchScimTokens,
|
|
||||||
prefetchShares,
|
prefetchShares,
|
||||||
prefetchSpaces,
|
prefetchSpaces,
|
||||||
prefetchSsoProviders,
|
prefetchSsoProviders,
|
||||||
prefetchWorkspaceMembers,
|
prefetchWorkspaceMembers,
|
||||||
prefetchAuditLogs,
|
|
||||||
prefetchVerifiedPages,
|
|
||||||
} from "@/components/settings/settings-queries.tsx";
|
} from "@/components/settings/settings-queries.tsx";
|
||||||
import AppVersion from "@/components/settings/app-version.tsx";
|
import AppVersion from "@/components/settings/app-version.tsx";
|
||||||
import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
|
import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
|
||||||
import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts";
|
import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts";
|
||||||
import { useSettingsNavigation } from "@/hooks/use-settings-navigation";
|
import { useSettingsNavigation } from "@/hooks/use-settings-navigation";
|
||||||
|
|
||||||
type DataItem = {
|
interface DataItem {
|
||||||
label: string;
|
label: string;
|
||||||
icon: React.ElementType;
|
icon: React.ElementType;
|
||||||
path: string;
|
path: string;
|
||||||
feature?: string;
|
isCloud?: boolean;
|
||||||
role?: "admin" | "owner";
|
isEnterprise?: boolean;
|
||||||
env?: "cloud" | "selfhosted";
|
isAdmin?: boolean;
|
||||||
};
|
isSelfhosted?: boolean;
|
||||||
|
showDisabledInNonEE?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
type DataGroup = {
|
interface DataGroup {
|
||||||
heading: string;
|
heading: string;
|
||||||
items: DataItem[];
|
items: DataItem[];
|
||||||
};
|
}
|
||||||
|
|
||||||
const groupedData: DataGroup[] = [
|
const groupedData: DataGroup[] = [
|
||||||
{
|
{
|
||||||
@@ -68,63 +60,36 @@ const groupedData: DataGroup[] = [
|
|||||||
icon: IconBrush,
|
icon: IconBrush,
|
||||||
path: "/settings/account/preferences",
|
path: "/settings/account/preferences",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "API keys",
|
|
||||||
icon: IconKey,
|
|
||||||
path: "/settings/account/api-keys",
|
|
||||||
feature: Feature.API_KEYS,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Workspace",
|
heading: "Workspace",
|
||||||
items: [
|
items: [
|
||||||
{ label: "General", icon: IconSettings, path: "/settings/workspace" },
|
{ label: "General", icon: IconSettings, path: "/settings/workspace" },
|
||||||
{ label: "Members", icon: IconUsers, path: "/settings/members" },
|
{
|
||||||
|
label: "Members",
|
||||||
|
icon: IconUsers,
|
||||||
|
path: "/settings/members",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Billing",
|
label: "Billing",
|
||||||
icon: IconCoin,
|
icon: IconCoin,
|
||||||
path: "/settings/billing",
|
path: "/settings/billing",
|
||||||
role: "admin",
|
isCloud: true,
|
||||||
env: "cloud",
|
isAdmin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Security & SSO",
|
label: "Security & SSO",
|
||||||
icon: IconLock,
|
icon: IconLock,
|
||||||
path: "/settings/security",
|
path: "/settings/security",
|
||||||
feature: Feature.SECURITY_SETTINGS,
|
isCloud: true,
|
||||||
role: "admin",
|
isEnterprise: true,
|
||||||
|
isAdmin: true,
|
||||||
|
showDisabledInNonEE: true,
|
||||||
},
|
},
|
||||||
{ label: "Groups", icon: IconUsersGroup, path: "/settings/groups" },
|
{ label: "Groups", icon: IconUsersGroup, path: "/settings/groups" },
|
||||||
{ label: "Spaces", icon: IconSpaces, path: "/settings/spaces" },
|
{ label: "Spaces", icon: IconSpaces, path: "/settings/spaces" },
|
||||||
{ label: "Public sharing", icon: IconWorld, path: "/settings/sharing" },
|
{ label: "Public sharing", icon: IconWorld, path: "/settings/sharing" },
|
||||||
{
|
|
||||||
label: "Verified pages",
|
|
||||||
icon: IconShieldCheck,
|
|
||||||
path: "/settings/verifications",
|
|
||||||
feature: Feature.PAGE_VERIFICATION,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "API management",
|
|
||||||
icon: IconKey,
|
|
||||||
path: "/settings/api-keys",
|
|
||||||
feature: Feature.API_KEYS,
|
|
||||||
role: "admin",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "AI settings",
|
|
||||||
icon: IconSparkles,
|
|
||||||
path: "/settings/ai",
|
|
||||||
role: "admin",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Audit log",
|
|
||||||
icon: IconHistory,
|
|
||||||
path: "/settings/audit",
|
|
||||||
feature: Feature.AUDIT_LOGS,
|
|
||||||
role: "owner",
|
|
||||||
env: "selfhosted",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -144,9 +109,8 @@ export default function SettingsSidebar() {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [active, setActive] = useState(location.pathname);
|
const [active, setActive] = useState(location.pathname);
|
||||||
const { goBack } = useSettingsNavigation();
|
const { goBack } = useSettingsNavigation();
|
||||||
const { isAdmin, isOwner } = useUserRole();
|
const { isAdmin } = useUserRole();
|
||||||
const [entitlements] = useAtom(entitlementAtom);
|
const [workspace] = useAtom(workspaceAtom);
|
||||||
const upgradeLabel = useUpgradeLabel();
|
|
||||||
const [mobileSidebarOpened] = useAtom(mobileSidebarAtom);
|
const [mobileSidebarOpened] = useAtom(mobileSidebarAtom);
|
||||||
const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom);
|
const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom);
|
||||||
|
|
||||||
@@ -154,20 +118,41 @@ export default function SettingsSidebar() {
|
|||||||
setActive(location.pathname);
|
setActive(location.pathname);
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
const hasFeature = (f: string) =>
|
|
||||||
entitlements?.features?.includes(f) ?? false;
|
|
||||||
|
|
||||||
const canShowItem = (item: DataItem) => {
|
const canShowItem = (item: DataItem) => {
|
||||||
if (item.env === "cloud" && !isCloud()) return false;
|
if (item.showDisabledInNonEE && item.isEnterprise) {
|
||||||
if (item.env === "selfhosted" && isCloud()) return false;
|
// Check admin permission regardless of license
|
||||||
if (item.role === "admin" && !isAdmin) return false;
|
return item.isAdmin ? isAdmin : true;
|
||||||
if (item.role === "owner" && !isOwner) return false;
|
}
|
||||||
|
|
||||||
|
if (item.isCloud && item.isEnterprise) {
|
||||||
|
if (!(isCloud() || workspace?.hasLicenseKey)) return false;
|
||||||
|
return item.isAdmin ? isAdmin : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.isCloud) {
|
||||||
|
return isCloud() ? (item.isAdmin ? isAdmin : true) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.isSelfhosted) {
|
||||||
|
return !isCloud() ? (item.isAdmin ? isAdmin : true) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.isEnterprise) {
|
||||||
|
return workspace?.hasLicenseKey ? (item.isAdmin ? isAdmin : true) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.isAdmin) {
|
||||||
|
return isAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isItemDisabled = (item: DataItem) => {
|
const isItemDisabled = (item: DataItem) => {
|
||||||
if (!item.feature) return false;
|
if (item.showDisabledInNonEE && item.isEnterprise) {
|
||||||
return !hasFeature(item.feature);
|
return !(isCloud() || workspace?.hasLicenseKey);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const menuItems = groupedData.map((group) => {
|
const menuItems = groupedData.map((group) => {
|
||||||
@@ -200,80 +185,62 @@ export default function SettingsSidebar() {
|
|||||||
prefetchHandler = prefetchBilling;
|
prefetchHandler = prefetchBilling;
|
||||||
break;
|
break;
|
||||||
case "License & Edition":
|
case "License & Edition":
|
||||||
if (entitlements?.tier !== "free") {
|
if (workspace?.hasLicenseKey) {
|
||||||
prefetchHandler = prefetchLicense;
|
prefetchHandler = prefetchLicense;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Security & SSO":
|
case "Security & SSO":
|
||||||
prefetchHandler = () => {
|
prefetchHandler = prefetchSsoProviders;
|
||||||
prefetchSsoProviders();
|
|
||||||
prefetchScimTokens();
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
case "Public sharing":
|
case "Public sharing":
|
||||||
prefetchHandler = prefetchShares;
|
prefetchHandler = prefetchShares;
|
||||||
break;
|
break;
|
||||||
case "API keys":
|
|
||||||
prefetchHandler = prefetchApiKeys;
|
|
||||||
break;
|
|
||||||
case "API management":
|
|
||||||
prefetchHandler = prefetchApiKeyManagement;
|
|
||||||
break;
|
|
||||||
case "Audit log":
|
|
||||||
prefetchHandler = prefetchAuditLogs;
|
|
||||||
break;
|
|
||||||
case "Verified pages":
|
|
||||||
prefetchHandler = prefetchVerifiedPages;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDisabled = isItemDisabled(item);
|
const isDisabled = isItemDisabled(item);
|
||||||
|
const linkElement = (
|
||||||
if (isDisabled) {
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
key={item.label}
|
|
||||||
label={upgradeLabel}
|
|
||||||
position="right"
|
|
||||||
withArrow
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={classes.link}
|
|
||||||
data-disabled
|
|
||||||
role="link"
|
|
||||||
aria-disabled="true"
|
|
||||||
tabIndex={0}
|
|
||||||
style={{
|
|
||||||
opacity: 0.5,
|
|
||||||
cursor: "not-allowed",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<item.icon className={classes.linkIcon} stroke={2} />
|
|
||||||
<span>{t(item.label)}</span>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Link
|
<Link
|
||||||
onMouseEnter={prefetchHandler}
|
onMouseEnter={!isDisabled ? prefetchHandler : undefined}
|
||||||
className={classes.link}
|
className={classes.link}
|
||||||
data-active={active.startsWith(item.path) || undefined}
|
data-active={active.startsWith(item.path) || undefined}
|
||||||
|
data-disabled={isDisabled || undefined}
|
||||||
key={item.label}
|
key={item.label}
|
||||||
to={item.path}
|
to={isDisabled ? "#" : item.path}
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
|
if (isDisabled) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (mobileSidebarOpened) {
|
if (mobileSidebarOpened) {
|
||||||
toggleMobileSidebar();
|
toggleMobileSidebar();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
style={{
|
||||||
|
opacity: isDisabled ? 0.5 : 1,
|
||||||
|
cursor: isDisabled ? "not-allowed" : "pointer",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<item.icon className={classes.linkIcon} stroke={2} />
|
<item.icon className={classes.linkIcon} stroke={2} />
|
||||||
<span>{t(item.label)}</span>
|
<span>{t(item.label)}</span>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isDisabled) {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
key={item.label}
|
||||||
|
label={t("Available in enterprise edition")}
|
||||||
|
position="right"
|
||||||
|
withArrow
|
||||||
|
>
|
||||||
|
{linkElement}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return linkElement;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -291,7 +258,7 @@ export default function SettingsSidebar() {
|
|||||||
}}
|
}}
|
||||||
variant="transparent"
|
variant="transparent"
|
||||||
c="gray"
|
c="gray"
|
||||||
aria-label={t("Back")}
|
aria-label="Back"
|
||||||
>
|
>
|
||||||
<IconArrowLeft stroke={2} />
|
<IconArrowLeft stroke={2} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
import { useRef, useState, ReactNode } from "react";
|
|
||||||
import { Text, TextProps, Tooltip } from "@mantine/core";
|
|
||||||
|
|
||||||
type AutoTooltipTextProps = TextProps & {
|
|
||||||
children: ReactNode;
|
|
||||||
tooltipLabel?: string;
|
|
||||||
tooltipProps?: Omit<
|
|
||||||
React.ComponentProps<typeof Tooltip>,
|
|
||||||
"children" | "label"
|
|
||||||
>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function AutoTooltipText({
|
|
||||||
children,
|
|
||||||
tooltipLabel,
|
|
||||||
tooltipProps,
|
|
||||||
...textProps
|
|
||||||
}: AutoTooltipTextProps) {
|
|
||||||
const textRef = useRef<HTMLParagraphElement>(null);
|
|
||||||
const [isTruncated, setIsTruncated] = useState(false);
|
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
const element = textRef.current;
|
|
||||||
if (element) {
|
|
||||||
setIsTruncated(element.scrollWidth > element.clientWidth);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const label = tooltipLabel ?? (typeof children === "string" ? children : "");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
label={label}
|
|
||||||
disabled={!isTruncated || !label}
|
|
||||||
multiline
|
|
||||||
withArrow
|
|
||||||
withinPortal={false}
|
|
||||||
{...tooltipProps}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
ref={textRef}
|
|
||||||
truncate
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
{...textProps}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Text>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
.root {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--mantine-spacing-md);
|
|
||||||
overflow-x: auto;
|
|
||||||
scroll-snap-type: x mandatory;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
scrollbar-width: none;
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
padding: 2px;
|
|
||||||
margin: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track > * {
|
|
||||||
scroll-snap-align: start;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
background: light-dark(var(--mantine-color-white), var(--mantine-color-dark-6));
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
transition: opacity 120ms ease, background-color 120ms ease, transform 120ms ease;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.root:hover .arrow.visible,
|
|
||||||
.arrow.visible:focus-visible {
|
|
||||||
opacity: 1;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow:hover {
|
|
||||||
background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-5));
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow:active {
|
|
||||||
transform: translateY(-50%) scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrowLeft {
|
|
||||||
left: -14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrowRight {
|
|
||||||
right: -14px;
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
|
|
||||||
import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import classes from "./card-carousel.module.css";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
children: ReactNode;
|
|
||||||
ariaLabel?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function CardCarousel({ children, ariaLabel }: Props) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const trackRef = useRef<HTMLDivElement>(null);
|
|
||||||
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
|
||||||
const [canScrollRight, setCanScrollRight] = useState(false);
|
|
||||||
|
|
||||||
const updateScrollState = useCallback(() => {
|
|
||||||
const el = trackRef.current;
|
|
||||||
if (!el) return;
|
|
||||||
const maxScroll = el.scrollWidth - el.clientWidth;
|
|
||||||
setCanScrollLeft(el.scrollLeft > 1);
|
|
||||||
setCanScrollRight(el.scrollLeft < maxScroll - 1);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateScrollState();
|
|
||||||
const el = trackRef.current;
|
|
||||||
if (!el) return;
|
|
||||||
|
|
||||||
const observer = new ResizeObserver(updateScrollState);
|
|
||||||
observer.observe(el);
|
|
||||||
for (const child of Array.from(el.children)) {
|
|
||||||
observer.observe(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => observer.disconnect();
|
|
||||||
}, [updateScrollState, children]);
|
|
||||||
|
|
||||||
const scrollBy = (direction: 1 | -1) => {
|
|
||||||
const el = trackRef.current;
|
|
||||||
if (!el) return;
|
|
||||||
el.scrollBy({ left: direction * el.clientWidth * 0.85, behavior: "smooth" });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.root}>
|
|
||||||
<div
|
|
||||||
ref={trackRef}
|
|
||||||
className={classes.track}
|
|
||||||
onScroll={updateScrollState}
|
|
||||||
{...(ariaLabel ? { role: "region", "aria-label": ariaLabel } : {})}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`${classes.arrow} ${classes.arrowLeft} ${canScrollLeft ? classes.visible : ""}`}
|
|
||||||
onClick={() => scrollBy(-1)}
|
|
||||||
aria-label={t("Scroll left")}
|
|
||||||
tabIndex={canScrollLeft ? 0 : -1}
|
|
||||||
>
|
|
||||||
<IconChevronLeft size={18} />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`${classes.arrow} ${classes.arrowRight} ${canScrollRight ? classes.visible : ""}`}
|
|
||||||
onClick={() => scrollBy(1)}
|
|
||||||
aria-label={t("Scroll right")}
|
|
||||||
tabIndex={canScrollRight ? 0 : -1}
|
|
||||||
>
|
|
||||||
<IconChevronRight size={18} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Avatar, MantineColor } from "@mantine/core";
|
import { Avatar } from "@mantine/core";
|
||||||
import { getAvatarUrl } from "@/lib/config.ts";
|
import { getAvatarUrl } from "@/lib/config.ts";
|
||||||
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
|
|
||||||
|
|
||||||
interface CustomAvatarProps {
|
interface CustomAvatarProps {
|
||||||
avatarUrl?: string;
|
avatarUrl: string;
|
||||||
name: string;
|
name: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
size?: string | number;
|
size?: string | number;
|
||||||
@@ -12,57 +11,21 @@ interface CustomAvatarProps {
|
|||||||
variant?: string;
|
variant?: string;
|
||||||
style?: any;
|
style?: any;
|
||||||
component?: any;
|
component?: any;
|
||||||
type?: AvatarIconType;
|
|
||||||
mt?: string | number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// `color.shade` pairs whose filled background meets WCAG AA (4.5:1) against
|
|
||||||
// white text. Avoids lime/yellow/green/orange — even their dark shades have
|
|
||||||
// weak white-text contrast.
|
|
||||||
const SAFE_INITIALS_COLORS: MantineColor[] = [
|
|
||||||
"blue.8",
|
|
||||||
"cyan.9",
|
|
||||||
"grape.7",
|
|
||||||
"indigo.7",
|
|
||||||
"pink.8",
|
|
||||||
"red.8",
|
|
||||||
"violet.7",
|
|
||||||
];
|
|
||||||
|
|
||||||
function hashName(input: string) {
|
|
||||||
let hash = 0;
|
|
||||||
for (let i = 0; i < input.length; i += 1) {
|
|
||||||
hash = (hash << 5) - hash + input.charCodeAt(i);
|
|
||||||
hash |= 0;
|
|
||||||
}
|
|
||||||
return Math.abs(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
function pickInitialsColor(name: string) {
|
|
||||||
return SAFE_INITIALS_COLORS[hashName(name) % SAFE_INITIALS_COLORS.length];
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizeInitialsSource(name: string) {
|
|
||||||
const sanitized = name.replace(/[^\p{L}\p{N}\s]/gu, " ").trim();
|
|
||||||
return sanitized || name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CustomAvatar = React.forwardRef<
|
export const CustomAvatar = React.forwardRef<
|
||||||
HTMLInputElement,
|
HTMLInputElement,
|
||||||
CustomAvatarProps
|
CustomAvatarProps
|
||||||
>(({ avatarUrl, name, type, color, ...props }: CustomAvatarProps, ref) => {
|
>(({ avatarUrl, name, ...props }: CustomAvatarProps, ref) => {
|
||||||
const avatarLink = getAvatarUrl(avatarUrl, type);
|
const avatarLink = getAvatarUrl(avatarUrl);
|
||||||
const resolvedColor =
|
|
||||||
!color || color === "initials" ? pickInitialsColor(name ?? "") : color;
|
|
||||||
const initialsSource = sanitizeInitialsSource(name ?? "");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
ref={ref}
|
ref={ref}
|
||||||
src={avatarLink}
|
src={avatarLink}
|
||||||
name={initialsSource}
|
name={name}
|
||||||
alt={name}
|
alt={name}
|
||||||
color={resolvedColor}
|
color="initials"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
import { useState, useEffect } from "react";
|
|
||||||
import { Modal, Button, Group } from "@mantine/core";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { DestinationPicker } from "./destination-picker";
|
|
||||||
import {
|
|
||||||
DestinationPickerModalProps,
|
|
||||||
DestinationSelection,
|
|
||||||
} from "./destination-picker.types";
|
|
||||||
|
|
||||||
export function DestinationPickerModal({
|
|
||||||
opened,
|
|
||||||
onClose,
|
|
||||||
title,
|
|
||||||
actionLabel,
|
|
||||||
onSelect,
|
|
||||||
loading,
|
|
||||||
excludePageId,
|
|
||||||
pageLimit,
|
|
||||||
}: DestinationPickerModalProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [selection, setSelection] = useState<DestinationSelection | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!opened) {
|
|
||||||
setSelection(null);
|
|
||||||
}
|
|
||||||
}, [opened]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal.Root
|
|
||||||
opened={opened}
|
|
||||||
onClose={onClose}
|
|
||||||
size={550}
|
|
||||||
padding="lg"
|
|
||||||
yOffset="10vh"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<Modal.Overlay />
|
|
||||||
<Modal.Content>
|
|
||||||
<Modal.Header py={0}>
|
|
||||||
<Modal.Title fw={500}>{title}</Modal.Title>
|
|
||||||
<Modal.CloseButton />
|
|
||||||
</Modal.Header>
|
|
||||||
<Modal.Body>
|
|
||||||
<DestinationPicker
|
|
||||||
onSelectionChange={setSelection}
|
|
||||||
excludePageId={excludePageId}
|
|
||||||
pageLimit={pageLimit}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Group justify="flex-end" mt="md">
|
|
||||||
<Button variant="default" onClick={onClose}>
|
|
||||||
{t("Close")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => selection && onSelect(selection)}
|
|
||||||
disabled={!selection}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
{actionLabel}
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Modal.Body>
|
|
||||||
</Modal.Content>
|
|
||||||
</Modal.Root>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
.searchInput {
|
|
||||||
margin-bottom: var(--mantine-spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollArea {
|
|
||||||
max-height: 50vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: var(--mantine-radius-sm);
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
transition: background-color 150ms ease;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
background-color: light-dark(
|
|
||||||
var(--mantine-color-gray-0),
|
|
||||||
var(--mantine-color-dark-6)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
|
||||||
background-color: light-dark(
|
|
||||||
var(--mantine-color-blue-0),
|
|
||||||
var(--mantine-color-dark-5)
|
|
||||||
);
|
|
||||||
border-left: 2px solid var(--mantine-primary-color-filled);
|
|
||||||
}
|
|
||||||
|
|
||||||
.spaceRow {
|
|
||||||
composes: row;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pageRow {
|
|
||||||
composes: row;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chevron {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: var(--mantine-radius-sm);
|
|
||||||
flex-shrink: 0;
|
|
||||||
transition: transform 150ms ease;
|
|
||||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
background-color: light-dark(
|
|
||||||
var(--mantine-color-gray-1),
|
|
||||||
var(--mantine-color-dark-5)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chevronExpanded {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loadMore {
|
|
||||||
text-align: center;
|
|
||||||
padding: 6px;
|
|
||||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectedIndicator {
|
|
||||||
padding: 8px 12px;
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
|
||||||
border-top: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-4));
|
|
||||||
margin-top: var(--mantine-spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emptyState {
|
|
||||||
padding: 12px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
|
||||||
}
|
|
||||||
|
|
||||||
.searchResult {
|
|
||||||
composes: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pageTitle {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.spaceName {
|
|
||||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconWrapper {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
import { useState, useCallback } from "react";
|
|
||||||
import { TextInput, ScrollArea, Loader } from "@mantine/core";
|
|
||||||
import { useDebouncedValue } from "@mantine/hooks";
|
|
||||||
import { IconSearch, IconFile } from "@tabler/icons-react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useGetSpacesQuery } from "@/features/space/queries/space-query";
|
|
||||||
import { useSearchSuggestionsQuery } from "@/features/search/queries/search-query";
|
|
||||||
import { ISpace } from "@/features/space/types/space.types";
|
|
||||||
import { IPage } from "@/features/page/types/page.types";
|
|
||||||
import { DestinationSelection } from "./destination-picker.types";
|
|
||||||
import { SpaceRow } from "./space-row";
|
|
||||||
import classes from "./destination-picker.module.css";
|
|
||||||
|
|
||||||
type DestinationPickerProps = {
|
|
||||||
onSelectionChange: (selection: DestinationSelection | null) => void;
|
|
||||||
excludePageId?: string;
|
|
||||||
pageLimit?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function DestinationPicker({
|
|
||||||
onSelectionChange,
|
|
||||||
excludePageId,
|
|
||||||
pageLimit = 15,
|
|
||||||
}: DestinationPickerProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
|
||||||
const [selection, setSelection] = useState<DestinationSelection | null>(null);
|
|
||||||
const [debouncedQuery] = useDebouncedValue(searchQuery, 300);
|
|
||||||
|
|
||||||
const { data: spacesData, isLoading: spacesLoading } = useGetSpacesQuery({
|
|
||||||
limit: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
const searchEnabled = debouncedQuery && debouncedQuery.length >= 2;
|
|
||||||
|
|
||||||
const { data: searchData, isLoading: searchLoading } =
|
|
||||||
useSearchSuggestionsQuery({
|
|
||||||
query: searchEnabled ? debouncedQuery : "",
|
|
||||||
includePages: true,
|
|
||||||
limit: 20,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isSearching = !!searchEnabled;
|
|
||||||
|
|
||||||
const selectedId =
|
|
||||||
selection?.type === "space" ? selection.spaceId : selection?.pageId ?? null;
|
|
||||||
|
|
||||||
const updateSelection = useCallback(
|
|
||||||
(next: DestinationSelection | null) => {
|
|
||||||
setSelection(next);
|
|
||||||
onSelectionChange(next);
|
|
||||||
},
|
|
||||||
[onSelectionChange],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSearchResultClick = (page: Partial<IPage>) => {
|
|
||||||
if (!page.space || !page.id) return;
|
|
||||||
|
|
||||||
updateSelection({
|
|
||||||
type: "page",
|
|
||||||
spaceId: page.space.id,
|
|
||||||
pageId: page.id,
|
|
||||||
page,
|
|
||||||
space: page.space,
|
|
||||||
});
|
|
||||||
setSearchQuery("");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectSpace = useCallback(
|
|
||||||
(space: ISpace) => {
|
|
||||||
updateSelection({ type: "space", spaceId: space.id, space });
|
|
||||||
},
|
|
||||||
[updateSelection],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSelectPage = useCallback(
|
|
||||||
(page: Partial<IPage>, space: ISpace) => {
|
|
||||||
if (!page.id) return;
|
|
||||||
updateSelection({
|
|
||||||
type: "page",
|
|
||||||
spaceId: page.spaceId ?? space.id,
|
|
||||||
pageId: page.id,
|
|
||||||
page,
|
|
||||||
space,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[updateSelection],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TextInput
|
|
||||||
leftSection={<IconSearch size={16} />}
|
|
||||||
placeholder={t("Search pages and spaces...")}
|
|
||||||
variant="filled"
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => setSearchQuery(e.currentTarget.value)}
|
|
||||||
className={classes.searchInput}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ScrollArea h="50vh" offsetScrollbars className={classes.scrollArea}>
|
|
||||||
{isSearching ? (
|
|
||||||
searchLoading ? (
|
|
||||||
<div className={classes.emptyState}>
|
|
||||||
<Loader size="xs" />
|
|
||||||
</div>
|
|
||||||
) : searchData?.pages && searchData.pages.length > 0 ? (
|
|
||||||
searchData.pages.map(
|
|
||||||
(page) =>
|
|
||||||
page && (
|
|
||||||
<div
|
|
||||||
key={page.id}
|
|
||||||
className={classes.searchResult}
|
|
||||||
onClick={() => handleSearchResultClick(page)}
|
|
||||||
>
|
|
||||||
<div className={classes.iconWrapper}>
|
|
||||||
{page.icon ? (
|
|
||||||
page.icon
|
|
||||||
) : (
|
|
||||||
<IconFile
|
|
||||||
size={16}
|
|
||||||
color="var(--mantine-color-gray-5)"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className={classes.pageTitle}>
|
|
||||||
{page.title || t("Untitled")}
|
|
||||||
</div>
|
|
||||||
{page.space && (
|
|
||||||
<div className={classes.spaceName}>
|
|
||||||
{page.space.name}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<div className={classes.emptyState}>{t("No results found")}</div>
|
|
||||||
)
|
|
||||||
) : spacesLoading ? (
|
|
||||||
<div className={classes.emptyState}>
|
|
||||||
<Loader size="xs" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
spacesData?.items?.map((space) => (
|
|
||||||
<SpaceRow
|
|
||||||
key={space.id}
|
|
||||||
space={space}
|
|
||||||
limit={pageLimit}
|
|
||||||
selectedId={selectedId}
|
|
||||||
excludePageId={excludePageId}
|
|
||||||
onSelectSpace={handleSelectSpace}
|
|
||||||
onSelectPage={handleSelectPage}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</ScrollArea>
|
|
||||||
|
|
||||||
{selection && (
|
|
||||||
<div className={classes.selectedIndicator}>
|
|
||||||
{selection.type === "space"
|
|
||||||
? selection.space.name
|
|
||||||
: `${selection.space.name} / ${selection.page.title || t("Untitled")}`}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { ISpace } from "@/features/space/types/space.types";
|
|
||||||
import { IPage } from "@/features/page/types/page.types";
|
|
||||||
|
|
||||||
export type DestinationSelection =
|
|
||||||
| { type: "space"; spaceId: string; space: ISpace }
|
|
||||||
| {
|
|
||||||
type: "page";
|
|
||||||
spaceId: string;
|
|
||||||
pageId: string;
|
|
||||||
page: Partial<IPage>;
|
|
||||||
space: Partial<ISpace>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DestinationPickerModalProps = {
|
|
||||||
opened: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
title: string;
|
|
||||||
actionLabel: string;
|
|
||||||
onSelect: (selection: DestinationSelection) => void | Promise<void>;
|
|
||||||
loading?: boolean;
|
|
||||||
excludePageId?: string;
|
|
||||||
pageLimit?: number;
|
|
||||||
};
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
||||||
import { Loader } from "@mantine/core";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { getSidebarPages } from "@/features/page/services/page-service";
|
|
||||||
import { IPage } from "@/features/page/types/page.types";
|
|
||||||
import { IPagination } from "@/lib/types";
|
|
||||||
import { PageRow } from "./page-row";
|
|
||||||
import classes from "./destination-picker.module.css";
|
|
||||||
|
|
||||||
type PageChildrenProps = {
|
|
||||||
spaceId: string;
|
|
||||||
pageId?: string;
|
|
||||||
depth: number;
|
|
||||||
limit: number;
|
|
||||||
selectedId: string | null;
|
|
||||||
excludePageId?: string;
|
|
||||||
onSelectPage: (page: Partial<IPage>) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function PageChildren({
|
|
||||||
spaceId,
|
|
||||||
pageId,
|
|
||||||
depth,
|
|
||||||
limit,
|
|
||||||
selectedId,
|
|
||||||
excludePageId,
|
|
||||||
onSelectPage,
|
|
||||||
}: PageChildrenProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const { data, isLoading, hasNextPage, fetchNextPage } = useInfiniteQuery({
|
|
||||||
queryKey: ["destination-pages", spaceId, pageId ?? "root"],
|
|
||||||
queryFn: ({ pageParam }) =>
|
|
||||||
getSidebarPages({
|
|
||||||
spaceId,
|
|
||||||
pageId,
|
|
||||||
limit,
|
|
||||||
cursor: pageParam,
|
|
||||||
}),
|
|
||||||
initialPageParam: undefined as string | undefined,
|
|
||||||
getNextPageParam: (lastPage: IPagination<IPage>) =>
|
|
||||||
lastPage.meta?.nextCursor ?? undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const pages = data?.pages.flatMap((page) => page.items) ?? [];
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<div className={classes.emptyState}>
|
|
||||||
<Loader size="xs" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pages.length === 0) {
|
|
||||||
return (
|
|
||||||
<div className={classes.emptyState}>
|
|
||||||
{pageId ? t("No pages inside") : t("No pages in this space")}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{pages.map((page) => (
|
|
||||||
<PageRow
|
|
||||||
key={page.id}
|
|
||||||
page={page}
|
|
||||||
depth={depth}
|
|
||||||
limit={limit}
|
|
||||||
selectedId={selectedId}
|
|
||||||
excludePageId={excludePageId}
|
|
||||||
onSelect={onSelectPage}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{hasNextPage && (
|
|
||||||
<div
|
|
||||||
className={classes.loadMore}
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
|
||||||
e.preventDefault();
|
|
||||||
fetchNextPage();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
{t("Load more")}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { IconChevronRight, IconFile } from "@tabler/icons-react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { IPage } from "@/features/page/types/page.types";
|
|
||||||
import { PageChildren } from "./page-children";
|
|
||||||
import classes from "./destination-picker.module.css";
|
|
||||||
|
|
||||||
type PageRowProps = {
|
|
||||||
page: Partial<IPage>;
|
|
||||||
depth: number;
|
|
||||||
limit: number;
|
|
||||||
selectedId: string | null;
|
|
||||||
excludePageId?: string;
|
|
||||||
onSelect: (page: Partial<IPage>) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function PageRow({
|
|
||||||
page,
|
|
||||||
depth,
|
|
||||||
limit,
|
|
||||||
selectedId,
|
|
||||||
excludePageId,
|
|
||||||
onSelect,
|
|
||||||
}: PageRowProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
|
||||||
|
|
||||||
const isExcluded = page.id === excludePageId;
|
|
||||||
const isSelected = page.id === selectedId;
|
|
||||||
|
|
||||||
const rowClasses = [
|
|
||||||
classes.pageRow,
|
|
||||||
isSelected && classes.selected,
|
|
||||||
isExcluded && classes.disabled,
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className={rowClasses}
|
|
||||||
style={{ paddingLeft: depth * 20 + 12 }}
|
|
||||||
onClick={() => !isExcluded && onSelect(page)}
|
|
||||||
>
|
|
||||||
{page.hasChildren ? (
|
|
||||||
<div
|
|
||||||
className={`${classes.chevron} ${expanded ? classes.chevronExpanded : ""}`}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setExpanded(!expanded);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconChevronRight size={14} />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div style={{ width: 20, flexShrink: 0 }} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className={classes.iconWrapper}>
|
|
||||||
{page.icon ? (
|
|
||||||
page.icon
|
|
||||||
) : (
|
|
||||||
<IconFile
|
|
||||||
size={16}
|
|
||||||
color="var(--mantine-color-gray-5)"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={classes.pageTitle}>
|
|
||||||
{page.title || t("Untitled")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{expanded && page.hasChildren && (
|
|
||||||
<PageChildren
|
|
||||||
spaceId={page.spaceId}
|
|
||||||
pageId={page.id}
|
|
||||||
depth={depth + 1}
|
|
||||||
limit={limit}
|
|
||||||
selectedId={selectedId}
|
|
||||||
excludePageId={excludePageId}
|
|
||||||
onSelectPage={onSelect}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { Tooltip } from "@mantine/core";
|
|
||||||
import { IconChevronRight, IconLock } from "@tabler/icons-react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { ISpace } from "@/features/space/types/space.types";
|
|
||||||
import { IPage } from "@/features/page/types/page.types";
|
|
||||||
import { SpaceRole } from "@/lib/types";
|
|
||||||
import { CustomAvatar } from "@/components/ui/custom-avatar";
|
|
||||||
import { AvatarIconType } from "@/features/attachments/types/attachment.types";
|
|
||||||
import { PageChildren } from "./page-children";
|
|
||||||
import classes from "./destination-picker.module.css";
|
|
||||||
|
|
||||||
type SpaceRowProps = {
|
|
||||||
space: ISpace;
|
|
||||||
limit: number;
|
|
||||||
selectedId: string | null;
|
|
||||||
excludePageId?: string;
|
|
||||||
onSelectSpace: (space: ISpace) => void;
|
|
||||||
onSelectPage: (page: Partial<IPage>, space: ISpace) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function SpaceRow({
|
|
||||||
space,
|
|
||||||
limit,
|
|
||||||
selectedId,
|
|
||||||
excludePageId,
|
|
||||||
onSelectSpace,
|
|
||||||
onSelectPage,
|
|
||||||
}: SpaceRowProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
|
||||||
|
|
||||||
const writable =
|
|
||||||
!!space.membership?.role && space.membership.role !== SpaceRole.READER;
|
|
||||||
const isSelected = space.id === selectedId;
|
|
||||||
|
|
||||||
const rowClasses = [
|
|
||||||
classes.spaceRow,
|
|
||||||
isSelected && classes.selected,
|
|
||||||
!writable && classes.disabled,
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
const rowContent = (
|
|
||||||
<div
|
|
||||||
className={rowClasses}
|
|
||||||
onClick={() => writable && onSelectSpace(space)}
|
|
||||||
>
|
|
||||||
{writable ? (
|
|
||||||
<div
|
|
||||||
className={`${classes.chevron} ${expanded ? classes.chevronExpanded : ""}`}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setExpanded(!expanded);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconChevronRight size={14} />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div style={{ width: 20, flexShrink: 0 }} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<CustomAvatar
|
|
||||||
name={space.name}
|
|
||||||
avatarUrl={space.logo}
|
|
||||||
type={AvatarIconType.SPACE_ICON}
|
|
||||||
size={22}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={classes.pageTitle}>{space.name}</div>
|
|
||||||
|
|
||||||
{!writable && (
|
|
||||||
<IconLock
|
|
||||||
size={14}
|
|
||||||
color="var(--mantine-color-gray-5)"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{writable ? (
|
|
||||||
rowContent
|
|
||||||
) : (
|
|
||||||
<Tooltip
|
|
||||||
label={t("You don't have permission to create pages here")}
|
|
||||||
position="right"
|
|
||||||
withArrow
|
|
||||||
>
|
|
||||||
<div>{rowContent}</div>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{expanded && writable && (
|
|
||||||
<PageChildren
|
|
||||||
spaceId={space.id}
|
|
||||||
depth={1}
|
|
||||||
limit={limit}
|
|
||||||
selectedId={selectedId}
|
|
||||||
excludePageId={excludePageId}
|
|
||||||
onSelectPage={(page) => onSelectPage(page, space)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -15,11 +15,6 @@ export interface EmojiPickerInterface {
|
|||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
removeEmojiAction: () => void;
|
removeEmojiAction: () => void;
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
actionIconProps?: {
|
|
||||||
size?: string;
|
|
||||||
variant?: string;
|
|
||||||
c?: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmojiPicker({
|
function EmojiPicker({
|
||||||
@@ -27,7 +22,6 @@ function EmojiPicker({
|
|||||||
icon,
|
icon,
|
||||||
removeEmojiAction,
|
removeEmojiAction,
|
||||||
readOnly,
|
readOnly,
|
||||||
actionIconProps,
|
|
||||||
}: EmojiPickerInterface) {
|
}: EmojiPickerInterface) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [opened, handlers] = useDisclosure(false);
|
const [opened, handlers] = useDisclosure(false);
|
||||||
@@ -70,15 +64,7 @@ function EmojiPicker({
|
|||||||
closeOnEscape={true}
|
closeOnEscape={true}
|
||||||
>
|
>
|
||||||
<Popover.Target ref={setTarget}>
|
<Popover.Target ref={setTarget}>
|
||||||
<ActionIcon
|
<ActionIcon c="gray" variant="transparent" onClick={handlers.toggle}>
|
||||||
c={actionIconProps?.c || "gray"}
|
|
||||||
variant={actionIconProps?.variant || "transparent"}
|
|
||||||
size={actionIconProps?.size}
|
|
||||||
onClick={handlers.toggle}
|
|
||||||
aria-label={t("Pick emoji")}
|
|
||||||
aria-haspopup="dialog"
|
|
||||||
aria-expanded={opened}
|
|
||||||
>
|
|
||||||
{icon}
|
{icon}
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Popover.Target>
|
</Popover.Target>
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
.root {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { Stack, Text } from "@mantine/core";
|
|
||||||
import { type TablerIcon } from "@tabler/icons-react";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
import classes from "./empty-state.module.css";
|
|
||||||
|
|
||||||
type EmptyStateProps = {
|
|
||||||
icon: TablerIcon;
|
|
||||||
title: string;
|
|
||||||
description?: string;
|
|
||||||
action?: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function EmptyState({ icon: Icon, title, description, action }: EmptyStateProps) {
|
|
||||||
return (
|
|
||||||
<div className={classes.root}>
|
|
||||||
<Stack align="center" gap="xs">
|
|
||||||
<Icon size={40} stroke={1.5} color="var(--mantine-color-dimmed)" />
|
|
||||||
<Text size="lg" fw={500}>
|
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
{description && (
|
|
||||||
<Text size="sm" c="dimmed" maw={350}>
|
|
||||||
{description}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
{action}
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { Box } from "@mantine/core";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
interface ResponsiveSettingsRowProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ResponsiveSettingsRow({ children }: ResponsiveSettingsRowProps) {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "1rem",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ResponsiveSettingsContentProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ResponsiveSettingsContent({ children }: ResponsiveSettingsContentProps) {
|
|
||||||
return (
|
|
||||||
<Box style={{ flex: "1 1 300px", minWidth: 0 }}>
|
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ResponsiveSettingsControlProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ResponsiveSettingsControl({ children }: ResponsiveSettingsControlProps) {
|
|
||||||
return (
|
|
||||||
<Box style={{ flex: "0 0 auto" }}>
|
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import { useEffect, useRef } from "react";
|
|
||||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
|
||||||
import { useChatInfoQuery } from "../queries/ai-chat-query";
|
|
||||||
import { useChatStream } from "../hooks/use-chat-stream";
|
|
||||||
import ChatMessageList from "./chat-message-list";
|
|
||||||
import ChatEmptyState from "./chat-empty-state";
|
|
||||||
import ChatInput from "./chat-input";
|
|
||||||
import type { HomeAiPromptInitialState } from "@/features/home/components/home-ai-prompt";
|
|
||||||
import classes from "../styles/ai-chat.module.css";
|
|
||||||
|
|
||||||
export default function AiChatLayout() {
|
|
||||||
const { chatId } = useParams<{ chatId: string }>();
|
|
||||||
const location = useLocation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const chatInfoQuery = useChatInfoQuery(chatId);
|
|
||||||
|
|
||||||
// If the URL points at a chat the user does not own, the info fetch 404s.
|
|
||||||
// Bounce them back to /ai so they cannot interact with any chat UI (including
|
|
||||||
// kicking off orphan uploads) tied to a chat they have no access to.
|
|
||||||
useEffect(() => {
|
|
||||||
if (chatId && chatInfoQuery.isError) {
|
|
||||||
navigate("/ai", { replace: true });
|
|
||||||
}
|
|
||||||
}, [chatId, chatInfoQuery.isError, navigate]);
|
|
||||||
const {
|
|
||||||
messages,
|
|
||||||
streamingContent,
|
|
||||||
streamingToolCalls,
|
|
||||||
isStreaming,
|
|
||||||
error,
|
|
||||||
sendMessage,
|
|
||||||
stopGeneration,
|
|
||||||
hydrateFromServer,
|
|
||||||
} = useChatStream(chatId);
|
|
||||||
|
|
||||||
const autoSentRef = useRef(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (chatInfoQuery.data?.messages) {
|
|
||||||
hydrateFromServer(chatInfoQuery.data.messages);
|
|
||||||
}
|
|
||||||
}, [chatInfoQuery.data, hydrateFromServer]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (autoSentRef.current || chatId) return;
|
|
||||||
const state = location.state as HomeAiPromptInitialState | null;
|
|
||||||
if (!state?.initialContent && !state?.initialAttachments?.length) return;
|
|
||||||
|
|
||||||
autoSentRef.current = true;
|
|
||||||
sendMessage(
|
|
||||||
state.initialContent ?? "",
|
|
||||||
state.initialMentions ?? [],
|
|
||||||
state.initialAttachments ?? [],
|
|
||||||
);
|
|
||||||
navigate(location.pathname, { replace: true, state: null });
|
|
||||||
}, [chatId, location, navigate, sendMessage]);
|
|
||||||
|
|
||||||
const hasMessages = messages.length > 0 || isStreaming || !!chatId;
|
|
||||||
|
|
||||||
// While the redirect effect is running (or if the user is still on this
|
|
||||||
// component for any reason) never render the chat UI for a forbidden chat.
|
|
||||||
if (chatId && chatInfoQuery.isError) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.main}>
|
|
||||||
{hasMessages ? (
|
|
||||||
<>
|
|
||||||
<ChatMessageList
|
|
||||||
messages={messages}
|
|
||||||
isStreaming={isStreaming}
|
|
||||||
streamingContent={streamingContent}
|
|
||||||
streamingToolCalls={streamingToolCalls}
|
|
||||||
/>
|
|
||||||
{error && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: "var(--mantine-spacing-sm) var(--mantine-spacing-lg)",
|
|
||||||
color: "var(--mantine-color-red-6)",
|
|
||||||
fontSize: "var(--mantine-font-size-sm)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={classes.inputArea}>
|
|
||||||
<ChatInput
|
|
||||||
isStreaming={isStreaming}
|
|
||||||
onSend={sendMessage}
|
|
||||||
onStop={stopGeneration}
|
|
||||||
chatId={chatId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<ChatEmptyState
|
|
||||||
isStreaming={isStreaming}
|
|
||||||
onSend={sendMessage}
|
|
||||||
onStop={stopGeneration}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
import { useState, useRef, useEffect, useMemo, useCallback } from "react";
|
|
||||||
import { ActionIcon, Menu, TextInput } from "@mantine/core";
|
|
||||||
import { IconDots, IconTrash, IconEdit } from "@tabler/icons-react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import type { AiChat } from "../types/ai-chat.types";
|
|
||||||
import classes from "../styles/chat-sidebar.module.css";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
chat: AiChat;
|
|
||||||
isActive: boolean;
|
|
||||||
onDelete: (chatId: string, title: string | null) => void;
|
|
||||||
onRename: (chatId: string, title: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
function formatChatDate(
|
|
||||||
isoString: string | Date,
|
|
||||||
locale: string | undefined,
|
|
||||||
): string {
|
|
||||||
const date = new Date(isoString);
|
|
||||||
if (Number.isNaN(date.getTime())) return "";
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const startOfToday = new Date(
|
|
||||||
now.getFullYear(),
|
|
||||||
now.getMonth(),
|
|
||||||
now.getDate(),
|
|
||||||
).getTime();
|
|
||||||
const ts = date.getTime();
|
|
||||||
const sameYear = date.getFullYear() === now.getFullYear();
|
|
||||||
|
|
||||||
if (ts >= startOfToday) {
|
|
||||||
return date.toLocaleTimeString(locale, {
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "2-digit",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sameYear) {
|
|
||||||
return date.toLocaleDateString(locale, {
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return date.toLocaleDateString(locale, {
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
year: "numeric",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AiChatSidebarItem({
|
|
||||||
chat,
|
|
||||||
isActive,
|
|
||||||
onDelete,
|
|
||||||
onRename,
|
|
||||||
}: Props) {
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
const [renaming, setRenaming] = useState(false);
|
|
||||||
const [renameValue, setRenameValue] = useState("");
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const formattedDate = useMemo(
|
|
||||||
() => formatChatDate(chat.updatedAt, i18n.language),
|
|
||||||
[chat.updatedAt, i18n.language],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (renaming) {
|
|
||||||
// Wait for the input to be mounted before selecting.
|
|
||||||
const id = window.setTimeout(() => inputRef.current?.select(), 0);
|
|
||||||
return () => window.clearTimeout(id);
|
|
||||||
}
|
|
||||||
}, [renaming]);
|
|
||||||
|
|
||||||
const startRename = useCallback(() => {
|
|
||||||
setRenameValue(chat.title || "");
|
|
||||||
setRenaming(true);
|
|
||||||
}, [chat.title]);
|
|
||||||
|
|
||||||
const submitRename = useCallback(() => {
|
|
||||||
const trimmed = renameValue.trim();
|
|
||||||
if (trimmed && trimmed !== chat.title) {
|
|
||||||
onRename(chat.id, trimmed);
|
|
||||||
}
|
|
||||||
setRenaming(false);
|
|
||||||
}, [renameValue, chat.id, chat.title, onRename]);
|
|
||||||
|
|
||||||
if (renaming) {
|
|
||||||
return (
|
|
||||||
<div className={classes.chatItem} data-active={isActive || undefined}>
|
|
||||||
<TextInput
|
|
||||||
ref={inputRef}
|
|
||||||
size="xs"
|
|
||||||
variant="unstyled"
|
|
||||||
placeholder={t("Chat name")}
|
|
||||||
value={renameValue}
|
|
||||||
onChange={(e) => setRenameValue(e.currentTarget.value)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
submitRename();
|
|
||||||
} else if (e.key === "Escape") {
|
|
||||||
e.preventDefault();
|
|
||||||
setRenaming(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onBlur={submitRename}
|
|
||||||
classNames={{ input: classes.chatItemRenameInput }}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
to={`/ai/chat/${chat.id}`}
|
|
||||||
className={classes.chatItem}
|
|
||||||
data-active={isActive || undefined}
|
|
||||||
>
|
|
||||||
<span className={classes.chatItemTitle}>
|
|
||||||
{chat.title || t("Untitled chat")}
|
|
||||||
</span>
|
|
||||||
<span className={classes.chatItemDate}>{formattedDate}</span>
|
|
||||||
<div className={classes.chatItemActions}>
|
|
||||||
<Menu position="bottom-end" withinPortal>
|
|
||||||
<Menu.Target>
|
|
||||||
<ActionIcon
|
|
||||||
variant="subtle"
|
|
||||||
size="xs"
|
|
||||||
color="gray"
|
|
||||||
onClick={(e) => e.preventDefault()}
|
|
||||||
aria-label={t("Chat menu")}
|
|
||||||
>
|
|
||||||
<IconDots size={14} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Menu.Target>
|
|
||||||
<Menu.Dropdown>
|
|
||||||
<Menu.Item
|
|
||||||
leftSection={<IconEdit size={14} />}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
startRename();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("Rename")}
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item
|
|
||||||
leftSection={<IconTrash size={14} />}
|
|
||||||
color="red"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
onDelete(chat.id, chat.title);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("Delete")}
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu.Dropdown>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
import { useState, useCallback, useEffect, useMemo, useRef } from "react";
|
|
||||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
|
||||||
import {
|
|
||||||
ActionIcon,
|
|
||||||
Center,
|
|
||||||
Text,
|
|
||||||
TextInput,
|
|
||||||
Loader,
|
|
||||||
Tooltip,
|
|
||||||
} from "@mantine/core";
|
|
||||||
import { modals } from "@mantine/modals";
|
|
||||||
import { useDebouncedValue } from "@mantine/hooks";
|
|
||||||
import { IconPlus, IconSearch, IconMessageCircle2 } from "@tabler/icons-react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import {
|
|
||||||
useChatsQuery,
|
|
||||||
useDeleteChatMutation,
|
|
||||||
useUpdateChatTitleMutation,
|
|
||||||
useSearchChatsQuery,
|
|
||||||
} from "../queries/ai-chat-query";
|
|
||||||
import AiChatSidebarItem from "./ai-chat-sidebar-item";
|
|
||||||
import { groupChatsByAge } from "../utils/group-chats-by-age";
|
|
||||||
import classes from "../styles/chat-sidebar.module.css";
|
|
||||||
|
|
||||||
export default function AiChatSidebar() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { chatId } = useParams<{ chatId: string }>();
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 300);
|
|
||||||
const chatsQuery = useChatsQuery();
|
|
||||||
const searchQuery = useSearchChatsQuery(debouncedSearch);
|
|
||||||
const deleteMutation = useDeleteChatMutation();
|
|
||||||
const renameMutation = useUpdateChatTitleMutation();
|
|
||||||
|
|
||||||
const chats = useMemo(() => {
|
|
||||||
if (debouncedSearch) {
|
|
||||||
return searchQuery.data || [];
|
|
||||||
}
|
|
||||||
return chatsQuery.data?.pages.flatMap((p) => p.items) || [];
|
|
||||||
}, [debouncedSearch, searchQuery.data, chatsQuery.data]);
|
|
||||||
|
|
||||||
const groupedChats = useMemo(() => groupChatsByAge(chats, t), [chats, t]);
|
|
||||||
|
|
||||||
const sentinelRef = useRef<HTMLDivElement>(null);
|
|
||||||
const { hasNextPage, fetchNextPage, isFetchingNextPage } = chatsQuery;
|
|
||||||
const isSearching = Boolean(debouncedSearch);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isSearching) return;
|
|
||||||
const sentinel = sentinelRef.current;
|
|
||||||
if (!sentinel) return;
|
|
||||||
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
(entries) => {
|
|
||||||
if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
|
|
||||||
fetchNextPage();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ threshold: 0.1 },
|
|
||||||
);
|
|
||||||
|
|
||||||
observer.observe(sentinel);
|
|
||||||
return () => observer.disconnect();
|
|
||||||
}, [isSearching, hasNextPage, isFetchingNextPage, fetchNextPage]);
|
|
||||||
|
|
||||||
const handleNewChat = useCallback(
|
|
||||||
(event: React.MouseEvent<HTMLAnchorElement>) => {
|
|
||||||
if (
|
|
||||||
event.button !== 0 ||
|
|
||||||
event.ctrlKey ||
|
|
||||||
event.metaKey ||
|
|
||||||
event.shiftKey
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
navigate("/ai");
|
|
||||||
},
|
|
||||||
[navigate],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDelete = useCallback(
|
|
||||||
(id: string, title: string | null) => {
|
|
||||||
modals.openConfirmModal({
|
|
||||||
title: t("Delete chat"),
|
|
||||||
centered: true,
|
|
||||||
children: (
|
|
||||||
<Text size="sm">
|
|
||||||
{t("Are you sure you want to delete '{{title}}'? This action cannot be undone.", {
|
|
||||||
title: title || t("Untitled"),
|
|
||||||
})}
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
labels: { confirm: t("Delete"), cancel: t("Cancel") },
|
|
||||||
confirmProps: { color: "red" },
|
|
||||||
onConfirm: () => {
|
|
||||||
deleteMutation.mutate(id, {
|
|
||||||
onSuccess: () => {
|
|
||||||
if (chatId === id) {
|
|
||||||
navigate("/ai");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[deleteMutation, chatId, navigate, t],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRename = useCallback(
|
|
||||||
(chatId: string, title: string) => {
|
|
||||||
renameMutation.mutate({ chatId, title });
|
|
||||||
},
|
|
||||||
[renameMutation],
|
|
||||||
);
|
|
||||||
|
|
||||||
const isLoading = chatsQuery.isLoading || searchQuery.isLoading;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.sidebar}>
|
|
||||||
<div className={classes.header}>
|
|
||||||
<span className={classes.title}>{t("AI Chat")}</span>
|
|
||||||
<Tooltip label={t("New chat")} openDelay={250} withArrow>
|
|
||||||
<ActionIcon
|
|
||||||
component={Link}
|
|
||||||
to="/ai"
|
|
||||||
variant="subtle"
|
|
||||||
color="gray"
|
|
||||||
onClick={handleNewChat}
|
|
||||||
aria-label={t("New chat")}
|
|
||||||
>
|
|
||||||
<IconPlus size={18} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
className={classes.searchInput}
|
|
||||||
placeholder={t("Search chats...")}
|
|
||||||
aria-label={t("Search chats")}
|
|
||||||
leftSection={<IconSearch size={14} />}
|
|
||||||
size="xs"
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={classes.chatList}>
|
|
||||||
{isLoading && <Loader size="xs" mx="auto" mt="md" />}
|
|
||||||
{!isLoading && chats.length === 0 && (
|
|
||||||
<div className={classes.chatListEmpty}>
|
|
||||||
<IconMessageCircle2
|
|
||||||
size={28}
|
|
||||||
stroke={1.5}
|
|
||||||
className={classes.chatListEmptyIcon}
|
|
||||||
/>
|
|
||||||
<div className={classes.chatListEmptyTitle}>
|
|
||||||
{isSearching ? t("No chats found") : t("No conversations yet")}
|
|
||||||
</div>
|
|
||||||
<div className={classes.chatListEmptyHint}>
|
|
||||||
{isSearching
|
|
||||||
? t("Try a different search term.")
|
|
||||||
: t("Start a new chat to see it here.")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isSearching
|
|
||||||
? chats.map((chat) => (
|
|
||||||
<AiChatSidebarItem
|
|
||||||
key={chat.id}
|
|
||||||
chat={chat}
|
|
||||||
isActive={chat.id === chatId}
|
|
||||||
onDelete={handleDelete}
|
|
||||||
onRename={handleRename}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
: groupedChats.map((group) => (
|
|
||||||
<div key={group.key} className={classes.chatGroup}>
|
|
||||||
<div className={classes.chatGroupLabel}>{group.label}</div>
|
|
||||||
{group.chats.map((chat) => (
|
|
||||||
<AiChatSidebarItem
|
|
||||||
key={chat.id}
|
|
||||||
chat={chat}
|
|
||||||
isActive={chat.id === chatId}
|
|
||||||
onDelete={handleDelete}
|
|
||||||
onRename={handleRename}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{!isSearching && (
|
|
||||||
<>
|
|
||||||
<div ref={sentinelRef} style={{ height: 1 }} />
|
|
||||||
{isFetchingNextPage && (
|
|
||||||
<Center py="xs">
|
|
||||||
<Loader size="xs" />
|
|
||||||
</Center>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { TextInput, Loader, Text, ScrollArea } from "@mantine/core";
|
|
||||||
import { IconSearch } from "@tabler/icons-react";
|
|
||||||
import { useChatsQuery, useSearchChatsQuery } from "../queries/ai-chat-query";
|
|
||||||
import { useDebouncedValue } from "@mantine/hooks";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import classes from "../styles/aside-chat-panel.module.css";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
activeChatId: string | undefined;
|
|
||||||
onSelect: (chatId: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function AsideChatHistory({ activeChatId, onSelect }: Props) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [searchValue, setSearchValue] = useState("");
|
|
||||||
const [debouncedSearch] = useDebouncedValue(searchValue, 300);
|
|
||||||
|
|
||||||
const chatsQuery = useChatsQuery();
|
|
||||||
const searchQuery = useSearchChatsQuery(debouncedSearch);
|
|
||||||
|
|
||||||
const isSearching = debouncedSearch.length > 0;
|
|
||||||
const chats = isSearching
|
|
||||||
? (searchQuery.data ?? [])
|
|
||||||
: (chatsQuery.data?.pages.flatMap((p) => p.items) ?? []);
|
|
||||||
const isLoading = isSearching ? searchQuery.isLoading : chatsQuery.isLoading;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<TextInput
|
|
||||||
placeholder={t("Search chats...")}
|
|
||||||
leftSection={<IconSearch size={14} />}
|
|
||||||
size="xs"
|
|
||||||
mb="xs"
|
|
||||||
value={searchValue}
|
|
||||||
onChange={(e) => setSearchValue(e.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{isLoading ? (
|
|
||||||
<div style={{ display: "flex", justifyContent: "center", padding: 16 }}>
|
|
||||||
<Loader size="sm" />
|
|
||||||
</div>
|
|
||||||
) : chats.length === 0 ? (
|
|
||||||
<Text size="sm" c="dimmed" ta="center" py="md">
|
|
||||||
{isSearching ? t("No chats found") : t("No chat history")}
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<ScrollArea.Autosize mah={300} scrollbars="y">
|
|
||||||
<div className={classes.historyList}>
|
|
||||||
{chats.map((chat) => (
|
|
||||||
<div
|
|
||||||
key={chat.id}
|
|
||||||
className={classes.historyItem}
|
|
||||||
data-active={chat.id === activeChatId || undefined}
|
|
||||||
onClick={() => onSelect(chat.id)}
|
|
||||||
>
|
|
||||||
<span className={classes.historyItemTitle}>
|
|
||||||
{chat.title || t("Untitled chat")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</ScrollArea.Autosize>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
import { useState, useEffect, useCallback } from "react";
|
|
||||||
import { ActionIcon, Popover, Tooltip, UnstyledButton } from "@mantine/core";
|
|
||||||
import {
|
|
||||||
IconPlus,
|
|
||||||
IconChevronDown,
|
|
||||||
IconArrowsDiagonal,
|
|
||||||
IconX,
|
|
||||||
IconSparkles,
|
|
||||||
IconFileText,
|
|
||||||
IconLanguage,
|
|
||||||
IconSearch,
|
|
||||||
} from "@tabler/icons-react";
|
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom";
|
|
||||||
import { usePageQuery } from "@/features/page/queries/page-query";
|
|
||||||
import { extractPageSlugId } from "@/lib";
|
|
||||||
import { useChatStream } from "../hooks/use-chat-stream";
|
|
||||||
import { useChatInfoQuery } from "../queries/ai-chat-query";
|
|
||||||
import ChatMessageList from "./chat-message-list";
|
|
||||||
import ChatInput from "./chat-input";
|
|
||||||
import AsideChatHistory from "./aside-chat-history";
|
|
||||||
import type { ChatAttachment, PageMention } from "../types/ai-chat.types";
|
|
||||||
import classes from "../styles/aside-chat-panel.module.css";
|
|
||||||
|
|
||||||
type QuickAction = {
|
|
||||||
icon: React.ReactNode;
|
|
||||||
label: string;
|
|
||||||
prompt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function AsideChatPanel() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [, setAsideState] = useAtom(asideStateAtom);
|
|
||||||
const [chatId, setChatId] = useState<string | undefined>(undefined);
|
|
||||||
const [historyOpen, setHistoryOpen] = useState(false);
|
|
||||||
const [contextPages, setContextPages] = useState<PageMention[]>([]);
|
|
||||||
const { pageSlug } = useParams();
|
|
||||||
const slugId = extractPageSlugId(pageSlug);
|
|
||||||
const { data: page } = usePageQuery({ pageId: slugId });
|
|
||||||
|
|
||||||
const chatInfoQuery = useChatInfoQuery(chatId);
|
|
||||||
const {
|
|
||||||
messages,
|
|
||||||
streamingContent,
|
|
||||||
streamingToolCalls,
|
|
||||||
isStreaming,
|
|
||||||
error,
|
|
||||||
sendMessage,
|
|
||||||
stopGeneration,
|
|
||||||
hydrateFromServer,
|
|
||||||
} = useChatStream(chatId, {
|
|
||||||
onChatCreated: (newChatId) => {
|
|
||||||
setChatId(newChatId);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (page && !chatId) {
|
|
||||||
setContextPages([{ id: page.id, title: page.title || "", slugId: page.slugId }]);
|
|
||||||
}
|
|
||||||
}, [page, chatId]);
|
|
||||||
|
|
||||||
const handleRemoveContextPage = useCallback((pageId: string) => {
|
|
||||||
setContextPages((prev) => prev.filter((p) => p.id !== pageId));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (chatInfoQuery.data?.messages) {
|
|
||||||
hydrateFromServer(chatInfoQuery.data.messages);
|
|
||||||
}
|
|
||||||
}, [chatInfoQuery.data, hydrateFromServer]);
|
|
||||||
|
|
||||||
// Drop the open chatId if the current user lost access to it (404/403 on
|
|
||||||
// the info fetch). Reverts the panel to a fresh chat instead of presenting
|
|
||||||
// an input tied to a chat the user does not own.
|
|
||||||
useEffect(() => {
|
|
||||||
if (chatId && chatInfoQuery.isError) {
|
|
||||||
setChatId(undefined);
|
|
||||||
}
|
|
||||||
}, [chatId, chatInfoQuery.isError]);
|
|
||||||
|
|
||||||
const handleNewChat = useCallback(
|
|
||||||
(event: React.MouseEvent<HTMLAnchorElement>) => {
|
|
||||||
if (
|
|
||||||
event.button !== 0 ||
|
|
||||||
event.ctrlKey ||
|
|
||||||
event.metaKey ||
|
|
||||||
event.shiftKey
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
setChatId(undefined);
|
|
||||||
if (page) {
|
|
||||||
setContextPages([
|
|
||||||
{ id: page.id, title: page.title || "", slugId: page.slugId },
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[page],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSelectChat = useCallback((selectedChatId: string) => {
|
|
||||||
setChatId(selectedChatId);
|
|
||||||
setHistoryOpen(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleExpand = useCallback(() => {
|
|
||||||
if (chatId) {
|
|
||||||
navigate(`/ai/chat/${chatId}`);
|
|
||||||
} else {
|
|
||||||
navigate("/ai");
|
|
||||||
}
|
|
||||||
setAsideState({ tab: "", isAsideOpen: false });
|
|
||||||
}, [chatId, navigate, setAsideState]);
|
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
|
||||||
setAsideState({ tab: "", isAsideOpen: false });
|
|
||||||
}, [setAsideState]);
|
|
||||||
|
|
||||||
const handleSend = useCallback(
|
|
||||||
(content: string, mentions: PageMention[], attachments: ChatAttachment[]) => {
|
|
||||||
const contextPageId = contextPages.length > 0 ? contextPages[0].id : undefined;
|
|
||||||
sendMessage(content, mentions, attachments, contextPageId);
|
|
||||||
},
|
|
||||||
[sendMessage, contextPages],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleQuickAction = useCallback(
|
|
||||||
(prompt: string) => {
|
|
||||||
handleSend(prompt, [], []);
|
|
||||||
},
|
|
||||||
[handleSend],
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasMessages = messages.length > 0 || isStreaming;
|
|
||||||
|
|
||||||
const quickActions: QuickAction[] = [
|
|
||||||
{ icon: <IconFileText size={16} />, label: t("Summarize this page"), prompt: "Summarize this page" },
|
|
||||||
{ icon: <IconLanguage size={16} />, label: t("Translate this page"), prompt: "Translate this page" },
|
|
||||||
{ icon: <IconSearch size={16} />, label: t("Analyze for insights"), prompt: "Analyze this page for insights" },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.panel}>
|
|
||||||
<div className={classes.toolbar}>
|
|
||||||
<Popover
|
|
||||||
opened={historyOpen}
|
|
||||||
onChange={setHistoryOpen}
|
|
||||||
position="bottom-start"
|
|
||||||
width={280}
|
|
||||||
shadow="md"
|
|
||||||
>
|
|
||||||
<Popover.Target>
|
|
||||||
<UnstyledButton
|
|
||||||
className={classes.titleButton}
|
|
||||||
onClick={() => setHistoryOpen((o) => !o)}
|
|
||||||
>
|
|
||||||
<span className={classes.titleText}>
|
|
||||||
{chatInfoQuery.data?.chat?.title || t("New chat")}
|
|
||||||
</span>
|
|
||||||
<IconChevronDown size={16} stroke={1.75} />
|
|
||||||
</UnstyledButton>
|
|
||||||
</Popover.Target>
|
|
||||||
<Popover.Dropdown>
|
|
||||||
<AsideChatHistory activeChatId={chatId} onSelect={handleSelectChat} />
|
|
||||||
</Popover.Dropdown>
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
<div className={classes.toolbarSpacer} />
|
|
||||||
|
|
||||||
<Tooltip label={t("New chat")} openDelay={250}>
|
|
||||||
<ActionIcon
|
|
||||||
component="a"
|
|
||||||
href="/ai"
|
|
||||||
variant="subtle"
|
|
||||||
color="dark"
|
|
||||||
aria-label={t("New chat")}
|
|
||||||
onClick={handleNewChat}
|
|
||||||
>
|
|
||||||
<IconPlus size={20} stroke={1.75} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip label={t("Open full page")} openDelay={250}>
|
|
||||||
<ActionIcon
|
|
||||||
variant="subtle"
|
|
||||||
color="dark"
|
|
||||||
aria-label={t("Open full page")}
|
|
||||||
onClick={handleExpand}
|
|
||||||
>
|
|
||||||
<IconArrowsDiagonal size={18} stroke={1.5} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip label={t("Close")} openDelay={250}>
|
|
||||||
<ActionIcon
|
|
||||||
variant="subtle"
|
|
||||||
color="dark"
|
|
||||||
aria-label={t("Close")}
|
|
||||||
onClick={handleClose}
|
|
||||||
>
|
|
||||||
<IconX size={20} stroke={1.75} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: "var(--mantine-spacing-xs) var(--mantine-spacing-sm)",
|
|
||||||
color: "var(--mantine-color-red-6)",
|
|
||||||
fontSize: "var(--mantine-font-size-xs)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasMessages ? (
|
|
||||||
<>
|
|
||||||
<div className={classes.messages} data-aside-chat>
|
|
||||||
<ChatMessageList
|
|
||||||
messages={messages}
|
|
||||||
isStreaming={isStreaming}
|
|
||||||
streamingContent={streamingContent}
|
|
||||||
streamingToolCalls={streamingToolCalls}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className={classes.emptyState}>
|
|
||||||
<IconSparkles size={36} stroke={1.5} className={classes.emptyStateIcon} />
|
|
||||||
<div className={classes.emptyStateTitle}>{t("How can I help you today?")}</div>
|
|
||||||
<div className={classes.quickActions}>
|
|
||||||
{quickActions.map((action) => (
|
|
||||||
<button
|
|
||||||
key={action.label}
|
|
||||||
type="button"
|
|
||||||
className={classes.quickAction}
|
|
||||||
onClick={() => handleQuickAction(action.prompt)}
|
|
||||||
>
|
|
||||||
<span className={classes.quickActionIcon}>{action.icon}</span>
|
|
||||||
{action.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className={classes.inputArea}>
|
|
||||||
<ChatInput
|
|
||||||
isStreaming={isStreaming}
|
|
||||||
onSend={handleSend}
|
|
||||||
onStop={stopGeneration}
|
|
||||||
placeholder={t("Ask anything...")}
|
|
||||||
autofocus={false}
|
|
||||||
contextPages={contextPages}
|
|
||||||
onRemoveContextPage={handleRemoveContextPage}
|
|
||||||
variant="flat"
|
|
||||||
chatId={chatId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import {
|
|
||||||
IconSparkles,
|
|
||||||
IconSearch,
|
|
||||||
IconFilePlus,
|
|
||||||
IconEdit,
|
|
||||||
IconFileText,
|
|
||||||
} from "@tabler/icons-react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import ChatInput from "./chat-input";
|
|
||||||
import type { ChatAttachment, PageMention } from "../types/ai-chat.types";
|
|
||||||
import classes from "../styles/ai-chat.module.css";
|
|
||||||
|
|
||||||
type Suggestion = {
|
|
||||||
icon: React.ReactNode;
|
|
||||||
text: string;
|
|
||||||
prompt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SUGGESTIONS: Suggestion[] = [
|
|
||||||
{
|
|
||||||
icon: <IconSearch size={16} />,
|
|
||||||
text: "Search across all pages",
|
|
||||||
prompt: "Search for pages about ",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <IconFilePlus size={16} />,
|
|
||||||
text: "Create a new page",
|
|
||||||
prompt: "Create a new page titled ",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <IconFileText size={16} />,
|
|
||||||
text: "Summarize a page",
|
|
||||||
prompt: "Summarize the page @",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <IconEdit size={16} />,
|
|
||||||
text: "Update page content",
|
|
||||||
prompt: "Update the page @",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isStreaming: boolean;
|
|
||||||
onSend: (content: string, mentions: PageMention[], attachments: ChatAttachment[]) => void;
|
|
||||||
onStop: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ChatEmptyState({ isStreaming, onSend, onStop }: Props) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const handleSuggestionClick = (prompt: string) => {
|
|
||||||
onSend(prompt, [], []);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.emptyState}>
|
|
||||||
<IconSparkles size={48} stroke={1.5} className={classes.emptyStateIcon} />
|
|
||||||
<div className={classes.emptyStateBrand}>{t("Docmost AI")}</div>
|
|
||||||
<div className={classes.emptyStateTitle}>
|
|
||||||
{t("What can I help you with?")}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={classes.emptyStateInput}>
|
|
||||||
<ChatInput
|
|
||||||
isStreaming={isStreaming}
|
|
||||||
onSend={onSend}
|
|
||||||
onStop={onStop}
|
|
||||||
placeholder={t("Ask anything... Use @ to mention pages")}
|
|
||||||
autofocus
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={classes.suggestionsSection}>
|
|
||||||
<div className={classes.suggestionsLabel}>Get started</div>
|
|
||||||
<div className={classes.suggestionsGrid}>
|
|
||||||
{SUGGESTIONS.map((s) => (
|
|
||||||
<button
|
|
||||||
key={s.text}
|
|
||||||
type="button"
|
|
||||||
className={classes.suggestionCard}
|
|
||||||
onClick={() => handleSuggestionClick(s.prompt)}
|
|
||||||
>
|
|
||||||
<span className={classes.suggestionIcon}>{s.icon}</span>
|
|
||||||
<span className={classes.suggestionText}>{s.text}</span>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,415 +0,0 @@
|
|||||||
import { useCallback, useRef, useEffect, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { IconArrowUp, IconPaperclip, IconPlayerStopFilled, IconX, IconFile, IconPhoto, IconPlus, IconAt, IconFileText } from "@tabler/icons-react";
|
|
||||||
import { Popover } from "@mantine/core";
|
|
||||||
import { notifications } from "@mantine/notifications";
|
|
||||||
import { EditorContent, ReactNodeViewRenderer, useEditor } from "@tiptap/react";
|
|
||||||
import { Placeholder } from "@tiptap/extension-placeholder";
|
|
||||||
import { CharacterCount } from "@tiptap/extensions";
|
|
||||||
import { StarterKit } from "@tiptap/starter-kit";
|
|
||||||
import { Mention, LinkExtension } from "@docmost/editor-ext";
|
|
||||||
import EmojiCommand from "@/features/editor/extensions/emoji-command";
|
|
||||||
import mentionRenderItems from "@/features/editor/components/mention/mention-suggestion";
|
|
||||||
import MentionView from "@/features/editor/components/mention/mention-view";
|
|
||||||
import { uploadChatFile } from "../services/ai-chat-service";
|
|
||||||
import type { ChatAttachment, PageMention } from "../types/ai-chat.types";
|
|
||||||
import classes from "../styles/chat-input.module.css";
|
|
||||||
|
|
||||||
type PendingAttachment = ChatAttachment & { uploading: boolean };
|
|
||||||
|
|
||||||
const IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "webp", "gif"];
|
|
||||||
const ACCEPTED_FILE_TYPES = ".pdf,.docx,.txt,.csv,.md,.png,.jpg,.jpeg,.webp";
|
|
||||||
// Kept in sync with MAX_ATTACHMENTS_PER_MESSAGE in apps/server/src/ee/ai-chat/ai-chat-limits.ts
|
|
||||||
const MAX_ATTACHMENTS_PER_MESSAGE = 5;
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isStreaming: boolean;
|
|
||||||
onSend: (content: string, mentions: PageMention[], attachments: ChatAttachment[]) => void;
|
|
||||||
onStop: () => void;
|
|
||||||
placeholder?: string;
|
|
||||||
autofocus?: boolean;
|
|
||||||
contextPages?: PageMention[];
|
|
||||||
onRemoveContextPage?: (pageId: string) => void;
|
|
||||||
variant?: "card" | "flat";
|
|
||||||
showDisclaimer?: boolean;
|
|
||||||
chatId?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function extractMentions(json: any): PageMention[] {
|
|
||||||
const mentions: PageMention[] = [];
|
|
||||||
const seen = new Set<string>();
|
|
||||||
|
|
||||||
function walk(node: any) {
|
|
||||||
if (node.type === "mention" && node.attrs?.entityType === "page" && node.attrs?.entityId) {
|
|
||||||
if (!seen.has(node.attrs.entityId)) {
|
|
||||||
seen.add(node.attrs.entityId);
|
|
||||||
mentions.push({
|
|
||||||
id: node.attrs.entityId,
|
|
||||||
title: node.attrs.label || "",
|
|
||||||
slugId: node.attrs.slugId || "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (node.content) {
|
|
||||||
for (const child of node.content) {
|
|
||||||
walk(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
walk(json);
|
|
||||||
return mentions;
|
|
||||||
}
|
|
||||||
|
|
||||||
function editorJsonToText(json: any): string {
|
|
||||||
let text = "";
|
|
||||||
|
|
||||||
function walk(node: any) {
|
|
||||||
if (node.type === "text") {
|
|
||||||
text += node.text || "";
|
|
||||||
} else if (node.type === "mention") {
|
|
||||||
text += `@${node.attrs?.label || ""}`;
|
|
||||||
} else if (node.type === "paragraph") {
|
|
||||||
if (text.length > 0) text += "\n";
|
|
||||||
if (node.content) {
|
|
||||||
for (const child of node.content) {
|
|
||||||
walk(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (node.content) {
|
|
||||||
for (const child of node.content) {
|
|
||||||
walk(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
walk(json);
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ChatInput({
|
|
||||||
isStreaming,
|
|
||||||
onSend,
|
|
||||||
onStop,
|
|
||||||
placeholder,
|
|
||||||
autofocus = true,
|
|
||||||
contextPages,
|
|
||||||
onRemoveContextPage,
|
|
||||||
variant = "card",
|
|
||||||
showDisclaimer = true,
|
|
||||||
chatId,
|
|
||||||
}: Props) {
|
|
||||||
const chatIdRef = useRef(chatId);
|
|
||||||
chatIdRef.current = chatId;
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [isEmpty, setIsEmpty] = useState(true);
|
|
||||||
const [pendingAttachments, setPendingAttachments] = useState<PendingAttachment[]>([]);
|
|
||||||
const [plusMenuOpen, setPlusMenuOpen] = useState(false);
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const onSendRef = useRef(onSend);
|
|
||||||
onSendRef.current = onSend;
|
|
||||||
|
|
||||||
const handleFileSelect = useCallback(async (files: FileList | null) => {
|
|
||||||
if (!files?.length) return;
|
|
||||||
|
|
||||||
const room = MAX_ATTACHMENTS_PER_MESSAGE - pendingAttachments.length;
|
|
||||||
if (room <= 0) {
|
|
||||||
notifications.show({
|
|
||||||
color: "yellow",
|
|
||||||
message: t("You can attach up to {{max}} files per message.", {
|
|
||||||
max: MAX_ATTACHMENTS_PER_MESSAGE,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const incoming = Array.from(files);
|
|
||||||
const accepted = incoming.slice(0, room);
|
|
||||||
|
|
||||||
if (incoming.length > accepted.length) {
|
|
||||||
notifications.show({
|
|
||||||
color: "yellow",
|
|
||||||
message: t(
|
|
||||||
"Only the first {{n}} file(s) were added (max {{max}} per message).",
|
|
||||||
{ n: accepted.length, max: MAX_ATTACHMENTS_PER_MESSAGE },
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const file of accepted) {
|
|
||||||
const tempId = `uploading-${Date.now()}-${Math.random()}`;
|
|
||||||
const ext = file.name.split(".").pop()?.toLowerCase() || "";
|
|
||||||
|
|
||||||
const placeholder: PendingAttachment = {
|
|
||||||
id: tempId,
|
|
||||||
fileName: file.name,
|
|
||||||
fileExt: ext,
|
|
||||||
fileSize: file.size,
|
|
||||||
mimeType: file.type,
|
|
||||||
uploading: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
setPendingAttachments((prev) => [...prev, placeholder]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const uploaded = await uploadChatFile(file, chatIdRef.current);
|
|
||||||
setPendingAttachments((prev) =>
|
|
||||||
prev.map((a) =>
|
|
||||||
a.id === tempId ? { ...uploaded, uploading: false } : a,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
setPendingAttachments((prev) => prev.filter((a) => a.id !== tempId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileInputRef.current) {
|
|
||||||
fileInputRef.current.value = "";
|
|
||||||
}
|
|
||||||
}, [pendingAttachments.length, t]);
|
|
||||||
|
|
||||||
const removeAttachment = useCallback((id: string) => {
|
|
||||||
setPendingAttachments((prev) => prev.filter((a) => a.id !== id));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
|
||||||
if (!editor || isStreaming) return;
|
|
||||||
const json = editor.getJSON();
|
|
||||||
const text = editorJsonToText(json).trim();
|
|
||||||
const readyAttachments = pendingAttachments.filter((a) => !a.uploading);
|
|
||||||
if (!text && readyAttachments.length === 0) return;
|
|
||||||
|
|
||||||
const mentions = extractMentions(json);
|
|
||||||
onSendRef.current(text, mentions, readyAttachments);
|
|
||||||
editor.commands.clearContent();
|
|
||||||
editor.commands.focus();
|
|
||||||
setPendingAttachments([]);
|
|
||||||
}, [isStreaming, pendingAttachments]);
|
|
||||||
|
|
||||||
const handleSubmitRef = useRef(handleSubmit);
|
|
||||||
handleSubmitRef.current = handleSubmit;
|
|
||||||
|
|
||||||
const editor = useEditor({
|
|
||||||
extensions: [
|
|
||||||
StarterKit.configure({
|
|
||||||
gapcursor: false,
|
|
||||||
dropcursor: false,
|
|
||||||
link: false,
|
|
||||||
}),
|
|
||||||
Placeholder.configure({
|
|
||||||
placeholder: placeholder || t("Ask anything... Use @ to mention pages"),
|
|
||||||
}),
|
|
||||||
CharacterCount.configure({
|
|
||||||
limit: 50000,
|
|
||||||
}),
|
|
||||||
LinkExtension,
|
|
||||||
EmojiCommand,
|
|
||||||
Mention.configure({
|
|
||||||
suggestion: {
|
|
||||||
allowSpaces: true,
|
|
||||||
items: () => [],
|
|
||||||
// @ts-ignore
|
|
||||||
render: mentionRenderItems,
|
|
||||||
},
|
|
||||||
HTMLAttributes: {
|
|
||||||
class: "mention",
|
|
||||||
},
|
|
||||||
}).extend({
|
|
||||||
addNodeView() {
|
|
||||||
this.editor.isInitialized = true;
|
|
||||||
return ReactNodeViewRenderer(MentionView);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
editorProps: {
|
|
||||||
attributes: {
|
|
||||||
"aria-label": placeholder || t("Ask anything... Use @ to mention pages"),
|
|
||||||
"aria-multiline": "true",
|
|
||||||
},
|
|
||||||
handleDOMEvents: {
|
|
||||||
keydown: (_view, event) => {
|
|
||||||
if (
|
|
||||||
["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Enter"].includes(
|
|
||||||
event.key,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const emojiCommand = document.querySelector("#emoji-command");
|
|
||||||
const mentionPopup = document.querySelector("#mention");
|
|
||||||
if (emojiCommand || mentionPopup) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === "Enter" && !event.shiftKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
handleSubmitRef.current();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
content: "",
|
|
||||||
editable: true,
|
|
||||||
immediatelyRender: true,
|
|
||||||
shouldRerenderOnTransaction: false,
|
|
||||||
autofocus: autofocus ? "end" : false,
|
|
||||||
onUpdate: ({ editor: e }) => {
|
|
||||||
setIsEmpty(!e.getText().trim());
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (editor && autofocus) {
|
|
||||||
editor.commands.focus();
|
|
||||||
}
|
|
||||||
}, [editor]);
|
|
||||||
|
|
||||||
const hasContent = !isEmpty || pendingAttachments.some((a) => !a.uploading) || (contextPages?.length ?? 0) > 0;
|
|
||||||
|
|
||||||
const wrapperClass = variant === "flat" ? classes.inputWrapperFlat : classes.inputWrapper;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={wrapperClass} data-chat-input>
|
|
||||||
<input
|
|
||||||
ref={fileInputRef}
|
|
||||||
type="file"
|
|
||||||
accept={ACCEPTED_FILE_TYPES}
|
|
||||||
multiple
|
|
||||||
aria-label={t("Add files")}
|
|
||||||
tabIndex={-1}
|
|
||||||
style={{ display: "none" }}
|
|
||||||
onChange={(e) => handleFileSelect(e.target.files)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{((contextPages?.length ?? 0) > 0 || pendingAttachments.length > 0) && (
|
|
||||||
<div className={classes.attachmentChips}>
|
|
||||||
{contextPages?.map((page) => (
|
|
||||||
<div key={page.id} className={classes.attachmentChip}>
|
|
||||||
<IconFileText size={14} />
|
|
||||||
<span className={classes.attachmentChipName}>
|
|
||||||
{page.title || "Untitled"}
|
|
||||||
</span>
|
|
||||||
{onRemoveContextPage && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classes.attachmentChipRemove}
|
|
||||||
onClick={() => onRemoveContextPage(page.id)}
|
|
||||||
aria-label={`Remove ${page.title}`}
|
|
||||||
>
|
|
||||||
<IconX size={12} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{pendingAttachments.map((attachment) => (
|
|
||||||
<div
|
|
||||||
key={attachment.id}
|
|
||||||
className={`${classes.attachmentChip} ${attachment.uploading ? classes.attachmentChipUploading : ""}`}
|
|
||||||
>
|
|
||||||
{IMAGE_EXTENSIONS.includes(attachment.fileExt) ? (
|
|
||||||
<IconPhoto size={14} />
|
|
||||||
) : (
|
|
||||||
<IconFile size={14} />
|
|
||||||
)}
|
|
||||||
<span className={classes.attachmentChipName}>
|
|
||||||
{attachment.fileName}
|
|
||||||
</span>
|
|
||||||
{!attachment.uploading && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classes.attachmentChipRemove}
|
|
||||||
onClick={() => removeAttachment(attachment.id)}
|
|
||||||
aria-label={`Remove ${attachment.fileName}`}
|
|
||||||
>
|
|
||||||
<IconX size={12} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<EditorContent editor={editor} className={classes.editorContent} />
|
|
||||||
<div className={classes.actions}>
|
|
||||||
<Popover opened={plusMenuOpen} onChange={setPlusMenuOpen} position="top-start" width={220} shadow="md">
|
|
||||||
<Popover.Target>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classes.plusButton}
|
|
||||||
onClick={() => setPlusMenuOpen((o) => !o)}
|
|
||||||
aria-label="Add content"
|
|
||||||
>
|
|
||||||
<IconPlus size={14} />
|
|
||||||
</button>
|
|
||||||
</Popover.Target>
|
|
||||||
<Popover.Dropdown p={4}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classes.plusMenuItem}
|
|
||||||
onClick={() => {
|
|
||||||
fileInputRef.current?.click();
|
|
||||||
setPlusMenuOpen(false);
|
|
||||||
}}
|
|
||||||
disabled={pendingAttachments.length >= MAX_ATTACHMENTS_PER_MESSAGE}
|
|
||||||
title={
|
|
||||||
pendingAttachments.length >= MAX_ATTACHMENTS_PER_MESSAGE
|
|
||||||
? t("Max {{max}} files per message", {
|
|
||||||
max: MAX_ATTACHMENTS_PER_MESSAGE,
|
|
||||||
})
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<IconPaperclip size={16} className={classes.plusMenuIcon} />
|
|
||||||
{t("Add files")}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classes.plusMenuItem}
|
|
||||||
onClick={() => {
|
|
||||||
editor?.commands.insertContent("@");
|
|
||||||
editor?.commands.focus();
|
|
||||||
setPlusMenuOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconAt size={16} className={classes.plusMenuIcon} />
|
|
||||||
Mention a page
|
|
||||||
</button>
|
|
||||||
</Popover.Dropdown>
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
<div style={{ flex: 1 }} />
|
|
||||||
|
|
||||||
{isStreaming ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classes.stopButton}
|
|
||||||
onClick={onStop}
|
|
||||||
aria-label="Stop generation"
|
|
||||||
>
|
|
||||||
<IconPlayerStopFilled size={14} />
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classes.sendButton}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
disabled={!hasContent}
|
|
||||||
aria-label="Send message"
|
|
||||||
>
|
|
||||||
<IconArrowUp size={16} stroke={2.5} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{showDisclaimer && (
|
|
||||||
<div className={classes.disclaimer}>
|
|
||||||
{t("AI-generated content may not be accurate.")}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
import { useEffect, useRef, useCallback, useState } from "react";
|
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
|
||||||
import { IconArrowDown, IconAlertTriangle } from "@tabler/icons-react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import type { AiChatMessage, AiChatToolCall } from "../types/ai-chat.types";
|
|
||||||
import ChatMessage from "./chat-message";
|
|
||||||
import classes from "../styles/ai-chat.module.css";
|
|
||||||
|
|
||||||
function ChatMessageErrorFallback() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<div className={classes.messageErrorFallback}>
|
|
||||||
<IconAlertTriangle size={14} />
|
|
||||||
<span>{t("Failed to render this message.")}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
messages: AiChatMessage[];
|
|
||||||
isStreaming: boolean;
|
|
||||||
streamingContent: string;
|
|
||||||
streamingToolCalls: AiChatToolCall[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const BOTTOM_THRESHOLD_PX = 32;
|
|
||||||
const SCROLL_UP_THRESHOLD_PX = 5;
|
|
||||||
const SMOOTH_SCROLL_SETTLE_MS = 600;
|
|
||||||
|
|
||||||
export default function ChatMessageList({
|
|
||||||
messages,
|
|
||||||
isStreaming,
|
|
||||||
streamingContent,
|
|
||||||
streamingToolCalls,
|
|
||||||
}: Props) {
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const bottomRef = useRef<HTMLDivElement>(null);
|
|
||||||
const isAtBottomRef = useRef(true);
|
|
||||||
const isAutoScrollingRef = useRef(false);
|
|
||||||
const prevScrollTopRef = useRef(0);
|
|
||||||
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
||||||
|
|
||||||
const scrollToBottom = useCallback((behavior: ScrollBehavior = "smooth") => {
|
|
||||||
const container = containerRef.current;
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
isAutoScrollingRef.current = true;
|
|
||||||
const target = container.scrollHeight - container.clientHeight;
|
|
||||||
container.scrollTo({ top: target, behavior });
|
|
||||||
prevScrollTopRef.current = target;
|
|
||||||
isAtBottomRef.current = true;
|
|
||||||
setShowScrollButton(false);
|
|
||||||
|
|
||||||
if (behavior === "smooth") {
|
|
||||||
setTimeout(() => {
|
|
||||||
isAutoScrollingRef.current = false;
|
|
||||||
if (containerRef.current) {
|
|
||||||
prevScrollTopRef.current = containerRef.current.scrollTop;
|
|
||||||
}
|
|
||||||
}, SMOOTH_SCROLL_SETTLE_MS);
|
|
||||||
} else {
|
|
||||||
isAutoScrollingRef.current = false;
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleScroll = useCallback(() => {
|
|
||||||
if (isAutoScrollingRef.current) return;
|
|
||||||
|
|
||||||
const container = containerRef.current;
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
const currentScrollTop = container.scrollTop;
|
|
||||||
const scrolledUp =
|
|
||||||
currentScrollTop < prevScrollTopRef.current - SCROLL_UP_THRESHOLD_PX;
|
|
||||||
prevScrollTopRef.current = currentScrollTop;
|
|
||||||
|
|
||||||
const distanceFromBottom =
|
|
||||||
container.scrollHeight - currentScrollTop - container.clientHeight;
|
|
||||||
const atBottom = distanceFromBottom <= BOTTOM_THRESHOLD_PX;
|
|
||||||
|
|
||||||
if (scrolledUp) {
|
|
||||||
isAtBottomRef.current = atBottom;
|
|
||||||
} else if (atBottom) {
|
|
||||||
isAtBottomRef.current = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
setShowScrollButton(!atBottom);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const container = containerRef.current;
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
container.addEventListener("scroll", handleScroll, { passive: true });
|
|
||||||
return () => container.removeEventListener("scroll", handleScroll);
|
|
||||||
}, [handleScroll]);
|
|
||||||
|
|
||||||
// Instant scroll during streaming to keep up with rapid updates
|
|
||||||
useEffect(() => {
|
|
||||||
if (isAtBottomRef.current) {
|
|
||||||
scrollToBottom("instant");
|
|
||||||
}
|
|
||||||
}, [streamingContent, streamingToolCalls.length, scrollToBottom]);
|
|
||||||
|
|
||||||
// Smooth scroll for new messages. Always force-scroll when the latest
|
|
||||||
// message is from the user (they just sent it), even if they were reading
|
|
||||||
// scrollback.
|
|
||||||
useEffect(() => {
|
|
||||||
const lastMessage = messages[messages.length - 1];
|
|
||||||
const lastIsUser = lastMessage?.role === "user";
|
|
||||||
if (lastIsUser || isAtBottomRef.current) {
|
|
||||||
scrollToBottom("smooth");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No auto-scroll: recompute from actual layout so that chat switches to
|
|
||||||
// content that doesn't overflow correctly hide the button even when no
|
|
||||||
// scroll event fires.
|
|
||||||
const container = containerRef.current;
|
|
||||||
if (!container) return;
|
|
||||||
const distanceFromBottom =
|
|
||||||
container.scrollHeight - container.scrollTop - container.clientHeight;
|
|
||||||
const atBottom = distanceFromBottom <= BOTTOM_THRESHOLD_PX;
|
|
||||||
isAtBottomRef.current = atBottom;
|
|
||||||
setShowScrollButton(!atBottom);
|
|
||||||
}, [messages, scrollToBottom]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.messageListWrapper}>
|
|
||||||
<div ref={containerRef} className={classes.messageList}>
|
|
||||||
{messages.map((msg) => (
|
|
||||||
<ErrorBoundary
|
|
||||||
key={msg.id}
|
|
||||||
fallback={<ChatMessageErrorFallback />}
|
|
||||||
>
|
|
||||||
<ChatMessage message={msg} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
))}
|
|
||||||
{isStreaming && (
|
|
||||||
<ErrorBoundary
|
|
||||||
resetKeys={[streamingContent, streamingToolCalls.length]}
|
|
||||||
fallback={<ChatMessageErrorFallback />}
|
|
||||||
>
|
|
||||||
<ChatMessage
|
|
||||||
message={{
|
|
||||||
id: "streaming",
|
|
||||||
chatId: "",
|
|
||||||
role: "assistant",
|
|
||||||
content: null,
|
|
||||||
toolCalls: null,
|
|
||||||
metadata: null,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
}}
|
|
||||||
isStreaming
|
|
||||||
streamingContent={streamingContent}
|
|
||||||
streamingToolCalls={streamingToolCalls}
|
|
||||||
/>
|
|
||||||
</ErrorBoundary>
|
|
||||||
)}
|
|
||||||
<div ref={bottomRef} />
|
|
||||||
</div>
|
|
||||||
{showScrollButton && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label="Scroll to bottom"
|
|
||||||
className={classes.scrollToBottomButton}
|
|
||||||
onClick={() => scrollToBottom("smooth")}
|
|
||||||
>
|
|
||||||
<IconArrowDown size={16} stroke={2} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
import { useCallback } from "react";
|
|
||||||
import { useNavigate } from "react-router";
|
|
||||||
import DOMPurify from "dompurify";
|
|
||||||
import { ActionIcon, Tooltip } from "@mantine/core";
|
|
||||||
import {
|
|
||||||
IconCheck,
|
|
||||||
IconCopy,
|
|
||||||
IconFile,
|
|
||||||
IconLoader2,
|
|
||||||
IconPhoto,
|
|
||||||
} from "@tabler/icons-react";
|
|
||||||
import { markdownToHtml } from "@docmost/editor-ext";
|
|
||||||
import { CopyButton } from "@/components/common/copy-button";
|
|
||||||
import type { AiChatMessage, AiChatToolCall } from "../types/ai-chat.types";
|
|
||||||
import ChatToolGroup from "./chat-tool-group";
|
|
||||||
import classes from "../styles/chat-message.module.css";
|
|
||||||
import CopyTextButton from "@/components/common/copy.tsx";
|
|
||||||
|
|
||||||
const chatSanitizer = DOMPurify();
|
|
||||||
chatSanitizer.addHook("afterSanitizeAttributes", (node) => {
|
|
||||||
if (node.tagName === "A") {
|
|
||||||
const href = node.getAttribute("href") || "";
|
|
||||||
if (href.startsWith("http://") || href.startsWith("https://")) {
|
|
||||||
node.setAttribute("target", "_blank");
|
|
||||||
node.setAttribute("rel", "noopener noreferrer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "webp", "gif"];
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
message: AiChatMessage;
|
|
||||||
isStreaming?: boolean;
|
|
||||||
streamingContent?: string;
|
|
||||||
streamingToolCalls?: AiChatToolCall[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ChatMessage({
|
|
||||||
message,
|
|
||||||
isStreaming,
|
|
||||||
streamingContent,
|
|
||||||
streamingToolCalls,
|
|
||||||
}: Props) {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const handleContentClick = useCallback(
|
|
||||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
|
||||||
const target = e.target as HTMLElement;
|
|
||||||
const anchor = target.closest("a");
|
|
||||||
if (!anchor) return;
|
|
||||||
|
|
||||||
const href = anchor.getAttribute("href");
|
|
||||||
if (href && (href.startsWith("/s/") || href.startsWith("/p/"))) {
|
|
||||||
e.preventDefault();
|
|
||||||
navigate(href);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[navigate],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (message.role === "tool") return null;
|
|
||||||
|
|
||||||
const isUser = message.role === "user";
|
|
||||||
const content = isStreaming ? streamingContent : message.content;
|
|
||||||
const toolCalls = isStreaming ? streamingToolCalls : message.toolCalls;
|
|
||||||
|
|
||||||
if (isUser) {
|
|
||||||
const displayContent = (content || "").replace(
|
|
||||||
/\n\n<referenced_pages>[\s\S]*<\/referenced_pages>$/,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
const attachments =
|
|
||||||
(message.metadata?.attachments as {
|
|
||||||
id: string;
|
|
||||||
fileName: string;
|
|
||||||
fileExt: string;
|
|
||||||
}[]) || [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.userMessage}>
|
|
||||||
<div className={classes.userBubble}>
|
|
||||||
{attachments.length > 0 && (
|
|
||||||
<div className={classes.messageAttachments}>
|
|
||||||
{attachments.map((a) => (
|
|
||||||
<span key={a.id} className={classes.messageAttachmentChip}>
|
|
||||||
{IMAGE_EXTENSIONS.includes(a.fileExt) ? (
|
|
||||||
<IconPhoto size={13} />
|
|
||||||
) : (
|
|
||||||
<IconFile size={13} />
|
|
||||||
)}
|
|
||||||
{a.fileName}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{displayContent}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.assistantMessage}>
|
|
||||||
<div className={classes.messageContent}>
|
|
||||||
{toolCalls && toolCalls.length > 0 && (
|
|
||||||
<ChatToolGroup toolCalls={toolCalls} isStreaming={isStreaming} />
|
|
||||||
)}
|
|
||||||
{content && (
|
|
||||||
<div
|
|
||||||
onClick={handleContentClick}
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: chatSanitizer.sanitize(
|
|
||||||
markdownToHtml(content) as string,
|
|
||||||
{ ADD_ATTR: ["target", "rel"] },
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isStreaming && (
|
|
||||||
<>
|
|
||||||
{!content && (
|
|
||||||
<span className={classes.processingIndicator}>
|
|
||||||
<IconLoader2 size={16} className={classes.processingSpinner} />
|
|
||||||
Thinking
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span className={classes.streamingCursor} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{!isStreaming && message.content && (
|
|
||||||
<div className={classes.messageActions}>
|
|
||||||
<CopyTextButton text={message?.content} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import {
|
|
||||||
IconChevronRight,
|
|
||||||
IconChevronDown,
|
|
||||||
IconLoader2,
|
|
||||||
} from "@tabler/icons-react";
|
|
||||||
import type { AiChatToolCall } from "../types/ai-chat.types";
|
|
||||||
import ChatToolResult, { TOOL_LABELS } from "./chat-tool-result";
|
|
||||||
import classes from "../styles/chat-message.module.css";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
toolCalls: AiChatToolCall[];
|
|
||||||
isStreaming?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ChatToolGroup({ toolCalls, isStreaming }: Props) {
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
|
||||||
|
|
||||||
if (!toolCalls || toolCalls.length === 0) return null;
|
|
||||||
|
|
||||||
const activeCall =
|
|
||||||
isStreaming && toolCalls.length > 0
|
|
||||||
? [...toolCalls].reverse().find((tc) => tc.result === undefined)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const activeLabel = activeCall
|
|
||||||
? TOOL_LABELS[activeCall.name] || activeCall.name
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.toolGroup}>
|
|
||||||
<div
|
|
||||||
className={classes.toolGroupHeader}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
aria-expanded={expanded}
|
|
||||||
onClick={() => setExpanded((prev) => !prev)}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === "Enter" || event.key === " ") {
|
|
||||||
event.preventDefault();
|
|
||||||
setExpanded((prev) => !prev);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{activeLabel ? (
|
|
||||||
<IconLoader2 size={12} className={classes.processingSpinner} />
|
|
||||||
) : expanded ? (
|
|
||||||
<IconChevronDown size={12} />
|
|
||||||
) : (
|
|
||||||
<IconChevronRight size={12} />
|
|
||||||
)}
|
|
||||||
<span className={classes.toolGroupLabel}>
|
|
||||||
{activeLabel ? `${activeLabel}…` : `Steps ${toolCalls.length}`}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{expanded && (
|
|
||||||
<div className={classes.toolGroupSteps}>
|
|
||||||
{toolCalls.map((tc) => (
|
|
||||||
<ChatToolResult key={tc.id} toolCall={tc} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { IconChevronRight, IconChevronDown } from "@tabler/icons-react";
|
|
||||||
import type { AiChatToolCall } from "../types/ai-chat.types";
|
|
||||||
import classes from "../styles/chat-message.module.css";
|
|
||||||
|
|
||||||
export const TOOL_LABELS: Record<string, string> = {
|
|
||||||
list_spaces: "Listed spaces",
|
|
||||||
search_pages: "Searched pages",
|
|
||||||
get_page: "Read page",
|
|
||||||
create_page: "Created page",
|
|
||||||
update_page: "Updated page",
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
toolCall: AiChatToolCall;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ChatToolResult({ toolCall }: Props) {
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
|
||||||
const label = TOOL_LABELS[toolCall.name] || toolCall.name;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.toolStep}>
|
|
||||||
<div
|
|
||||||
className={classes.toolStepRow}
|
|
||||||
onClick={() => setExpanded((prev) => !prev)}
|
|
||||||
>
|
|
||||||
<span className={classes.toolStepBullet}>·</span>
|
|
||||||
{expanded ? (
|
|
||||||
<IconChevronDown size={12} />
|
|
||||||
) : (
|
|
||||||
<IconChevronRight size={12} />
|
|
||||||
)}
|
|
||||||
<span>{label}</span>
|
|
||||||
</div>
|
|
||||||
{expanded && (
|
|
||||||
<div className={classes.toolStepDetails}>
|
|
||||||
<pre style={{ margin: 0, whiteSpace: "pre-wrap" }}>
|
|
||||||
{JSON.stringify(
|
|
||||||
{ args: toolCall.args, result: toolCall.result },
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { Badge, Group, Text, Switch, Tooltip } from "@mantine/core";
|
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts";
|
|
||||||
import { notifications } from "@mantine/notifications";
|
|
||||||
import { useHasFeature } from "@/ee/hooks/use-feature";
|
|
||||||
import { Feature } from "@/ee/features";
|
|
||||||
import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label";
|
|
||||||
|
|
||||||
export default function EnableAiChat() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
|
||||||
<div>
|
|
||||||
<Group gap="xs" align="center">
|
|
||||||
<Text size="md">{t("AI Chat")}</Text>
|
|
||||||
<Badge color="gray" variant="light" size="sm" radius="sm">
|
|
||||||
{t("Beta")}
|
|
||||||
</Badge>
|
|
||||||
</Group>
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
{t(
|
|
||||||
"Enable AI Chat to allow users to have multi-turn conversations with AI about your workspace content.",
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<AiChatToggle />
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function AiChatToggle() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [workspace, setWorkspace] = useAtom(workspaceAtom);
|
|
||||||
const [checked, setChecked] = useState(workspace?.settings?.ai?.chat);
|
|
||||||
const hasAccess = useHasFeature(Feature.AI);
|
|
||||||
const upgradeLabel = useUpgradeLabel();
|
|
||||||
|
|
||||||
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const value = event.currentTarget.checked;
|
|
||||||
try {
|
|
||||||
const updatedWorkspace = await updateWorkspace({ aiChat: value } as any);
|
|
||||||
setChecked(value);
|
|
||||||
setWorkspace(updatedWorkspace);
|
|
||||||
} catch (err: any) {
|
|
||||||
notifications.show({
|
|
||||||
message: err?.response?.data?.message,
|
|
||||||
color: "red",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip label={upgradeLabel} disabled={hasAccess} refProp="rootRef">
|
|
||||||
<Switch
|
|
||||||
defaultChecked={checked}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={!hasAccess}
|
|
||||||
aria-label={t("Toggle AI Chat")}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
import { useState, useCallback, useEffect, useRef } from "react";
|
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { sendChatMessage } from "../services/ai-chat-service";
|
|
||||||
import type {
|
|
||||||
AiChatMessage,
|
|
||||||
AiChatStreamEvent,
|
|
||||||
AiChatToolCall,
|
|
||||||
ChatAttachment,
|
|
||||||
PageMention,
|
|
||||||
} from "../types/ai-chat.types";
|
|
||||||
|
|
||||||
type ChatStreamOptions = {
|
|
||||||
onChatCreated?: (chatId: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useChatStream(
|
|
||||||
chatId: string | undefined,
|
|
||||||
options?: ChatStreamOptions,
|
|
||||||
) {
|
|
||||||
const [messages, setMessages] = useState<AiChatMessage[]>([]);
|
|
||||||
const [streamingContent, setStreamingContent] = useState("");
|
|
||||||
const [streamingToolCalls, setStreamingToolCalls] = useState<AiChatToolCall[]>(
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
const [isStreaming, setIsStreaming] = useState(false);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [errorCode, setErrorCode] = useState<string | null>(null);
|
|
||||||
const [isRetryable, setIsRetryable] = useState(false);
|
|
||||||
const abortRef = useRef<AbortController | null>(null);
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const currentChatIdRef = useRef(chatId);
|
|
||||||
currentChatIdRef.current = chatId;
|
|
||||||
// Tracks which chatId the local `messages` state currently represents.
|
|
||||||
// Set when we seed from a server fetch AND when we optimistically own a
|
|
||||||
// freshly-created chat after `chat_created`. This is the single authority
|
|
||||||
// marker that keeps server-state effects from clobbering in-flight streams.
|
|
||||||
const hydratedChatIdRef = useRef<string | undefined>(undefined);
|
|
||||||
|
|
||||||
// Reset local state when the consumer switches to a different chat.
|
|
||||||
// Skip the reset if the new chatId is one the hook itself already claimed
|
|
||||||
// during a new-chat flow — in that case our optimistic state is the truth.
|
|
||||||
useEffect(() => {
|
|
||||||
if (chatId && chatId === hydratedChatIdRef.current) return;
|
|
||||||
hydratedChatIdRef.current = undefined;
|
|
||||||
setMessages([]);
|
|
||||||
setError(null);
|
|
||||||
setErrorCode(null);
|
|
||||||
setIsRetryable(false);
|
|
||||||
}, [chatId]);
|
|
||||||
|
|
||||||
const hydrateFromServer = useCallback((msgs: AiChatMessage[]) => {
|
|
||||||
const forId = currentChatIdRef.current;
|
|
||||||
if (!forId) return;
|
|
||||||
if (hydratedChatIdRef.current === forId) return;
|
|
||||||
hydratedChatIdRef.current = forId;
|
|
||||||
setMessages(msgs);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const sendMessage = useCallback(
|
|
||||||
(content: string, mentions: PageMention[] = [], attachments: ChatAttachment[] = [], contextPageId?: string) => {
|
|
||||||
if (isStreaming || (!content.trim() && attachments.length === 0)) return;
|
|
||||||
|
|
||||||
setError(null);
|
|
||||||
setErrorCode(null);
|
|
||||||
setIsRetryable(false);
|
|
||||||
setIsStreaming(true);
|
|
||||||
setStreamingContent("");
|
|
||||||
setStreamingToolCalls([]);
|
|
||||||
|
|
||||||
const metadata: Record<string, unknown> = {};
|
|
||||||
if (mentions.length) {
|
|
||||||
metadata.mentionedPageIds = mentions.map((m) => m.id);
|
|
||||||
}
|
|
||||||
if (attachments.length) {
|
|
||||||
metadata.attachments = attachments.map((a) => ({
|
|
||||||
id: a.id,
|
|
||||||
fileName: a.fileName,
|
|
||||||
fileExt: a.fileExt,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const userMessage: AiChatMessage = {
|
|
||||||
id: `temp-${Date.now()}`,
|
|
||||||
chatId: currentChatIdRef.current || "",
|
|
||||||
role: "user",
|
|
||||||
content,
|
|
||||||
toolCalls: null,
|
|
||||||
metadata: Object.keys(metadata).length ? metadata : null,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
setMessages((prev) => [...prev, userMessage]);
|
|
||||||
|
|
||||||
const attachmentIds = attachments.map((a) => a.id);
|
|
||||||
|
|
||||||
const abortController = sendChatMessage(
|
|
||||||
{
|
|
||||||
chatId: currentChatIdRef.current,
|
|
||||||
content,
|
|
||||||
mentionedPageIds: mentions.map((m) => m.id),
|
|
||||||
...(contextPageId && { contextPageId }),
|
|
||||||
...(attachmentIds.length && { attachmentIds }),
|
|
||||||
},
|
|
||||||
(event: AiChatStreamEvent) => {
|
|
||||||
switch (event.type) {
|
|
||||||
case "chat_created":
|
|
||||||
currentChatIdRef.current = event.chatId;
|
|
||||||
// Claim authority over this new chatId so when the consumer's
|
|
||||||
// prop catches up via navigation/onChatCreated, the reset effect
|
|
||||||
// sees a match and preserves our optimistic messages.
|
|
||||||
hydratedChatIdRef.current = event.chatId;
|
|
||||||
if (options?.onChatCreated) {
|
|
||||||
options.onChatCreated(event.chatId);
|
|
||||||
} else {
|
|
||||||
navigate(`/ai/chat/${event.chatId}`, { replace: true });
|
|
||||||
}
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["ai-chats"] });
|
|
||||||
break;
|
|
||||||
case "content":
|
|
||||||
setStreamingContent((prev) => prev + event.text);
|
|
||||||
break;
|
|
||||||
case "tool_call":
|
|
||||||
setStreamingToolCalls((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
id: event.id,
|
|
||||||
name: event.name,
|
|
||||||
args: event.args,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case "tool_result":
|
|
||||||
setStreamingToolCalls((prev) =>
|
|
||||||
prev.map((tc) =>
|
|
||||||
tc.id === event.id ? { ...tc, result: event.result } : tc,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "done": {
|
|
||||||
setStreamingContent((currentContent) => {
|
|
||||||
setStreamingToolCalls((currentToolCalls) => {
|
|
||||||
const assistantMessage: AiChatMessage = {
|
|
||||||
id: event.messageId,
|
|
||||||
chatId: currentChatIdRef.current || "",
|
|
||||||
role: "assistant",
|
|
||||||
content: currentContent || null,
|
|
||||||
toolCalls: currentToolCalls.length
|
|
||||||
? currentToolCalls
|
|
||||||
: null,
|
|
||||||
metadata: event.usage ? { tokenUsage: event.usage } : null,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
setMessages((prev) => [...prev, assistantMessage]);
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
return "";
|
|
||||||
});
|
|
||||||
setIsStreaming(false);
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ["ai-chat", currentChatIdRef.current],
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "error":
|
|
||||||
setError(event.message);
|
|
||||||
setErrorCode(event.code || null);
|
|
||||||
setIsRetryable(event.retryable || false);
|
|
||||||
setIsStreaming(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(errorMsg) => {
|
|
||||||
setError(errorMsg);
|
|
||||||
setIsStreaming(false);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
setIsStreaming(false);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
abortRef.current = abortController;
|
|
||||||
},
|
|
||||||
[isStreaming, navigate, queryClient],
|
|
||||||
);
|
|
||||||
|
|
||||||
const stopGeneration = useCallback(() => {
|
|
||||||
abortRef.current?.abort();
|
|
||||||
abortRef.current = null;
|
|
||||||
|
|
||||||
setStreamingContent((currentContent) => {
|
|
||||||
setStreamingToolCalls((currentToolCalls) => {
|
|
||||||
if (currentContent || currentToolCalls.length > 0) {
|
|
||||||
const partialMessage: AiChatMessage = {
|
|
||||||
id: `stopped-${Date.now()}`,
|
|
||||||
chatId: currentChatIdRef.current || "",
|
|
||||||
role: "assistant",
|
|
||||||
content: currentContent || null,
|
|
||||||
toolCalls: currentToolCalls.length ? currentToolCalls : null,
|
|
||||||
metadata: null,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
setMessages((prev) => [...prev, partialMessage]);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
return "";
|
|
||||||
});
|
|
||||||
|
|
||||||
setIsStreaming(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
|
||||||
messages,
|
|
||||||
streamingContent,
|
|
||||||
streamingToolCalls,
|
|
||||||
isStreaming,
|
|
||||||
error,
|
|
||||||
errorCode,
|
|
||||||
isRetryable,
|
|
||||||
sendMessage,
|
|
||||||
stopGeneration,
|
|
||||||
hydrateFromServer,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
|
||||||
import { Button } from "@mantine/core";
|
|
||||||
import { IconAlertTriangle } from "@tabler/icons-react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import AiChatLayout from "../components/ai-chat-layout";
|
|
||||||
import { EmptyState } from "@/components/ui/empty-state.tsx";
|
|
||||||
import classes from "../styles/ai-chat.module.css";
|
|
||||||
|
|
||||||
export default function AiChat() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { chatId } = useParams<{ chatId: string }>();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.layout}>
|
|
||||||
<ErrorBoundary
|
|
||||||
resetKeys={[chatId]}
|
|
||||||
fallbackRender={({ resetErrorBoundary }) => (
|
|
||||||
<EmptyState
|
|
||||||
icon={IconAlertTriangle}
|
|
||||||
title={t("Failed to load chat. An error occurred.")}
|
|
||||||
action={
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
size="sm"
|
|
||||||
mt="xs"
|
|
||||||
onClick={resetErrorBoundary}
|
|
||||||
>
|
|
||||||
{t("Try again")}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<AiChatLayout />
|
|
||||||
</ErrorBoundary>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import {
|
|
||||||
useQuery,
|
|
||||||
useMutation,
|
|
||||||
useQueryClient,
|
|
||||||
useInfiniteQuery,
|
|
||||||
} from "@tanstack/react-query";
|
|
||||||
import {
|
|
||||||
listChats,
|
|
||||||
getChatInfo,
|
|
||||||
deleteChat,
|
|
||||||
updateChatTitle,
|
|
||||||
searchChats,
|
|
||||||
} from "../services/ai-chat-service";
|
|
||||||
|
|
||||||
export function useChatsQuery() {
|
|
||||||
return useInfiniteQuery({
|
|
||||||
queryKey: ["ai-chats"],
|
|
||||||
queryFn: ({ pageParam }) =>
|
|
||||||
listChats({ cursor: pageParam, limit: 30 }),
|
|
||||||
initialPageParam: undefined as string | undefined,
|
|
||||||
getNextPageParam: (lastPage) =>
|
|
||||||
lastPage.meta.hasNextPage ? lastPage.meta.nextCursor : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useChatInfoQuery(chatId: string | undefined) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ["ai-chat", chatId],
|
|
||||||
queryFn: () => getChatInfo(chatId!),
|
|
||||||
enabled: !!chatId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDeleteChatMutation() {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: (chatId: string) => deleteChat(chatId),
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["ai-chats"] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useUpdateChatTitleMutation() {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: ({ chatId, title }: { chatId: string; title: string }) =>
|
|
||||||
updateChatTitle(chatId, title),
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["ai-chats"] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSearchChatsQuery(query: string) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ["ai-chats-search", query],
|
|
||||||
queryFn: () => searchChats(query),
|
|
||||||
enabled: query.length > 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
import api from "@/lib/api-client.ts";
|
|
||||||
import type {
|
|
||||||
AiChat,
|
|
||||||
AiChatMessage,
|
|
||||||
AiChatStreamEvent,
|
|
||||||
ChatAttachment,
|
|
||||||
} from "../types/ai-chat.types";
|
|
||||||
import { IPagination } from "@/lib/types.ts";
|
|
||||||
|
|
||||||
export async function createChat(): Promise<AiChat> {
|
|
||||||
const req = await api.post<AiChat>("/ai/chats/create");
|
|
||||||
return req.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function listChats(params?: {
|
|
||||||
limit?: number;
|
|
||||||
cursor?: string;
|
|
||||||
}): Promise<IPagination<AiChat>> {
|
|
||||||
const req = await api.post("/ai/chats", params);
|
|
||||||
return req.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getChatInfo(
|
|
||||||
chatId: string,
|
|
||||||
): Promise<{ chat: AiChat; messages: AiChatMessage[] }> {
|
|
||||||
const req = await api.post("/ai/chats/info", { chatId });
|
|
||||||
return req.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteChat(chatId: string): Promise<void> {
|
|
||||||
await api.post("/ai/chats/delete", { chatId });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateChatTitle(
|
|
||||||
chatId: string,
|
|
||||||
title: string,
|
|
||||||
): Promise<void> {
|
|
||||||
await api.post("/ai/chats/update", { chatId, title });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function searchChats(query: string): Promise<AiChat[]> {
|
|
||||||
const req = await api.post("/ai/chats/search", { query });
|
|
||||||
return req.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function uploadChatFile(
|
|
||||||
file: File,
|
|
||||||
chatId?: string,
|
|
||||||
): Promise<ChatAttachment> {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("file", file);
|
|
||||||
if (chatId) {
|
|
||||||
formData.append("chatId", chatId);
|
|
||||||
}
|
|
||||||
return await api.post("/ai/chats/upload", formData, {
|
|
||||||
headers: { "Content-Type": "multipart/form-data" },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendChatMessage(
|
|
||||||
params: {
|
|
||||||
chatId?: string;
|
|
||||||
content: string;
|
|
||||||
mentionedPageIds?: string[];
|
|
||||||
contextPageId?: string;
|
|
||||||
attachmentIds?: string[];
|
|
||||||
},
|
|
||||||
onEvent: (event: AiChatStreamEvent) => void,
|
|
||||||
onError?: (error: string) => void,
|
|
||||||
onComplete?: () => void,
|
|
||||||
): AbortController {
|
|
||||||
const abortController = new AbortController();
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch("/api/ai/chats/send", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(params),
|
|
||||||
signal: abortController.signal,
|
|
||||||
credentials: "include",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorBody = await response.text();
|
|
||||||
let errorMessage = `HTTP error ${response.status}`;
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(errorBody);
|
|
||||||
errorMessage = parsed.message || errorMessage;
|
|
||||||
} catch {
|
|
||||||
// use default
|
|
||||||
}
|
|
||||||
onError?.(errorMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = response.body?.getReader();
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
|
|
||||||
if (!reader) {
|
|
||||||
onError?.("Response body is not readable");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let buffer = "";
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) break;
|
|
||||||
|
|
||||||
buffer += decoder.decode(value, { stream: true });
|
|
||||||
const lines = buffer.split("\n");
|
|
||||||
buffer = lines.pop() || "";
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.startsWith("data: ")) {
|
|
||||||
const data = line.slice(6);
|
|
||||||
if (data === "[DONE]") {
|
|
||||||
onComplete?.();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(data) as AiChatStreamEvent;
|
|
||||||
onEvent(parsed);
|
|
||||||
} catch {
|
|
||||||
// Skip invalid JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
reader.releaseLock();
|
|
||||||
}
|
|
||||||
|
|
||||||
onComplete?.();
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error.name !== "AbortError") {
|
|
||||||
onError?.(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
return abortController;
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
.layout {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: calc(100vh - 45px - 2 * var(--mantine-spacing-md));
|
|
||||||
max-width: 900px;
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageListWrapper {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageList {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: var(--mantine-spacing-md) var(--mantine-spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageErrorFallback {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
margin-bottom: var(--mantine-spacing-lg);
|
|
||||||
border-radius: var(--mantine-radius-sm);
|
|
||||||
background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
|
||||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollToBottomButton {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 12px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
background: light-dark(var(--mantine-color-white), var(--mantine-color-dark-6));
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
||||||
transition: background-color 120ms ease, border-color 120ms ease, transform 120ms ease;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollToBottomButton:hover {
|
|
||||||
background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-5));
|
|
||||||
border-color: light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-3));
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollToBottomButton:active {
|
|
||||||
transform: translateX(-50%) scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputArea {
|
|
||||||
padding: var(--mantine-spacing-xs) var(--mantine-spacing-lg) var(--mantine-spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Empty state - Notion AI style centered layout */
|
|
||||||
.emptyState {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: var(--mantine-spacing-xl) var(--mantine-spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emptyStateIcon {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
margin-bottom: var(--mantine-spacing-sm);
|
|
||||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
|
||||||
}
|
|
||||||
|
|
||||||
.emptyStateBrand {
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--mantine-color-dimmed);
|
|
||||||
margin-bottom: var(--mantine-spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emptyStateTitle {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0));
|
|
||||||
margin-bottom: var(--mantine-spacing-xl);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emptyStateInput {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600px;
|
|
||||||
margin-bottom: var(--mantine-spacing-xl);
|
|
||||||
padding: 6px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggestionsSection {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggestionsLabel {
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--mantine-color-dimmed);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
margin-bottom: var(--mantine-spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggestionsGrid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: var(--mantine-spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggestionCard {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: var(--mantine-spacing-sm);
|
|
||||||
padding: var(--mantine-spacing-sm) var(--mantine-spacing-md);
|
|
||||||
border: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
|
||||||
border-radius: var(--mantine-radius-md);
|
|
||||||
cursor: pointer;
|
|
||||||
background: transparent;
|
|
||||||
transition: background-color 150ms, border-color 150ms;
|
|
||||||
text-align: left;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
|
||||||
border-color: light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggestionIcon {
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
|
||||||
margin-top: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggestionText {
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
.panel {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 0 0 var(--mantine-spacing-sm) 0;
|
|
||||||
border-bottom: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbarSpacer {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titleButton {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: var(--mantine-radius-sm);
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
font-weight: 500;
|
|
||||||
color: light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0));
|
|
||||||
max-width: 60%;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titleButton:hover {
|
|
||||||
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-6));
|
|
||||||
}
|
|
||||||
|
|
||||||
.titleText {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: var(--mantine-spacing-sm) 0;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputArea {
|
|
||||||
padding-top: var(--mantine-spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emptyState {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: var(--mantine-spacing-md);
|
|
||||||
padding: var(--mantine-spacing-xl) var(--mantine-spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emptyStateIcon {
|
|
||||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
|
||||||
}
|
|
||||||
|
|
||||||
.emptyStateTitle {
|
|
||||||
font-size: var(--mantine-font-size-lg);
|
|
||||||
font-weight: 600;
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quickActions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quickAction {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--mantine-spacing-sm);
|
|
||||||
padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm);
|
|
||||||
border: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
|
||||||
border-radius: var(--mantine-radius-md);
|
|
||||||
cursor: pointer;
|
|
||||||
background: transparent;
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
text-align: left;
|
|
||||||
width: 100%;
|
|
||||||
transition: background-color 150ms, border-color 150ms;
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
|
||||||
border-color: light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.quickActionIcon {
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
|
||||||
}
|
|
||||||
|
|
||||||
.historyList {
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.historyItem {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm);
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: var(--mantine-radius-sm);
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
transition: background-color 150ms;
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-active] {
|
|
||||||
background: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.historyItemTitle {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
@@ -1,242 +0,0 @@
|
|||||||
.inputWrapper {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid
|
|
||||||
light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
|
||||||
border-radius: 16px;
|
|
||||||
background: light-dark(var(--mantine-color-white), var(--mantine-color-dark-7));
|
|
||||||
box-shadow: light-dark(
|
|
||||||
0 2px 40px 4px rgba(0, 0, 0, 0.07),
|
|
||||||
0 2px 40px 4px rgba(0, 0, 0, 0.5)
|
|
||||||
);
|
|
||||||
transition:
|
|
||||||
border-color 150ms,
|
|
||||||
box-shadow 150ms;
|
|
||||||
|
|
||||||
&:focus-within {
|
|
||||||
border-color: light-dark(
|
|
||||||
var(--mantine-color-gray-3),
|
|
||||||
var(--mantine-color-dark-4)
|
|
||||||
);
|
|
||||||
box-shadow: light-dark(
|
|
||||||
0 4px 48px 6px rgba(0, 0, 0, 0.09),
|
|
||||||
0 4px 48px 6px rgba(0, 0, 0, 0.6)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputWrapperFlat {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
|
||||||
border-radius: 12px;
|
|
||||||
background: light-dark(var(--mantine-color-white), var(--mantine-color-dark-7));
|
|
||||||
box-shadow: none;
|
|
||||||
transition: border-color 150ms;
|
|
||||||
|
|
||||||
&:focus-within {
|
|
||||||
border-color: light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.disclaimer {
|
|
||||||
margin-top: 6px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
color: var(--mantine-color-dimmed);
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachmentChips {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 10px 14px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachmentChip {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-6));
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachmentChipUploading {
|
|
||||||
opacity: 0.55;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachmentChipName {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachmentChipRemove {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0;
|
|
||||||
margin-left: 2px;
|
|
||||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
|
||||||
border-radius: 50%;
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
color: light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.editorContent {
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
:global(.ProseMirror) {
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 14px 18px 8px;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.6;
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
min-height: 24px;
|
|
||||||
color: light-dark(var(--mantine-color-gray-9), var(--mantine-color-dark-0));
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.ProseMirror p) {
|
|
||||||
margin-block-start: 0;
|
|
||||||
margin-block-end: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.ProseMirror p.is-editor-empty:first-child::before) {
|
|
||||||
color: light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-3));
|
|
||||||
content: attr(data-placeholder);
|
|
||||||
float: left;
|
|
||||||
height: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding: 4px 12px 10px;
|
|
||||||
gap: var(--mantine-spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sendButton {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
min-width: 28px;
|
|
||||||
min-height: 28px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 150ms, opacity 150ms;
|
|
||||||
background: light-dark(var(--mantine-color-dark-9), var(--mantine-color-gray-0));
|
|
||||||
color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-9));
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: 0.25;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
&:not(:disabled) {
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachButton {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 2px;
|
|
||||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
|
||||||
transition: color 150ms;
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.plusButton {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
background: none;
|
|
||||||
cursor: pointer;
|
|
||||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
|
||||||
transition: color 150ms, background-color 150ms;
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.plusMenuItem {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--mantine-spacing-sm);
|
|
||||||
padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm);
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 100%;
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
border-radius: var(--mantine-radius-sm);
|
|
||||||
transition: background-color 150ms;
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: 0.45;
|
|
||||||
cursor: not-allowed;
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.plusMenuIcon {
|
|
||||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
|
||||||
}
|
|
||||||
|
|
||||||
.stopButton {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
min-width: 28px;
|
|
||||||
min-height: 28px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 150ms;
|
|
||||||
background: light-dark(var(--mantine-color-white), var(--mantine-color-dark-6));
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
background: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
.message {
|
|
||||||
margin-bottom: var(--mantine-spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.userMessage {
|
|
||||||
composes: message;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.userBubble {
|
|
||||||
max-width: 75%;
|
|
||||||
padding: 10px 16px;
|
|
||||||
border-radius: 18px;
|
|
||||||
background: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-6));
|
|
||||||
color: light-dark(var(--mantine-color-gray-9), var(--mantine-color-dark-0));
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.6;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-aside-chat] .userBubble {
|
|
||||||
background: light-dark(var(--mantine-color-white), var(--mantine-color-dark-7));
|
|
||||||
border: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
|
||||||
}
|
|
||||||
|
|
||||||
.userBubble p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageAttachments {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 4px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageAttachmentChip {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-2));
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
max-width: 180px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assistantMessage {
|
|
||||||
composes: message;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent {
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.7;
|
|
||||||
color: light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-1));
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent p {
|
|
||||||
margin: 0 0 0.75em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent ul,
|
|
||||||
.messageContent ol {
|
|
||||||
margin: 0.5em 0 0.75em 0;
|
|
||||||
padding-left: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent li {
|
|
||||||
margin-bottom: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent h1,
|
|
||||||
.messageContent h2,
|
|
||||||
.messageContent h3 {
|
|
||||||
margin: 1em 0 0.5em 0;
|
|
||||||
font-weight: 600;
|
|
||||||
color: light-dark(var(--mantine-color-gray-9), var(--mantine-color-dark-0));
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent h1 {
|
|
||||||
font-size: 1.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent h2 {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent h3 {
|
|
||||||
font-size: 1.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent pre {
|
|
||||||
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7));
|
|
||||||
padding: var(--mantine-spacing-sm) var(--mantine-spacing-md);
|
|
||||||
border-radius: var(--mantine-radius-md);
|
|
||||||
overflow-x: auto;
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
margin: 0.75em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent code {
|
|
||||||
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-6));
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.88em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent pre code {
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent blockquote {
|
|
||||||
border-left: 3px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
padding-left: var(--mantine-spacing-md);
|
|
||||||
margin: 0.75em 0;
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-2));
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent a {
|
|
||||||
color: light-dark(var(--mantine-color-blue-7), var(--mantine-color-blue-4));
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent a[href^="/s/"],
|
|
||||||
.messageContent a[href^="/p/"] {
|
|
||||||
color: light-dark(var(--mantine-color-dark-4), var(--mantine-color-dark-1));
|
|
||||||
font-weight: 500;
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
@mixin light {
|
|
||||||
border-bottom: 0.05em solid var(--mantine-color-dark-0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin dark {
|
|
||||||
border-bottom: 0.05em solid var(--mantine-color-dark-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
text-decoration: none;
|
|
||||||
@mixin light {
|
|
||||||
border-bottom-color: var(--mantine-color-dark-2);
|
|
||||||
}
|
|
||||||
@mixin dark {
|
|
||||||
border-bottom-color: var(--mantine-color-dark-0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent hr {
|
|
||||||
border: none;
|
|
||||||
border-top: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolGroup {
|
|
||||||
margin: 6px 0;
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolGroupHeader {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
|
||||||
line-height: 1.4;
|
|
||||||
transition: color 120ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolGroupHeader:hover {
|
|
||||||
color: light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0));
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolGroupLabel {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolGroupSteps {
|
|
||||||
margin-top: 4px;
|
|
||||||
padding-left: 14px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolStep {
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolStepRow {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
|
||||||
line-height: 1.5;
|
|
||||||
transition: color 120ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolStepRow:hover {
|
|
||||||
color: light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0));
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolStepBullet {
|
|
||||||
display: inline-block;
|
|
||||||
width: 8px;
|
|
||||||
text-align: center;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolStepDetails {
|
|
||||||
margin-top: 4px;
|
|
||||||
margin-left: 18px;
|
|
||||||
padding: 6px 10px;
|
|
||||||
border-radius: var(--mantine-radius-sm);
|
|
||||||
background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7));
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 1.5;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageActions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
margin-top: 4px;
|
|
||||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
|
||||||
}
|
|
||||||
|
|
||||||
.processingIndicator {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.processingSpinner {
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.streamingCursor {
|
|
||||||
display: inline-block;
|
|
||||||
width: 2px;
|
|
||||||
height: 1em;
|
|
||||||
background: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
|
|
||||||
animation: blink 1s step-end infinite;
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
margin-left: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes blink {
|
|
||||||
50% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
.sidebar {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
padding: var(--mantine-spacing-md);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--mantine-spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding-bottom: var(--mantine-spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.searchInput {
|
|
||||||
margin-bottom: var(--mantine-spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatList {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatGroup + .chatGroup {
|
|
||||||
margin-top: var(--mantine-spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatGroupLabel {
|
|
||||||
padding: 4px var(--mantine-spacing-xs);
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--mantine-color-dimmed);
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatListEmpty {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: var(--mantine-spacing-xl) var(--mantine-spacing-md);
|
|
||||||
text-align: center;
|
|
||||||
gap: 4px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatListEmptyIcon {
|
|
||||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
|
||||||
margin-bottom: var(--mantine-spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatListEmptyTitle {
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
font-weight: 600;
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatListEmptyHint {
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-3));
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatItem {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px var(--mantine-spacing-xs);
|
|
||||||
border-radius: var(--mantine-radius-sm);
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
user-select: none;
|
|
||||||
gap: var(--mantine-spacing-xs);
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
background-color: light-dark(
|
|
||||||
var(--mantine-color-gray-1),
|
|
||||||
var(--mantine-color-dark-6)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-active] {
|
|
||||||
background-color: light-dark(
|
|
||||||
var(--mantine-color-gray-2),
|
|
||||||
var(--mantine-color-dark-6)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatItemTitle {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatItemDate {
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
color: var(--mantine-color-dimmed);
|
|
||||||
white-space: nowrap;
|
|
||||||
transition: opacity 150ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatItemRenameInput {
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
padding: 0;
|
|
||||||
height: auto;
|
|
||||||
min-height: 0;
|
|
||||||
background: transparent;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatItem:hover .chatItemDate {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatItemActions {
|
|
||||||
position: absolute;
|
|
||||||
right: var(--mantine-spacing-xs);
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 150ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatItem {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatItem:hover .chatItemActions {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
export type AiChat = {
|
|
||||||
id: string;
|
|
||||||
workspaceId: string;
|
|
||||||
creatorId: string;
|
|
||||||
title: string | null;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AiChatToolCall = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
args: Record<string, unknown>;
|
|
||||||
result?: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AiChatMessage = {
|
|
||||||
id: string;
|
|
||||||
chatId: string;
|
|
||||||
role: 'user' | 'assistant' | 'tool';
|
|
||||||
content: string | null;
|
|
||||||
toolCalls: AiChatToolCall[] | null;
|
|
||||||
metadata: Record<string, unknown> | null;
|
|
||||||
createdAt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AiChatStreamEvent =
|
|
||||||
| { type: 'chat_created'; chatId: string }
|
|
||||||
| { type: 'content'; text: string }
|
|
||||||
| { type: 'tool_call'; id: string; name: string; args: Record<string, unknown> }
|
|
||||||
| { type: 'tool_result'; id: string; result: unknown }
|
|
||||||
| { type: 'done'; messageId: string; usage?: Record<string, number> }
|
|
||||||
| { type: 'error'; message: string; code?: string; retryable?: boolean };
|
|
||||||
|
|
||||||
export type PageMention = {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
slugId: string;
|
|
||||||
spaceSlug?: string;
|
|
||||||
icon?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ChatAttachment = {
|
|
||||||
id: string;
|
|
||||||
fileName: string;
|
|
||||||
fileExt: string;
|
|
||||||
fileSize: number;
|
|
||||||
mimeType: string;
|
|
||||||
};
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import type { AiChat } from "../types/ai-chat.types";
|
|
||||||
|
|
||||||
export type ChatGroup = { key: string; label: string; chats: AiChat[] };
|
|
||||||
|
|
||||||
export function groupChatsByAge(
|
|
||||||
chats: AiChat[],
|
|
||||||
t: (key: string) => string,
|
|
||||||
): ChatGroup[] {
|
|
||||||
if (chats.length === 0) return [];
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const startOfToday = new Date(
|
|
||||||
now.getFullYear(),
|
|
||||||
now.getMonth(),
|
|
||||||
now.getDate(),
|
|
||||||
).getTime();
|
|
||||||
const startOfYesterday = startOfToday - 24 * 60 * 60 * 1000;
|
|
||||||
const startOfLast7 = startOfToday - 7 * 24 * 60 * 60 * 1000;
|
|
||||||
const startOfLast30 = startOfToday - 30 * 24 * 60 * 60 * 1000;
|
|
||||||
|
|
||||||
const buckets: Record<string, ChatGroup> = {
|
|
||||||
today: { key: "today", label: t("Today"), chats: [] },
|
|
||||||
yesterday: { key: "yesterday", label: t("Yesterday"), chats: [] },
|
|
||||||
last7: { key: "last7", label: t("Previous 7 days"), chats: [] },
|
|
||||||
last30: { key: "last30", label: t("Previous 30 days"), chats: [] },
|
|
||||||
older: { key: "older", label: t("Older"), chats: [] },
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const chat of chats) {
|
|
||||||
const ts = new Date(chat.updatedAt).getTime();
|
|
||||||
if (ts >= startOfToday) buckets.today.chats.push(chat);
|
|
||||||
else if (ts >= startOfYesterday) buckets.yesterday.chats.push(chat);
|
|
||||||
else if (ts >= startOfLast7) buckets.last7.chats.push(chat);
|
|
||||||
else if (ts >= startOfLast30) buckets.last30.chats.push(chat);
|
|
||||||
else buckets.older.chats.push(chat);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
buckets.today,
|
|
||||||
buckets.yesterday,
|
|
||||||
buckets.last7,
|
|
||||||
buckets.last30,
|
|
||||||
buckets.older,
|
|
||||||
].filter((b) => b.chats.length > 0);
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import React, { useMemo } from "react";
|
|
||||||
import { Paper, Text, Group, Stack, Loader, Box } from "@mantine/core";
|
|
||||||
import { IconSparkles, IconFileText } from "@tabler/icons-react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { IAiSearchResponse } from "../services/ai-search-service.ts";
|
|
||||||
import { buildPageUrl } from "@/features/page/page.utils.ts";
|
|
||||||
import { markdownToHtml } from "@docmost/editor-ext";
|
|
||||||
import DOMPurify from "dompurify";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
interface AiSearchResultProps {
|
|
||||||
result?: IAiSearchResponse;
|
|
||||||
isLoading?: boolean;
|
|
||||||
streamingAnswer?: string;
|
|
||||||
streamingSources?: any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AiSearchResult({
|
|
||||||
result,
|
|
||||||
isLoading,
|
|
||||||
streamingAnswer = "",
|
|
||||||
streamingSources = [],
|
|
||||||
}: AiSearchResultProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
// Use streaming data if available, otherwise fall back to result
|
|
||||||
const answer = streamingAnswer || result?.answer || "";
|
|
||||||
const sources =
|
|
||||||
streamingSources.length > 0 ? streamingSources : result?.sources || [];
|
|
||||||
|
|
||||||
// Deduplicate sources by pageId, keeping the one with highest similarity
|
|
||||||
const deduplicatedSources = useMemo(() => {
|
|
||||||
if (!sources || sources.length === 0) return [];
|
|
||||||
|
|
||||||
const pageMap = new Map();
|
|
||||||
sources.forEach((source) => {
|
|
||||||
const existing = pageMap.get(source.pageId);
|
|
||||||
if (!existing || source.similarity > existing.similarity) {
|
|
||||||
pageMap.set(source.pageId, source);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Array.from(pageMap.values());
|
|
||||||
}, [sources]);
|
|
||||||
|
|
||||||
if (isLoading && !answer) {
|
|
||||||
return (
|
|
||||||
<Paper p="md" radius="md" withBorder>
|
|
||||||
<Group>
|
|
||||||
<Loader size="sm" />
|
|
||||||
<Text size="sm">{t("AI is thinking...")}</Text>
|
|
||||||
</Group>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!answer && !isLoading) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack gap="md" p="md">
|
|
||||||
<Paper p="md" radius="md" withBorder>
|
|
||||||
<Group gap="xs" mb="sm">
|
|
||||||
<IconSparkles size={20} color="var(--mantine-color-blue-6)" />
|
|
||||||
<Text fw={600} size="sm">
|
|
||||||
{t("AI Answer")}
|
|
||||||
</Text>
|
|
||||||
{isLoading && <Loader size="xs" />}
|
|
||||||
</Group>
|
|
||||||
<div
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: DOMPurify.sanitize(markdownToHtml(answer) as string),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{deduplicatedSources.length > 0 && (
|
|
||||||
<Stack gap="xs">
|
|
||||||
<Text size="xs" fw={600} c="dimmed">
|
|
||||||
{t("Sources")}
|
|
||||||
</Text>
|
|
||||||
{deduplicatedSources.map((source) => (
|
|
||||||
<Box
|
|
||||||
key={source.pageId}
|
|
||||||
component={Link}
|
|
||||||
to={buildPageUrl(source.spaceSlug, source.slugId, source.title)}
|
|
||||||
style={{
|
|
||||||
textDecoration: "none",
|
|
||||||
color: "inherit",
|
|
||||||
display: "block",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paper
|
|
||||||
p="xs"
|
|
||||||
radius="sm"
|
|
||||||
withBorder
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
>
|
|
||||||
<Group gap="xs">
|
|
||||||
<IconFileText size={16} />
|
|
||||||
<Text size="sm" truncate>
|
|
||||||
{source.title}
|
|
||||||
</Text>
|
|
||||||
</Group>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
.aiMenu {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600px;
|
|
||||||
min-height: 2.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aiInput {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
& input {
|
|
||||||
height: 44px;
|
|
||||||
border-radius: 22px;
|
|
||||||
padding-left: 20px;
|
|
||||||
padding-right: 40px;
|
|
||||||
border: 1px solid
|
|
||||||
light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: light-dark(
|
|
||||||
var(--mantine-color-gray-4),
|
|
||||||
var(--mantine-color-dark-3)
|
|
||||||
);
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.menuItemSelected {
|
|
||||||
background-color: var(--mantine-color-gray-1);
|
|
||||||
|
|
||||||
@mixin dark {
|
|
||||||
background-color: var(--mantine-color-dark-5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.resultPreview {
|
|
||||||
background-color: light-dark(
|
|
||||||
var(--mantine-color-white),
|
|
||||||
var(--mantine-color-dark-6)
|
|
||||||
);
|
|
||||||
border: 1px solid
|
|
||||||
light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resultPreviewWrapper {
|
|
||||||
font-size: var(--mantine-font-size-md);
|
|
||||||
line-height: 1.6;
|
|
||||||
padding: var(--mantine-spacing-md);
|
|
||||||
|
|
||||||
*:first-child {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
*:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,349 +0,0 @@
|
|||||||
import { Editor } from "@tiptap/react";
|
|
||||||
import { ActionIcon, TextInput } from "@mantine/core";
|
|
||||||
import { useDebouncedCallback, useMediaQuery } from "@mantine/hooks";
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
||||||
import { createPortal } from "react-dom";
|
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { IconArrowUp } from "@tabler/icons-react";
|
|
||||||
import { showAiMenuAtom } from "@/features/editor/atoms/editor-atoms.ts";
|
|
||||||
import { useAiGenerateStreamMutation } from "@/ee/ai/queries/ai-query.ts";
|
|
||||||
import { AiAction } from "@/ee/ai/types/ai.types.ts";
|
|
||||||
import { CommandItem, commandItems, CommandSet } from "./command-items.ts";
|
|
||||||
import { CommandSelector } from "./command-selector.tsx";
|
|
||||||
import { ResultPreview } from "./result-preview.tsx";
|
|
||||||
import classes from "./ai-menu.module.css";
|
|
||||||
import { marked } from "marked";
|
|
||||||
import { DOMSerializer } from "@tiptap/pm/model";
|
|
||||||
import { copyToClipboard, htmlToMarkdown } from "@docmost/editor-ext";
|
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
|
|
||||||
interface EditorAiMenuProps {
|
|
||||||
editor: Editor | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditorAiMenu = ({ editor }: EditorAiMenuProps): JSX.Element | null => {
|
|
||||||
const aiGenerateStreamMutation = useAiGenerateStreamMutation();
|
|
||||||
const location = useLocation();
|
|
||||||
const isSmBreakpoint = useMediaQuery("(max-width: 48em)");
|
|
||||||
const [showAiMenu, setShowAiMenu] = useAtom(showAiMenuAtom);
|
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
||||||
const [prompt, setPrompt] = useState("");
|
|
||||||
const [output, setOutput] = useState("");
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
|
||||||
const [activeCommandSet, setActiveCommandSet] = useState<CommandSet>("main");
|
|
||||||
const [lastAction, setLastAction] = useState<CommandItem | null>(null);
|
|
||||||
const [menuPlacement, setMenuPlacement] = useState<{
|
|
||||||
top: number;
|
|
||||||
left: number;
|
|
||||||
width: number;
|
|
||||||
}>({
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: 0,
|
|
||||||
});
|
|
||||||
const currentItems = useMemo(() => {
|
|
||||||
return commandItems[activeCommandSet].filter((item) => {
|
|
||||||
return item.name.toLowerCase().includes(prompt.toLowerCase());
|
|
||||||
});
|
|
||||||
}, [prompt, output, activeCommandSet]);
|
|
||||||
const updateMenuPlacement = useCallback(() => {
|
|
||||||
if (!editor || !showAiMenu) return;
|
|
||||||
|
|
||||||
const { view } = editor;
|
|
||||||
const { from, to } = editor.state.selection;
|
|
||||||
const editorRect = view.dom.getBoundingClientRect();
|
|
||||||
const fromCoords = view.coordsAtPos(from);
|
|
||||||
const toCoords = view.coordsAtPos(to);
|
|
||||||
const topOffset = 8;
|
|
||||||
const editorPadding = isSmBreakpoint ? 16 : 48;
|
|
||||||
|
|
||||||
const anchorBottom =
|
|
||||||
toCoords.bottom > 0 && toCoords.bottom < window.innerHeight
|
|
||||||
? toCoords.bottom
|
|
||||||
: fromCoords.bottom;
|
|
||||||
|
|
||||||
const menuMaxWidth = 600;
|
|
||||||
const editorLeft = editorRect.left + editorPadding;
|
|
||||||
const editorRight = editorRect.right - editorPadding;
|
|
||||||
const availableWidth = editorRight - editorLeft;
|
|
||||||
const menuWidth = Math.min(menuMaxWidth, availableWidth);
|
|
||||||
|
|
||||||
let menuLeft = Math.max(editorLeft, fromCoords.left);
|
|
||||||
if (menuLeft + menuWidth > editorRight) {
|
|
||||||
menuLeft = editorRight - menuWidth;
|
|
||||||
}
|
|
||||||
menuLeft = Math.max(editorLeft, menuLeft);
|
|
||||||
|
|
||||||
setMenuPlacement({
|
|
||||||
top: anchorBottom + topOffset + window.scrollY,
|
|
||||||
left: menuLeft + window.scrollX,
|
|
||||||
width: menuWidth,
|
|
||||||
});
|
|
||||||
}, [editor, showAiMenu, isSmBreakpoint]);
|
|
||||||
const resetMenu = useCallback(() => {
|
|
||||||
setPrompt("");
|
|
||||||
setOutput("");
|
|
||||||
setActiveCommandSet("main");
|
|
||||||
setLastAction(null);
|
|
||||||
aiGenerateStreamMutation.reset();
|
|
||||||
}, [aiGenerateStreamMutation.reset]);
|
|
||||||
const debouncedUpdateMenuPlacement = useDebouncedCallback(
|
|
||||||
updateMenuPlacement,
|
|
||||||
60,
|
|
||||||
);
|
|
||||||
const handleGenerate = useCallback(
|
|
||||||
(item?: CommandItem) => {
|
|
||||||
if (!editor || isLoading) return;
|
|
||||||
|
|
||||||
let command: CommandItem | null = item || null;
|
|
||||||
|
|
||||||
if (!command) {
|
|
||||||
if (!prompt) return;
|
|
||||||
|
|
||||||
command = {
|
|
||||||
id: "custom",
|
|
||||||
name: "Custom",
|
|
||||||
action: AiAction.CUSTOM,
|
|
||||||
prompt,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const { from, to } = editor.state.selection;
|
|
||||||
const slice = editor.state.doc.slice(from, to);
|
|
||||||
const serializer = DOMSerializer.fromSchema(editor.schema);
|
|
||||||
const fragment = serializer.serializeFragment(slice.content);
|
|
||||||
const wrapper = document.createElement("div");
|
|
||||||
wrapper.appendChild(fragment);
|
|
||||||
const content = htmlToMarkdown(wrapper.innerHTML);
|
|
||||||
|
|
||||||
setOutput("");
|
|
||||||
setIsLoading(true);
|
|
||||||
aiGenerateStreamMutation.mutate({
|
|
||||||
action: command.action,
|
|
||||||
prompt: command.prompt,
|
|
||||||
content,
|
|
||||||
onChunk: (chunk) => {
|
|
||||||
setOutput((output) => output + chunk.content);
|
|
||||||
},
|
|
||||||
onComplete: () => {
|
|
||||||
setPrompt("");
|
|
||||||
setIsLoading(false);
|
|
||||||
setActiveCommandSet("result");
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
setIsLoading(false);
|
|
||||||
resetMenu();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
setLastAction(command);
|
|
||||||
},
|
|
||||||
[
|
|
||||||
editor,
|
|
||||||
prompt,
|
|
||||||
isLoading,
|
|
||||||
aiGenerateStreamMutation.mutateAsync,
|
|
||||||
resetMenu,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
const handleCommand = useCallback(
|
|
||||||
(item?: CommandItem) => {
|
|
||||||
setPrompt("");
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
return handleGenerate();
|
|
||||||
}
|
|
||||||
if (item.id === "back") {
|
|
||||||
return setActiveCommandSet("main");
|
|
||||||
}
|
|
||||||
if (item.id === "result-replace") {
|
|
||||||
const chain = editor.chain().focus();
|
|
||||||
|
|
||||||
if (lastAction.action === AiAction.CONTINUE_WRITING) {
|
|
||||||
chain.setTextSelection(editor.state.selection.to);
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = (marked.parse(output) as string).trim();
|
|
||||||
const isSingleParagraph =
|
|
||||||
html.startsWith("<p>") &&
|
|
||||||
html.endsWith("</p>") &&
|
|
||||||
html.lastIndexOf("<p>") === 0;
|
|
||||||
|
|
||||||
// Strip <p> wrapper for single-paragraph output to preserve inline context,
|
|
||||||
// then decode HTML entities via DOMParser since TipTap would otherwise
|
|
||||||
// treat the tagless string as plain text and insert entities literally.
|
|
||||||
const content = isSingleParagraph
|
|
||||||
? new DOMParser().parseFromString(html.slice(3, -4), "text/html")
|
|
||||||
.body.innerHTML
|
|
||||||
: html;
|
|
||||||
|
|
||||||
chain.insertContent(content).run();
|
|
||||||
|
|
||||||
return setShowAiMenu(false);
|
|
||||||
}
|
|
||||||
if (item.id === "result-insert-below") {
|
|
||||||
editor
|
|
||||||
.chain()
|
|
||||||
.focus()
|
|
||||||
.setTextSelection(editor.state.selection.to)
|
|
||||||
.insertContent(marked.parse(output))
|
|
||||||
.run();
|
|
||||||
|
|
||||||
return setShowAiMenu(false);
|
|
||||||
}
|
|
||||||
if (item.id === "result-copy") {
|
|
||||||
copyToClipboard(output);
|
|
||||||
|
|
||||||
return setShowAiMenu(false);
|
|
||||||
}
|
|
||||||
if (item.id === "result-discard") {
|
|
||||||
setOutput("");
|
|
||||||
|
|
||||||
return resetMenu();
|
|
||||||
}
|
|
||||||
if (item.id === "result-try-again" && lastAction) {
|
|
||||||
return handleGenerate(lastAction);
|
|
||||||
}
|
|
||||||
if (item.subCommandSet) {
|
|
||||||
return setActiveCommandSet(item.subCommandSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
return handleGenerate(item);
|
|
||||||
},
|
|
||||||
[editor, output, lastAction, handleGenerate, resetMenu],
|
|
||||||
);
|
|
||||||
const handleKeyDown = useCallback(
|
|
||||||
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
const totalItems = currentItems.length;
|
|
||||||
const cycleSize = totalItems + 1;
|
|
||||||
|
|
||||||
if (event.key === "Escape") {
|
|
||||||
return setShowAiMenu(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
return setSelectedIndex((selectedIndex) => {
|
|
||||||
const direction = event.key === "ArrowDown" ? 1 : -1;
|
|
||||||
const newIndex = selectedIndex + direction;
|
|
||||||
|
|
||||||
if (newIndex < -1) return cycleSize - 1;
|
|
||||||
if (newIndex >= cycleSize) return 0;
|
|
||||||
|
|
||||||
return newIndex;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === "Enter") {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
return handleCommand(currentItems[selectedIndex]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[currentItems, selectedIndex],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!editor) return;
|
|
||||||
|
|
||||||
const handleClose = () => setShowAiMenu(false);
|
|
||||||
const observer = new ResizeObserver(() => {
|
|
||||||
debouncedUpdateMenuPlacement();
|
|
||||||
});
|
|
||||||
|
|
||||||
updateMenuPlacement();
|
|
||||||
editor.on("focus", handleClose);
|
|
||||||
editor.on("blur", handleClose);
|
|
||||||
window.addEventListener("resize", debouncedUpdateMenuPlacement);
|
|
||||||
window.addEventListener("scroll", debouncedUpdateMenuPlacement, true);
|
|
||||||
observer.observe(editor.view.dom);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
editor.off("focus", handleClose);
|
|
||||||
editor.off("blur", handleClose);
|
|
||||||
window.removeEventListener("resize", debouncedUpdateMenuPlacement);
|
|
||||||
window.removeEventListener("scroll", debouncedUpdateMenuPlacement, true);
|
|
||||||
observer.disconnect();
|
|
||||||
};
|
|
||||||
}, [editor, updateMenuPlacement, debouncedUpdateMenuPlacement]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setShowAiMenu(false);
|
|
||||||
}, [location]);
|
|
||||||
useEffect(() => {
|
|
||||||
if (showAiMenu) {
|
|
||||||
resetMenu();
|
|
||||||
}
|
|
||||||
}, [showAiMenu, resetMenu]);
|
|
||||||
useEffect(() => {
|
|
||||||
// Focus input when menu opens or command set changes
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
inputRef.current?.focus({ preventScroll: true });
|
|
||||||
});
|
|
||||||
}, [showAiMenu, isLoading, currentItems]);
|
|
||||||
useEffect(() => {
|
|
||||||
if (!currentItems.length) {
|
|
||||||
setSelectedIndex(-1);
|
|
||||||
}
|
|
||||||
setSelectedIndex(prompt || activeCommandSet !== "main" ? 0 : -1);
|
|
||||||
}, [prompt, activeCommandSet, currentItems]);
|
|
||||||
|
|
||||||
if (!showAiMenu) return null;
|
|
||||||
|
|
||||||
return createPortal(
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
zIndex: 199,
|
|
||||||
position: "absolute",
|
|
||||||
top: menuPlacement.top,
|
|
||||||
left: menuPlacement.left,
|
|
||||||
width: menuPlacement.width,
|
|
||||||
pointerEvents: "none",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={classes.aiMenu}
|
|
||||||
style={{ pointerEvents: "auto" }}
|
|
||||||
tabIndex={0}
|
|
||||||
ref={containerRef}
|
|
||||||
>
|
|
||||||
<ResultPreview output={output} isLoading={isLoading} />
|
|
||||||
<CommandSelector
|
|
||||||
selectedIndex={selectedIndex}
|
|
||||||
isLoading={isLoading}
|
|
||||||
output={output}
|
|
||||||
currentItems={currentItems}
|
|
||||||
handleCommand={handleCommand}
|
|
||||||
>
|
|
||||||
<TextInput
|
|
||||||
ref={inputRef}
|
|
||||||
className={classes.aiInput}
|
|
||||||
placeholder="Ask AI..."
|
|
||||||
data-autofocus
|
|
||||||
value={prompt}
|
|
||||||
disabled={isLoading}
|
|
||||||
onChange={(e) => setPrompt(e.currentTarget.value)}
|
|
||||||
rightSection={
|
|
||||||
<ActionIcon
|
|
||||||
disabled={!prompt || isLoading}
|
|
||||||
variant="filled"
|
|
||||||
color="blue"
|
|
||||||
radius="xl"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => handleGenerate()}
|
|
||||||
>
|
|
||||||
<IconArrowUp size={14} stroke={2.5} />
|
|
||||||
</ActionIcon>
|
|
||||||
}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
/>
|
|
||||||
</CommandSelector>
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
document.body,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { EditorAiMenu };
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
import { AiAction } from "@/ee/ai/types/ai.types.ts";
|
|
||||||
import {
|
|
||||||
IconSparkles,
|
|
||||||
IconArrowsMaximize,
|
|
||||||
IconArrowsMinimize,
|
|
||||||
IconWriting,
|
|
||||||
IconHelp,
|
|
||||||
IconList,
|
|
||||||
IconMoodSmile,
|
|
||||||
IconLanguage,
|
|
||||||
IconTrash,
|
|
||||||
IconRefresh,
|
|
||||||
IconChevronLeft,
|
|
||||||
IconCheck,
|
|
||||||
IconArrowDownLeft,
|
|
||||||
IconCopy,
|
|
||||||
IconTextPlus,
|
|
||||||
IconAlignJustified,
|
|
||||||
} from "@tabler/icons-react";
|
|
||||||
|
|
||||||
interface CommandItem {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
icon?: typeof IconSparkles;
|
|
||||||
action?: AiAction;
|
|
||||||
prompt?: string;
|
|
||||||
subCommandSet?: CommandSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandSet = "main" | "tone" | "translate" | "result";
|
|
||||||
|
|
||||||
const mainItems: CommandItem[] = [
|
|
||||||
{
|
|
||||||
id: "improve-writing",
|
|
||||||
name: "Improve writing",
|
|
||||||
icon: IconSparkles,
|
|
||||||
action: AiAction.IMPROVE_WRITING,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "fix-spelling-grammar",
|
|
||||||
name: "Fix spelling & grammar",
|
|
||||||
icon: IconCheck,
|
|
||||||
action: AiAction.FIX_SPELLING_GRAMMAR,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "make-longer",
|
|
||||||
name: "Make longer",
|
|
||||||
icon: IconTextPlus,
|
|
||||||
action: AiAction.MAKE_LONGER,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "make-shorter",
|
|
||||||
name: "Make shorter",
|
|
||||||
icon: IconAlignJustified,
|
|
||||||
action: AiAction.MAKE_SHORTER,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "continue-writing",
|
|
||||||
name: "Continue writing",
|
|
||||||
icon: IconWriting,
|
|
||||||
action: AiAction.CONTINUE_WRITING,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "explain",
|
|
||||||
name: "Explain",
|
|
||||||
icon: IconHelp,
|
|
||||||
action: AiAction.EXPLAIN,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "summarize",
|
|
||||||
name: "Summarize",
|
|
||||||
icon: IconList,
|
|
||||||
action: AiAction.SUMMARIZE,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "change-tone",
|
|
||||||
name: "Change tone",
|
|
||||||
icon: IconMoodSmile,
|
|
||||||
subCommandSet: "tone",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "translate",
|
|
||||||
name: "Translate",
|
|
||||||
icon: IconLanguage,
|
|
||||||
subCommandSet: "translate",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const toneItems: CommandItem[] = [
|
|
||||||
{
|
|
||||||
id: "back",
|
|
||||||
name: "Back",
|
|
||||||
icon: IconChevronLeft,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "tone-professional",
|
|
||||||
name: "Professional",
|
|
||||||
icon: IconMoodSmile,
|
|
||||||
action: AiAction.CHANGE_TONE,
|
|
||||||
prompt: "Professional",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "tone-casual",
|
|
||||||
name: "Casual",
|
|
||||||
icon: IconMoodSmile,
|
|
||||||
action: AiAction.CHANGE_TONE,
|
|
||||||
prompt: "Casual",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "tone-friendly",
|
|
||||||
name: "Friendly",
|
|
||||||
icon: IconMoodSmile,
|
|
||||||
action: AiAction.CHANGE_TONE,
|
|
||||||
prompt: "Friendly",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const translateItems: CommandItem[] = [
|
|
||||||
{
|
|
||||||
id: "back",
|
|
||||||
name: "Back",
|
|
||||||
icon: IconChevronLeft,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "translate-english",
|
|
||||||
name: "English",
|
|
||||||
icon: IconLanguage,
|
|
||||||
action: AiAction.TRANSLATE,
|
|
||||||
prompt: "English",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "translate-spanish",
|
|
||||||
name: "Spanish",
|
|
||||||
icon: IconLanguage,
|
|
||||||
action: AiAction.TRANSLATE,
|
|
||||||
prompt: "Spanish",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "translate-german",
|
|
||||||
name: "German",
|
|
||||||
icon: IconLanguage,
|
|
||||||
action: AiAction.TRANSLATE,
|
|
||||||
prompt: "German",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "translate-french",
|
|
||||||
name: "French",
|
|
||||||
icon: IconLanguage,
|
|
||||||
action: AiAction.TRANSLATE,
|
|
||||||
prompt: "French",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "translate-dutch",
|
|
||||||
name: "Dutch",
|
|
||||||
icon: IconLanguage,
|
|
||||||
action: AiAction.TRANSLATE,
|
|
||||||
prompt: "Dutch",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "translate-portuguese",
|
|
||||||
name: "Portuguese",
|
|
||||||
icon: IconLanguage,
|
|
||||||
action: AiAction.TRANSLATE,
|
|
||||||
prompt: "Portuguese",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "translate-italian",
|
|
||||||
name: "Italian",
|
|
||||||
icon: IconLanguage,
|
|
||||||
action: AiAction.TRANSLATE,
|
|
||||||
prompt: "Italian",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "translate-japanese",
|
|
||||||
name: "Japanese",
|
|
||||||
icon: IconLanguage,
|
|
||||||
action: AiAction.TRANSLATE,
|
|
||||||
prompt: "Japanese",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "translate-korean",
|
|
||||||
name: "Korean",
|
|
||||||
icon: IconLanguage,
|
|
||||||
action: AiAction.TRANSLATE,
|
|
||||||
prompt: "Korean",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "translate-swedish",
|
|
||||||
name: "Swedish",
|
|
||||||
icon: IconLanguage,
|
|
||||||
action: AiAction.TRANSLATE,
|
|
||||||
prompt: "Swedish",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "translate-chinese",
|
|
||||||
name: "Chinese (Simplified)",
|
|
||||||
icon: IconLanguage,
|
|
||||||
action: AiAction.TRANSLATE,
|
|
||||||
prompt: "Simplified Chinese",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const resultItems: CommandItem[] = [
|
|
||||||
{ id: "result-replace", name: "Replace", icon: IconCheck },
|
|
||||||
{ id: "result-insert-below", name: "Insert below", icon: IconArrowDownLeft },
|
|
||||||
{ id: "result-copy", name: "Copy", icon: IconCopy },
|
|
||||||
{ id: "result-discard", name: "Discard", icon: IconTrash },
|
|
||||||
{
|
|
||||||
id: "result-try-again",
|
|
||||||
name: "Try again",
|
|
||||||
icon: IconRefresh,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const commandItems: Record<CommandSet, CommandItem[]> = {
|
|
||||||
main: mainItems,
|
|
||||||
tone: toneItems,
|
|
||||||
translate: translateItems,
|
|
||||||
result: resultItems,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type { CommandItem, CommandSet };
|
|
||||||
export { commandItems };
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import { Loader, Menu, ScrollArea } from "@mantine/core";
|
|
||||||
import { IconChevronRight } from "@tabler/icons-react";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
import { CommandItem } from "./command-items.ts";
|
|
||||||
import classes from "./ai-menu.module.css";
|
|
||||||
|
|
||||||
interface CommandSelectorProps {
|
|
||||||
selectedIndex: number;
|
|
||||||
|
|
||||||
isLoading: boolean;
|
|
||||||
output: string;
|
|
||||||
currentItems: CommandItem[];
|
|
||||||
children: ReactNode;
|
|
||||||
handleCommand(item: CommandItem): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CommandSelector = ({
|
|
||||||
selectedIndex,
|
|
||||||
children,
|
|
||||||
isLoading,
|
|
||||||
output,
|
|
||||||
currentItems,
|
|
||||||
handleCommand,
|
|
||||||
}: CommandSelectorProps) => {
|
|
||||||
return (
|
|
||||||
<Menu
|
|
||||||
opened={!isLoading && currentItems.length > 0}
|
|
||||||
middlewares={{ flip: false }}
|
|
||||||
position="bottom-start"
|
|
||||||
offset={4}
|
|
||||||
width={250}
|
|
||||||
trapFocus={false}
|
|
||||||
shadow="lg"
|
|
||||||
>
|
|
||||||
<Menu.Target>{children}</Menu.Target>
|
|
||||||
<Menu.Dropdown>
|
|
||||||
<ScrollArea.Autosize type="scroll" scrollbarSize={5} mah={300}>
|
|
||||||
{currentItems.map((item, index) => {
|
|
||||||
const isSelected = selectedIndex === index;
|
|
||||||
const showLoader =
|
|
||||||
isLoading && output === "" && !item.subCommandSet;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu.Item
|
|
||||||
key={item.id}
|
|
||||||
className={isSelected ? classes.menuItemSelected : undefined}
|
|
||||||
leftSection={
|
|
||||||
showLoader ? (
|
|
||||||
<Loader size={14} />
|
|
||||||
) : item.icon ? (
|
|
||||||
<item.icon size={16} />
|
|
||||||
) : undefined
|
|
||||||
}
|
|
||||||
rightSection={
|
|
||||||
item.subCommandSet ? (
|
|
||||||
<IconChevronRight size={14} />
|
|
||||||
) : undefined
|
|
||||||
}
|
|
||||||
onClick={() => handleCommand(item)}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</Menu.Item>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ScrollArea.Autosize>
|
|
||||||
</Menu.Dropdown>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { CommandSelector };
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { Loader, Paper, ScrollArea } from "@mantine/core";
|
|
||||||
import DOMPurify from "dompurify";
|
|
||||||
import { marked } from "marked";
|
|
||||||
import { memo } from "react";
|
|
||||||
import classes from "./ai-menu.module.css";
|
|
||||||
|
|
||||||
interface ResultPreviewProps {
|
|
||||||
output: string;
|
|
||||||
isLoading: boolean;
|
|
||||||
}
|
|
||||||
const ResultPreview = memo(({ output, isLoading }: ResultPreviewProps) => {
|
|
||||||
if (!output && !isLoading) return;
|
|
||||||
|
|
||||||
const parsedOutput = `${marked.parse(output)}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paper mb={4} shadow="lg" radius="md" className={classes.resultPreview}>
|
|
||||||
<ScrollArea.Autosize mah={300} type="scroll" scrollbarSize={5}>
|
|
||||||
<div className={classes.resultPreviewWrapper}>
|
|
||||||
{parsedOutput && (
|
|
||||||
<div
|
|
||||||
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(parsedOutput) }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isLoading && <Loader size={12} ml="xs" display="inline-block" />}
|
|
||||||
</div>
|
|
||||||
</ScrollArea.Autosize>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export { ResultPreview };
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import { Group, Text, Switch, MantineSize, Tooltip } from "@mantine/core";
|
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts";
|
|
||||||
import { notifications } from "@mantine/notifications";
|
|
||||||
import { useHasFeature } from "@/ee/hooks/use-feature";
|
|
||||||
import { Feature } from "@/ee/features";
|
|
||||||
import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label";
|
|
||||||
|
|
||||||
export default function EnableAiSearch() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
|
||||||
<div>
|
|
||||||
<Text size="md">{t("AI-powered search (AI Answers)")}</Text>
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
{t(
|
|
||||||
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.",
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<AiSearchToggle />
|
|
||||||
</Group>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AiSearchToggleProps {
|
|
||||||
size?: MantineSize;
|
|
||||||
label?: string;
|
|
||||||
}
|
|
||||||
export function AiSearchToggle({ size, label }: AiSearchToggleProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [workspace, setWorkspace] = useAtom(workspaceAtom);
|
|
||||||
const [checked, setChecked] = useState(workspace?.settings?.ai?.search);
|
|
||||||
const hasAccess = useHasFeature(Feature.AI);
|
|
||||||
const upgradeLabel = useUpgradeLabel();
|
|
||||||
|
|
||||||
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const value = event.currentTarget.checked;
|
|
||||||
try {
|
|
||||||
const updatedWorkspace = await updateWorkspace({ aiSearch: value });
|
|
||||||
setChecked(value);
|
|
||||||
setWorkspace(updatedWorkspace);
|
|
||||||
} catch (err) {
|
|
||||||
notifications.show({
|
|
||||||
message: err?.response?.data?.message,
|
|
||||||
color: "red",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip label={upgradeLabel} disabled={hasAccess} refProp="rootRef">
|
|
||||||
<Switch
|
|
||||||
size={size}
|
|
||||||
label={label}
|
|
||||||
labelPosition="left"
|
|
||||||
defaultChecked={checked}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={!hasAccess}
|
|
||||||
aria-label={t("Toggle AI search")}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { Group, Text, Switch, Tooltip } from "@mantine/core";
|
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts";
|
|
||||||
import { notifications } from "@mantine/notifications";
|
|
||||||
import { useHasFeature } from "@/ee/hooks/use-feature";
|
|
||||||
import { Feature } from "@/ee/features";
|
|
||||||
import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label";
|
|
||||||
|
|
||||||
export default function EnableGenerativeAi() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [workspace, setWorkspace] = useAtom(workspaceAtom);
|
|
||||||
const [checked, setChecked] = useState(workspace?.settings?.ai?.generative);
|
|
||||||
const hasAccess = useHasFeature(Feature.AI);
|
|
||||||
const upgradeLabel = useUpgradeLabel();
|
|
||||||
|
|
||||||
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const value = event.currentTarget.checked;
|
|
||||||
try {
|
|
||||||
const updatedWorkspace = await updateWorkspace({ generativeAi: value });
|
|
||||||
setChecked(value);
|
|
||||||
setWorkspace(updatedWorkspace);
|
|
||||||
} catch (err) {
|
|
||||||
notifications.show({
|
|
||||||
message: err?.response?.data?.message,
|
|
||||||
color: "red",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
|
||||||
<div>
|
|
||||||
<Text size="md">{t("Generative AI (Ask AI)")}</Text>
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
{t(
|
|
||||||
"Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.",
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Tooltip label={upgradeLabel} disabled={hasAccess} refProp="rootRef">
|
|
||||||
<Switch
|
|
||||||
defaultChecked={checked}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={!hasAccess}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
import {
|
|
||||||
Anchor,
|
|
||||||
Group,
|
|
||||||
List,
|
|
||||||
Text,
|
|
||||||
Switch,
|
|
||||||
TextInput,
|
|
||||||
ActionIcon,
|
|
||||||
Tooltip,
|
|
||||||
Stack,
|
|
||||||
Alert,
|
|
||||||
} from "@mantine/core";
|
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
|
||||||
import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts";
|
|
||||||
import { notifications } from "@mantine/notifications";
|
|
||||||
import { useHasFeature } from "@/ee/hooks/use-feature";
|
|
||||||
import { Feature } from "@/ee/features";
|
|
||||||
import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label";
|
|
||||||
import { getAppUrl } from "@/lib/config.ts";
|
|
||||||
import { IconCheck, IconCopy, IconInfoCircle } from "@tabler/icons-react";
|
|
||||||
import { CopyButton } from "@/components/common/copy-button.tsx";
|
|
||||||
|
|
||||||
export default function McpSettings() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [workspace, setWorkspace] = useAtom(workspaceAtom);
|
|
||||||
const [checked, setChecked] = useState(workspace?.settings?.ai?.mcp);
|
|
||||||
const hasAccess = useHasFeature(Feature.MCP);
|
|
||||||
const upgradeLabel = useUpgradeLabel();
|
|
||||||
|
|
||||||
const mcpUrl = `${getAppUrl()}/mcp`;
|
|
||||||
|
|
||||||
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const value = event.currentTarget.checked;
|
|
||||||
try {
|
|
||||||
const updatedWorkspace = await updateWorkspace({ mcpEnabled: value });
|
|
||||||
setChecked(value);
|
|
||||||
setWorkspace(updatedWorkspace);
|
|
||||||
} catch (err) {
|
|
||||||
notifications.show({
|
|
||||||
message: err?.response?.data?.message,
|
|
||||||
color: "red",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack gap="lg">
|
|
||||||
{!hasAccess && (
|
|
||||||
<Alert icon={<IconInfoCircle />} title={upgradeLabel} color="blue">
|
|
||||||
{t(
|
|
||||||
"MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.",
|
|
||||||
)}
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
|
||||||
<div>
|
|
||||||
<Text size="md">{t("Model Context Protocol (MCP)")}</Text>
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
{t(
|
|
||||||
"Enable the MCP server to allow AI assistants and tools to interact with your workspace content.",
|
|
||||||
)}{" "}
|
|
||||||
<Trans
|
|
||||||
i18nKey="View the <anchor>MCP documentation</anchor>."
|
|
||||||
components={{
|
|
||||||
anchor: <Anchor href="https://docmost.com/docs/user-guide/mcp" target="_blank" size="sm" />,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Tooltip label={upgradeLabel} disabled={hasAccess} refProp="rootRef">
|
|
||||||
<Switch
|
|
||||||
defaultChecked={checked}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={!hasAccess}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
{checked && (
|
|
||||||
<div>
|
|
||||||
<Text size="sm" fw={500} mb={4}>
|
|
||||||
{t("MCP Server URL")}
|
|
||||||
</Text>
|
|
||||||
<Group gap="xs">
|
|
||||||
<TextInput value={mcpUrl} readOnly style={{ flex: 1 }} />
|
|
||||||
<CopyButton value={mcpUrl} timeout={2000}>
|
|
||||||
{({ copied, copy }) => (
|
|
||||||
<Tooltip
|
|
||||||
label={copied ? t("Copied") : t("Copy")}
|
|
||||||
withArrow
|
|
||||||
position="right"
|
|
||||||
>
|
|
||||||
<ActionIcon
|
|
||||||
color={copied ? "teal" : "gray"}
|
|
||||||
variant="subtle"
|
|
||||||
onClick={copy}
|
|
||||||
>
|
|
||||||
{copied ? <IconCheck size={16} /> : <IconCopy size={16} />}
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</CopyButton>
|
|
||||||
</Group>
|
|
||||||
<Text size="sm" c="dimmed" mt="xs">
|
|
||||||
{t(
|
|
||||||
"Use your API key for authentication. You can manage API keys in your account settings.",
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text size="sm" fw={500} mt="md" mb={4}>
|
|
||||||
{t("Supported tools")}
|
|
||||||
</Text>
|
|
||||||
<List size="sm" spacing={2}>
|
|
||||||
<List.Item>
|
|
||||||
<Text size="sm" c="dimmed" span>
|
|
||||||
search_pages, get_page, create_page, update_page
|
|
||||||
</Text>
|
|
||||||
</List.Item>
|
|
||||||
<List.Item>
|
|
||||||
<Text size="sm" c="dimmed" span>
|
|
||||||
list_pages, list_child_pages, duplicate_page
|
|
||||||
</Text>
|
|
||||||
</List.Item>
|
|
||||||
<List.Item>
|
|
||||||
<Text size="sm" c="dimmed" span>
|
|
||||||
copy_page_to_space, move_page, move_page_to_space
|
|
||||||
</Text>
|
|
||||||
</List.Item>
|
|
||||||
<List.Item>
|
|
||||||
<Text size="sm" c="dimmed" span>
|
|
||||||
get_space, list_spaces, create_space, update_space
|
|
||||||
</Text>
|
|
||||||
</List.Item>
|
|
||||||
<List.Item>
|
|
||||||
<Text size="sm" c="dimmed" span>
|
|
||||||
get_comments, create_comment, update_comment
|
|
||||||
</Text>
|
|
||||||
</List.Item>
|
|
||||||
<List.Item>
|
|
||||||
<Text size="sm" c="dimmed" span>
|
|
||||||
search_attachments, list_workspace_members, get_current_user
|
|
||||||
</Text>
|
|
||||||
</List.Item>
|
|
||||||
</List>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { useMutation, UseMutationResult } from "@tanstack/react-query";
|
|
||||||
import { useState, useCallback } from "react";
|
|
||||||
import { aiAnswers, IAiSearchResponse } from "@/ee/ai/services/ai-search-service.ts";
|
|
||||||
import { IPageSearchParams } from "@/features/search/types/search.types.ts";
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
interface UseAiSearchResult extends UseMutationResult<IAiSearchResponse, Error, IPageSearchParams> {
|
|
||||||
streamingAnswer: string;
|
|
||||||
streamingSources: any[];
|
|
||||||
clearStreaming: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAiSearch(): UseAiSearchResult {
|
|
||||||
const [streamingAnswer, setStreamingAnswer] = useState("");
|
|
||||||
const [streamingSources, setStreamingSources] = useState<any[]>([]);
|
|
||||||
|
|
||||||
const clearStreaming = useCallback(() => {
|
|
||||||
setStreamingAnswer("");
|
|
||||||
setStreamingSources([]);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const mutation = useMutation({
|
|
||||||
mutationFn: async (params: IPageSearchParams & { contentType?: string }) => {
|
|
||||||
setStreamingAnswer("");
|
|
||||||
setStreamingSources([]);
|
|
||||||
|
|
||||||
const { contentType, ...apiParams } = params;
|
|
||||||
|
|
||||||
return await aiAnswers(apiParams, (chunk) => {
|
|
||||||
if (chunk.content) {
|
|
||||||
setStreamingAnswer((prev) => prev + chunk.content);
|
|
||||||
}
|
|
||||||
if (chunk.sources) {
|
|
||||||
setStreamingSources(chunk.sources);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
...mutation,
|
|
||||||
streamingAnswer,
|
|
||||||
streamingSources,
|
|
||||||
clearStreaming,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import { Helmet } from "react-helmet-async";
|
|
||||||
import { getAppName } from "@/lib/config.ts";
|
|
||||||
import SettingsTitle from "@/components/settings/settings-title.tsx";
|
|
||||||
import React from "react";
|
|
||||||
import useUserRole from "@/hooks/use-user-role.tsx";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import EnableAiSearch from "@/ee/ai/components/enable-ai-search.tsx";
|
|
||||||
import EnableGenerativeAi from "@/ee/ai/components/enable-generative-ai.tsx";
|
|
||||||
import EnableAiChat from "@/ee/ai-chat/components/enable-ai-chat.tsx";
|
|
||||||
import McpSettings from "@/ee/ai/components/mcp-settings.tsx";
|
|
||||||
import { Alert, Stack, Tabs } from "@mantine/core";
|
|
||||||
import { IconInfoCircle } from "@tabler/icons-react";
|
|
||||||
import { useHasFeature } from "@/ee/hooks/use-feature";
|
|
||||||
import { Feature } from "@/ee/features";
|
|
||||||
import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label";
|
|
||||||
import { isCloud } from "@/lib/config.ts";
|
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
export default function AiSettings() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { isAdmin } = useUserRole();
|
|
||||||
const hasAccess = useHasFeature(Feature.AI);
|
|
||||||
const upgradeLabel = useUpgradeLabel();
|
|
||||||
const location = useLocation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const activeTab = location.pathname.endsWith("/mcp") ? "mcp" : "ai";
|
|
||||||
|
|
||||||
if (!isAdmin) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
|
||||||
if (value === "mcp") {
|
|
||||||
navigate("/settings/ai/mcp");
|
|
||||||
} else {
|
|
||||||
navigate("/settings/ai");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Helmet>
|
|
||||||
<title>AI settings - {getAppName()}</title>
|
|
||||||
</Helmet>
|
|
||||||
<SettingsTitle title={t("AI settings")} />
|
|
||||||
|
|
||||||
<Tabs color="dark" value={activeTab} onChange={handleTabChange}>
|
|
||||||
<Tabs.List>
|
|
||||||
<Tabs.Tab fw={500} value="ai">
|
|
||||||
{t("AI")}
|
|
||||||
</Tabs.Tab>
|
|
||||||
<Tabs.Tab fw={500} value="mcp">
|
|
||||||
{t("MCP")}
|
|
||||||
</Tabs.Tab>
|
|
||||||
</Tabs.List>
|
|
||||||
|
|
||||||
<Tabs.Panel value="ai" pt="md">
|
|
||||||
{!hasAccess && (
|
|
||||||
<Alert
|
|
||||||
icon={<IconInfoCircle />}
|
|
||||||
title={upgradeLabel}
|
|
||||||
color="blue"
|
|
||||||
mb="lg"
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.",
|
|
||||||
)}
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Stack gap="md">
|
|
||||||
{!isCloud() && <EnableAiSearch />}
|
|
||||||
<EnableGenerativeAi />
|
|
||||||
<EnableAiChat />
|
|
||||||
</Stack>
|
|
||||||
</Tabs.Panel>
|
|
||||||
|
|
||||||
<Tabs.Panel value="mcp" pt="md">
|
|
||||||
<McpSettings />
|
|
||||||
</Tabs.Panel>
|
|
||||||
</Tabs>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
generateAiContent,
|
generateAiContent,
|
||||||
generateAiContentStream,
|
generateAiContentStream,
|
||||||
|
getAiConfig,
|
||||||
} from "@/ee/ai/services/ai-service.ts";
|
} from "@/ee/ai/services/ai-service.ts";
|
||||||
import {
|
import {
|
||||||
AiConfigResponse,
|
AiConfigResponse,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user