mirror of
https://github.com/docmost/docmost.git
synced 2025-11-19 06:21:10 +10:00
Merge branch 'main' into ai-vector
This commit is contained in:
@ -2,10 +2,18 @@
|
|||||||
<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="/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32x32.png" />
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no" />
|
||||||
<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>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.22.2",
|
"version": "0.23.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"katex": "0.16.22",
|
"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.6.0",
|
"mermaid": "^11.11.0",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"posthog-js": "^1.255.1",
|
"posthog-js": "^1.255.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 562 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.0 KiB |
BIN
apps/client/public/icons/app-icon-192x192.png
Normal file
BIN
apps/client/public/icons/app-icon-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
BIN
apps/client/public/icons/app-icon-512x512.png
Normal file
BIN
apps/client/public/icons/app-icon-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
apps/client/public/icons/favicon-16x16.png
Normal file
BIN
apps/client/public/icons/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 509 B |
BIN
apps/client/public/icons/favicon-32x32.png
Normal file
BIN
apps/client/public/icons/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 881 B |
@ -53,6 +53,7 @@
|
|||||||
"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 für das Vertriebsteam zur Zusammenarbeit",
|
"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",
|
||||||
@ -495,5 +496,36 @@
|
|||||||
"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": "Fehler beim Laden von Unterseiten",
|
||||||
|
"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 deinen Bereichen suchen",
|
||||||
|
"Type": "Art",
|
||||||
|
"Enterprise": "Unternehmen",
|
||||||
|
"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 über SSO registrieren.",
|
||||||
|
"Enter valid domain names separated by comma or space": "Geben Sie gültige Domainnamen ein, durch Kommas oder Leerzeichen getrennt",
|
||||||
|
"Enforce two-factor authentication": "Erzwingen der Zwei-Faktor-Authentifizierung",
|
||||||
|
"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": "Umschalten der MFA-Erzwingung",
|
||||||
|
"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": "Gruppensynchronisation",
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
"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",
|
||||||
@ -500,5 +501,31 @@
|
|||||||
"Failed to load subpages": "Failed to load subpages",
|
"Failed to load subpages": "Failed to load subpages",
|
||||||
"No subpages": "No subpages",
|
"No subpages": "No subpages",
|
||||||
"Subpages (Child pages)": "Subpages (Child pages)",
|
"Subpages (Child pages)": "Subpages (Child pages)",
|
||||||
"List all subpages of the current page": "List all subpages of the current page"
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
"e.g Space for product team": "ej: Espacio para el equipo de producto",
|
"e.g Space for product team": "ej: Espacio para el equipo de producto",
|
||||||
"e.g Space for sales team to collaborate": "ej: Espacio para que el equipo de ventas colabore",
|
"e.g Space for sales team to collaborate": "ej: Espacio para que el equipo de ventas colabore",
|
||||||
"Edit": "Editar",
|
"Edit": "Editar",
|
||||||
|
"Read": "Leer",
|
||||||
"Edit group": "Editar grupo",
|
"Edit group": "Editar grupo",
|
||||||
"Email": "Correo electrónico",
|
"Email": "Correo electrónico",
|
||||||
"Enter a strong password": "Introduce una contraseña fuerte",
|
"Enter a strong password": "Introduce una contraseña fuerte",
|
||||||
@ -495,5 +496,36 @@
|
|||||||
"Page restored successfully": "Página restaurada con éxito",
|
"Page restored successfully": "Página restaurada con éxito",
|
||||||
"Deleted by": "Eliminado por",
|
"Deleted by": "Eliminado por",
|
||||||
"Deleted at": "Eliminado en",
|
"Deleted at": "Eliminado en",
|
||||||
"Preview": "Vista previa"
|
"Preview": "Vista previa",
|
||||||
|
"Subpages": "Subpáginas",
|
||||||
|
"Failed to load subpages": "Error al cargar subpáginas",
|
||||||
|
"No subpages": "Sin subpáginas",
|
||||||
|
"Subpages (Child pages)": "Subpáginas (Páginas hijas)",
|
||||||
|
"List all subpages of the current page": "Listar todas las subpáginas de la página actual",
|
||||||
|
"Attachments": "Adjuntos",
|
||||||
|
"All spaces": "Todos los espacios",
|
||||||
|
"Unknown": "Desconocido",
|
||||||
|
"Find a space": "Encontrar un espacio",
|
||||||
|
"Search in all your spaces": "Buscar en todos tus espacios",
|
||||||
|
"Type": "Tipo",
|
||||||
|
"Enterprise": "Empresa",
|
||||||
|
"Download attachment": "Descargar adjunto",
|
||||||
|
"Allowed email domains": "Dominios de correo electrónico permitidos",
|
||||||
|
"Only users with email addresses from these domains can signup via SSO.": "Solo los usuarios con direcciones de correo electrónico de estos dominios pueden registrarse a través de SSO.",
|
||||||
|
"Enter valid domain names separated by comma or space": "Introduce nombres de dominio válidos separados por coma o espacio",
|
||||||
|
"Enforce two-factor authentication": "Aplicar autenticación de dos factores",
|
||||||
|
"Once enforced, all members must enable two-factor authentication to access the workspace.": "Una vez aplicada, todos los miembros deben habilitar la autenticación de dos factores para acceder al espacio de trabajo.",
|
||||||
|
"Toggle MFA enforcement": "Alternar la aplicación de MFA",
|
||||||
|
"Display name": "Nombre para mostrar",
|
||||||
|
"Allow signup": "Permitir registro",
|
||||||
|
"Enabled": "Habilitado",
|
||||||
|
"Advanced Settings": "Configuración avanzada",
|
||||||
|
"Enable TLS/SSL": "Habilitar TLS/SSL",
|
||||||
|
"Use secure connection to LDAP server": "Usar conexión segura al servidor LDAP",
|
||||||
|
"Group sync": "Sincronización de grupos",
|
||||||
|
"No SSO providers found.": "No se encontraron proveedores de SSO.",
|
||||||
|
"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}}"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
"e.g Space for product team": "par ex. Espace pour l'équipe produit",
|
"e.g Space for product team": "par ex. Espace pour l'équipe produit",
|
||||||
"e.g Space for sales team to collaborate": "par ex. Espace pour l'équipe de vente pour collaborer",
|
"e.g Space for sales team to collaborate": "par ex. Espace pour l'équipe de vente pour collaborer",
|
||||||
"Edit": "Modifier",
|
"Edit": "Modifier",
|
||||||
|
"Read": "Lire",
|
||||||
"Edit group": "Modifier groupe",
|
"Edit group": "Modifier groupe",
|
||||||
"Email": "Email",
|
"Email": "Email",
|
||||||
"Enter a strong password": "Entrez un mot de passe fort",
|
"Enter a strong password": "Entrez un mot de passe fort",
|
||||||
@ -495,5 +496,36 @@
|
|||||||
"Page restored successfully": "Page restaurée avec succès",
|
"Page restored successfully": "Page restaurée avec succès",
|
||||||
"Deleted by": "Supprimé par",
|
"Deleted by": "Supprimé par",
|
||||||
"Deleted at": "Supprimé à",
|
"Deleted at": "Supprimé à",
|
||||||
"Preview": "Aperçu"
|
"Preview": "Aperçu",
|
||||||
|
"Subpages": "Sous-pages",
|
||||||
|
"Failed to load subpages": "Échec du chargement des sous-pages",
|
||||||
|
"No subpages": "Pas de sous-pages",
|
||||||
|
"Subpages (Child pages)": "Sous-pages (Pages enfants)",
|
||||||
|
"List all subpages of the current page": "Lister toutes les sous-pages de la page actuelle",
|
||||||
|
"Attachments": "Pièces jointes",
|
||||||
|
"All spaces": "Tous les espaces",
|
||||||
|
"Unknown": "Inconnu",
|
||||||
|
"Find a space": "Trouver un espace",
|
||||||
|
"Search in all your spaces": "Rechercher dans tous vos espaces",
|
||||||
|
"Type": "Type",
|
||||||
|
"Enterprise": "Entreprise",
|
||||||
|
"Download attachment": "Télécharger la pièce jointe",
|
||||||
|
"Allowed email domains": "Domaines de messagerie autorisés",
|
||||||
|
"Only users with email addresses from these domains can signup via SSO.": "Seuls les utilisateurs possédant des adresses e-mail provenant de ces domaines peuvent s'inscrire via SSO.",
|
||||||
|
"Enter valid domain names separated by comma or space": "Entrez des noms de domaine valides séparés par une virgule ou un espace",
|
||||||
|
"Enforce two-factor authentication": "Imposer l'authentification à deux facteurs",
|
||||||
|
"Once enforced, all members must enable two-factor authentication to access the workspace.": "Une fois appliquée, tous les membres doivent activer l'authentification à deux facteurs pour accéder à l'espace de travail.",
|
||||||
|
"Toggle MFA enforcement": "Basculer l'application de l'AMF",
|
||||||
|
"Display name": "Nom d'affichage",
|
||||||
|
"Allow signup": "Autoriser l'inscription",
|
||||||
|
"Enabled": "Activé",
|
||||||
|
"Advanced Settings": "Paramètres avancés",
|
||||||
|
"Enable TLS/SSL": "Activer TLS/SSL",
|
||||||
|
"Use secure connection to LDAP server": "Utiliser une connexion sécurisée au serveur LDAP",
|
||||||
|
"Group sync": "Synchronisation de groupe",
|
||||||
|
"No SSO providers found.": "Aucun fournisseur SSO trouvé.",
|
||||||
|
"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}}"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
"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 di vendita",
|
"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",
|
||||||
@ -495,5 +496,36 @@
|
|||||||
"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": "Impresa",
|
||||||
|
"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 provenienti da questi domini possono registrarsi tramite SSO.",
|
||||||
|
"Enter valid domain names separated by comma or space": "Inserisci nomi di dominio validi separati da virgole o spazi",
|
||||||
|
"Enforce two-factor authentication": "Imponi 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 l'applicazione MFA",
|
||||||
|
"Display name": "Nome visualizzato",
|
||||||
|
"Allow signup": "Consenti iscrizione",
|
||||||
|
"Enabled": "Abilitato",
|
||||||
|
"Advanced Settings": "Impostazioni avanzate",
|
||||||
|
"Enable TLS/SSL": "Abilita TLS/SSL",
|
||||||
|
"Use secure connection to LDAP server": "Usa 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}}"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
"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": "強力なパスワードを入力してください",
|
||||||
@ -495,5 +496,36 @@
|
|||||||
"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}}の構成"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
"e.g Space for product team": "예: 제품 팀을 위한 Space",
|
"e.g Space for product team": "예: 제품 팀을 위한 Space",
|
||||||
"e.g Space for sales team to collaborate": "예: 영업 팀의 Space",
|
"e.g Space for sales team to collaborate": "예: 영업 팀의 Space",
|
||||||
"Edit": "편집",
|
"Edit": "편집",
|
||||||
|
"Read": "읽기",
|
||||||
"Edit group": "팀 편집",
|
"Edit group": "팀 편집",
|
||||||
"Email": "이메일",
|
"Email": "이메일",
|
||||||
"Enter a strong password": "강력한 비밀번호를 입력하세요",
|
"Enter a strong password": "강력한 비밀번호를 입력하세요",
|
||||||
@ -495,5 +496,36 @@
|
|||||||
"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}} 구성"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
"e.g Space for product team": "bijv. Ruimte voor productteam",
|
"e.g Space for product team": "bijv. Ruimte voor productteam",
|
||||||
"e.g Space for sales team to collaborate": "bijv. Ruimte voor verkoopteam om samen te werken",
|
"e.g Space for sales team to collaborate": "bijv. Ruimte voor verkoopteam om samen te werken",
|
||||||
"Edit": "Bewerken",
|
"Edit": "Bewerken",
|
||||||
|
"Read": "Lezen",
|
||||||
"Edit group": "Groep bewerken",
|
"Edit group": "Groep bewerken",
|
||||||
"Email": "E-mailadres",
|
"Email": "E-mailadres",
|
||||||
"Enter a strong password": "Voer een sterk wachtwoord in",
|
"Enter a strong password": "Voer een sterk wachtwoord in",
|
||||||
@ -495,5 +496,36 @@
|
|||||||
"Page restored successfully": "Pagina succesvol hersteld",
|
"Page restored successfully": "Pagina succesvol hersteld",
|
||||||
"Deleted by": "Verwijderd door",
|
"Deleted by": "Verwijderd door",
|
||||||
"Deleted at": "Verwijderd op",
|
"Deleted at": "Verwijderd op",
|
||||||
"Preview": "Voorbeeld"
|
"Preview": "Voorbeeld",
|
||||||
|
"Subpages": "Subpagina's",
|
||||||
|
"Failed to load subpages": "Laden van subpagina's mislukt",
|
||||||
|
"No subpages": "Geen subpagina's",
|
||||||
|
"Subpages (Child pages)": "Subpagina's (Kindpagina's)",
|
||||||
|
"List all subpages of the current page": "Lijst van alle subpagina's van de huidige pagina",
|
||||||
|
"Attachments": "Bijlagen",
|
||||||
|
"All spaces": "Alle ruimtes",
|
||||||
|
"Unknown": "Onbekend",
|
||||||
|
"Find a space": "Vind een ruimte",
|
||||||
|
"Search in all your spaces": "Zoek in al je ruimtes",
|
||||||
|
"Type": "Type",
|
||||||
|
"Enterprise": "Onderneming",
|
||||||
|
"Download attachment": "Bijlage downloaden",
|
||||||
|
"Allowed email domains": "Toegestane e-maildomeinen",
|
||||||
|
"Only users with email addresses from these domains can signup via SSO.": "Alleen gebruikers met e-mailadressen van deze domeinen kunnen zich aanmelden via SSO.",
|
||||||
|
"Enter valid domain names separated by comma or space": "Voer geldige domeinnamen in, gescheiden door komma of spatie",
|
||||||
|
"Enforce two-factor authentication": "Handhaaf tweefactorauthenticatie",
|
||||||
|
"Once enforced, all members must enable two-factor authentication to access the workspace.": "Na handhaving moeten alle leden tweefactorauthenticatie inschakelen om toegang te krijgen tot de werkomgeving.",
|
||||||
|
"Toggle MFA enforcement": "Schakel MFA-handhaving in of uit",
|
||||||
|
"Display name": "Weergavenaam",
|
||||||
|
"Allow signup": "Aanmelden toestaan",
|
||||||
|
"Enabled": "Ingeschakeld",
|
||||||
|
"Advanced Settings": "Geavanceerde instellingen",
|
||||||
|
"Enable TLS/SSL": "TLS/SSL inschakelen",
|
||||||
|
"Use secure connection to LDAP server": "Gebruik een beveiligde verbinding met de LDAP-server",
|
||||||
|
"Group sync": "Groepssynchronisatie",
|
||||||
|
"No SSO providers found.": "Geen SSO-providers gevonden.",
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
"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",
|
||||||
@ -495,5 +496,36 @@
|
|||||||
"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 subpáginas",
|
||||||
|
"No subpages": "Sem subpáginas",
|
||||||
|
"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": "Empresa",
|
||||||
|
"Download attachment": "Baixar anexo",
|
||||||
|
"Allowed email domains": "Domínios de email permitidos",
|
||||||
|
"Only users with email addresses from these domains can signup via SSO.": "Apenas usuários com endereços de email desses domínios podem se inscrever 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": "Impor 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 imposição de MFA",
|
||||||
|
"Display name": "Nome de exibição",
|
||||||
|
"Allow signup": "Permitir inscrição",
|
||||||
|
"Enabled": "Habilitado",
|
||||||
|
"Advanced Settings": "Configurações Avançadas",
|
||||||
|
"Enable TLS/SSL": "Habilitar TLS/SSL",
|
||||||
|
"Use secure connection to LDAP server": "Usar conexão segura com o servidor LDAP",
|
||||||
|
"Group sync": "Sincronização de grupo",
|
||||||
|
"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}}"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
"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": "Введите надёжный пароль",
|
||||||
@ -495,5 +496,36 @@
|
|||||||
"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}}"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
"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": "Введіть надійний пароль",
|
||||||
@ -495,5 +496,36 @@
|
|||||||
"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}}"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
"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": "输入一个强密码",
|
||||||
@ -495,5 +496,36 @@
|
|||||||
"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": "切换多因素认证实施",
|
||||||
|
"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}} 配置"
|
||||||
}
|
}
|
||||||
|
|||||||
30
apps/client/public/manifest.json
Normal file
30
apps/client/public/manifest.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -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"
|
||||||
>
|
>
|
||||||
v{APP_VERSION}
|
{appVersion?.currentVersion && <>v{appVersion?.currentVersion}</>}
|
||||||
</Text>
|
</Text>
|
||||||
</Indicator>
|
</Indicator>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
47
apps/client/src/components/ui/responsive-settings-row.tsx
Normal file
47
apps/client/src/components/ui/responsive-settings-row.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ import { MfaDisableModal } from "@/ee/mfa";
|
|||||||
import { MfaBackupCodesModal } from "@/ee/mfa";
|
import { MfaBackupCodesModal } from "@/ee/mfa";
|
||||||
import { isCloud } from "@/lib/config.ts";
|
import { isCloud } from "@/lib/config.ts";
|
||||||
import useLicense from "@/ee/hooks/use-license.tsx";
|
import useLicense from "@/ee/hooks/use-license.tsx";
|
||||||
|
import { ResponsiveSettingsRow, ResponsiveSettingsContent, ResponsiveSettingsControl } from "@/components/ui/responsive-settings-row";
|
||||||
|
|
||||||
export function MfaSettings() {
|
export function MfaSettings() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -53,8 +54,8 @@ export function MfaSettings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
<ResponsiveSettingsRow>
|
||||||
<div style={{ minWidth: 0, flex: 1 }}>
|
<ResponsiveSettingsContent>
|
||||||
<Text size="md">{t("2-step verification")}</Text>
|
<Text size="md">{t("2-step verification")}</Text>
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
{!isMfaEnabled
|
{!isMfaEnabled
|
||||||
@ -63,8 +64,9 @@ export function MfaSettings() {
|
|||||||
)
|
)
|
||||||
: t("Two-factor authentication is active on your account.")}
|
: t("Two-factor authentication is active on your account.")}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</ResponsiveSettingsContent>
|
||||||
|
|
||||||
|
<ResponsiveSettingsControl>
|
||||||
{!isMfaEnabled ? (
|
{!isMfaEnabled ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={t("Available in enterprise edition")}
|
label={t("Available in enterprise edition")}
|
||||||
@ -100,7 +102,8 @@ export function MfaSettings() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</ResponsiveSettingsControl>
|
||||||
|
</ResponsiveSettingsRow>
|
||||||
|
|
||||||
<MfaSetupModal
|
<MfaSetupModal
|
||||||
opened={setupModalOpen}
|
opened={setupModalOpen}
|
||||||
|
|||||||
@ -55,9 +55,11 @@ export default function AllowedDomains() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<Text size="md">Allowed email domains</Text>
|
<Text size="md">{t("Allowed email domains")}</Text>
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
Only users with email addresses from these domains can signup via SSO.
|
{t(
|
||||||
|
"Only users with email addresses from these domains can signup via SSO.",
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm, zodResolver } from "@mantine/form";
|
import { useForm } from "@mantine/form";
|
||||||
|
import { zodResolver } from "mantine-form-zod-resolver";
|
||||||
import { Box, Button, Group, Stack, Switch, TextInput } from "@mantine/core";
|
import { Box, Button, Group, Stack, Switch, TextInput } from "@mantine/core";
|
||||||
import classes from "@/ee/security/components/sso.module.css";
|
import classes from "@/ee/security/components/sso.module.css";
|
||||||
import { IAuthProvider } from "@/ee/security/types/security.types.ts";
|
import { IAuthProvider } from "@/ee/security/types/security.types.ts";
|
||||||
|
|||||||
@ -110,7 +110,7 @@ export function SsoLDAPForm({ provider, onClose }: SsoFormProps) {
|
|||||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Display name"
|
label={t("Display name")}
|
||||||
placeholder="e.g Company LDAP"
|
placeholder="e.g Company LDAP"
|
||||||
data-autofocus
|
data-autofocus
|
||||||
{...form.getInputProps("name")}
|
{...form.getInputProps("name")}
|
||||||
@ -155,7 +155,7 @@ export function SsoLDAPForm({ provider, onClose }: SsoFormProps) {
|
|||||||
<Accordion variant="separated">
|
<Accordion variant="separated">
|
||||||
<Accordion.Item value="advanced">
|
<Accordion.Item value="advanced">
|
||||||
<Accordion.Control icon={<IconInfoCircle size={20} />}>
|
<Accordion.Control icon={<IconInfoCircle size={20} />}>
|
||||||
Advanced Settings
|
{t("Advanced Settings")}
|
||||||
</Accordion.Control>
|
</Accordion.Control>
|
||||||
<Accordion.Panel>
|
<Accordion.Panel>
|
||||||
<Stack>
|
<Stack>
|
||||||
|
|||||||
@ -83,7 +83,7 @@ export function SsoOIDCForm({ provider, onClose }: SsoFormProps) {
|
|||||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Display name"
|
label={t("Display name")}
|
||||||
placeholder="e.g Google SSO"
|
placeholder="e.g Google SSO"
|
||||||
data-autofocus
|
data-autofocus
|
||||||
{...form.getInputProps("name")}
|
{...form.getInputProps("name")}
|
||||||
|
|||||||
@ -69,7 +69,7 @@ export default function SsoProviderList() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card shadow="sm" radius="sm">
|
<Card shadow="sm" radius="sm">
|
||||||
<Table.ScrollContainer minWidth={500}>
|
<Table.ScrollContainer minWidth={600}>
|
||||||
<Table verticalSpacing="sm">
|
<Table verticalSpacing="sm">
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
@ -104,7 +104,7 @@ export default function SsoProviderList() {
|
|||||||
</Group>
|
</Group>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Badge color={"gray"} variant="light">
|
<Badge color={"gray"} variant="light" style={{ whiteSpace: "nowrap" }}>
|
||||||
{provider.type.toUpperCase()}
|
{provider.type.toUpperCase()}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
@ -133,6 +133,7 @@ export default function SsoProviderList() {
|
|||||||
)}
|
)}
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
|
<Group gap="xs" wrap="nowrap">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
color="gray"
|
color="gray"
|
||||||
@ -168,6 +169,7 @@ export default function SsoProviderList() {
|
|||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu.Dropdown>
|
</Menu.Dropdown>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
</Group>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { SSO_PROVIDER } from "@/ee/security/contants.ts";
|
|||||||
import { SsoOIDCForm } from "@/ee/security/components/sso-oidc-form.tsx";
|
import { SsoOIDCForm } from "@/ee/security/components/sso-oidc-form.tsx";
|
||||||
import { SsoGoogleForm } from "@/ee/security/components/sso-google-form.tsx";
|
import { SsoGoogleForm } from "@/ee/security/components/sso-google-form.tsx";
|
||||||
import { SsoLDAPForm } from "@/ee/security/components/sso-ldap-form.tsx";
|
import { SsoLDAPForm } from "@/ee/security/components/sso-ldap-form.tsx";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface SsoModalProps {
|
interface SsoModalProps {
|
||||||
opened: boolean;
|
opened: boolean;
|
||||||
@ -18,6 +19,8 @@ export default function SsoProviderModal({
|
|||||||
onClose,
|
onClose,
|
||||||
provider,
|
provider,
|
||||||
}: SsoModalProps) {
|
}: SsoModalProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -25,7 +28,9 @@ export default function SsoProviderModal({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
opened={opened}
|
opened={opened}
|
||||||
title={`${provider.type.toUpperCase()} Configuration`}
|
title={t("{{ssoProviderType}} configuration", {
|
||||||
|
ssoProviderType: provider.type.toUpperCase(),
|
||||||
|
})}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
{provider.type === SSO_PROVIDER.SAML && (
|
{provider.type === SSO_PROVIDER.SAML && (
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm, zodResolver } from "@mantine/form";
|
import { useForm } from "@mantine/form";
|
||||||
|
import { zodResolver } from "mantine-form-zod-resolver";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@ -91,7 +92,7 @@ export function SsoSamlForm({ provider, onClose }: SsoFormProps) {
|
|||||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Display name"
|
label={t("Display name")}
|
||||||
placeholder="e.g Azure Entra"
|
placeholder="e.g Azure Entra"
|
||||||
data-autofocus
|
data-autofocus
|
||||||
{...form.getInputProps("name")}
|
{...form.getInputProps("name")}
|
||||||
|
|||||||
@ -164,7 +164,6 @@ export function CalloutMenu({ editor }: EditorMenuProps) {
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip position="top" label={t("Custom emoji")}>
|
|
||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
onEmojiSelect={setCalloutIcon}
|
onEmojiSelect={setCalloutIcon}
|
||||||
removeEmojiAction={removeCalloutIcon}
|
removeEmojiAction={removeCalloutIcon}
|
||||||
@ -173,10 +172,9 @@ export function CalloutMenu({ editor }: EditorMenuProps) {
|
|||||||
actionIconProps={{
|
actionIconProps={{
|
||||||
size: "lg",
|
size: "lg",
|
||||||
variant: "default",
|
variant: "default",
|
||||||
c: undefined
|
c: undefined,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
|
||||||
</ActionIcon.Group>
|
</ActionIcon.Group>
|
||||||
</BaseBubbleMenu>
|
</BaseBubbleMenu>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -145,6 +145,7 @@ export default function DrawioView(props: NodeViewProps) {
|
|||||||
variant="default"
|
variant="default"
|
||||||
color="gray"
|
color="gray"
|
||||||
mx="xs"
|
mx="xs"
|
||||||
|
className="print-hide"
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 8,
|
top: 8,
|
||||||
|
|||||||
@ -183,6 +183,7 @@ export default function ExcalidrawView(props: NodeViewProps) {
|
|||||||
variant="default"
|
variant="default"
|
||||||
color="gray"
|
color="gray"
|
||||||
mx="xs"
|
mx="xs"
|
||||||
|
className="print-hide"
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 8,
|
top: 8,
|
||||||
|
|||||||
@ -33,8 +33,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin-top: 0.65em;
|
margin-top: 0.5em;
|
||||||
margin-bottom: 0.65em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul,
|
ul,
|
||||||
@ -57,6 +57,7 @@
|
|||||||
h5,
|
h5,
|
||||||
h6 {
|
h6 {
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
@ -136,7 +137,7 @@
|
|||||||
|
|
||||||
.selection,
|
.selection,
|
||||||
*::selection {
|
*::selection {
|
||||||
background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-gray-7));
|
background-color: Highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-mark {
|
.comment-mark {
|
||||||
|
|||||||
@ -1,11 +1,23 @@
|
|||||||
@media print {
|
@media print {
|
||||||
.mantine-AppShell-header,
|
.mantine-AppShell-header,
|
||||||
.mantine-AppShell-navbar,
|
.mantine-AppShell-navbar,
|
||||||
.mantine-AppShell-aside{
|
.mantine-AppShell-aside,
|
||||||
|
.print-hide,
|
||||||
|
.drag-handle {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mantine-AppShell-main {
|
.mantine-AppShell-main {
|
||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
|
min-height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ProseMirror-selectednode {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableWrapper {
|
||||||
|
overflow: hidden !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ ul[data-type="taskList"] {
|
|||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
> label {
|
> label {
|
||||||
|
padding-top: 0.2rem;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|||||||
@ -1,5 +1,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Group, Center, Text, Badge, ActionIcon } from "@mantine/core";
|
import {
|
||||||
|
Group,
|
||||||
|
Center,
|
||||||
|
Text,
|
||||||
|
Badge,
|
||||||
|
ActionIcon,
|
||||||
|
Tooltip,
|
||||||
|
getDefaultZIndex,
|
||||||
|
} from "@mantine/core";
|
||||||
import { Spotlight } from "@mantine/spotlight";
|
import { Spotlight } from "@mantine/spotlight";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { IconFile, IconDownload } from "@tabler/icons-react";
|
import { IconFile, IconDownload } from "@tabler/icons-react";
|
||||||
@ -10,6 +18,7 @@ import {
|
|||||||
IPageSearch,
|
IPageSearch,
|
||||||
} from "@/features/search/types/search.types";
|
} from "@/features/search/types/search.types";
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface SearchResultItemProps {
|
interface SearchResultItemProps {
|
||||||
result: IPageSearch | IAttachmentSearch;
|
result: IPageSearch | IAttachmentSearch;
|
||||||
@ -22,6 +31,8 @@ export function SearchResultItem({
|
|||||||
isAttachmentResult,
|
isAttachmentResult,
|
||||||
showSpace,
|
showSpace,
|
||||||
}: SearchResultItemProps) {
|
}: SearchResultItemProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (isAttachmentResult) {
|
if (isAttachmentResult) {
|
||||||
const attachmentResult = result as IAttachmentSearch;
|
const attachmentResult = result as IAttachmentSearch;
|
||||||
|
|
||||||
@ -45,7 +56,7 @@ export function SearchResultItem({
|
|||||||
>
|
>
|
||||||
<Group wrap="nowrap" w="100%">
|
<Group wrap="nowrap" w="100%">
|
||||||
<Center>
|
<Center>
|
||||||
<IconFile size={20} />
|
<IconFile size={16} />
|
||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
@ -61,21 +72,22 @@ export function SearchResultItem({
|
|||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: DOMPurify.sanitize(attachmentResult.highlight, {
|
__html: DOMPurify.sanitize(attachmentResult.highlight, {
|
||||||
ALLOWED_TAGS: ["mark", "em", "strong", "b"],
|
ALLOWED_TAGS: ["mark", "em", "strong", "b"],
|
||||||
ALLOWED_ATTR: []
|
ALLOWED_ATTR: [],
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ActionIcon
|
<Tooltip
|
||||||
variant="subtle"
|
label={t("Download attachment")}
|
||||||
color="gray"
|
zIndex={getDefaultZIndex("max")}
|
||||||
onClick={handleDownload}
|
withArrow
|
||||||
title="Download attachment"
|
|
||||||
>
|
>
|
||||||
|
<ActionIcon variant="subtle" color="gray" onClick={handleDownload}>
|
||||||
<IconDownload size={18} />
|
<IconDownload size={18} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
</Group>
|
</Group>
|
||||||
</Spotlight.Action>
|
</Spotlight.Action>
|
||||||
);
|
);
|
||||||
@ -111,7 +123,7 @@ export function SearchResultItem({
|
|||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: DOMPurify.sanitize(pageResult.highlight, {
|
__html: DOMPurify.sanitize(pageResult.highlight, {
|
||||||
ALLOWED_TAGS: ["mark", "em", "strong", "b"],
|
ALLOWED_TAGS: ["mark", "em", "strong", "b"],
|
||||||
ALLOWED_ATTR: []
|
ALLOWED_ATTR: [],
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import { useDebouncedValue } from "@mantine/hooks";
|
|||||||
import { useGetSpacesQuery } from "@/features/space/queries/space-query";
|
import { useGetSpacesQuery } from "@/features/space/queries/space-query";
|
||||||
import { useLicense } from "@/ee/hooks/use-license";
|
import { useLicense } from "@/ee/hooks/use-license";
|
||||||
import classes from "./search-spotlight-filters.module.css";
|
import classes from "./search-spotlight-filters.module.css";
|
||||||
|
import { isCloud } from "@/lib/config.ts";
|
||||||
|
|
||||||
interface SearchSpotlightFiltersProps {
|
interface SearchSpotlightFiltersProps {
|
||||||
onFiltersChange?: (filters: any) => void;
|
onFiltersChange?: (filters: any) => void;
|
||||||
@ -81,11 +82,11 @@ export function SearchSpotlightFilters({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const contentTypeOptions = [
|
const contentTypeOptions = [
|
||||||
{ value: "page", label: "Pages" },
|
{ value: "page", label: t("Pages") },
|
||||||
{
|
{
|
||||||
value: "attachment",
|
value: "attachment",
|
||||||
label: "Attachments",
|
label: t("Attachments"),
|
||||||
disabled: !hasLicenseKey,
|
disabled: !isCloud() && !hasLicenseKey,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -165,13 +166,13 @@ export function SearchSpotlightFilters({
|
|||||||
fw={500}
|
fw={500}
|
||||||
>
|
>
|
||||||
{selectedSpaceId
|
{selectedSpaceId
|
||||||
? `Space: ${selectedSpaceData?.name || "Unknown"}`
|
? `${t("Space")}: ${selectedSpaceData?.name || t("Unknown")}`
|
||||||
: "Space: All spaces"}
|
: `${t("Space")}: ${t("All spaces")}`}
|
||||||
</Button>
|
</Button>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
<Menu.Dropdown>
|
<Menu.Dropdown>
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder="Find a space"
|
placeholder={t("Find a space")}
|
||||||
data-autofocus
|
data-autofocus
|
||||||
autoFocus
|
autoFocus
|
||||||
leftSection={<IconSearch size={16} />}
|
leftSection={<IconSearch size={16} />}
|
||||||
@ -189,15 +190,15 @@ export function SearchSpotlightFilters({
|
|||||||
<Avatar
|
<Avatar
|
||||||
color="initials"
|
color="initials"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
name="All spaces"
|
name={t("All spaces")}
|
||||||
size={20}
|
size={20}
|
||||||
/>
|
/>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<Text size="sm" fw={500}>
|
<Text size="sm" fw={500}>
|
||||||
All spaces
|
{t("All spaces")}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
Search in all your spaces
|
{t("Search in all your spaces")}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
{!selectedSpaceId && <IconCheck size={20} />}
|
{!selectedSpaceId && <IconCheck size={20} />}
|
||||||
@ -246,8 +247,8 @@ export function SearchSpotlightFilters({
|
|||||||
fw={500}
|
fw={500}
|
||||||
>
|
>
|
||||||
{contentType
|
{contentType
|
||||||
? `Type: ${contentTypeOptions.find((opt) => opt.value === contentType)?.label || contentType}`
|
? `${t("Type")}: ${contentTypeOptions.find((opt) => opt.value === contentType)?.label || t(contentType === "page" ? "Pages" : "Attachments")}`
|
||||||
: "Type"}
|
: t("Type")}
|
||||||
</Button>
|
</Button>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
<Menu.Dropdown>
|
<Menu.Dropdown>
|
||||||
@ -266,7 +267,7 @@ export function SearchSpotlightFilters({
|
|||||||
<Text size="sm">{option.label}</Text>
|
<Text size="sm">{option.label}</Text>
|
||||||
{option.disabled && (
|
{option.disabled && (
|
||||||
<Badge size="xs" mt={4}>
|
<Badge size="xs" mt={4}>
|
||||||
Enterprise
|
{t("Enterprise")}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
IPageSearchParams,
|
IPageSearchParams,
|
||||||
} from "@/features/search/types/search.types";
|
} from "@/features/search/types/search.types";
|
||||||
import { useLicense } from "@/ee/hooks/use-license";
|
import { useLicense } from "@/ee/hooks/use-license";
|
||||||
|
import { isCloud } from "@/lib/config";
|
||||||
|
|
||||||
export type UnifiedSearchResult = IPageSearch | IAttachmentSearch;
|
export type UnifiedSearchResult = IPageSearch | IAttachmentSearch;
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ export function useUnifiedSearch(
|
|||||||
const { hasLicenseKey } = useLicense();
|
const { hasLicenseKey } = useLicense();
|
||||||
|
|
||||||
const isAttachmentSearch =
|
const isAttachmentSearch =
|
||||||
params.contentType === "attachment" && hasLicenseKey;
|
params.contentType === "attachment" && (isCloud() || hasLicenseKey);
|
||||||
const searchType = isAttachmentSearch ? "attachment" : "page";
|
const searchType = isAttachmentSearch ? "attachment" : "page";
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
|
|||||||
@ -1,25 +1,28 @@
|
|||||||
import { Group, Text, MantineSize, SegmentedControl } from "@mantine/core";
|
import { Text, MantineSize, SegmentedControl } from "@mantine/core";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { userAtom } from "@/features/user/atoms/current-user-atom.ts";
|
import { userAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||||
import { updateUser } from "@/features/user/services/user-service.ts";
|
import { updateUser } from "@/features/user/services/user-service.ts";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
||||||
|
import { ResponsiveSettingsRow, ResponsiveSettingsContent, ResponsiveSettingsControl } from "@/components/ui/responsive-settings-row";
|
||||||
|
|
||||||
export default function PageStatePref() {
|
export default function PageStatePref() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
<ResponsiveSettingsRow>
|
||||||
<div>
|
<ResponsiveSettingsContent>
|
||||||
<Text size="md">{t("Default page edit mode")}</Text>
|
<Text size="md">{t("Default page edit mode")}</Text>
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
{t("Choose your preferred page edit mode. Avoid accidental edits.")}
|
{t("Choose your preferred page edit mode. Avoid accidental edits.")}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</ResponsiveSettingsContent>
|
||||||
|
|
||||||
|
<ResponsiveSettingsControl>
|
||||||
<PageStateSegmentedControl />
|
<PageStateSegmentedControl />
|
||||||
</Group>
|
</ResponsiveSettingsControl>
|
||||||
|
</ResponsiveSettingsRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,24 +1,27 @@
|
|||||||
import { userAtom } from "@/features/user/atoms/current-user-atom.ts";
|
import { userAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||||
import { updateUser } from "@/features/user/services/user-service.ts";
|
import { updateUser } from "@/features/user/services/user-service.ts";
|
||||||
import { Group, MantineSize, Switch, Text } from "@mantine/core";
|
import { MantineSize, Switch, Text } from "@mantine/core";
|
||||||
import { useAtom } from "jotai/index";
|
import { useAtom } from "jotai/index";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ResponsiveSettingsRow, ResponsiveSettingsContent, ResponsiveSettingsControl } from "@/components/ui/responsive-settings-row";
|
||||||
|
|
||||||
export default function PageWidthPref() {
|
export default function PageWidthPref() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
<ResponsiveSettingsRow>
|
||||||
<div>
|
<ResponsiveSettingsContent>
|
||||||
<Text size="md">{t("Full page width")}</Text>
|
<Text size="md">{t("Full page width")}</Text>
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
{t("Choose your preferred page width.")}
|
{t("Choose your preferred page width.")}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</ResponsiveSettingsContent>
|
||||||
|
|
||||||
|
<ResponsiveSettingsControl>
|
||||||
<PageWidthToggle />
|
<PageWidthToggle />
|
||||||
</Group>
|
</ResponsiveSettingsControl>
|
||||||
|
</ResponsiveSettingsRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export default function WorkspaceInvitesTable() {
|
|||||||
)}
|
)}
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<Table.ScrollContainer minWidth={500}>
|
<Table.ScrollContainer minWidth={600}>
|
||||||
<Table highlightOnHover verticalSpacing="sm">
|
<Table highlightOnHover verticalSpacing="sm">
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
useWorkspaceMembersQuery,
|
useWorkspaceMembersQuery,
|
||||||
} from "@/features/workspace/queries/workspace-query.ts";
|
} from "@/features/workspace/queries/workspace-query.ts";
|
||||||
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
import React, { useCallback, useRef, useState } from "react";
|
import React from "react";
|
||||||
import RoleSelectMenu from "@/components/ui/role-select-menu.tsx";
|
import RoleSelectMenu from "@/components/ui/role-select-menu.tsx";
|
||||||
import {
|
import {
|
||||||
getUserRoleLabel,
|
getUserRoleLabel,
|
||||||
@ -54,7 +54,7 @@ export default function WorkspaceMembersTable() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SearchInput onSearch={handleSearch} />
|
<SearchInput onSearch={handleSearch} />
|
||||||
<Table.ScrollContainer minWidth={500}>
|
<Table.ScrollContainer minWidth={600}>
|
||||||
<Table highlightOnHover verticalSpacing="sm">
|
<Table highlightOnHover verticalSpacing="sm">
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.22.2",
|
"version": "0.23.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export class AttachmentProcessor extends WorkerHost implements OnModuleDestroy {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||||
AttachmentEeModule = require('./../../../ee/attachments-ee/attachment-ee.service');
|
AttachmentEeModule = require('./../../../ee/attachments-ee/attachment-ee.service');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error(
|
this.logger.debug(
|
||||||
'Attachment enterprise module requested but EE module not bundled in this build',
|
'Attachment enterprise module requested but EE module not bundled in this build',
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -67,10 +67,16 @@ export class AttachmentProcessor extends WorkerHost implements OnModuleDestroy {
|
|||||||
|
|
||||||
@OnWorkerEvent('failed')
|
@OnWorkerEvent('failed')
|
||||||
onError(job: Job) {
|
onError(job: Job) {
|
||||||
|
if (job.name === QueueJob.ATTACHMENT_INDEX_CONTENT) {
|
||||||
|
this.logger.debug(
|
||||||
|
`Error processing ${job.name} job for attachment ${job.data?.attachmentId}. Reason: ${job.failedReason}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Error processing ${job.name} job. Reason: ${job.failedReason}`,
|
`Error processing ${job.name} job. Reason: ${job.failedReason}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OnWorkerEvent('completed')
|
@OnWorkerEvent('completed')
|
||||||
onCompleted(job: Job) {
|
onCompleted(job: Job) {
|
||||||
|
|||||||
Submodule apps/server/src/ee updated: 6ee9fafebd...469eb56bb5
@ -6,6 +6,7 @@ import { cleanUrlString } from '../utils/file.utils';
|
|||||||
import { StorageService } from '../../storage/storage.service';
|
import { StorageService } from '../../storage/storage.service';
|
||||||
import { createReadStream } from 'node:fs';
|
import { createReadStream } from 'node:fs';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { Readable } from 'stream';
|
||||||
import { getMimeType, sanitizeFileName } from '../../../common/helpers';
|
import { getMimeType, sanitizeFileName } from '../../../common/helpers';
|
||||||
import { v7 } from 'uuid';
|
import { v7 } from 'uuid';
|
||||||
import { FileTask } from '@docmost/db/types/entity.types';
|
import { FileTask } from '@docmost/db/types/entity.types';
|
||||||
@ -15,6 +16,21 @@ import { unwrapFromParagraph } from '../utils/import-formatter';
|
|||||||
import { resolveRelativeAttachmentPath } from '../utils/import.utils';
|
import { resolveRelativeAttachmentPath } from '../utils/import.utils';
|
||||||
import { load } from 'cheerio';
|
import { load } from 'cheerio';
|
||||||
import pLimit from 'p-limit';
|
import pLimit from 'p-limit';
|
||||||
|
import { InjectQueue } from '@nestjs/bullmq';
|
||||||
|
import { Queue } from 'bullmq';
|
||||||
|
import { QueueJob, QueueName } from '../../queue/constants';
|
||||||
|
|
||||||
|
interface AttachmentInfo {
|
||||||
|
href: string;
|
||||||
|
fileName: string;
|
||||||
|
mimeType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DrawioPair {
|
||||||
|
drawioFile?: AttachmentInfo;
|
||||||
|
pngFile?: AttachmentInfo;
|
||||||
|
baseName: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImportAttachmentService {
|
export class ImportAttachmentService {
|
||||||
@ -26,6 +42,7 @@ export class ImportAttachmentService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly storageService: StorageService,
|
private readonly storageService: StorageService,
|
||||||
@InjectKysely() private readonly db: KyselyDB,
|
@InjectKysely() private readonly db: KyselyDB,
|
||||||
|
@InjectQueue(QueueName.ATTACHMENT_QUEUE) private attachmentQueue: Queue,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async processAttachments(opts: {
|
async processAttachments(opts: {
|
||||||
@ -35,6 +52,7 @@ export class ImportAttachmentService {
|
|||||||
pageId: string;
|
pageId: string;
|
||||||
fileTask: FileTask;
|
fileTask: FileTask;
|
||||||
attachmentCandidates: Map<string, string>;
|
attachmentCandidates: Map<string, string>;
|
||||||
|
pageAttachments?: AttachmentInfo[];
|
||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
const {
|
const {
|
||||||
html,
|
html,
|
||||||
@ -43,6 +61,7 @@ export class ImportAttachmentService {
|
|||||||
pageId,
|
pageId,
|
||||||
fileTask,
|
fileTask,
|
||||||
attachmentCandidates,
|
attachmentCandidates,
|
||||||
|
pageAttachments = [],
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
const attachmentTasks: (() => Promise<void>)[] = [];
|
const attachmentTasks: (() => Promise<void>)[] = [];
|
||||||
@ -57,7 +76,7 @@ export class ImportAttachmentService {
|
|||||||
/**
|
/**
|
||||||
* Cache keyed by the *relative* path that appears in the HTML.
|
* Cache keyed by the *relative* path that appears in the HTML.
|
||||||
* Ensures we upload (and DB-insert) each attachment at most once,
|
* Ensures we upload (and DB-insert) each attachment at most once,
|
||||||
* even if it’s referenced multiple times on the page.
|
* even if it's referenced multiple times on the page.
|
||||||
*/
|
*/
|
||||||
const processed = new Map<
|
const processed = new Map<
|
||||||
string,
|
string,
|
||||||
@ -70,6 +89,99 @@ export class ImportAttachmentService {
|
|||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
// Analyze attachments to identify Draw.io pairs
|
||||||
|
const { drawioPairs, skipFiles } = this.analyzeAttachments(pageAttachments);
|
||||||
|
|
||||||
|
// Map to store processed Draw.io SVGs
|
||||||
|
const drawioSvgMap = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
attachmentId: string;
|
||||||
|
apiFilePath: string;
|
||||||
|
fileName: string;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
|
//this.logger.debug(`Found ${drawioPairs.size} Draw.io pairs to process`);
|
||||||
|
|
||||||
|
// Process Draw.io pairs and create combined SVG files
|
||||||
|
for (const [drawioHref, pair] of drawioPairs) {
|
||||||
|
if (!pair.drawioFile) continue;
|
||||||
|
|
||||||
|
const drawioAbsPath = attachmentCandidates.get(drawioHref);
|
||||||
|
if (!drawioAbsPath) continue;
|
||||||
|
|
||||||
|
const pngAbsPath = pair.pngFile
|
||||||
|
? attachmentCandidates.get(pair.pngFile.href)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create combined SVG with Draw.io data and PNG image
|
||||||
|
const svgBuffer = await this.createDrawioSvg(drawioAbsPath, pngAbsPath);
|
||||||
|
|
||||||
|
// Generate file details - always use "diagram.drawio.svg" as filename
|
||||||
|
const attachmentId = v7();
|
||||||
|
const fileName = 'diagram.drawio.svg';
|
||||||
|
const storageFilePath = `${getAttachmentFolderPath(
|
||||||
|
AttachmentType.File,
|
||||||
|
fileTask.workspaceId,
|
||||||
|
)}/${attachmentId}/${fileName}`;
|
||||||
|
const apiFilePath = `/api/files/${attachmentId}/${fileName}`;
|
||||||
|
|
||||||
|
// Upload the SVG file
|
||||||
|
attachmentTasks.push(async () => {
|
||||||
|
try {
|
||||||
|
const stream = Readable.from(svgBuffer);
|
||||||
|
|
||||||
|
// Upload to storage
|
||||||
|
await this.storageService.uploadStream(storageFilePath, stream);
|
||||||
|
|
||||||
|
// Insert into database
|
||||||
|
await this.db
|
||||||
|
.insertInto('attachments')
|
||||||
|
.values({
|
||||||
|
id: attachmentId,
|
||||||
|
filePath: storageFilePath,
|
||||||
|
fileName: fileName,
|
||||||
|
fileSize: svgBuffer.length,
|
||||||
|
mimeType: 'image/svg+xml',
|
||||||
|
type: 'file',
|
||||||
|
fileExt: '.svg',
|
||||||
|
creatorId: fileTask.creatorId,
|
||||||
|
workspaceId: fileTask.workspaceId,
|
||||||
|
pageId,
|
||||||
|
spaceId: fileTask.spaceId,
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
uploadStats.completed++;
|
||||||
|
} catch (error) {
|
||||||
|
uploadStats.failed++;
|
||||||
|
uploadStats.failedFiles.push(fileName);
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to upload Draw.io SVG ${fileName}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store the mapping for both Draw.io and PNG references
|
||||||
|
drawioSvgMap.set(drawioHref, { attachmentId, apiFilePath, fileName });
|
||||||
|
if (pair.pngFile) {
|
||||||
|
drawioSvgMap.set(pair.pngFile.href, {
|
||||||
|
attachmentId,
|
||||||
|
apiFilePath,
|
||||||
|
fileName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to process Draw.io pair ${pair.baseName}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const uploadOnce = (relPath: string) => {
|
const uploadOnce = (relPath: string) => {
|
||||||
const abs = attachmentCandidates.get(relPath)!;
|
const abs = attachmentCandidates.get(relPath)!;
|
||||||
const attachmentId = v7();
|
const attachmentId = v7();
|
||||||
@ -85,7 +197,8 @@ export class ImportAttachmentService {
|
|||||||
|
|
||||||
const apiFilePath = `/api/files/${attachmentId}/${fileNameWithExt}`;
|
const apiFilePath = `/api/files/${attachmentId}/${fileNameWithExt}`;
|
||||||
|
|
||||||
attachmentTasks.push(() => this.uploadWithRetry({
|
attachmentTasks.push(() =>
|
||||||
|
this.uploadWithRetry({
|
||||||
abs,
|
abs,
|
||||||
storageFilePath,
|
storageFilePath,
|
||||||
attachmentId,
|
attachmentId,
|
||||||
@ -94,7 +207,8 @@ export class ImportAttachmentService {
|
|||||||
pageId,
|
pageId,
|
||||||
fileTask,
|
fileTask,
|
||||||
uploadStats,
|
uploadStats,
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
attachmentId,
|
attachmentId,
|
||||||
@ -121,79 +235,108 @@ export class ImportAttachmentService {
|
|||||||
const pageDir = path.dirname(pageRelativePath);
|
const pageDir = path.dirname(pageRelativePath);
|
||||||
const $ = load(html);
|
const $ = load(html);
|
||||||
|
|
||||||
// image
|
// Cache for resolved paths to avoid repeated lookups
|
||||||
for (const imgEl of $('img').toArray()) {
|
const resolvedPathCache = new Map<string, string | null>();
|
||||||
const $img = $(imgEl);
|
|
||||||
const src = cleanUrlString($img.attr('src') ?? '')!;
|
|
||||||
if (!src || src.startsWith('http')) continue;
|
|
||||||
|
|
||||||
const relPath = resolveRelativeAttachmentPath(
|
const getCachedResolvedPath = (rawPath: string): string | null => {
|
||||||
src,
|
if (resolvedPathCache.has(rawPath)) {
|
||||||
|
return resolvedPathCache.get(rawPath)!;
|
||||||
|
}
|
||||||
|
const resolved = resolveRelativeAttachmentPath(
|
||||||
|
rawPath,
|
||||||
pageDir,
|
pageDir,
|
||||||
attachmentCandidates,
|
attachmentCandidates,
|
||||||
);
|
);
|
||||||
if (!relPath) continue;
|
resolvedPathCache.set(rawPath, resolved);
|
||||||
|
return resolved;
|
||||||
|
};
|
||||||
|
|
||||||
const { attachmentId, apiFilePath, abs } = processFile(relPath);
|
// Cache for file stats to avoid repeated file system calls
|
||||||
const stat = await fs.stat(abs);
|
const statCache = new Map<string, any>();
|
||||||
|
|
||||||
const width = $img.attr('width') ?? '100%';
|
const getCachedStat = async (absPath: string) => {
|
||||||
const align = $img.attr('data-align') ?? 'center';
|
if (statCache.has(absPath)) {
|
||||||
|
return statCache.get(absPath);
|
||||||
$img
|
|
||||||
.attr('src', apiFilePath)
|
|
||||||
.attr('data-attachment-id', attachmentId)
|
|
||||||
.attr('data-size', stat.size.toString())
|
|
||||||
.attr('width', width)
|
|
||||||
.attr('data-align', align);
|
|
||||||
|
|
||||||
unwrapFromParagraph($, $img);
|
|
||||||
}
|
}
|
||||||
|
const stat = await fs.stat(absPath);
|
||||||
|
statCache.set(absPath, stat);
|
||||||
|
return stat;
|
||||||
|
};
|
||||||
|
|
||||||
// video
|
// Single DOM traversal for all attachment elements
|
||||||
for (const vidEl of $('video').toArray()) {
|
const selector =
|
||||||
const $vid = $(vidEl);
|
'img, video, div[data-type="attachment"], a, div[data-type="excalidraw"], div[data-type="drawio"]';
|
||||||
const src = cleanUrlString($vid.attr('src') ?? '')!;
|
const elements = $(selector).toArray();
|
||||||
|
|
||||||
|
for (const element of elements) {
|
||||||
|
const $el = $(element);
|
||||||
|
const tagName = element.tagName.toLowerCase();
|
||||||
|
|
||||||
|
// Process based on element type
|
||||||
|
if (tagName === 'img') {
|
||||||
|
const src = cleanUrlString($el.attr('src') ?? '');
|
||||||
if (!src || src.startsWith('http')) continue;
|
if (!src || src.startsWith('http')) continue;
|
||||||
|
|
||||||
const relPath = resolveRelativeAttachmentPath(
|
const relPath = getCachedResolvedPath(src);
|
||||||
src,
|
|
||||||
pageDir,
|
|
||||||
attachmentCandidates,
|
|
||||||
);
|
|
||||||
if (!relPath) continue;
|
if (!relPath) continue;
|
||||||
|
|
||||||
|
// Check if this image is part of a Draw.io pair
|
||||||
|
const drawioSvg = drawioSvgMap.get(relPath);
|
||||||
|
if (drawioSvg) {
|
||||||
|
const $drawio = $('<div>')
|
||||||
|
.attr('data-type', 'drawio')
|
||||||
|
.attr('data-src', drawioSvg.apiFilePath)
|
||||||
|
.attr('data-title', 'diagram')
|
||||||
|
.attr('data-width', '100%')
|
||||||
|
.attr('data-align', 'center')
|
||||||
|
.attr('data-attachment-id', drawioSvg.attachmentId);
|
||||||
|
|
||||||
|
$el.replaceWith($drawio);
|
||||||
|
unwrapFromParagraph($, $drawio);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const { attachmentId, apiFilePath, abs } = processFile(relPath);
|
const { attachmentId, apiFilePath, abs } = processFile(relPath);
|
||||||
const stat = await fs.stat(abs);
|
const stat = await getCachedStat(abs);
|
||||||
|
|
||||||
const width = $vid.attr('width') ?? '100%';
|
$el
|
||||||
const align = $vid.attr('data-align') ?? 'center';
|
|
||||||
|
|
||||||
$vid
|
|
||||||
.attr('src', apiFilePath)
|
.attr('src', apiFilePath)
|
||||||
.attr('data-attachment-id', attachmentId)
|
.attr('data-attachment-id', attachmentId)
|
||||||
.attr('data-size', stat.size.toString())
|
.attr('data-size', stat.size.toString())
|
||||||
.attr('width', width)
|
.attr('width', $el.attr('width') ?? '100%')
|
||||||
.attr('data-align', align);
|
.attr('data-align', $el.attr('data-align') ?? 'center');
|
||||||
|
|
||||||
unwrapFromParagraph($, $vid);
|
unwrapFromParagraph($, $el);
|
||||||
}
|
} else if (tagName === 'video') {
|
||||||
|
const src = cleanUrlString($el.attr('src') ?? '');
|
||||||
|
if (!src || src.startsWith('http')) continue;
|
||||||
|
|
||||||
// <div data-type="attachment">
|
const relPath = getCachedResolvedPath(src);
|
||||||
for (const el of $('div[data-type="attachment"]').toArray()) {
|
if (!relPath) continue;
|
||||||
const $oldDiv = $(el);
|
|
||||||
const rawUrl = cleanUrlString($oldDiv.attr('data-attachment-url') ?? '')!;
|
const { attachmentId, apiFilePath, abs } = processFile(relPath);
|
||||||
|
const stat = await getCachedStat(abs);
|
||||||
|
|
||||||
|
$el
|
||||||
|
.attr('src', apiFilePath)
|
||||||
|
.attr('data-attachment-id', attachmentId)
|
||||||
|
.attr('data-size', stat.size.toString())
|
||||||
|
.attr('width', $el.attr('width') ?? '100%')
|
||||||
|
.attr('data-align', $el.attr('data-align') ?? 'center');
|
||||||
|
|
||||||
|
unwrapFromParagraph($, $el);
|
||||||
|
} else if (tagName === 'div') {
|
||||||
|
const dataType = $el.attr('data-type');
|
||||||
|
|
||||||
|
if (dataType === 'attachment') {
|
||||||
|
const rawUrl = cleanUrlString($el.attr('data-attachment-url') ?? '');
|
||||||
if (!rawUrl || rawUrl.startsWith('http')) continue;
|
if (!rawUrl || rawUrl.startsWith('http')) continue;
|
||||||
|
|
||||||
const relPath = resolveRelativeAttachmentPath(
|
const relPath = getCachedResolvedPath(rawUrl);
|
||||||
rawUrl,
|
|
||||||
pageDir,
|
|
||||||
attachmentCandidates,
|
|
||||||
);
|
|
||||||
if (!relPath) continue;
|
if (!relPath) continue;
|
||||||
|
|
||||||
const { attachmentId, apiFilePath, abs } = processFile(relPath);
|
const { attachmentId, apiFilePath, abs } = processFile(relPath);
|
||||||
const stat = await fs.stat(abs);
|
const stat = await getCachedStat(abs);
|
||||||
const fileName = path.basename(abs);
|
const fileName = path.basename(abs);
|
||||||
const mime = getMimeType(abs);
|
const mime = getMimeType(abs);
|
||||||
|
|
||||||
@ -205,25 +348,62 @@ export class ImportAttachmentService {
|
|||||||
.attr('data-attachment-size', stat.size.toString())
|
.attr('data-attachment-size', stat.size.toString())
|
||||||
.attr('data-attachment-id', attachmentId);
|
.attr('data-attachment-id', attachmentId);
|
||||||
|
|
||||||
$oldDiv.replaceWith($newDiv);
|
$el.replaceWith($newDiv);
|
||||||
unwrapFromParagraph($, $newDiv);
|
unwrapFromParagraph($, $newDiv);
|
||||||
}
|
} else if (dataType === 'excalidraw' || dataType === 'drawio') {
|
||||||
|
const rawSrc = cleanUrlString($el.attr('data-src') ?? '');
|
||||||
|
if (!rawSrc || rawSrc.startsWith('http')) continue;
|
||||||
|
|
||||||
// rewrite other attachments via <a>
|
const relPath = getCachedResolvedPath(rawSrc);
|
||||||
for (const aEl of $('a').toArray()) {
|
|
||||||
const $a = $(aEl);
|
|
||||||
const href = cleanUrlString($a.attr('href') ?? '')!;
|
|
||||||
if (!href || href.startsWith('http')) continue;
|
|
||||||
|
|
||||||
const relPath = resolveRelativeAttachmentPath(
|
|
||||||
href,
|
|
||||||
pageDir,
|
|
||||||
attachmentCandidates,
|
|
||||||
);
|
|
||||||
if (!relPath) continue;
|
if (!relPath) continue;
|
||||||
|
|
||||||
const { attachmentId, apiFilePath, abs } = processFile(relPath);
|
const { attachmentId, apiFilePath, abs } = processFile(relPath);
|
||||||
const stat = await fs.stat(abs);
|
const stat = await getCachedStat(abs);
|
||||||
|
const fileName = path.basename(abs);
|
||||||
|
|
||||||
|
const $newDiv = $('<div>')
|
||||||
|
.attr('data-type', dataType)
|
||||||
|
.attr('data-src', apiFilePath)
|
||||||
|
.attr('data-title', fileName)
|
||||||
|
.attr('data-width', $el.attr('data-width') || '100%')
|
||||||
|
.attr('data-size', stat.size.toString())
|
||||||
|
.attr('data-align', $el.attr('data-align') || 'center')
|
||||||
|
.attr('data-attachment-id', attachmentId);
|
||||||
|
|
||||||
|
$el.replaceWith($newDiv);
|
||||||
|
unwrapFromParagraph($, $newDiv);
|
||||||
|
}
|
||||||
|
} else if (tagName === 'a') {
|
||||||
|
const href = cleanUrlString($el.attr('href') ?? '');
|
||||||
|
if (!href || href.startsWith('http')) continue;
|
||||||
|
|
||||||
|
const relPath = getCachedResolvedPath(href);
|
||||||
|
if (!relPath) continue;
|
||||||
|
|
||||||
|
// Check if this is a Draw.io file
|
||||||
|
const drawioSvg = drawioSvgMap.get(relPath);
|
||||||
|
if (drawioSvg) {
|
||||||
|
const $drawio = $('<div>')
|
||||||
|
.attr('data-type', 'drawio')
|
||||||
|
.attr('data-src', drawioSvg.apiFilePath)
|
||||||
|
.attr('data-title', 'diagram')
|
||||||
|
.attr('data-width', '100%')
|
||||||
|
.attr('data-align', 'center')
|
||||||
|
.attr('data-attachment-id', drawioSvg.attachmentId);
|
||||||
|
|
||||||
|
$el.replaceWith($drawio);
|
||||||
|
unwrapFromParagraph($, $drawio);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip files that should be ignored
|
||||||
|
if (skipFiles.has(relPath)) {
|
||||||
|
$el.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { attachmentId, apiFilePath, abs } = processFile(relPath);
|
||||||
|
const stat = await getCachedStat(abs);
|
||||||
const ext = path.extname(relPath).toLowerCase();
|
const ext = path.extname(relPath).toLowerCase();
|
||||||
|
|
||||||
if (ext === '.mp4') {
|
if (ext === '.mp4') {
|
||||||
@ -233,10 +413,10 @@ export class ImportAttachmentService {
|
|||||||
.attr('data-size', stat.size.toString())
|
.attr('data-size', stat.size.toString())
|
||||||
.attr('width', '100%')
|
.attr('width', '100%')
|
||||||
.attr('data-align', 'center');
|
.attr('data-align', 'center');
|
||||||
$a.replaceWith($video);
|
$el.replaceWith($video);
|
||||||
unwrapFromParagraph($, $video);
|
unwrapFromParagraph($, $video);
|
||||||
} else {
|
} else {
|
||||||
const confAliasName = $a.attr('data-linked-resource-default-alias');
|
const confAliasName = $el.attr('data-linked-resource-default-alias');
|
||||||
let attachmentName = path.basename(abs);
|
let attachmentName = path.basename(abs);
|
||||||
if (confAliasName) attachmentName = confAliasName;
|
if (confAliasName) attachmentName = confAliasName;
|
||||||
|
|
||||||
@ -248,43 +428,87 @@ export class ImportAttachmentService {
|
|||||||
.attr('data-attachment-size', stat.size.toString())
|
.attr('data-attachment-size', stat.size.toString())
|
||||||
.attr('data-attachment-id', attachmentId);
|
.attr('data-attachment-id', attachmentId);
|
||||||
|
|
||||||
$a.replaceWith($div);
|
$el.replaceWith($div);
|
||||||
unwrapFromParagraph($, $div);
|
unwrapFromParagraph($, $div);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// excalidraw and drawio
|
// Collect all attachment IDs in the HTML in a single DOM traversal - O(n)
|
||||||
for (const type of ['excalidraw', 'drawio'] as const) {
|
const usedAttachmentIds = new Set<string>();
|
||||||
for (const el of $(`div[data-type="${type}"]`).toArray()) {
|
$.root()
|
||||||
const $oldDiv = $(el);
|
.find('[data-attachment-id]')
|
||||||
const rawSrc = cleanUrlString($oldDiv.attr('data-src') ?? '')!;
|
.each((_, el) => {
|
||||||
if (!rawSrc || rawSrc.startsWith('http')) continue;
|
const attachmentId = $(el).attr('data-attachment-id');
|
||||||
|
if (attachmentId) {
|
||||||
|
usedAttachmentIds.add(attachmentId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const relPath = resolveRelativeAttachmentPath(
|
// Add Draw.io diagrams that weren't referenced in the HTML content
|
||||||
rawSrc,
|
for (const [drawioHref, pair] of drawioPairs) {
|
||||||
pageDir,
|
const drawioSvg = drawioSvgMap.get(drawioHref);
|
||||||
attachmentCandidates,
|
if (!drawioSvg) continue;
|
||||||
);
|
|
||||||
if (!relPath) continue;
|
|
||||||
|
|
||||||
const { attachmentId, apiFilePath, abs } = processFile(relPath);
|
if (usedAttachmentIds.has(drawioSvg.attachmentId)) {
|
||||||
|
continue; // Already in content
|
||||||
|
}
|
||||||
|
|
||||||
|
const $drawio = $('<div>')
|
||||||
|
.attr('data-type', 'drawio')
|
||||||
|
.attr('data-src', drawioSvg.apiFilePath)
|
||||||
|
.attr('data-title', 'diagram')
|
||||||
|
.attr('data-width', '100%')
|
||||||
|
.attr('data-align', 'center')
|
||||||
|
.attr('data-attachment-id', drawioSvg.attachmentId);
|
||||||
|
|
||||||
|
$.root().append($drawio);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process attachments from the attachment section that weren't referenced in HTML
|
||||||
|
// These need to be added as attachment nodes so they get uploaded
|
||||||
|
for (const attachment of pageAttachments) {
|
||||||
|
const { href, fileName, mimeType } = attachment;
|
||||||
|
|
||||||
|
// Skip temporary files or files that should be ignored
|
||||||
|
if (skipFiles.has(href)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this was part of a Draw.io pair that was already handled
|
||||||
|
if (drawioSvgMap.has(href)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already processed (was referenced in HTML)
|
||||||
|
if (processed.has(href)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if the file doesn't exist
|
||||||
|
if (!attachmentCandidates.has(href)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This attachment was in the list but not referenced in HTML - add it
|
||||||
|
const { attachmentId, apiFilePath, abs } = processFile(href);
|
||||||
|
|
||||||
|
try {
|
||||||
const stat = await fs.stat(abs);
|
const stat = await fs.stat(abs);
|
||||||
const fileName = path.basename(abs);
|
const mime = mimeType || getMimeType(abs);
|
||||||
|
|
||||||
const width = $oldDiv.attr('data-width') || '100%';
|
// Add as attachment node at the end
|
||||||
const align = $oldDiv.attr('data-align') || 'center';
|
const $attachmentDiv = $('<div>')
|
||||||
|
.attr('data-type', 'attachment')
|
||||||
const $newDiv = $('<div>')
|
.attr('data-attachment-url', apiFilePath)
|
||||||
.attr('data-type', type)
|
.attr('data-attachment-name', fileName)
|
||||||
.attr('data-src', apiFilePath)
|
.attr('data-attachment-mime', mime)
|
||||||
.attr('data-title', fileName)
|
.attr('data-attachment-size', stat.size.toString())
|
||||||
.attr('data-width', width)
|
|
||||||
.attr('data-size', stat.size.toString())
|
|
||||||
.attr('data-align', align)
|
|
||||||
.attr('data-attachment-id', attachmentId);
|
.attr('data-attachment-id', attachmentId);
|
||||||
|
|
||||||
$oldDiv.replaceWith($newDiv);
|
$.root().append($attachmentDiv);
|
||||||
unwrapFromParagraph($, $newDiv);
|
} catch (error) {
|
||||||
|
this.logger.error(`Failed to process attachment ${fileName}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,24 +516,20 @@ export class ImportAttachmentService {
|
|||||||
uploadStats.total = attachmentTasks.length;
|
uploadStats.total = attachmentTasks.length;
|
||||||
|
|
||||||
if (uploadStats.total > 0) {
|
if (uploadStats.total > 0) {
|
||||||
this.logger.debug(`Starting upload of ${uploadStats.total} attachments...`);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(attachmentTasks.map((task) => limit(task)));
|
||||||
attachmentTasks.map(task => limit(task))
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error('Import attachment upload error', err);
|
this.logger.error('Import attachment upload error', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Upload completed: ${uploadStats.completed}/${uploadStats.total} successful, ${uploadStats.failed} failed`
|
`Upload completed: ${uploadStats.completed}/${uploadStats.total} successful, ${uploadStats.failed} failed`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (uploadStats.failed > 0) {
|
if (uploadStats.failed > 0) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`Failed to upload ${uploadStats.failed} files:`,
|
`Failed to upload ${uploadStats.failed} files:`,
|
||||||
uploadStats.failedFiles
|
uploadStats.failedFiles,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -317,6 +537,214 @@ export class ImportAttachmentService {
|
|||||||
return $.root().html() || '';
|
return $.root().html() || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private analyzeAttachments(attachments: AttachmentInfo[]): {
|
||||||
|
drawioPairs: Map<string, DrawioPair>;
|
||||||
|
skipFiles: Set<string>;
|
||||||
|
} {
|
||||||
|
const drawioPairs = new Map<string, DrawioPair>();
|
||||||
|
const skipFiles = new Set<string>();
|
||||||
|
|
||||||
|
// Group attachments by type
|
||||||
|
const drawioFiles: AttachmentInfo[] = [];
|
||||||
|
const pngByBaseName = new Map<string, AttachmentInfo[]>();
|
||||||
|
|
||||||
|
const nonDrawioExtensions = new Set([
|
||||||
|
'.png',
|
||||||
|
'.jpg',
|
||||||
|
'.jpeg',
|
||||||
|
'.gif',
|
||||||
|
'.svg',
|
||||||
|
'.txt',
|
||||||
|
'.pdf',
|
||||||
|
'.doc',
|
||||||
|
'.docx',
|
||||||
|
'.xls',
|
||||||
|
'.xlsx',
|
||||||
|
'.csv',
|
||||||
|
'.zip',
|
||||||
|
'.tar',
|
||||||
|
'.gz',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Single pass through attachments
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
const { fileName, mimeType, href } = attachment;
|
||||||
|
const fileNameLower = fileName.toLowerCase();
|
||||||
|
|
||||||
|
// Skip temporary files
|
||||||
|
if (fileName.endsWith('.tmp') || fileName.includes('~drawio~')) {
|
||||||
|
skipFiles.add(href);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Draw.io files
|
||||||
|
if (mimeType === 'application/vnd.jgraph.mxfile') {
|
||||||
|
const ext = fileNameLower.substring(fileNameLower.lastIndexOf('.'));
|
||||||
|
if (!nonDrawioExtensions.has(ext)) {
|
||||||
|
drawioFiles.push(attachment);
|
||||||
|
} else {
|
||||||
|
//Skipped non-Draw.io file with mxfile MIME.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeType === 'image/png' || fileNameLower.endsWith('.png')) {
|
||||||
|
const baseNames: string[] = [];
|
||||||
|
|
||||||
|
if (fileName.endsWith('.drawio.png')) {
|
||||||
|
// Cloud format: "name.drawio.png" -> base is "name"
|
||||||
|
baseNames.push(fileName.slice(0, -11)); // Remove .drawio.png
|
||||||
|
} else if (fileName.endsWith('.png')) {
|
||||||
|
// Server format: "name.png" -> base is "name"
|
||||||
|
baseNames.push(fileName.slice(0, -4)); // Remove .png
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const baseName of baseNames) {
|
||||||
|
if (!pngByBaseName.has(baseName)) {
|
||||||
|
pngByBaseName.set(baseName, []);
|
||||||
|
}
|
||||||
|
pngByBaseName.get(baseName)!.push(attachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match Draw.io files with PNG counterparts
|
||||||
|
for (const drawio of drawioFiles) {
|
||||||
|
let baseName: string;
|
||||||
|
|
||||||
|
if (drawio.fileName.endsWith('.drawio')) {
|
||||||
|
baseName = drawio.fileName.slice(0, -7); // Remove .drawio
|
||||||
|
} else {
|
||||||
|
// Confluence Server: no extension
|
||||||
|
baseName = drawio.fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidatePngs = pngByBaseName.get(baseName) || [];
|
||||||
|
let matchingPng: AttachmentInfo | undefined;
|
||||||
|
|
||||||
|
// Extract the attachment ID from the Draw.io href
|
||||||
|
// Format: attachments/16941088/36044817.png -> ID is 36044817
|
||||||
|
const drawioIdMatch = drawio.href.match(/\/(\d+)\.\w+$/);
|
||||||
|
const drawioId = drawioIdMatch ? drawioIdMatch[1] : null;
|
||||||
|
|
||||||
|
if (drawioId) {
|
||||||
|
// Look for PNG with adjacent ID (usually PNG ID = Draw.io ID + small increment)
|
||||||
|
// In Confluence, related files often have sequential or near-sequential IDs
|
||||||
|
for (const png of candidatePngs) {
|
||||||
|
const pngIdMatch = png.href.match(/\/(\d+)\.png$/);
|
||||||
|
const pngId = pngIdMatch ? pngIdMatch[1] : null;
|
||||||
|
|
||||||
|
//TODO: should revisit this
|
||||||
|
// but seem to be the best option for now
|
||||||
|
// to prevent reusing the first drawio preview image if there are more with the same name
|
||||||
|
if (pngId && drawioId) {
|
||||||
|
const idDiff = Math.abs(parseInt(pngId) - parseInt(drawioId));
|
||||||
|
// PNG is usually within ~30 IDs of the Draw.io file
|
||||||
|
if (idDiff <= 30) {
|
||||||
|
// Verify filename match
|
||||||
|
if (
|
||||||
|
png.fileName === `${baseName}.drawio.png` ||
|
||||||
|
(!drawio.fileName.endsWith('.drawio') &&
|
||||||
|
png.fileName === `${baseName}.png`)
|
||||||
|
) {
|
||||||
|
matchingPng = png;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to name-only matching if ID-based matching fails
|
||||||
|
if (!matchingPng) {
|
||||||
|
for (const png of candidatePngs) {
|
||||||
|
if (png.fileName === `${baseName}.drawio.png`) {
|
||||||
|
matchingPng = png;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!drawio.fileName.endsWith('.drawio') &&
|
||||||
|
png.fileName === `${baseName}.png`
|
||||||
|
) {
|
||||||
|
matchingPng = png;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchingPng) {
|
||||||
|
this.logger.debug(
|
||||||
|
`Found Draw.io pair: ${drawio.fileName} -> ${matchingPng.fileName}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.debug(`No PNG found for Draw.io file: ${drawio.fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pair: DrawioPair = {
|
||||||
|
drawioFile: drawio,
|
||||||
|
pngFile: matchingPng,
|
||||||
|
baseName,
|
||||||
|
};
|
||||||
|
|
||||||
|
drawioPairs.set(drawio.href, pair);
|
||||||
|
skipFiles.add(drawio.href);
|
||||||
|
if (matchingPng) {
|
||||||
|
skipFiles.add(matchingPng.href);
|
||||||
|
// Remove the matched PNG from the candidates to prevent reuse
|
||||||
|
const remainingPngs = pngByBaseName
|
||||||
|
.get(baseName)
|
||||||
|
?.filter((png) => png.href !== matchingPng.href);
|
||||||
|
if (remainingPngs && remainingPngs.length > 0) {
|
||||||
|
pngByBaseName.set(baseName, remainingPngs);
|
||||||
|
} else {
|
||||||
|
pngByBaseName.delete(baseName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { drawioPairs, skipFiles };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createDrawioSvg(
|
||||||
|
drawioPath: string,
|
||||||
|
pngPath?: string,
|
||||||
|
): Promise<Buffer> {
|
||||||
|
try {
|
||||||
|
const drawioContent = await fs.readFile(drawioPath, 'utf-8');
|
||||||
|
const drawioBase64 = Buffer.from(drawioContent).toString('base64');
|
||||||
|
|
||||||
|
let imageElement = '';
|
||||||
|
// If we have a PNG, include it in the SVG
|
||||||
|
if (pngPath) {
|
||||||
|
try {
|
||||||
|
const pngBuffer = await fs.readFile(pngPath);
|
||||||
|
const pngBase64 = pngBuffer.toString('base64');
|
||||||
|
|
||||||
|
imageElement = `<image href="data:image/png;base64,${pngBase64}" width="100%" height="100%"/>`;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Could not read PNG file for Draw.io diagram: ${pngPath}`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the SVG with embedded Draw.io data and image
|
||||||
|
// Default dimensions for Draw.io diagrams if no image is provided
|
||||||
|
const svgContent = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="600"
|
||||||
|
height="400"
|
||||||
|
viewBox="0 0 600 400"
|
||||||
|
content="${drawioBase64}">${imageElement}</svg>`;
|
||||||
|
|
||||||
|
return Buffer.from(svgContent, 'utf-8');
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Failed to create Draw.io SVG: ${error}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async uploadWithRetry(opts: {
|
private async uploadWithRetry(opts: {
|
||||||
abs: string;
|
abs: string;
|
||||||
storageFilePath: string;
|
storageFilePath: string;
|
||||||
@ -368,11 +796,41 @@ export class ImportAttachmentService {
|
|||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
|
// Queue PDF and DOCX files for indexing
|
||||||
|
const supportedExtensions = ['.pdf', '.docx'];
|
||||||
|
if (supportedExtensions.includes(ext.toLowerCase())) {
|
||||||
|
try {
|
||||||
|
await this.attachmentQueue.add(
|
||||||
|
QueueJob.ATTACHMENT_INDEX_CONTENT,
|
||||||
|
{ attachmentId },
|
||||||
|
{
|
||||||
|
attempts: 1,
|
||||||
|
backoff: {
|
||||||
|
type: 'exponential',
|
||||||
|
delay: 30 * 1000,
|
||||||
|
},
|
||||||
|
deduplication: {
|
||||||
|
id: attachmentId,
|
||||||
|
},
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.logger.debug(
|
||||||
|
`Queued ${fileNameWithExt} for indexing (attachment ID: ${attachmentId})`,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to queue indexing for imported attachment ${attachmentId}: ${err}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uploadStats.completed++;
|
uploadStats.completed++;
|
||||||
|
|
||||||
if (uploadStats.completed % 10 === 0) {
|
if (uploadStats.completed % 10 === 0) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Upload progress: ${uploadStats.completed}/${uploadStats.total}`
|
`Upload progress: ${uploadStats.completed}/${uploadStats.total}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,12 +838,12 @@ export class ImportAttachmentService {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
lastError = error as Error;
|
lastError = error as Error;
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`Upload attempt ${attempt}/${this.MAX_RETRIES} failed for ${fileNameWithExt}: ${error instanceof Error ? error.message : String(error)}`
|
`Upload attempt ${attempt}/${this.MAX_RETRIES} failed for ${fileNameWithExt}: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (attempt < this.MAX_RETRIES) {
|
if (attempt < this.MAX_RETRIES) {
|
||||||
await new Promise(resolve =>
|
await new Promise((resolve) =>
|
||||||
setTimeout(resolve, this.RETRY_DELAY * attempt)
|
setTimeout(resolve, this.RETRY_DELAY * attempt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -395,7 +853,7 @@ export class ImportAttachmentService {
|
|||||||
uploadStats.failedFiles.push(fileNameWithExt);
|
uploadStats.failedFiles.push(fileNameWithExt);
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Failed to upload ${fileNameWithExt} after ${this.MAX_RETRIES} attempts:`,
|
`Failed to upload ${fileNameWithExt} after ${this.MAX_RETRIES} attempts:`,
|
||||||
lastError
|
lastError,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
docmost:
|
docmost:
|
||||||
image: docmost/docmost:latest
|
image: docmost/docmost:latest
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "docmost",
|
"name": "docmost",
|
||||||
"homepage": "https://docmost.com",
|
"homepage": "https://docmost.com",
|
||||||
"version": "0.22.2",
|
"version": "0.23.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nx run-many -t build",
|
"build": "nx run-many -t build",
|
||||||
|
|||||||
156
pnpm-lock.yaml
generated
156
pnpm-lock.yaml
generated
@ -306,8 +306,8 @@ importers:
|
|||||||
specifier: ^1.3.0
|
specifier: ^1.3.0
|
||||||
version: 1.3.0(@mantine/form@8.1.3(react@18.3.1))(zod@3.25.56)
|
version: 1.3.0(@mantine/form@8.1.3(react@18.3.1))(zod@3.25.56)
|
||||||
mermaid:
|
mermaid:
|
||||||
specifier: ^11.6.0
|
specifier: ^11.11.0
|
||||||
version: 11.6.0
|
version: 11.11.0
|
||||||
mitt:
|
mitt:
|
||||||
specifier: ^3.0.1
|
specifier: ^3.0.1
|
||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
@ -746,11 +746,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-AP6FvhMybCYs3gs+vzEAzSU1K//AFT3SVTRFv+C3WMO5dLeAHeGzM8I2dxD5EHQQtqIE/8apP6CxGrnpA5YlFg==}
|
resolution: {integrity: sha512-AP6FvhMybCYs3gs+vzEAzSU1K//AFT3SVTRFv+C3WMO5dLeAHeGzM8I2dxD5EHQQtqIE/8apP6CxGrnpA5YlFg==}
|
||||||
engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
|
engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
|
||||||
|
|
||||||
'@antfu/install-pkg@0.4.1':
|
'@antfu/install-pkg@1.1.0':
|
||||||
resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==}
|
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
|
||||||
|
|
||||||
'@antfu/utils@0.7.10':
|
'@antfu/utils@9.2.0':
|
||||||
resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==}
|
resolution: {integrity: sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw==}
|
||||||
|
|
||||||
'@asamuzakjp/css-color@2.8.3':
|
'@asamuzakjp/css-color@2.8.3':
|
||||||
resolution: {integrity: sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==}
|
resolution: {integrity: sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==}
|
||||||
@ -2292,8 +2292,8 @@ packages:
|
|||||||
'@iconify/types@2.0.0':
|
'@iconify/types@2.0.0':
|
||||||
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
||||||
|
|
||||||
'@iconify/utils@2.1.33':
|
'@iconify/utils@3.0.1':
|
||||||
resolution: {integrity: sha512-jP9h6v/g0BIZx0p7XGJJVtkVnydtbgTgt9mVNcGDYwaa7UhdHdI9dvoq+gKj9sijMSJKxUPEG2JyjsgXjxL7Kw==}
|
resolution: {integrity: sha512-A78CUEnFGX8I/WlILxJCuIJXloL0j/OJ9PSchPAfCargEIKmUBWvvEMmKWB5oONwiUqlNt+5eRufdkLxeHIWYw==}
|
||||||
|
|
||||||
'@inquirer/checkbox@4.1.2':
|
'@inquirer/checkbox@4.1.2':
|
||||||
resolution: {integrity: sha512-PL9ixC5YsPXzXhAZFUPmkXGxfgjkdfZdPEPPmt4kFwQ4LBMDG9n/nHXYRGGZSKZJs+d1sGKWgS2GiPzVRKUdtQ==}
|
resolution: {integrity: sha512-PL9ixC5YsPXzXhAZFUPmkXGxfgjkdfZdPEPPmt4kFwQ4LBMDG9n/nHXYRGGZSKZJs+d1sGKWgS2GiPzVRKUdtQ==}
|
||||||
@ -2614,8 +2614,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
|
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@mermaid-js/parser@0.4.0':
|
'@mermaid-js/parser@0.6.2':
|
||||||
resolution: {integrity: sha512-wla8XOWvQAwuqy+gxiZqY+c7FokraOTHRWMsbB4AgRx9Sy7zKslNyejy7E+a77qHfey5GXw/ik3IXv/NHMJgaA==}
|
resolution: {integrity: sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ==}
|
||||||
|
|
||||||
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2':
|
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2':
|
||||||
resolution: {integrity: sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==}
|
resolution: {integrity: sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==}
|
||||||
@ -4786,6 +4786,11 @@ packages:
|
|||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
acorn@8.15.0:
|
||||||
|
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
address@1.2.2:
|
address@1.2.2:
|
||||||
resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==}
|
resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
@ -5338,6 +5343,9 @@ packages:
|
|||||||
confbox@0.1.8:
|
confbox@0.1.8:
|
||||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||||
|
|
||||||
|
confbox@0.2.2:
|
||||||
|
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
|
||||||
|
|
||||||
config-chain@1.1.13:
|
config-chain@1.1.13:
|
||||||
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
|
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
|
||||||
|
|
||||||
@ -5674,6 +5682,15 @@ packages:
|
|||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
debug@4.4.1:
|
||||||
|
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
|
||||||
decamelize@1.2.0:
|
decamelize@1.2.0:
|
||||||
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -6098,6 +6115,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
|
resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
|
||||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
|
|
||||||
|
exsolve@1.0.7:
|
||||||
|
resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==}
|
||||||
|
|
||||||
external-editor@3.1.0:
|
external-editor@3.1.0:
|
||||||
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
|
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -7236,8 +7256,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
|
resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
|
||||||
engines: {node: '>=6.11.5'}
|
engines: {node: '>=6.11.5'}
|
||||||
|
|
||||||
local-pkg@0.5.1:
|
local-pkg@1.1.2:
|
||||||
resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==}
|
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
locate-path@5.0.0:
|
locate-path@5.0.0:
|
||||||
@ -7422,8 +7442,8 @@ packages:
|
|||||||
mermaid@10.9.3:
|
mermaid@10.9.3:
|
||||||
resolution: {integrity: sha512-V80X1isSEvAewIL3xhmz/rVmc27CVljcsbWxkxlWJWY/1kQa4XOABqpDl2qQLGKzpKm6WbTfUEKImBlUfFYArw==}
|
resolution: {integrity: sha512-V80X1isSEvAewIL3xhmz/rVmc27CVljcsbWxkxlWJWY/1kQa4XOABqpDl2qQLGKzpKm6WbTfUEKImBlUfFYArw==}
|
||||||
|
|
||||||
mermaid@11.6.0:
|
mermaid@11.11.0:
|
||||||
resolution: {integrity: sha512-PE8hGUy1LDlWIHWBP05SFdqUHGmRcCcK4IzpOKPE35eOw+G9zZgcnMpyunJVUEOgb//KBORPjysKndw8bFLuRg==}
|
resolution: {integrity: sha512-9lb/VNkZqWTRjVgCV+l1N+t4kyi94y+l5xrmBmbbxZYkfRl5hEDaTPMOcaWKCl1McG8nBEaMlWwkcAEEgjhBgg==}
|
||||||
|
|
||||||
methods@1.1.2:
|
methods@1.1.2:
|
||||||
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
|
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
|
||||||
@ -7577,8 +7597,8 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
mlly@1.7.3:
|
mlly@1.8.0:
|
||||||
resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==}
|
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
|
||||||
|
|
||||||
mri@1.2.0:
|
mri@1.2.0:
|
||||||
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||||
@ -7912,8 +7932,8 @@ packages:
|
|||||||
package-json-from-dist@1.0.0:
|
package-json-from-dist@1.0.0:
|
||||||
resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==}
|
resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==}
|
||||||
|
|
||||||
package-manager-detector@0.2.5:
|
package-manager-detector@1.3.0:
|
||||||
resolution: {integrity: sha512-3dS7y28uua+UDbRCLBqltMBrbI+A5U2mI9YuxHRxIWYmLj3DwntEBmERYzIAQ4DMeuCUOBSak7dBHHoXKpOTYQ==}
|
resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==}
|
||||||
|
|
||||||
pako@1.0.11:
|
pako@1.0.11:
|
||||||
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
||||||
@ -8001,8 +8021,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
pathe@1.1.2:
|
pathe@2.0.3:
|
||||||
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
|
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||||
|
|
||||||
pause@0.0.1:
|
pause@0.0.1:
|
||||||
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
|
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
|
||||||
@ -8119,8 +8139,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
|
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
pkg-types@1.2.1:
|
pkg-types@1.3.1:
|
||||||
resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==}
|
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
||||||
|
|
||||||
|
pkg-types@2.3.0:
|
||||||
|
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
|
||||||
|
|
||||||
pluralize@8.0.0:
|
pluralize@8.0.0:
|
||||||
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
|
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
|
||||||
@ -8388,6 +8411,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==}
|
resolution: {integrity: sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
|
quansync@0.2.11:
|
||||||
|
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
|
||||||
|
|
||||||
queue-microtask@1.2.3:
|
queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
@ -9133,8 +9159,8 @@ packages:
|
|||||||
thread-stream@3.0.2:
|
thread-stream@3.0.2:
|
||||||
resolution: {integrity: sha512-cBL4xF2A3lSINV4rD5tyqnKH4z/TgWPvT+NaVhJDSwK962oo/Ye7cHSMbDzwcu7tAE1SfU6Q4XtV6Hucmi6Hlw==}
|
resolution: {integrity: sha512-cBL4xF2A3lSINV4rD5tyqnKH4z/TgWPvT+NaVhJDSwK962oo/Ye7cHSMbDzwcu7tAE1SfU6Q4XtV6Hucmi6Hlw==}
|
||||||
|
|
||||||
tinyexec@0.3.1:
|
tinyexec@1.0.1:
|
||||||
resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==}
|
resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
|
||||||
|
|
||||||
tinyglobby@0.2.12:
|
tinyglobby@0.2.12:
|
||||||
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
|
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
|
||||||
@ -9379,8 +9405,8 @@ packages:
|
|||||||
uc.micro@2.1.0:
|
uc.micro@2.1.0:
|
||||||
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||||
|
|
||||||
ufo@1.5.4:
|
ufo@1.6.1:
|
||||||
resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
|
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
|
||||||
|
|
||||||
uid2@0.0.4:
|
uid2@0.0.4:
|
||||||
resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==}
|
resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==}
|
||||||
@ -9965,12 +9991,12 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- chokidar
|
- chokidar
|
||||||
|
|
||||||
'@antfu/install-pkg@0.4.1':
|
'@antfu/install-pkg@1.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
package-manager-detector: 0.2.5
|
package-manager-detector: 1.3.0
|
||||||
tinyexec: 0.3.1
|
tinyexec: 1.0.1
|
||||||
|
|
||||||
'@antfu/utils@0.7.10': {}
|
'@antfu/utils@9.2.0': {}
|
||||||
|
|
||||||
'@asamuzakjp/css-color@2.8.3':
|
'@asamuzakjp/css-color@2.8.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12190,15 +12216,16 @@ snapshots:
|
|||||||
|
|
||||||
'@iconify/types@2.0.0': {}
|
'@iconify/types@2.0.0': {}
|
||||||
|
|
||||||
'@iconify/utils@2.1.33':
|
'@iconify/utils@3.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@antfu/install-pkg': 0.4.1
|
'@antfu/install-pkg': 1.1.0
|
||||||
'@antfu/utils': 0.7.10
|
'@antfu/utils': 9.2.0
|
||||||
'@iconify/types': 2.0.0
|
'@iconify/types': 2.0.0
|
||||||
debug: 4.4.0
|
debug: 4.4.1
|
||||||
|
globals: 15.15.0
|
||||||
kolorist: 1.8.0
|
kolorist: 1.8.0
|
||||||
local-pkg: 0.5.1
|
local-pkg: 1.1.2
|
||||||
mlly: 1.7.3
|
mlly: 1.8.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -12661,7 +12688,7 @@ snapshots:
|
|||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@mermaid-js/parser@0.4.0':
|
'@mermaid-js/parser@0.6.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
langium: 3.3.1
|
langium: 3.3.1
|
||||||
|
|
||||||
@ -14971,6 +14998,8 @@ snapshots:
|
|||||||
|
|
||||||
acorn@8.14.0: {}
|
acorn@8.14.0: {}
|
||||||
|
|
||||||
|
acorn@8.15.0: {}
|
||||||
|
|
||||||
address@1.2.2: {}
|
address@1.2.2: {}
|
||||||
|
|
||||||
agent-base@6.0.2:
|
agent-base@6.0.2:
|
||||||
@ -15637,6 +15666,8 @@ snapshots:
|
|||||||
|
|
||||||
confbox@0.1.8: {}
|
confbox@0.1.8: {}
|
||||||
|
|
||||||
|
confbox@0.2.2: {}
|
||||||
|
|
||||||
config-chain@1.1.13:
|
config-chain@1.1.13:
|
||||||
dependencies:
|
dependencies:
|
||||||
ini: 1.3.8
|
ini: 1.3.8
|
||||||
@ -16006,6 +16037,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
|
debug@4.4.1:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.3
|
||||||
|
|
||||||
decamelize@1.2.0: {}
|
decamelize@1.2.0: {}
|
||||||
|
|
||||||
decimal.js@10.4.3: {}
|
decimal.js@10.4.3: {}
|
||||||
@ -16589,6 +16624,8 @@ snapshots:
|
|||||||
jest-message-util: 29.7.0
|
jest-message-util: 29.7.0
|
||||||
jest-util: 29.7.0
|
jest-util: 29.7.0
|
||||||
|
|
||||||
|
exsolve@1.0.7: {}
|
||||||
|
|
||||||
external-editor@3.1.0:
|
external-editor@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
chardet: 0.7.0
|
chardet: 0.7.0
|
||||||
@ -17957,10 +17994,11 @@ snapshots:
|
|||||||
|
|
||||||
loader-runner@4.3.0: {}
|
loader-runner@4.3.0: {}
|
||||||
|
|
||||||
local-pkg@0.5.1:
|
local-pkg@1.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
mlly: 1.7.3
|
mlly: 1.8.0
|
||||||
pkg-types: 1.2.1
|
pkg-types: 2.3.0
|
||||||
|
quansync: 0.2.11
|
||||||
|
|
||||||
locate-path@5.0.0:
|
locate-path@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -18163,11 +18201,11 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
mermaid@11.6.0:
|
mermaid@11.11.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@braintree/sanitize-url': 7.1.0
|
'@braintree/sanitize-url': 7.1.0
|
||||||
'@iconify/utils': 2.1.33
|
'@iconify/utils': 3.0.1
|
||||||
'@mermaid-js/parser': 0.4.0
|
'@mermaid-js/parser': 0.6.2
|
||||||
'@types/d3': 7.4.3
|
'@types/d3': 7.4.3
|
||||||
cytoscape: 3.30.2
|
cytoscape: 3.30.2
|
||||||
cytoscape-cose-bilkent: 4.1.0(cytoscape@3.30.2)
|
cytoscape-cose-bilkent: 4.1.0(cytoscape@3.30.2)
|
||||||
@ -18391,12 +18429,12 @@ snapshots:
|
|||||||
|
|
||||||
mkdirp@1.0.4: {}
|
mkdirp@1.0.4: {}
|
||||||
|
|
||||||
mlly@1.7.3:
|
mlly@1.8.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.14.0
|
acorn: 8.15.0
|
||||||
pathe: 1.1.2
|
pathe: 2.0.3
|
||||||
pkg-types: 1.2.1
|
pkg-types: 1.3.1
|
||||||
ufo: 1.5.4
|
ufo: 1.6.1
|
||||||
|
|
||||||
mri@1.2.0: {}
|
mri@1.2.0: {}
|
||||||
|
|
||||||
@ -18755,7 +18793,7 @@ snapshots:
|
|||||||
|
|
||||||
package-json-from-dist@1.0.0: {}
|
package-json-from-dist@1.0.0: {}
|
||||||
|
|
||||||
package-manager-detector@0.2.5: {}
|
package-manager-detector@1.3.0: {}
|
||||||
|
|
||||||
pako@1.0.11: {}
|
pako@1.0.11: {}
|
||||||
|
|
||||||
@ -18846,7 +18884,7 @@ snapshots:
|
|||||||
|
|
||||||
path-type@4.0.0: {}
|
path-type@4.0.0: {}
|
||||||
|
|
||||||
pathe@1.1.2: {}
|
pathe@2.0.3: {}
|
||||||
|
|
||||||
pause@0.0.1: {}
|
pause@0.0.1: {}
|
||||||
|
|
||||||
@ -18963,11 +19001,17 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
find-up: 4.1.0
|
find-up: 4.1.0
|
||||||
|
|
||||||
pkg-types@1.2.1:
|
pkg-types@1.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
confbox: 0.1.8
|
confbox: 0.1.8
|
||||||
mlly: 1.7.3
|
mlly: 1.8.0
|
||||||
pathe: 1.1.2
|
pathe: 2.0.3
|
||||||
|
|
||||||
|
pkg-types@2.3.0:
|
||||||
|
dependencies:
|
||||||
|
confbox: 0.2.2
|
||||||
|
exsolve: 1.0.7
|
||||||
|
pathe: 2.0.3
|
||||||
|
|
||||||
pluralize@8.0.0: {}
|
pluralize@8.0.0: {}
|
||||||
|
|
||||||
@ -19248,6 +19292,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
side-channel: 1.0.6
|
side-channel: 1.0.6
|
||||||
|
|
||||||
|
quansync@0.2.11: {}
|
||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
quick-format-unescaped@4.0.4: {}
|
quick-format-unescaped@4.0.4: {}
|
||||||
@ -20090,7 +20136,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
real-require: 0.2.0
|
real-require: 0.2.0
|
||||||
|
|
||||||
tinyexec@0.3.1: {}
|
tinyexec@1.0.1: {}
|
||||||
|
|
||||||
tinyglobby@0.2.12:
|
tinyglobby@0.2.12:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -20348,7 +20394,7 @@ snapshots:
|
|||||||
|
|
||||||
uc.micro@2.1.0: {}
|
uc.micro@2.1.0: {}
|
||||||
|
|
||||||
ufo@1.5.4: {}
|
ufo@1.6.1: {}
|
||||||
|
|
||||||
uid2@0.0.4: {}
|
uid2@0.0.4: {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user