Compare commits

..

1 Commits

Author SHA1 Message Date
Philipinho c354bc7be3 feat: page break command 2025-12-06 22:08:12 +00:00
51 changed files with 129 additions and 1247 deletions
+2 -4
View File
@@ -1,4 +1,4 @@
FROM node:22-slim AS base
FROM node:22-alpine AS base
LABEL org.opencontainers.image.source="https://github.com/docmost/docmost"
FROM base AS builder
@@ -13,9 +13,7 @@ RUN pnpm build
FROM base AS installer
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl bash \
&& rm -rf /var/lib/apt/lists/*
RUN apk add --no-cache curl bash
WORKDIR /app
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "client",
"private": true,
"version": "0.24.1",
"version": "0.23.2",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
@@ -42,7 +42,7 @@
"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? Dadurch werden ihre Unterseiten und die Seitengeschichte gelöscht. Diese Aktion ist unwiderruflich.",
"Description": "Beschreibung",
"Details": "Details",
"Details": "Einzelheiten",
"e.g ACME": "z.B. ACME",
"e.g ACME Inc": "z.B. ACME Inc.",
"e.g Developers": "z.B. Entwickler",
@@ -525,47 +525,5 @@
"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": "Icon",
"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 key created successfully": "API-Schlüssel erfolgreich erstellt",
"API keys": "API-Schlüssel",
"API management": "API-Verwaltung",
"Are you sure you want to revoke this API key": "Sind Sie sicher, dass Sie diesen API-Schlüssel widerrufen möchten?",
"Create API Key": "API-Schlüssel erstellen",
"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",
"I've saved my API key": "Ich habe meinen API-Schlüssel gespeichert",
"Last use": "Zuletzt verwendet",
"No API keys found": "Keine API-Schlüssel gefunden",
"No expiration": "Kein Ablauf",
"Revoke API key": "API-Schlüssel widerrufen",
"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 API key": "API-Schlüssel aktualisieren",
"Manage API keys for all users in the workspace": "Verwalten Sie API-Schlüssel für alle Benutzer im Arbeitsbereich",
"AI settings": "KI-Einstellungen",
"AI search": "KI-Suche",
"AI Answer": "KI-Antwort",
"Ask AI": "KI fragen",
"AI is thinking...": "Die KI überlegt...",
"Ask a question...": "Fragen stellen...",
"AI-powered search (Ask AI)": "KI-gestützte Suche (KI fragen)",
"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",
"Sources": "Quellen",
"Ask AI not available for attachments": "KI fragen nicht für Anhänge verfügbar",
"No answer available": "Keine Antwort verfügbar",
"Background color": "Hintergrundfarbe",
"Highlight color": "Hervorhebungsfarbe",
"Remove color": "Farbe entfernen"
"{{ssoProviderType}} configuration": "{{ssoProviderType}}-Konfiguration"
}
@@ -527,47 +527,5 @@
"Delete SSO provider": "Eliminar proveedor de SSO",
"Are you sure you want to delete this SSO provider?": "¿Está seguro de que desea eliminar este proveedor de SSO?",
"Action": "Acción",
"{{ssoProviderType}} configuration": "Configuración de {{ssoProviderType}}",
"Icon": "Icono",
"Upload image": "Subir imagen",
"Remove image": "Eliminar imagen",
"Failed to remove image": "No se ha podido eliminar la imagen",
"Image exceeds 10MB limit.": "La imagen excede del límite de 10 MB",
"Image removed successfully": "Imagen eliminada correctamente",
"API key": "Clave API",
"API key created successfully": "Clave API creada correctamente",
"API keys": "Claves API",
"API management": "Gestión de API",
"Are you sure you want to revoke this API key": "¿Está seguro de que desea revocar esta clave API? ",
"Create API Key": "Crear clave API",
"Custom expiration date": "Fecha de vencimiento personalizada",
"Enter a descriptive token name": "Introduce un nombre descriptivo del token",
"Expiration": "Vencimiento",
"Expired": "Vencido",
"Expires": "Vence",
"I've saved my API key": "He guardado mi clave API",
"Last use": "Último uso",
"No API keys found": "No se han encontrado claves API",
"No expiration": "Sin vencimiento",
"Revoke API key": "Revocar clave API",
"Revoked successfully": "Revocada correctamente",
"Select expiration date": "Seleccionar fecha de vencimiento",
"This action cannot be undone. Any applications using this API key will stop working.": "Esta acción no se puede deshacer. Las aplicaciones que utilicen esta clave API dejarán de funcionar.",
"Update API key": "Actualizar clave API",
"Manage API keys for all users in the workspace": "Gestionar claves API para todos los usuarios en el espacio de trabajo",
"AI settings": "Configuración de IA",
"AI search": "Búsqueda de IA",
"AI Answer": "Respuesta de IA",
"Ask AI": "Preguntar a IA",
"AI is thinking...": "IA está pensando...",
"Ask a question...": "Haz una pregunta...",
"AI-powered search (Ask AI)": "Búsqueda impulsada por IA (Preguntar a IA)",
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "La búsqueda de IA utiliza incrustaciones vectoriales para proporcionar capacidades de búsqueda semántica en todo el contenido de su espacio de trabajo.",
"Toggle AI search": "Alternar búsqueda de IA",
"Sources": "Fuentes",
"Ask AI not available for attachments": "Preguntar a IA no está disponible para adjuntos",
"No answer available": "No hay respuesta disponible",
"Background color": "Color de fondo",
"Highlight color": "Color de resaltado",
"Remove color": "Eliminar color"
"{{ssoProviderType}} configuration": "Configuración de {{ssoProviderType}}"
}
@@ -527,47 +527,5 @@
"Delete SSO provider": "Supprimer le fournisseur SSO",
"Are you sure you want to delete this SSO provider?": "Êtes-vous sûr de vouloir supprimer ce fournisseur SSO ?",
"Action": "Action",
"{{ssoProviderType}} configuration": "Configuration {{ssoProviderType}}",
"Icon": "Icône",
"Upload image": "Téléverser une image",
"Remove image": "Supprimer l'image",
"Failed to remove image": "Échec de la suppression de l'image",
"Image exceeds 10MB limit.": "L'image dépasse la limite de 10 Mo.",
"Image removed successfully": "Image supprimée avec succès",
"API key": "Clé API",
"API key created successfully": "Clé API créée avec succès",
"API keys": "Clés API",
"API management": "Gestion des API",
"Are you sure you want to revoke this API key": "Êtes-vous sûr de vouloir révoquer cette clé API",
"Create API Key": "Créer une clé API",
"Custom expiration date": "Date d'expiration personnalisée",
"Enter a descriptive token name": "Entrez un nom descriptif pour le jeton",
"Expiration": "Expiration",
"Expired": "Expiré(e)",
"Expires": "Expire",
"I've saved my API key": "J'ai enregistré ma clé API",
"Last use": "Dernière utilisation",
"No API keys found": "Aucune clé API trouvée",
"No expiration": "Pas d'expiration",
"Revoke API key": "Révoquer la clé API",
"Revoked successfully": "Révoqué(e) avec succès",
"Select expiration date": "Sélectionnez la date d'expiration",
"This action cannot be undone. Any applications using this API key will stop working.": "Cette action ne peut pas être annulée. Toutes les applications utilisant cette clé API cesseront de fonctionner.",
"Update API key": "Mettre à jour la clé API",
"Manage API keys for all users in the workspace": "Gérer les clés API pour tous les utilisateurs dans l'espace de travail",
"AI settings": "Paramètres de l'IA",
"AI search": "Recherche IA",
"AI Answer": "Réponse IA",
"Ask AI": "Demander à l'IA",
"AI is thinking...": "L'IA réfléchit...",
"Ask a question...": "Posez une question...",
"AI-powered search (Ask AI)": "Recherche assistée par l'IA (Demander à l'IA)",
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "La recherche IA utilise des incorporations vectorielles pour fournir des capacités de recherche sémantique à travers le contenu de votre espace de travail.",
"Toggle AI search": "Basculer la recherche IA",
"Sources": "Sources",
"Ask AI not available for attachments": "Demande à l'IA non disponible pour les pièces jointes",
"No answer available": "Pas de réponse disponible",
"Background color": "Couleur de fond",
"Highlight color": "Couleur de surbrillance",
"Remove color": "Supprimer la couleur"
"{{ssoProviderType}} configuration": "Configuration {{ssoProviderType}}"
}
@@ -527,47 +527,5 @@
"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 key created successfully": "Chiave API creata con successo",
"API keys": "Chiavi API",
"API management": "Gestione API",
"Are you sure you want to revoke this API key": "Sei sicuro di voler revocare questa chiave API",
"Create API Key": "Crea Chiave 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",
"I've saved my API key": "Ho salvato la mia chiave API",
"Last use": "Ultimo utilizzo",
"No API keys found": "Nessuna chiave API trovata",
"No expiration": "Nessuna scadenza",
"Revoke API key": "Revoca chiave API",
"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 API key": "Aggiorna chiave API",
"Manage API keys for all users in the workspace": "Gestisci le chiavi API per tutti gli utenti nell'area 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...",
"Ask a question...": "Fai una domanda...",
"AI-powered search (Ask AI)": "Ricerca potenziata dall'AI (Chiedi all'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",
"Sources": "Fonti",
"Ask AI not available for attachments": "Chiedere all'AI non è disponibile per gli allegati",
"No answer available": "Nessuna risposta disponibile",
"Background color": "Colore di sfondo",
"Highlight color": "Colore evidenziato",
"Remove color": "Rimuovi colore"
"{{ssoProviderType}} configuration": "Configurazione {{ssoProviderType}}"
}
@@ -527,47 +527,5 @@
"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 key created successfully": "APIキーが正常に作成されました",
"API keys": "APIキー",
"API management": "API管理",
"Are you sure you want to revoke this API key": "このAPIキーを無効にしてもよろしいですか",
"Create API Key": "APIキーを作成",
"Custom expiration date": "カスタム有効期限",
"Enter a descriptive token name": "説明的なトークン名を入力してください",
"Expiration": "有効期限",
"Expired": "期限切れ",
"Expires": "期限が切れます",
"I've saved my API key": "APIキーを保存しました",
"Last use": "最終使用",
"No API keys found": "APIキーが見つかりません",
"No expiration": "期限なし",
"Revoke API key": "APIキーを無効にする",
"Revoked successfully": "正常に無効化されました",
"Select expiration date": "有効期限を選択してください",
"This action cannot be undone. Any applications using this API key will stop working.": "この操作は元に戻せません。このAPIキーを使用しているアプリケーションは動作を停止します。",
"Update API key": "APIキーを更新",
"Manage API keys for all users in the workspace": "ワークスペース内のすべてのユーザーのAPIキーを管理",
"AI settings": "AI設定",
"AI search": "AI検索",
"AI Answer": "AI回答",
"Ask AI": "AIに質問する",
"AI is thinking...": "AIが考え中...",
"Ask a question...": "質問を入力...",
"AI-powered search (Ask AI)": "AIによる検索(AIに質問)",
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI検索はベクター埋め込みを使用して、ワークスペースコンテンツ全体にわたって意味検索機能を提供します。",
"Toggle AI search": "AI検索を切り替え",
"Sources": "ソース",
"Ask AI not available for attachments": "添付ファイルにはAI質問は利用できません",
"No answer available": "回答がありません",
"Background color": "背景色",
"Highlight color": "ハイライト色",
"Remove color": "色を削除"
"{{ssoProviderType}} configuration": "{{ssoProviderType}}の構成"
}
@@ -527,47 +527,5 @@
"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 key created successfully": "API 키 생성 완료",
"API keys": "API 키",
"API management": "API 관리",
"Are you sure you want to revoke this API key": "이 API 키를 취소하시겠습니까?",
"Create API Key": "API 키 생성",
"Custom expiration date": "사용자 정의 만료일",
"Enter a descriptive token name": "토큰 이름을 입력하세요",
"Expiration": "만료",
"Expired": "만료됨",
"Expires": "만료일",
"I've saved my API key": "API 키를 저장했습니다",
"Last use": "최근 사용",
"No API keys found": "API 키를 찾을 수 없습니다",
"No expiration": "유효기간 없음",
"Revoke API key": "API 키 취소",
"Revoked successfully": "성공적으로 취소되었습니다",
"Select expiration date": "만료일 선택",
"This action cannot be undone. Any applications using this API key will stop working.": "이 작업은 되돌릴 수 없습니다. 이 API 키를 사용하는 모든 응용 프로그램이 작동을 멈출 것입니다.",
"Update API key": "API 키 갱신",
"Manage API keys for all users in the workspace": "워크스페이스 내 모든 사용자의 API 키 관리",
"AI settings": "AI 설정",
"AI search": "AI 검색",
"AI Answer": "AI 답변",
"Ask AI": "AI에게 묻기",
"AI is thinking...": "AI가 생각 중입니다...",
"Ask a question...": "질문하세요...",
"AI-powered search (Ask AI)": "AI 지원 검색 (AI에게 묻기)",
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI 검색은 벡터 임베딩을 사용하여 작업공간 콘텐츠에 대한 의미 검색 기능을 제공합니다.",
"Toggle AI search": "AI 검색 전환",
"Sources": "출처",
"Ask AI not available for attachments": "AI에게 묻기 기능은 첨부 파일에 대해 사용할 수 없습니다",
"No answer available": "답변을 제공할 수 없습니다",
"Background color": "배경 색",
"Highlight color": "강조 색",
"Remove color": "색 제거"
"{{ssoProviderType}} configuration": "{{ssoProviderType}} 구성"
}
@@ -34,7 +34,7 @@
"Create group": "Groep aanmaken",
"Create page": "Pagina aanmaken",
"Create space": "Ruimte aanmaken",
"Create workspace": "Werkruimte aanmaken",
"Create workspace": "Wwerkruimte aanmaken",
"Current password": "Huidig wachtwoord",
"Dark": "Donker",
"Date": "Datum",
@@ -91,7 +91,7 @@
"Invite by email": "Uitnodigen via e-mail",
"Invite members": "Leden uitnodigen",
"Invite new members": "Nieuwe leden uitnodigen",
"Invited members who are yet to accept their invitation will appear here.": "Uitgenodigde leden die hun uitnodiging nog moeten accepteren zullen hier worden getoond.",
"Invited members who are yet to accept their invitation will appear here.": "Uigenodigde leden die hun uitnodiging nog moeten accepteren zullen hier worden getoond.",
"Invited members will be granted access to spaces the groups can access": "Uitgenodigde leden wordt toegang gegeven tot ruimtes de groepen toegang toe heeft",
"Join the workspace": "Word lid van de werkruimte",
"Language": "Taal",
@@ -527,47 +527,5 @@
"Delete SSO provider": "Verwijder SSO-provider",
"Are you sure you want to delete this SSO provider?": "Weet u zeker dat u deze SSO-provider wilt verwijderen?",
"Action": "Actie",
"{{ssoProviderType}} configuration": "{{ssoProviderType}} configuratie",
"Icon": "Icoon",
"Upload image": "Afbeelding uploaden",
"Remove image": "Afbeelding verwijderen",
"Failed to remove image": "Afbeelding verwijderen mislukt",
"Image exceeds 10MB limit.": "Afbeelding overschrijdt de limiet van 10MB.",
"Image removed successfully": "Afbeelding succesvol verwijderd",
"API key": "API-sleutel",
"API key created successfully": "API-sleutel succesvol aangemaakt",
"API keys": "API-sleutels",
"API management": "API-beheer",
"Are you sure you want to revoke this API key": "Weet u zeker dat u deze API-sleutel wilt intrekken",
"Create API Key": "API-sleutel aanmaken",
"Custom expiration date": "Aangepaste vervaldatum",
"Enter a descriptive token name": "Voer een beschrijvende tokennaam in",
"Expiration": "Vervaldatum",
"Expired": "Verlopen",
"Expires": "Verloopt",
"I've saved my API key": "Ik heb mijn API-sleutel opgeslagen",
"Last use": "Laatst gebruikt",
"No API keys found": "Geen API-sleutels gevonden",
"No expiration": "Geen vervaldatum",
"Revoke API key": "API-sleutel intrekken",
"Revoked successfully": "Succesvol ingetrokken",
"Select expiration date": "Selecteer vervaldatum",
"This action cannot be undone. Any applications using this API key will stop working.": "Deze actie kan niet ongedaan worden gemaakt. Alle toepassingen die deze API-sleutel gebruiken, zullen niet meer werken.",
"Update API key": "API-sleutel bijwerken",
"Manage API keys for all users in the workspace": "Beheer API-sleutels voor alle gebruikers in de werkruimte",
"AI settings": "AI-instellingen",
"AI search": "AI-zoekopdracht",
"AI Answer": "AI Antwoord",
"Ask AI": "Vraag AI",
"AI is thinking...": "AI is aan het nadenken...",
"Ask a question...": "Stel een vraag...",
"AI-powered search (Ask AI)": "AI-ondersteunde zoekopdracht (Vraag AI)",
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI-zoekopdracht maakt gebruik van vectorembeddings om semantische zoekmogelijkheden te bieden in uw werkruimte-inhoud.",
"Toggle AI search": "Schakel AI-zoekopdracht in/uit",
"Sources": "Bronnen",
"Ask AI not available for attachments": "Vraag AI is niet beschikbaar voor bijlages",
"No answer available": "Geen antwoord beschikbaar",
"Background color": "Achtergrondkleur",
"Highlight color": "Markeerkleur",
"Remove color": "Kleur verwijderen"
"{{ssoProviderType}} configuration": "{{ssoProviderType}} configuratie"
}
@@ -527,47 +527,5 @@
"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": "Fazer upload da 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 key created successfully": "Chave API criada com sucesso",
"API keys": "Chaves API",
"API management": "Gestão de API",
"Are you sure you want to revoke this API key": "Tem certeza de que deseja revogar esta chave API",
"Create API Key": "Criar Chave 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",
"I've saved my API key": "Salvei minha chave API",
"Last use": "Último uso",
"No API keys found": "Nenhuma chave API encontrada",
"No expiration": "Sem expiração",
"Revoke API key": "Revogar chave API",
"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 API key": "Atualizar chave API",
"Manage API keys for all users in the workspace": "Gerenciar chaves API para todos os usuários no espaço de trabalho",
"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...",
"Ask a question...": "Faça uma pergunta...",
"AI-powered search (Ask AI)": "Pesquisa com IA (Pergunte à 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",
"Sources": "Fontes",
"Ask AI not available for attachments": "Perguntar à IA não está disponível para anexos",
"No answer available": "Nenhuma resposta disponível",
"Background color": "Cor de fundo",
"Highlight color": "Cor de destaque",
"Remove color": "Remover cor"
"{{ssoProviderType}} configuration": "Configuração de {{ssoProviderType}}"
}
@@ -498,10 +498,10 @@
"Deleted at": "Удалено в",
"Preview": "Предпросмотр",
"Subpages": "Подстраницы",
"Failed to load subpages": "Не удалось загрузить под страницы",
"Failed to load subpages": "Не удалось загрузить подстраницы",
"No subpages": "Нет подстраниц",
"Subpages (Child pages)": "Подстраницы (вложенные страницы)",
"List all subpages of the current page": "Показать все под страницы",
"List all subpages of the current page": "Показать все подстраницы текущей страницы",
"Attachments": "Вложения",
"All spaces": "Все пространства",
"Unknown": "Неизвестно",
@@ -527,47 +527,5 @@
"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 key created successfully": "API ключ успешно создан",
"API keys": "API ключи",
"API management": "Управление API",
"Are you sure you want to revoke this API key": "Вы уверены, что хотите отозвать этот API ключ",
"Create API Key": "Создать API ключ",
"Custom expiration date": "Пользовательская дата срока действия",
"Enter a descriptive token name": "Введите понятное имя токена",
"Expiration": "Срок действия",
"Expired": "Истек",
"Expires": "Истекает",
"I've saved my API key": "Я сохранил мой API ключ",
"Last use": "Последнее использование",
"No API keys found": "API ключи не найдены",
"No expiration": "Не истекает",
"Revoke API key": "Отозвать API ключ",
"Revoked successfully": "Отозван успешно",
"Select expiration date": "Выберете срок действия",
"This action cannot be undone. Any applications using this API key will stop working.": "Это действие необратимо. Любые приложения, использующие этот API ключ, перестанут работать.",
"Update API key": "Обновить API ключ",
"Manage API keys for all users in the workspace": "Управлять API ключами для всех пользователей в рабочей области",
"AI settings": "Настройки ИИ",
"AI search": "Поиск ИИ",
"AI Answer": "Ответ ИИ",
"Ask AI": "Спросить ИИ",
"AI is thinking...": "ИИ обрабатывает запрос...",
"Ask a question...": "Задайте вопрос...",
"AI-powered search (Ask AI)": "Поиск на базе ИИ (Спросить ИИ)",
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "Поиск ИИ использует векторные встраивания для обеспечения семантического поиска по содержимому вашего рабочего пространства.",
"Toggle AI search": "Переключить поиск ИИ",
"Sources": "Источники",
"Ask AI not available for attachments": "Функция \"Спросить ИИ\" недоступна для вложений",
"No answer available": "Ответ недоступен",
"Background color": "Цвет фона",
"Highlight color": "Цвет выделения",
"Remove color": "Удалить цвет"
"{{ssoProviderType}} configuration": "Настройка {{ssoProviderType}}"
}
@@ -527,47 +527,5 @@
"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.": "Зображення має займати менше, ніж 10 МБ.",
"Image removed successfully": "Зображення видалено",
"API key": "Ключ API",
"API key created successfully": "Ключ API успішно створено",
"API keys": "Ключі API",
"API management": "Управління API",
"Are you sure you want to revoke this API key": "Ви впевнені, що хочете відкликати цей ключ API",
"Create API Key": "Створити ключ API",
"Custom expiration date": "Користувацька дата закінчення",
"Enter a descriptive token name": "Введіть описову назву токена",
"Expiration": "Термін дії",
"Expired": "Закінчився",
"Expires": "Закінчується",
"I've saved my API key": "Я зберіг свій ключ API",
"Last use": "Останнє використання",
"No API keys found": "Ключі API не знайдено",
"No expiration": "Без терміну дії",
"Revoke API key": "Відкликати ключ API",
"Revoked successfully": "Успішно відкликано",
"Select expiration date": "Виберіть дату закінчення",
"This action cannot be undone. Any applications using this API key will stop working.": "Цю дію не можна скасувати. Будь-які додатки, що використовують цей ключ API, перестануть працювати.",
"Update API key": "Оновити ключ API",
"Manage API keys for all users in the workspace": "Керувати ключами API для всіх користувачів у робочій області",
"AI settings": "Налаштування ШІ",
"AI search": "Пошук з ШІ",
"AI Answer": "Відповідь ШІ",
"Ask AI": "Запитати ШІ",
"AI is thinking...": "ШІ думає...",
"Ask a question...": "Задайте питання...",
"AI-powered search (Ask AI)": "Пошук на базі ШІ (Запитати ШІ)",
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "Пошук з ШІ використовує векторні вбудовування для надання можливостей семантичного пошуку у вашому робочому вмісті.",
"Toggle AI search": "Переключити пошук з ШІ",
"Sources": "Джерела",
"Ask AI not available for attachments": "Запитати ШІ недоступно для вкладень",
"No answer available": "Відповідь недоступна",
"Background color": "Колір фону",
"Highlight color": "Колір підсвічування",
"Remove color": "Видалити колір"
"{{ssoProviderType}} configuration": "Конфігурація {{ssoProviderType}}"
}
@@ -527,47 +527,5 @@
"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 key created successfully": "API密钥创建成功",
"API keys": "API密钥",
"API management": "API管理",
"Are you sure you want to revoke this API key": "确定要撤销此API密钥吗",
"Create API Key": "创建API密钥",
"Custom expiration date": "自定义到期日期",
"Enter a descriptive token name": "输入描述性令牌名称",
"Expiration": "到期",
"Expired": "已过期",
"Expires": "到期",
"I've saved my API key": "我已保存我的API密钥",
"Last use": "上次使用",
"No API keys found": "找不到API密钥",
"No expiration": "无到期",
"Revoke API key": "撤销API密钥",
"Revoked successfully": "撤销成功",
"Select expiration date": "选择到期日期",
"This action cannot be undone. Any applications using this API key will stop working.": "此操作无法撤销。使用此API密钥的任何应用程序将停止工作。",
"Update API key": "更新API密钥",
"Manage API keys for all users in the workspace": "管理工作空间中所有用户的API密钥",
"AI settings": "AI设置",
"AI search": "AI搜索",
"AI Answer": "AI回答",
"Ask AI": "询问AI",
"AI is thinking...": "AI正在思考...",
"Ask a question...": "提问...",
"AI-powered search (Ask AI)": "AI驱动的搜索(询问AI",
"AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI搜索使用向量嵌入提供跨工作空间内容的语义搜索功能。",
"Toggle AI search": "切换AI搜索",
"Sources": "来源",
"Ask AI not available for attachments": "附件不支持询问AI",
"No answer available": "无可用答案",
"Background color": "背景颜色",
"Highlight color": "突出显示颜色",
"Remove color": "移除颜色"
"{{ssoProviderType}} configuration": "{{ssoProviderType}} 配置"
}
@@ -37,18 +37,14 @@ export async function askAi(
let answer = "";
let sources: any[] = [];
let buffer = "";
if (reader) {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
// Keep the last incomplete line in the buffer
buffer = lines.pop() || "";
const chunk = decoder.decode(value);
const lines = chunk.split("\n");
for (const line of lines) {
if (line.startsWith("data: ")) {
@@ -5,7 +5,6 @@ import { v4 as uuidv4 } from "uuid";
import classes from "./code-block.module.css";
import { useTranslation } from "react-i18next";
import { useComputedColorScheme } from "@mantine/core";
import DOMPurify from "dompurify";
interface MermaidViewProps {
props: NodeViewProps;
@@ -38,7 +37,7 @@ export default function MermaidView({ props }: MermaidViewProps) {
.catch((err) => {
if (props.editor.isEditable) {
setPreview(
`<div class="${classes.error}">${t("Mermaid diagram error:")} ${DOMPurify.sanitize(err)}</div>`,
`<div class="${classes.error}">${t("Mermaid diagram error:")} ${err}</div>`,
);
} else {
setPreview(
@@ -87,7 +87,7 @@ export default function DrawioView(props: NodeViewProps) {
};
return (
<NodeViewWrapper data-drag-handle>
<NodeViewWrapper>
<Modal.Root opened={opened} onClose={close} fullScreen>
<Modal.Overlay />
<Modal.Content style={{ overflow: "hidden" }}>
@@ -85,7 +85,7 @@ export default function EmbedView(props: NodeViewProps) {
}
return (
<NodeViewWrapper data-drag-handle>
<NodeViewWrapper>
{embedUrl ? (
<ResizableWrapper
initialHeight={nodeHeight || 480}
@@ -118,7 +118,7 @@ export default function ExcalidrawView(props: NodeViewProps) {
};
return (
<NodeViewWrapper data-drag-handle>
<NodeViewWrapper>
<ReactClearModal
style={{
backgroundColor: "rgba(0, 0, 0, 0.5)",
@@ -16,7 +16,7 @@ export default function ImageView(props: NodeViewProps) {
}, [align]);
return (
<NodeViewWrapper data-drag-handle>
<NodeViewWrapper>
<Image
radius="md"
fit="contain"
@@ -31,7 +31,7 @@ export default function MentionView(props: NodeViewProps) {
});
return (
<NodeViewWrapper style={{ display: "inline" }} data-drag-handle>
<NodeViewWrapper style={{ display: "inline" }}>
{entityType === "user" && (
<Text className={classes.userMention} component="span">
@{label}
@@ -20,7 +20,7 @@ import {
IconCalendar,
IconAppWindow,
IconSitemap,
IconLayoutColumns,
IconPageBreak,
} from "@tabler/icons-react";
import {
CommandProps,
@@ -154,6 +154,19 @@ const CommandGroups: SlashMenuGroupedItemsType = {
command: ({ editor, range }: CommandProps) =>
editor.chain().focus().deleteRange(range).setHorizontalRule().run(),
},
{
title: "Page break",
description: "Insert page break",
searchTerms: ["page break", "hr"],
icon: IconPageBreak,
command: ({ editor, range }: CommandProps) =>
editor
.chain()
.focus()
.deleteRange(range)
.insertContent('<hr data-type="pagebreak" /><p></p>')
.run(),
},
{
title: "Image",
description: "Upload any image from your device.",
@@ -244,51 +257,6 @@ const CommandGroups: SlashMenuGroupedItemsType = {
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
.run(),
},
{
title: "Columns",
description: "Insert 2 columns layout.",
searchTerms: ["columns", "layout", "grid", "side by side"],
icon: IconLayoutColumns,
command: ({ editor, range }: CommandProps) => {
editor
.chain()
.focus()
.deleteRange(range)
.insertContent({
type: "column_container",
content: [
{
type: "column",
attrs: { colWidth: 200 },
content: [
{
type: "paragraph",
},
],
},
{
type: "column",
attrs: { colWidth: 200 },
content: [
{
type: "paragraph",
},
],
},
{
type: "column",
attrs: { colWidth: 200 },
content: [
{
type: "paragraph",
},
],
},
],
})
.run();
},
},
{
title: "Toggle block",
description: "Insert collapsible block.",
@@ -52,7 +52,7 @@ export default function SubpagesView(props: NodeViewProps) {
if (error && !shareId) {
return (
<NodeViewWrapper data-drag-handle>
<NodeViewWrapper>
<Text c="dimmed" size="md" py="md">
{t("Failed to load subpages")}
</Text>
@@ -62,7 +62,7 @@ export default function SubpagesView(props: NodeViewProps) {
if (subpages.length === 0) {
return (
<NodeViewWrapper data-drag-handle>
<NodeViewWrapper>
<div className={classes.container}>
<Text c="dimmed" size="md" py="md">
{t("No subpages")}
@@ -73,7 +73,7 @@ export default function SubpagesView(props: NodeViewProps) {
}
return (
<NodeViewWrapper data-drag-handle>
<NodeViewWrapper>
<div className={classes.container}>
<Stack gap={5}>
{subpages.map((page) => (
@@ -15,7 +15,7 @@ export default function VideoView(props: NodeViewProps) {
}, [align]);
return (
<NodeViewWrapper data-drag-handle>
<NodeViewWrapper>
<video
preload="metadata"
width={width}
@@ -46,7 +46,7 @@ import {
Heading,
Highlight,
UniqueID,
ColumnsExtension,
HorizontalRule,
} from "@docmost/editor-ext";
import {
randomElement,
@@ -109,7 +109,9 @@ export const mainExtensions = [
spellcheck: false,
},
},
horizontalRule: false,
}),
HorizontalRule,
Heading,
UniqueID.configure({
types: ["heading", "paragraph"],
@@ -230,7 +232,6 @@ export const mainExtensions = [
Subpages.configure({
view: SubpagesView,
}),
ColumnsExtension,
MarkdownClipboard.configure({
transformPastedText: true,
}),
@@ -1,123 +0,0 @@
.resize-cursor {
cursor: col-resize;
}
.prosemirror-column-container {
display: flex;
flex-direction: row;
width: calc(100% - 8px);
gap: 12px;
margin: 16px 0;
}
.prosemirror-column-container.has-focus .prosemirror-column,
.prosemirror-column-container:hover .prosemirror-column {
background-color: rgba(100, 106, 115, 0.05);
}
.prosemirror-column-container .prosemirror-column {
position: relative;
border-radius: 8px;
min-width: 50px;
padding: 12px;
background-color: transparent;
transition: background-color 0.2s ease;
}
.prosemirror-column-container
.prosemirror-column
> :not(div.grid-resize-handle):nth-child(1),
.prosemirror-column-container
.prosemirror-column
> div.grid-resize-handle
+ :nth-child(2) {
margin-top: 0;
}
.prosemirror-column-container .prosemirror-column > :nth-last-child(1) {
margin-bottom: 0;
}
.prosemirror-column-container .prosemirror-column .grid-resize-handle {
position: absolute;
right: -7px;
top: 0;
bottom: 0;
width: 2px;
z-index: 20;
background-color: #336df4;
pointer-events: none;
}
.prosemirror-column-container
.prosemirror-column
.grid-resize-handle
.circle-button {
top: -8px;
left: -9px;
width: 12px;
height: 12px;
background-color: #007bff;
border: 4px solid white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
pointer-events: auto;
cursor: pointer;
transition: transform 0.1s ease-in-out;
}
.prosemirror-column-container
.prosemirror-column
.grid-resize-handle
.circle-button:hover {
transform: scale(1.35);
}
.prosemirror-column-container
.prosemirror-column
.grid-resize-handle
.circle-button
.plus {
position: relative;
width: 8px;
height: 8px;
}
.prosemirror-column-container
.prosemirror-column
.grid-resize-handle
.circle-button
.plus::before,
.prosemirror-column-container
.prosemirror-column
.grid-resize-handle
.circle-button
.plus::after {
content: '';
position: absolute;
background-color: white;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.prosemirror-column-container
.prosemirror-column
.grid-resize-handle
.circle-button
.plus::before {
width: 8px;
height: 2px;
}
.prosemirror-column-container
.prosemirror-column
.grid-resize-handle
.circle-button
.plus::after {
width: 24px;
height: 8px;
}
@@ -110,6 +110,14 @@
border-top: 1px solid #68cef8;
}
hr[data-type="pagebreak"] {
border-top: 1px dashed var(--mantine-color-dark-2) !important;
}
.ProseMirror[contenteditable="false"] hr[data-type="pagebreak"] {
display: none !important;
}
.ProseMirror-selectednode {
outline: 2px solid #70cff8;
}
@@ -186,7 +194,6 @@
margin-left: auto;
margin-right: auto;
}
}
.ProseMirror > h1,
@@ -195,13 +202,11 @@
.ProseMirror > h4,
.ProseMirror > h5,
.ProseMirror > h6 {
> .link-btn {
cursor: pointer;
position: relative;
}
> .link-btn > .link-btn-content {
opacity: 0;
position: absolute;
@@ -213,7 +218,7 @@
justify-content: center;
flex-direction: column;
}
&:hover > .link-btn > .link-btn-content {
opacity: 1;
}
@@ -13,4 +13,3 @@
@import "./mention.css";
@import "./ordered-list.css";
@import "./highlight.css";
@import "./column.css";
@@ -20,4 +20,10 @@
.tableWrapper {
overflow: hidden !important;
}
hr[data-type="pagebreak"] {
break-before: always;
page-break-before: always;
visibility: hidden;
}
}
@@ -62,17 +62,14 @@ export default function SpaceSettingsModal({
</Tabs.List>
<Tabs.Panel value="general">
<ScrollArea h={580} scrollbarSize={5} pr={8}>
<div style={{ paddingBottom: "100px"}}>
<SpaceDetails
spaceId={space?.id}
readOnly={spaceAbility.cannot(
SpaceCaslAction.Manage,
SpaceCaslSubject.Settings,
)}
/>
</div>
<ScrollArea h={550} scrollbarSize={4} pr={8}>
<SpaceDetails
spaceId={space?.id}
readOnly={spaceAbility.cannot(
SpaceCaslAction.Manage,
SpaceCaslSubject.Settings,
)}
/>
</ScrollArea>
</Tabs.Panel>
@@ -113,7 +113,7 @@ export default function SpaceMembersList({
return (
<>
<SearchInput onSearch={handleSearch} />
<ScrollArea h={450}>
<ScrollArea h={400}>
<Table.ScrollContainer minWidth={500}>
<Table highlightOnHover verticalSpacing={8}>
<Table.Thead>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "server",
"version": "0.24.1",
"version": "0.23.2",
"description": "",
"author": "",
"private": true,
@@ -35,8 +35,8 @@ import {
Subpages,
Highlight,
UniqueID,
ColumnsExtension,
addUniqueIdsToDoc,
HorizontalRule,
} from '@docmost/editor-ext';
import { generateText, getSchema, JSONContent } from '@tiptap/core';
import { generateHTML, generateJSON } from '../common/helpers/prosemirror/html';
@@ -49,7 +49,9 @@ export const tiptapExtensions = [
StarterKit.configure({
codeBlock: false,
heading: false,
horizontalRule: false,
}),
HorizontalRule,
Heading,
UniqueID.configure({
types: ['heading', 'paragraph'],
@@ -89,7 +91,6 @@ export const tiptapExtensions = [
Embed,
Mention,
Subpages,
ColumnsExtension
] as any;
export function jsonToHtml(tiptapJson: any) {
+2 -2
View File
@@ -69,8 +69,8 @@ export class ShareService {
return await this.shareRepo.insertShare({
key: nanoIdGen().toLowerCase(),
pageId: page.id,
includeSubPages: createShareDto.includeSubPages ?? false,
searchIndexing: createShareDto.searchIndexing ?? false,
includeSubPages: createShareDto.includeSubPages || true,
searchIndexing: createShareDto.searchIndexing || true,
creatorId: authUserId,
spaceId: page.spaceId,
workspaceId,
@@ -117,7 +117,7 @@ export class EnvironmentVariables {
@IsOptional()
@ValidateIf((obj) => obj.AI_EMBEDDING_DIMENSION)
@IsIn(['768', '1024', '1536', '2000'])
@IsIn(['768', '1024', '1536'])
@IsString()
AI_EMBEDDING_DIMENSION: string;
+3 -5
View File
@@ -15,12 +15,10 @@ async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({
ignoreTrailingSlash: true,
ignoreDuplicateSlashes: true,
maxParamLength: 1000,
trustProxy: true,
routerOptions: {
maxParamLength: 1000,
ignoreTrailingSlash: true,
ignoreDuplicateSlashes: true,
},
}),
{
rawBody: true,
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "docmost",
"homepage": "https://docmost.com",
"version": "0.24.1",
"version": "0.23.2",
"private": true,
"scripts": {
"build": "nx run-many -t build",
@@ -38,6 +38,7 @@
"@tiptap/extension-heading": "2.27.1",
"@tiptap/extension-highlight": "2.27.1",
"@tiptap/extension-history": "2.27.1",
"@tiptap/extension-horizontal-rule": "2.27.1",
"@tiptap/extension-image": "2.27.1",
"@tiptap/extension-link": "2.27.1",
"@tiptap/extension-list-item": "2.27.1",
@@ -100,7 +101,6 @@
},
"overrides": {
"jsdom": "25.0.1",
"jsonwebtoken": "9.0.3",
"y-prosemirror": "1.3.7"
},
"neverBuiltDependencies": []
+1 -1
View File
@@ -23,4 +23,4 @@ export * from "./lib/subpages";
export * from "./lib/highlight";
export * from "./lib/heading/heading";
export * from "./lib/unique-id";
export * from "./lib/columns";
export * from "./lib/hr";
-152
View File
@@ -1,152 +0,0 @@
import { EditorState } from '@tiptap/pm/state';
import { Decoration, DecorationSet, EditorView } from '@tiptap/pm/view';
import { gridResizingPluginKey } from './state';
import {
draggedWidth,
findBoundaryPosition,
getColumnInfoAtPos,
updateColumnNodeWidth,
} from './utils';
function updateActiveHandle(view: EditorView, value: number) {
view.dispatch(
view.state.tr.setMeta(gridResizingPluginKey, {
setHandle: value,
}),
);
}
export function handleMouseMove(
view: EditorView,
event: MouseEvent,
handleWidth: number,
): boolean {
const pluginState = gridResizingPluginKey.getState(view.state);
if (!pluginState) return false;
// TODO: limit call
if (pluginState.dragging) return false;
const boundaryPos = findBoundaryPosition(view, event, handleWidth);
if (boundaryPos !== pluginState.activeHandle) {
updateActiveHandle(view, boundaryPos);
}
return false;
}
export function handleMouseLeave(view: EditorView) {
const pluginState = gridResizingPluginKey.getState(view.state);
if (!pluginState) return false;
if (pluginState.activeHandle > -1 && !pluginState.dragging) {
updateActiveHandle(view, -1);
}
return false;
}
export function handleMouseDown(
view: EditorView,
event: MouseEvent,
columnMinWidth: number,
): boolean {
const pluginState = gridResizingPluginKey.getState(view.state);
if (!pluginState) return false;
if (pluginState.activeHandle === -1) return false;
if (pluginState.dragging) return false;
const columnInfo = getColumnInfoAtPos(view, pluginState.activeHandle);
if (!columnInfo) return false;
const { domWidth, $pos, node } = columnInfo;
const nodeAttrs = { ...(node.attrs || {}) };
const nodePos = $pos.before();
view.dispatch(
view.state.tr.setMeta(gridResizingPluginKey, {
setDragging: { startX: event.clientX, startWidth: domWidth },
}),
);
const win = view.dom.ownerDocument.defaultView || window;
const finish = (e: MouseEvent) => {
win.removeEventListener('mouseup', finish);
win.removeEventListener('mousemove', move);
const pluginState = gridResizingPluginKey.getState(view.state);
if (!pluginState?.dragging) return;
const finalWidth = draggedWidth(pluginState.dragging, e, columnMinWidth);
updateColumnNodeWidth(view, nodePos, nodeAttrs, finalWidth);
view.dispatch(
view.state.tr.setMeta(gridResizingPluginKey, {
setDragging: null,
}),
);
};
const move = (e: MouseEvent) => {
if (!e.buttons) {
finish(e);
return;
}
const pluginState = gridResizingPluginKey.getState(view.state);
if (!pluginState?.dragging) return;
const newWidth = draggedWidth(pluginState.dragging, e, columnMinWidth);
updateColumnNodeWidth(view, nodePos, nodeAttrs, newWidth);
};
win.addEventListener('mouseup', finish);
win.addEventListener('mousemove', move);
updateColumnNodeWidth(view, nodePos, nodeAttrs, domWidth);
event.preventDefault();
return true;
}
export function handleGridDecorations(
state: EditorState,
boundaryPos: number,
): DecorationSet {
const decorations = [];
const $pos = state.doc.resolve(boundaryPos);
if ($pos.nodeAfter !== null) {
const widget = document.createElement('div');
widget.className = 'grid-resize-handle';
const circleButton = document.createElement('div');
circleButton.className = 'circle-button';
widget.appendChild(circleButton);
const plusIcon = document.createElement('div');
plusIcon.className = 'plus';
circleButton.appendChild(plusIcon);
decorations.push(Decoration.widget(boundaryPos, widget));
}
return DecorationSet.create(state.doc, decorations);
}
export function handleMouseUp(view: EditorView, event: MouseEvent): boolean {
const div = event.target as HTMLElement;
if (!div) return false;
if (div.className !== 'circle-button' && div.className !== 'plus')
return false;
const column = div.closest('.prosemirror-column');
if (!column) return false;
const boundryPos = view.posAtDOM(column, 0);
if (!boundryPos) return false;
const $pos = view.state.doc.resolve(boundryPos);
const { state } = view;
view.dispatch(
state.tr.insert(
$pos.pos + $pos.parent.nodeSize - 1,
state.schema.nodes.column.create(
{
colWidth: 100,
},
state.schema.nodes.paragraph.create(),
),
),
);
return true;
}
@@ -1,4 +0,0 @@
export * from './schema';
export * from './resize';
export * from './keymap';
export * from './tiptap';
@@ -1,63 +0,0 @@
import { liftTarget, canSplit } from "@tiptap/pm/transform";
import { TextSelection, Command } from "@tiptap/pm/state";
import {
splitBlock,
chainCommands,
newlineInCode,
createParagraphNear,
} from "@tiptap/pm/commands";
import { keymap } from "@tiptap/pm/keymap";
import { ResolvedPos } from "@tiptap/pm/model";
function findParentColumn($pos: ResolvedPos) {
for (let depth = $pos.depth; depth > 0; depth--) {
const node = $pos.node(depth);
if (node.type.name === "column") {
return { node, depth };
}
}
return null;
}
export const liftEmptyBlock: Command = (state, dispatch) => {
const { $cursor } = state.selection as TextSelection;
if (!$cursor || $cursor.parent.content.size) return false;
if ("column" === $cursor.node($cursor.depth - 1).type.name) return false;
if ($cursor.depth > 1 && $cursor.after() != $cursor.end(-1)) {
const before = $cursor.before();
if (canSplit(state.doc, before)) {
if (dispatch) dispatch(state.tr.split(before).scrollIntoView());
return true;
}
}
const range = $cursor.blockRange(),
target = range && liftTarget(range);
if (target == null) return false;
if (dispatch) dispatch(state.tr.lift(range!, target).scrollIntoView());
return true;
};
export const columnsKeymap = keymap({
Enter: chainCommands(
newlineInCode,
createParagraphNear,
liftEmptyBlock,
splitBlock,
),
"Mod-a": (state, dispatch, view) => {
const { selection } = state;
const { $from } = selection;
const found = findParentColumn($from);
if (found) {
const { depth } = found;
const start = $from.start(depth);
const end = $from.end(depth);
const tr = state.tr.setSelection(
TextSelection.create(state.doc, start, end),
);
if (dispatch) dispatch(tr);
return true;
}
return false;
},
} as { [key: string]: Command });
@@ -1,62 +0,0 @@
import { Plugin } from '@tiptap/pm/state';
import {
handleGridDecorations,
handleMouseDown,
handleMouseLeave,
handleMouseMove,
handleMouseUp,
} from './dom';
import { GridResizeState, gridResizingPluginKey } from './state';
export function gridResizingPlugin(options?: {
handleWidth?: number;
columnMinWidth?: number;
}) {
const handleWidth = options?.handleWidth ?? 2;
const columnMinWidth = options?.columnMinWidth ?? 50;
return new Plugin<GridResizeState>({
key: gridResizingPluginKey,
state: {
init: () => new GridResizeState(-1, false),
apply: (tr, prev) => {
return prev.apply(tr);
},
},
props: {
attributes: (state): Record<string, string> => {
const pluginState = gridResizingPluginKey.getState(state);
if (pluginState && pluginState.activeHandle > -1) {
return { class: 'resize-cursor' };
}
return {};
},
// The main event handlers
handleDOMEvents: {
mousemove: (view, event: MouseEvent) => {
return handleMouseMove(view, event, handleWidth);
},
mouseleave: (view) => {
return handleMouseLeave(view);
},
mousedown: (view, event: MouseEvent) => {
return handleMouseDown(view, event, columnMinWidth);
},
mouseup: (view, event: MouseEvent) => {
return handleMouseUp(view, event);
},
},
decorations: (state) => {
const pluginState = gridResizingPluginKey.getState(state);
if (!pluginState) return null;
if (pluginState.activeHandle === -1) return null;
return handleGridDecorations(state, pluginState.activeHandle);
},
},
});
}
@@ -1,51 +0,0 @@
import { NodeSpec } from '@tiptap/pm/model';
export type ColumnNodes = Record<'column' | 'column_container', NodeSpec>;
export function columnNodes(): ColumnNodes {
return {
column: {
group: 'block',
content: 'block+',
attrs: {
colWidth: { default: 200 },
},
parseDOM: [
{
tag: 'div.prosemirror-column',
getAttrs(dom) {
if (!(dom instanceof HTMLElement)) return false;
const width = dom.style.width.replace('px', '') || 200;
return {
colWidth: width,
};
},
},
],
toDOM(node) {
const { colWidth } = node.attrs;
const style = colWidth ? `width: ${colWidth}px;` : '';
return [
'div',
{
class: 'prosemirror-column',
style,
},
0,
];
},
},
column_container: {
group: 'block',
content: 'column+',
parseDOM: [
{
tag: 'div.prosemirror-column-container',
},
],
toDOM() {
return ['div', { class: 'prosemirror-column-container' }, 0];
},
},
};
}
@@ -1,33 +0,0 @@
import { PluginKey, Transaction } from '@tiptap/pm/state';
export const gridResizingPluginKey = new PluginKey<GridResizeState>(
'gridResizingPlugin',
);
export type Dragging = {
startX: number;
startWidth: number;
};
export class GridResizeState {
constructor(
public activeHandle: number,
public dragging: Dragging | false,
) {}
apply(tr: Transaction): GridResizeState {
const action = tr.getMeta(gridResizingPluginKey);
if (!action) return this;
if (typeof action.setHandle === 'number') {
return new GridResizeState(action.setHandle, false);
}
if (action.setDragging !== undefined) {
return new GridResizeState(this.activeHandle, action.setDragging);
}
if (this.activeHandle > -1 && tr.docChanged) {
// remap when doc changes
}
return this;
}
}
@@ -1,84 +0,0 @@
import { Node, mergeAttributes, Extension } from '@tiptap/core';
import { columnsKeymap } from './keymap';
import { gridResizingPlugin } from './resize';
const Column = Node.create({
name: 'column',
group: 'block',
content: 'block+',
addAttributes() {
return {
colWidth: {
default: 200,
parseHTML: (element) => {
const width = (element as HTMLElement).style.width.replace('px', '');
return Number(width) || 200;
},
renderHTML: (attributes) => {
const style = attributes.colWidth
? `width: ${attributes.colWidth}px;`
: '';
return { style };
},
},
};
},
parseHTML() {
return [
{
tag: 'div.prosemirror-column',
},
];
},
renderHTML({ HTMLAttributes }) {
return [
'div',
mergeAttributes(HTMLAttributes, { class: 'prosemirror-column' }),
0,
];
},
});
const ColumnContainer = Node.create({
name: 'column_container',
group: 'block',
content: 'column+',
parseHTML() {
return [
{
tag: 'div.prosemirror-column-container',
},
];
},
renderHTML({ HTMLAttributes }) {
return [
'div',
mergeAttributes(HTMLAttributes, {
class: 'prosemirror-column-container',
}),
0,
];
},
});
export const ColumnsExtension = Extension.create({
name: 'columns',
addExtensions() {
return [Column, ColumnContainer];
},
addProseMirrorPlugins() {
return [
gridResizingPlugin({ handleWidth: 2, columnMinWidth: 50 }),
columnsKeymap,
];
},
});
@@ -1,75 +0,0 @@
import { EditorView } from '@tiptap/pm/view';
import { type Dragging } from './state';
export function findBoundaryPosition(
view: EditorView,
event: MouseEvent,
handleWidth: number,
): number {
const gridDOM = event
.composedPath()
.find((el) =>
(el as HTMLElement).classList?.contains('prosemirror-column-container'),
) as HTMLElement | undefined;
if (!gridDOM) return -1;
const children = Array.from(gridDOM.children).filter((el) =>
el.classList.contains('prosemirror-column'),
);
for (let i = 0; i < children.length; i++) {
const colEl = children[i] as HTMLElement;
const rect = colEl.getBoundingClientRect();
if (
event.clientX >= rect.right - handleWidth - 2 &&
event.clientX <= rect.right + 10 + handleWidth
) {
const pos = view.posAtDOM(colEl, 0);
if (pos != null) {
return pos;
}
}
}
return -1;
}
export function draggedWidth(
dragging: Dragging,
event: MouseEvent,
minWidth: number,
): number {
const offset = event.clientX - dragging.startX;
return Math.max(minWidth, dragging.startWidth + offset);
}
export function updateColumnNodeWidth(
view: EditorView,
pos: number,
attrs: Record<string, string>,
width: number,
) {
view.dispatch(
view.state.tr.setNodeMarkup(pos, undefined, {
...attrs,
colWidth: width - 12 * 2,
}),
);
}
export function getColumnInfoAtPos(view: EditorView, boundaryPos: number) {
const $pos = view.state.doc.resolve(boundaryPos);
const node = $pos.parent;
if (!node || node.type.name !== 'column') return null;
const dom = view.domAtPos($pos.pos);
if (!dom.node) return null;
const columnEl =
dom.node instanceof HTMLElement
? dom.node
: (dom.node.childNodes[dom.offset] as HTMLElement);
const domWidth = columnEl.offsetWidth;
return { $pos, node, columnEl, domWidth };
}
+21
View File
@@ -0,0 +1,21 @@
import { HorizontalRule as TiptapHorizontalRule } from "@tiptap/extension-horizontal-rule";
export type HorizontalRuleType = "pageBreak";
export const HorizontalRule = TiptapHorizontalRule.extend({
addAttributes() {
return {
type: {
default: null,
parseHTML: (element) => element.getAttribute("data-type"),
renderHTML: (attributes) => {
if (attributes.type) {
return {
"data-type": attributes.type,
};
}
},
},
};
},
});
-1
View File
@@ -165,7 +165,6 @@ export const Mention = Node.create<MentionOptions>({
inline: true,
selectable: true,
atom: true,
draggable: true,
addAttributes() {
return {
@@ -28,7 +28,7 @@ export const Subpages = Node.create<SubpagesOptions>({
group: "block",
atom: true,
draggable: true,
draggable: false,
parseHTML() {
return [
+2 -1
View File
@@ -3,6 +3,7 @@ import { Editor, findParentNode, isTextSelection } from "@tiptap/core";
import { Selection, Transaction } from "@tiptap/pm/state";
import { CellSelection, TableMap } from "@tiptap/pm/tables";
import { Node, ResolvedPos } from "@tiptap/pm/model";
import Table from "@tiptap/extension-table";
import { sanitizeUrl as braintreeSanitizeUrl } from "@braintree/sanitize-url";
import { customAlphabet } from "nanoid";
@@ -288,7 +289,7 @@ export const isColumnGripSelected = ({
const node = nodeDOM || domAtPos;
if (
!editor.isActive("table") ||
!editor.isActive(Table.name) ||
!node ||
isTableSelected(state.selection)
) {
+18 -16
View File
@@ -6,7 +6,6 @@ settings:
overrides:
jsdom: 25.0.1
jsonwebtoken: 9.0.3
y-prosemirror: 1.3.7
patchedDependencies:
@@ -78,6 +77,9 @@ importers:
'@tiptap/extension-history':
specifier: 2.27.1
version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)
'@tiptap/extension-horizontal-rule':
specifier: 2.27.1
version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)
'@tiptap/extension-image':
specifier: 2.27.1
version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))
@@ -558,8 +560,8 @@ importers:
specifier: ^5.4.1
version: 5.4.1
jsonwebtoken:
specifier: 9.0.3
version: 9.0.3
specifier: ^9.0.2
version: 9.0.2
kysely:
specifier: ^0.28.2
version: 0.28.2
@@ -7405,8 +7407,8 @@ packages:
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
jsonwebtoken@9.0.3:
resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==}
jsonwebtoken@9.0.2:
resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==}
engines: {node: '>=12', npm: '>=6'}
jsx-ast-utils@3.3.5:
@@ -7416,11 +7418,11 @@ packages:
jszip@3.10.1:
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
jwa@2.0.1:
resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==}
jwa@1.4.1:
resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==}
jws@4.0.1:
resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
jws@3.2.2:
resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
jwt-decode@4.0.0:
resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==}
@@ -13414,7 +13416,7 @@ snapshots:
dependencies:
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@types/jsonwebtoken': 9.0.7
jsonwebtoken: 9.0.3
jsonwebtoken: 9.0.2
'@nestjs/mapped-types@2.1.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)':
dependencies:
@@ -18471,9 +18473,9 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
jsonwebtoken@9.0.3:
jsonwebtoken@9.0.2:
dependencies:
jws: 4.0.1
jws: 3.2.2
lodash.includes: 4.3.0
lodash.isboolean: 3.0.3
lodash.isinteger: 4.0.4
@@ -18498,15 +18500,15 @@ snapshots:
readable-stream: 2.3.8
setimmediate: 1.0.5
jwa@2.0.1:
jwa@1.4.1:
dependencies:
buffer-equal-constant-time: 1.0.1
ecdsa-sig-formatter: 1.0.11
safe-buffer: 5.2.1
jws@4.0.1:
jws@3.2.2:
dependencies:
jwa: 2.0.1
jwa: 1.4.1
safe-buffer: 5.2.1
jwt-decode@4.0.0: {}
@@ -19479,7 +19481,7 @@ snapshots:
passport-jwt@4.0.1:
dependencies:
jsonwebtoken: 9.0.3
jsonwebtoken: 9.0.2
passport-strategy: 1.0.0
passport-oauth2@1.8.0: