mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 14:43:06 +08:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7778482377 | |||
| 674769df02 | |||
| de57d05199 | |||
| 89ec990232 | |||
| 49d0f1cc9a | |||
| 268001ae26 | |||
| 27fa45a769 | |||
| f9711918a3 | |||
| 29bb52db0c | |||
| f2241db5ee | |||
| 58d1855a36 | |||
| 7fe3c5f177 | |||
| 5fd477d074 | |||
| 4aa5d7e326 | |||
| 7f7f2bccd0 | |||
| a9f370660b | |||
| 117c7049ff | |||
| cd10365f71 | |||
| ee30d9d0f2 | |||
| 276ececbf2 | |||
| fa194a497c | |||
| 1eaba6e77f | |||
| 651e5f6153 | |||
| 7431804a46 | |||
| 3559358d14 | |||
| 06270ff747 | |||
| 233536314f | |||
| 17ce3bab8a | |||
| b27d1708b0 | |||
| 64f0531093 | |||
| 8aa604637e | |||
| 7ca2b437d4 | |||
| 595bd1dc81 | |||
| a74d3feae4 |
@@ -9,9 +9,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> Docmost is currently in **beta**. We value your feedback as we progress towards a stable release.
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
To get started with Docmost, please refer to our [documentation](https://docmost.com/docs).
|
To get started with Docmost, please refer to our [documentation](https://docmost.com/docs).
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.9.0",
|
"version": "0.10.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
"jotai": "^2.12.1",
|
"jotai": "^2.12.1",
|
||||||
"jotai-optics": "^0.4.0",
|
"jotai-optics": "^0.4.0",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
"katex": "0.16.21",
|
"katex": "0.16.21",
|
||||||
"lowlight": "^3.2.0",
|
"lowlight": "^3.2.0",
|
||||||
"mermaid": "^11.4.1",
|
"mermaid": "^11.4.1",
|
||||||
|
|||||||
@@ -351,5 +351,16 @@
|
|||||||
"Created at: {{time}}": "Erstellt am: {{time}}",
|
"Created at: {{time}}": "Erstellt am: {{time}}",
|
||||||
"Edited by {{name}} {{time}}": "Bearbeitet von {{name}} {{time}}",
|
"Edited by {{name}} {{time}}": "Bearbeitet von {{name}} {{time}}",
|
||||||
"Word count: {{wordCount}}": "Wortanzahl: {{wordCount}}",
|
"Word count: {{wordCount}}": "Wortanzahl: {{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Zeichenzahl: {{characterCount}}"
|
"Character count: {{characterCount}}": "Zeichenzahl: {{characterCount}}",
|
||||||
|
"New update": "Neues Update",
|
||||||
|
"{{latestVersion}} is available": "{{latestVersion}} ist verfügbar",
|
||||||
|
"Delete member": "Mitglied löschen",
|
||||||
|
"Member deleted successfully": "Mitglied erfolgreich gelöscht",
|
||||||
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "Sind Sie sicher, dass Sie dieses Arbeitsbereichsmitglied löschen möchten? Diese Aktion ist unwiderruflich.",
|
||||||
|
"Move": "Verschieben",
|
||||||
|
"Move page": "Seite verschieben",
|
||||||
|
"Move page to a different space.": "Seite in einen anderen Bereich verschieben.",
|
||||||
|
"Real-time editor connection lost. Retrying...": "Echtzeit-Editor-Verbindung verloren. Wiederholen...",
|
||||||
|
"Table of contents": "Inhaltsverzeichnis",
|
||||||
|
"Add headings (H1, H2, H3) to generate a table of contents.": "Fügen Sie Überschriften (H1, H2, H3) hinzu, um ein Inhaltsverzeichnis zu erstellen."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -353,5 +353,14 @@
|
|||||||
"Word count: {{wordCount}}": "Word count: {{wordCount}}",
|
"Word count: {{wordCount}}": "Word count: {{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Character count: {{characterCount}}",
|
"Character count: {{characterCount}}": "Character count: {{characterCount}}",
|
||||||
"New update": "New update",
|
"New update": "New update",
|
||||||
"{{latestVersion}} is available": "{{latestVersion}} is available"
|
"{{latestVersion}} is available": "{{latestVersion}} is available",
|
||||||
|
"Delete member": "Delete member",
|
||||||
|
"Member deleted successfully": "Member deleted successfully",
|
||||||
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "Are you sure you want to delete this workspace member? This action is irreversible.",
|
||||||
|
"Move": "Move",
|
||||||
|
"Move page": "Move page",
|
||||||
|
"Move page to a different space.": "Move page to a different space.",
|
||||||
|
"Real-time editor connection lost. Retrying...": "Real-time editor connection lost. Retrying...",
|
||||||
|
"Table of contents": "Table of contents",
|
||||||
|
"Add headings (H1, H2, H3) to generate a table of contents.": "Add headings (H1, H2, H3) to generate a table of contents."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -351,5 +351,16 @@
|
|||||||
"Created at: {{time}}": "Creado a: {{time}}",
|
"Created at: {{time}}": "Creado a: {{time}}",
|
||||||
"Edited by {{name}} {{time}}": "Editado por {{name}} {{time}}",
|
"Edited by {{name}} {{time}}": "Editado por {{name}} {{time}}",
|
||||||
"Word count: {{wordCount}}": "Conteo de palabras: {{wordCount}}",
|
"Word count: {{wordCount}}": "Conteo de palabras: {{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Recuento de caracteres: {{characterCount}}"
|
"Character count: {{characterCount}}": "Recuento de caracteres: {{characterCount}}",
|
||||||
|
"New update": "Nueva actualización",
|
||||||
|
"{{latestVersion}} is available": "{{latestVersion}} está disponible",
|
||||||
|
"Delete member": "Eliminar miembro",
|
||||||
|
"Member deleted successfully": "Miembro eliminado con éxito",
|
||||||
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "¿Está seguro que desea eliminar este miembro del área de trabajo? Esta acción es irreversible.",
|
||||||
|
"Move": "Mover",
|
||||||
|
"Move page": "Mover página",
|
||||||
|
"Move page to a different space.": "Mover página a un espacio diferente.",
|
||||||
|
"Real-time editor connection lost. Retrying...": "Conexión del editor en tiempo real perdida. Reintentando...",
|
||||||
|
"Table of contents": "Índice de contenidos",
|
||||||
|
"Add headings (H1, H2, H3) to generate a table of contents.": "Añadir encabezados (H1, H2, H3) para generar un índice de contenidos."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"Can view": "Peut voir",
|
"Can view": "Peut voir",
|
||||||
"Can view pages in space but not edit.": "Peut voir les pages dans l'espace mais ne peut pas les modifier.",
|
"Can view pages in space but not edit.": "Peut voir les pages dans l'espace mais ne peut pas les modifier.",
|
||||||
"Cancel": "Annuler",
|
"Cancel": "Annuler",
|
||||||
"Change email": "Changer l'email",
|
"Change email": "Changer le courriel",
|
||||||
"Change password": "Changer le mot de passe",
|
"Change password": "Changer le mot de passe",
|
||||||
"Change photo": "Changer la photo",
|
"Change photo": "Changer la photo",
|
||||||
"Choose a role": "Choisir un rôle",
|
"Choose a role": "Choisir un rôle",
|
||||||
@@ -351,5 +351,16 @@
|
|||||||
"Created at: {{time}}": "Créé à : {{time}}",
|
"Created at: {{time}}": "Créé à : {{time}}",
|
||||||
"Edited by {{name}} {{time}}": "Modifié par {{name}} {{time}}",
|
"Edited by {{name}} {{time}}": "Modifié par {{name}} {{time}}",
|
||||||
"Word count: {{wordCount}}": "Nombre de mots : {{wordCount}}",
|
"Word count: {{wordCount}}": "Nombre de mots : {{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Nombre de caractères : {{characterCount}}"
|
"Character count: {{characterCount}}": "Nombre de caractères : {{characterCount}}",
|
||||||
|
"New update": "Nouvelle mise à jour",
|
||||||
|
"{{latestVersion}} is available": "{{latestVersion}} est disponible",
|
||||||
|
"Delete member": "Supprimer le membre",
|
||||||
|
"Member deleted successfully": "Membre supprimé avec succès",
|
||||||
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "Êtes-vous sûr de vouloir supprimer ce membre de l'espace de travail? Cette action est irréversible.",
|
||||||
|
"Move": "Déplacer",
|
||||||
|
"Move page": "Déplacer la page",
|
||||||
|
"Move page to a different space.": "Déplacer la page vers un autre espace.",
|
||||||
|
"Real-time editor connection lost. Retrying...": "Connexion avec l'éditeur en temps réel perdue. Nouvelle tentative...",
|
||||||
|
"Table of contents": "",
|
||||||
|
"Add headings (H1, H2, H3) to generate a table of contents.": "Ajoutez des titres (H1, H2, H3) pour générer une table des matières."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -347,9 +347,20 @@
|
|||||||
"Members added successfully": "Membri aggiunti con successo",
|
"Members added successfully": "Membri aggiunti con successo",
|
||||||
"Member removed successfully": "Membro rimosso con successo",
|
"Member removed successfully": "Membro rimosso con successo",
|
||||||
"Member role updated successfully": "Ruolo del membro aggiornato con successo",
|
"Member role updated successfully": "Ruolo del membro aggiornato con successo",
|
||||||
"Created by: <b>{{creatorName}}</b>": "Created by: <b>{{creatorName}}</b>",
|
"Created by: <b>{{creatorName}}</b>": "Creato da: <b>{{creatorName}}</b>",
|
||||||
"Created at: {{time}}": "Created at: {{time}}",
|
"Created at: {{time}}": "Creato il: {{time}}",
|
||||||
"Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}",
|
"Edited by {{name}} {{time}}": "Modificato da {{name}} il {{time}}",
|
||||||
"Word count: {{wordCount}}": "Word count: {{wordCount}}",
|
"Word count: {{wordCount}}": "Conteggio parole: {{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Character count: {{characterCount}}"
|
"Character count: {{characterCount}}": "Conteggio caratteri: {{characterCount}}",
|
||||||
|
"New update": "Nuovo aggiornamento",
|
||||||
|
"{{latestVersion}} is available": "{{latestVersion}} è disponibile",
|
||||||
|
"Delete member": "Elimina membro",
|
||||||
|
"Member deleted successfully": "Membro eliminato con successo",
|
||||||
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "Sei sicuro di voler eliminare questo membro del workspace? Questa azione è irreversibile.",
|
||||||
|
"Move": "Sposta",
|
||||||
|
"Move page": "Sposta pagina",
|
||||||
|
"Move page to a different space.": "Sposta la pagina in un altro spazio.",
|
||||||
|
"Real-time editor connection lost. Retrying...": "Connessione all'editor in tempo reale persa. Riprovo...",
|
||||||
|
"Table of contents": "Indice dei contenuti",
|
||||||
|
"Add headings (H1, H2, H3) to generate a table of contents.": "Aggiungi intestazioni (H1, H2, H3) per generare un sommario."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -347,9 +347,20 @@
|
|||||||
"Members added successfully": "メンバーを追加しました",
|
"Members added successfully": "メンバーを追加しました",
|
||||||
"Member removed successfully": "メンバーが削除されました",
|
"Member removed successfully": "メンバーが削除されました",
|
||||||
"Member role updated successfully": "メンバーのロールを更新しました",
|
"Member role updated successfully": "メンバーのロールを更新しました",
|
||||||
"Created by: <b>{{creatorName}}</b>": "Created by: <b>{{creatorName}}</b>",
|
"Created by: <b>{{creatorName}}</b>": "作成者: <b>{{creatorName}}</b>",
|
||||||
"Created at: {{time}}": "Created at: {{time}}",
|
"Created at: {{time}}": "が作成しました:{{time}}",
|
||||||
"Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}",
|
"Edited by {{name}} {{time}}": "最終編集: {{name}} {{time}}",
|
||||||
"Word count: {{wordCount}}": "Word count: {{wordCount}}",
|
"Word count: {{wordCount}}": "ワード数: {{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Character count: {{characterCount}}"
|
"Character count: {{characterCount}}": "文字数: {{characterCount}}",
|
||||||
|
"New update": "新規更新",
|
||||||
|
"{{latestVersion}} is available": "{{latestVersion}}は利用可能です",
|
||||||
|
"Delete member": "メンバーを削除する",
|
||||||
|
"Member deleted successfully": "メンバーが削除されました",
|
||||||
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "ワークスペースメンバーを削除してもよろしいですか?この操作は元に戻せません。",
|
||||||
|
"Move": "移動",
|
||||||
|
"Move page": "ページを移動",
|
||||||
|
"Move page to a different space.": "ページを別のスペースに移動します。",
|
||||||
|
"Real-time editor connection lost. Retrying...": "リアルタイムエディターの接続が失われました。再試行しています…",
|
||||||
|
"Table of contents": "目次",
|
||||||
|
"Add headings (H1, H2, H3) to generate a table of contents.": "見出し(H1、H2、H3)を追加して目次を生成します。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
"Select role to assign to all invited members": "초대된 모든 사용자에게 할당할 역할 선택",
|
"Select role to assign to all invited members": "초대된 모든 사용자에게 할당할 역할 선택",
|
||||||
"Select theme": "배경 선택",
|
"Select theme": "배경 선택",
|
||||||
"Send invitation": "초대 보내기",
|
"Send invitation": "초대 보내기",
|
||||||
"Invitation sent": "Invitation sent",
|
"Invitation sent": "초대 발송 완료",
|
||||||
"Settings": "설정",
|
"Settings": "설정",
|
||||||
"Setup workspace": "Workspace 설정",
|
"Setup workspace": "Workspace 설정",
|
||||||
"Sign In": "로그인",
|
"Sign In": "로그인",
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
"Align left": "왼쪽 정렬",
|
"Align left": "왼쪽 정렬",
|
||||||
"Align right": "오른쪽 정렬",
|
"Align right": "오른쪽 정렬",
|
||||||
"Align center": "가운데 정렬",
|
"Align center": "가운데 정렬",
|
||||||
"Justify": "Justify",
|
"Justify": "정렬",
|
||||||
"Merge cells": "셀 병합",
|
"Merge cells": "셀 병합",
|
||||||
"Split cell": "셀 분할",
|
"Split cell": "셀 분할",
|
||||||
"Delete column": "열 삭제",
|
"Delete column": "열 삭제",
|
||||||
@@ -341,15 +341,26 @@
|
|||||||
"Names do not match": "이름이 일치하지 않습니다",
|
"Names do not match": "이름이 일치하지 않습니다",
|
||||||
"Today, {{time}}": "오늘, {{time}}",
|
"Today, {{time}}": "오늘, {{time}}",
|
||||||
"Yesterday, {{time}}": "어제, {{time}}",
|
"Yesterday, {{time}}": "어제, {{time}}",
|
||||||
"Space created successfully": "Space created successfully",
|
"Space created successfully": "공간 생성 완료",
|
||||||
"Space updated successfully": "Space updated successfully",
|
"Space updated successfully": "공간이 성공적으로 업데이트되었습니다",
|
||||||
"Space deleted successfully": "Space deleted successfully",
|
"Space deleted successfully": "스페이스 삭제 완료",
|
||||||
"Members added successfully": "Members added successfully",
|
"Members added successfully": "회원 추가 완료",
|
||||||
"Member removed successfully": "Member removed successfully",
|
"Member removed successfully": "멤버가 성공적으로 제거되었습니다",
|
||||||
"Member role updated successfully": "Member role updated successfully",
|
"Member role updated successfully": "회원 역할이 성공적으로 업데이트되었습니다",
|
||||||
"Created by: <b>{{creatorName}}</b>": "Created by: <b>{{creatorName}}</b>",
|
"Created by: <b>{{creatorName}}</b>": "작성자: <b>{{creatorName}}</b>",
|
||||||
"Created at: {{time}}": "Created at: {{time}}",
|
"Created at: {{time}}": "생성 날짜: {{time}}",
|
||||||
"Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}",
|
"Edited by {{name}} {{time}}": "{{name}}님이 편집함 {{time}}",
|
||||||
"Word count: {{wordCount}}": "Word count: {{wordCount}}",
|
"Word count: {{wordCount}}": "단어 수: {{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Character count: {{characterCount}}"
|
"Character count: {{characterCount}}": "문자 수: {{characterCount}}",
|
||||||
|
"New update": "새로운 업데이트",
|
||||||
|
"{{latestVersion}} is available": "{{latestVersion}}이 사용 가능합니다",
|
||||||
|
"Delete member": "회원 삭제",
|
||||||
|
"Member deleted successfully": "멤버가 성공적으로 제거되었습니다",
|
||||||
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "이 워크스페이스 멤버를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
|
||||||
|
"Move": "이동",
|
||||||
|
"Move page": "페이지 이동",
|
||||||
|
"Move page to a different space.": "페이지를 다른 공간으로 이동합니다.",
|
||||||
|
"Real-time editor connection lost. Retrying...": "실시간 편집기 연결이 끊어졌습니다. 재시도 중...",
|
||||||
|
"Table of contents": "목차",
|
||||||
|
"Add headings (H1, H2, H3) to generate a table of contents.": "목차를 생성하려면 제목 (H1, H2, H3)을 추가하세요."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -351,5 +351,16 @@
|
|||||||
"Created at: {{time}}": "Aangemaakt op: {{time}}",
|
"Created at: {{time}}": "Aangemaakt op: {{time}}",
|
||||||
"Edited by {{name}} {{time}}": "Bewerkt door {{name}} {{time}}",
|
"Edited by {{name}} {{time}}": "Bewerkt door {{name}} {{time}}",
|
||||||
"Word count: {{wordCount}}": "Aantal woorden: {{wordCount}}",
|
"Word count: {{wordCount}}": "Aantal woorden: {{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Aantal tekens: {{characterCount}}"
|
"Character count: {{characterCount}}": "Aantal tekens: {{characterCount}}",
|
||||||
|
"New update": "Nieuwe update",
|
||||||
|
"{{latestVersion}} is available": "{{latestVersion}} is beschikbaar",
|
||||||
|
"Delete member": "Verwijder lid",
|
||||||
|
"Member deleted successfully": "Lid succesvol verwijderd",
|
||||||
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "Weet u zeker dat u dit lid van de werkruimte wilt verwijderen? Deze actie kan niet ongedaan gemaakt worden.",
|
||||||
|
"Move": "Verplaatsen",
|
||||||
|
"Move page": "Pagina verplaatsen",
|
||||||
|
"Move page to a different space.": "Verplaats pagina naar een andere ruimte.",
|
||||||
|
"Real-time editor connection lost. Retrying...": "Realtime editorverbinding verloren. Opnieuw proberen...",
|
||||||
|
"Table of contents": "Inhoudsopgave",
|
||||||
|
"Add headings (H1, H2, H3) to generate a table of contents.": "Voeg koppen (H1, H2, H3) toe om een inhoudsopgave te genereren."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
"Select role to assign to all invited members": "Selecione a função para atribuir a todos os membros convidados",
|
"Select role to assign to all invited members": "Selecione a função para atribuir a todos os membros convidados",
|
||||||
"Select theme": "Selecionar tema",
|
"Select theme": "Selecionar tema",
|
||||||
"Send invitation": "Enviar convite",
|
"Send invitation": "Enviar convite",
|
||||||
"Invitation sent": "Invitation sent",
|
"Invitation sent": "Convite enviado",
|
||||||
"Settings": "Configurações",
|
"Settings": "Configurações",
|
||||||
"Setup workspace": "Configurar workspace",
|
"Setup workspace": "Configurar workspace",
|
||||||
"Sign In": "Entrar",
|
"Sign In": "Entrar",
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
"Align left": "Alinhar à esquerda",
|
"Align left": "Alinhar à esquerda",
|
||||||
"Align right": "Alinhar à direita",
|
"Align right": "Alinhar à direita",
|
||||||
"Align center": "Alinhar ao centro",
|
"Align center": "Alinhar ao centro",
|
||||||
"Justify": "Justify",
|
"Justify": "Justificar",
|
||||||
"Merge cells": "Mesclar células",
|
"Merge cells": "Mesclar células",
|
||||||
"Split cell": "Dividir célula",
|
"Split cell": "Dividir célula",
|
||||||
"Delete column": "Excluir coluna",
|
"Delete column": "Excluir coluna",
|
||||||
@@ -341,15 +341,26 @@
|
|||||||
"Names do not match": "Os nomes não coincidem",
|
"Names do not match": "Os nomes não coincidem",
|
||||||
"Today, {{time}}": "Hoje, {{time}}",
|
"Today, {{time}}": "Hoje, {{time}}",
|
||||||
"Yesterday, {{time}}": "Ontem, {{time}}",
|
"Yesterday, {{time}}": "Ontem, {{time}}",
|
||||||
"Space created successfully": "Space created successfully",
|
"Space created successfully": "Espaço criado com sucesso",
|
||||||
"Space updated successfully": "Space updated successfully",
|
"Space updated successfully": "Espaço atualizado com sucesso",
|
||||||
"Space deleted successfully": "Space deleted successfully",
|
"Space deleted successfully": "Espaço excluído com sucesso",
|
||||||
"Members added successfully": "Members added successfully",
|
"Members added successfully": "Membros adicionados com sucesso",
|
||||||
"Member removed successfully": "Member removed successfully",
|
"Member removed successfully": "Membro removido com sucesso",
|
||||||
"Member role updated successfully": "Member role updated successfully",
|
"Member role updated successfully": "Função do membro atualizada com sucesso",
|
||||||
"Created by: <b>{{creatorName}}</b>": "Created by: <b>{{creatorName}}</b>",
|
"Created by: <b>{{creatorName}}</b>": "Criado por: <b>{{creatorName}}</b>",
|
||||||
"Created at: {{time}}": "Created at: {{time}}",
|
"Created at: {{time}}": "Criado em: {{time}}",
|
||||||
"Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}",
|
"Edited by {{name}} {{time}}": "Editado por {{name}} {{time}}",
|
||||||
"Word count: {{wordCount}}": "Word count: {{wordCount}}",
|
"Word count: {{wordCount}}": "Contagem de palavras: {{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Character count: {{characterCount}}"
|
"Character count: {{characterCount}}": "Contagem de caracteres: {{characterCount}}",
|
||||||
|
"New update": "Nova atualização",
|
||||||
|
"{{latestVersion}} is available": "{{latestVersion}} está disponível",
|
||||||
|
"Delete member": "Excluir membro",
|
||||||
|
"Member deleted successfully": "Membro removido com sucesso",
|
||||||
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "Você tem certeza que deseja deletar este membro do workspace? Esta ação é irreversível.",
|
||||||
|
"Move": "Mover",
|
||||||
|
"Move page": "Mover página",
|
||||||
|
"Move page to a different space.": "Mover página para um espaço diferente.",
|
||||||
|
"Real-time editor connection lost. Retrying...": "Conexão do editor em tempo real perdida. Tentando novamente...",
|
||||||
|
"Table of contents": "Tabela de conteúdos",
|
||||||
|
"Add headings (H1, H2, H3) to generate a table of contents.": "Adicionar títulos (H1, H2, H3) para gerar uma tabela de conteúdo."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
"Select role to assign to all invited members": "Выберите роль для всех приглашённых участников",
|
"Select role to assign to all invited members": "Выберите роль для всех приглашённых участников",
|
||||||
"Select theme": "Выберите тему",
|
"Select theme": "Выберите тему",
|
||||||
"Send invitation": "Отправить приглашение",
|
"Send invitation": "Отправить приглашение",
|
||||||
"Invitation sent": "Invitation sent",
|
"Invitation sent": "Приглашение отправлено",
|
||||||
"Settings": "Настройки",
|
"Settings": "Настройки",
|
||||||
"Setup workspace": "Настроить рабочее пространство",
|
"Setup workspace": "Настроить рабочее пространство",
|
||||||
"Sign In": "Вход",
|
"Sign In": "Вход",
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
"Align left": "По левому краю",
|
"Align left": "По левому краю",
|
||||||
"Align right": "По правому краю",
|
"Align right": "По правому краю",
|
||||||
"Align center": "По центру",
|
"Align center": "По центру",
|
||||||
"Justify": "Justify",
|
"Justify": "По ширине",
|
||||||
"Merge cells": "Объединить ячейки",
|
"Merge cells": "Объединить ячейки",
|
||||||
"Split cell": "Разделить ячейку",
|
"Split cell": "Разделить ячейку",
|
||||||
"Delete column": "Удалить столбец",
|
"Delete column": "Удалить столбец",
|
||||||
@@ -331,25 +331,36 @@
|
|||||||
"Insert math equation": "Вставить математическое выражение",
|
"Insert math equation": "Вставить математическое выражение",
|
||||||
"Mermaid diagram": "Диаграмма Mermaid",
|
"Mermaid diagram": "Диаграмма Mermaid",
|
||||||
"Insert mermaid diagram": "Вставить диаграмму Mermaid",
|
"Insert mermaid diagram": "Вставить диаграмму Mermaid",
|
||||||
"Insert and design Drawio diagrams": "Вставьте и редактируйте диаграммы Draw.io",
|
"Insert and design Drawio diagrams": "Вставить и рисовать диаграммы Draw.io",
|
||||||
"Insert current date": "Вставить текущую дату",
|
"Insert current date": "Вставить текущую дату",
|
||||||
"Draw and sketch excalidraw diagrams": "Создайте и рисуйте диаграммы Excalidraw",
|
"Draw and sketch excalidraw diagrams": "Вставить и рисовать диаграммы Excalidraw",
|
||||||
"Multiple": "Несколько",
|
"Multiple": "Несколько",
|
||||||
"Heading {{level}}": "Заголовок {{level}}",
|
"Heading {{level}}": "Заголовок {{level}}",
|
||||||
"Toggle title": "Переключить заголовок",
|
"Toggle title": "Переключить заголовок",
|
||||||
"Write anything. Enter \"/\" for commands": "Пишите что угодно. Введите \"/\" для выбора команд",
|
"Write anything. Enter \"/\" for commands": "Начните писать. Введите \"/\" для списка команд",
|
||||||
"Names do not match": "Названия не совпадают",
|
"Names do not match": "Названия не совпадают",
|
||||||
"Today, {{time}}": "Сегодня, {{time}}",
|
"Today, {{time}}": "Сегодня, {{time}}",
|
||||||
"Yesterday, {{time}}": "Вчера, {{time}}",
|
"Yesterday, {{time}}": "Вчера, {{time}}",
|
||||||
"Space created successfully": "Space created successfully",
|
"Space created successfully": "Пространство успешно создано",
|
||||||
"Space updated successfully": "Space updated successfully",
|
"Space updated successfully": "Пространство успешно обновлено",
|
||||||
"Space deleted successfully": "Space deleted successfully",
|
"Space deleted successfully": "Пространство успешно удалено",
|
||||||
"Members added successfully": "Members added successfully",
|
"Members added successfully": "Участники успешно добавлены",
|
||||||
"Member removed successfully": "Member removed successfully",
|
"Member removed successfully": "Участник успешно удален",
|
||||||
"Member role updated successfully": "Member role updated successfully",
|
"Member role updated successfully": "Роль участника успешно обновлена",
|
||||||
"Created by: <b>{{creatorName}}</b>": "Created by: <b>{{creatorName}}</b>",
|
"Created by: <b>{{creatorName}}</b>": "Автор: <b>{{creatorName}}</b>",
|
||||||
"Created at: {{time}}": "Created at: {{time}}",
|
"Created at: {{time}}": "Дата создания: {{time}}",
|
||||||
"Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}",
|
"Edited by {{name}} {{time}}": "Изменено {{name}} {{time}}",
|
||||||
"Word count: {{wordCount}}": "Word count: {{wordCount}}",
|
"Word count: {{wordCount}}": "Количество слов: {{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Character count: {{characterCount}}"
|
"Character count: {{characterCount}}": "Количество символов: {{characterCount}}",
|
||||||
|
"New update": "Новое обновление",
|
||||||
|
"{{latestVersion}} is available": "Доступна новая версия {{latestVersion}}",
|
||||||
|
"Delete member": "Удалить участника",
|
||||||
|
"Member deleted successfully": "Участник успешно удален",
|
||||||
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "Вы уверены, что хотите удалить этого участника рабочей области? Это действие необратимо.",
|
||||||
|
"Move": "Переместить",
|
||||||
|
"Move page": "Переместить страницу",
|
||||||
|
"Move page to a different space.": "Переместите страницу в другое пространство.",
|
||||||
|
"Real-time editor connection lost. Retrying...": "Соединение с редактором в реальном времени потеряно. Повторная попытка...",
|
||||||
|
"Table of contents": "Содержание",
|
||||||
|
"Add headings (H1, H2, H3) to generate a table of contents.": "Добавьте заголовки (H1, H2, H3), чтобы создать оглавление."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
"Select role to assign to all invited members": "选择要分配给所有被邀请成员的角色",
|
"Select role to assign to all invited members": "选择要分配给所有被邀请成员的角色",
|
||||||
"Select theme": "选择主题",
|
"Select theme": "选择主题",
|
||||||
"Send invitation": "发送邀请",
|
"Send invitation": "发送邀请",
|
||||||
"Invitation sent": "Invitation sent",
|
"Invitation sent": "邀请邮件已发送",
|
||||||
"Settings": "设置",
|
"Settings": "设置",
|
||||||
"Setup workspace": "设置工作空间",
|
"Setup workspace": "设置工作空间",
|
||||||
"Sign In": "登录",
|
"Sign In": "登录",
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
"Align left": "靠左对齐",
|
"Align left": "靠左对齐",
|
||||||
"Align right": "靠右对齐",
|
"Align right": "靠右对齐",
|
||||||
"Align center": "居中对齐",
|
"Align center": "居中对齐",
|
||||||
"Justify": "Justify",
|
"Justify": "两端对齐",
|
||||||
"Merge cells": "合并单元格",
|
"Merge cells": "合并单元格",
|
||||||
"Split cell": "分割单元格",
|
"Split cell": "分割单元格",
|
||||||
"Delete column": "删除整列",
|
"Delete column": "删除整列",
|
||||||
@@ -341,15 +341,26 @@
|
|||||||
"Names do not match": "名称不匹配",
|
"Names do not match": "名称不匹配",
|
||||||
"Today, {{time}}": "今天,{{time}}",
|
"Today, {{time}}": "今天,{{time}}",
|
||||||
"Yesterday, {{time}}": "昨天,{{time}}",
|
"Yesterday, {{time}}": "昨天,{{time}}",
|
||||||
"Space created successfully": "Space created successfully",
|
"Space created successfully": "空间创建成功",
|
||||||
"Space updated successfully": "Space updated successfully",
|
"Space updated successfully": "空间更新成功",
|
||||||
"Space deleted successfully": "Space deleted successfully",
|
"Space deleted successfully": "空间已成功删除",
|
||||||
"Members added successfully": "Members added successfully",
|
"Members added successfully": "成员添加成功",
|
||||||
"Member removed successfully": "Member removed successfully",
|
"Member removed successfully": "成员移除成功",
|
||||||
"Member role updated successfully": "Member role updated successfully",
|
"Member role updated successfully": "成员角色更新成功",
|
||||||
"Created by: <b>{{creatorName}}</b>": "Created by: <b>{{creatorName}}</b>",
|
"Created by: <b>{{creatorName}}</b>": "创建者:<b>{{creatorName}}</b>",
|
||||||
"Created at: {{time}}": "Created at: {{time}}",
|
"Created at: {{time}}": "创建于:{{time}}",
|
||||||
"Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}",
|
"Edited by {{name}} {{time}}": "由{{name}} 编辑于 {{time}}",
|
||||||
"Word count: {{wordCount}}": "Word count: {{wordCount}}",
|
"Word count: {{wordCount}}": "字数:{{wordCount}}",
|
||||||
"Character count: {{characterCount}}": "Character count: {{characterCount}}"
|
"Character count: {{characterCount}}": "字符数:{{characterCount}}",
|
||||||
|
"New update": "新更新",
|
||||||
|
"{{latestVersion}} is available": "{{latestVersion}} 已经可以使用",
|
||||||
|
"Delete member": "删除成员",
|
||||||
|
"Member deleted successfully": "成员删除成功",
|
||||||
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "您确定要删除此工作区成员吗?此操作不可逆。",
|
||||||
|
"Move": "移动",
|
||||||
|
"Move page": "移动页面",
|
||||||
|
"Move page to a different space.": "将页面移动到不同的空间。",
|
||||||
|
"Real-time editor connection lost. Retrying...": "实时编辑器连接丢失。重试中……",
|
||||||
|
"Table of contents": "目录",
|
||||||
|
"Add headings (H1, H2, H3) to generate a table of contents.": "添加标题(H1,H2,H3)以生成目录。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,11 +65,12 @@ export default function ExportModal({
|
|||||||
yOffset="10vh"
|
yOffset="10vh"
|
||||||
xOffset={0}
|
xOffset={0}
|
||||||
mah={400}
|
mah={400}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<Modal.Overlay />
|
<Modal.Overlay />
|
||||||
<Modal.Content style={{ overflow: "hidden" }}>
|
<Modal.Content style={{ overflow: "hidden" }}>
|
||||||
<Modal.Header py={0}>
|
<Modal.Header py={0}>
|
||||||
<Modal.Title fw={500}>Export {type}</Modal.Title>
|
<Modal.Title fw={500}>{t(`Export ${type}`)}</Modal.Title>
|
||||||
<Modal.CloseButton />
|
<Modal.CloseButton />
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default function Paginate({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group mt="md">
|
<Group mt="md" justify="flex-end">
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="compact-sm"
|
size="compact-sm"
|
||||||
|
|||||||
@@ -4,10 +4,14 @@ import { useAtom } from "jotai";
|
|||||||
import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
|
import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { TableOfContents } from "@/features/editor/components/table-of-contents/table-of-contents.tsx";
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
|
import { pageEditorAtom } from "@/features/editor/atoms/editor-atoms.ts";
|
||||||
|
|
||||||
export default function Aside() {
|
export default function Aside() {
|
||||||
const [{ tab }] = useAtom(asideStateAtom);
|
const [{ tab }] = useAtom(asideStateAtom);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const pageEditor = useAtomValue(pageEditorAtom);
|
||||||
|
|
||||||
let title: string;
|
let title: string;
|
||||||
let component: ReactNode;
|
let component: ReactNode;
|
||||||
@@ -17,6 +21,10 @@ export default function Aside() {
|
|||||||
component = <CommentList />;
|
component = <CommentList />;
|
||||||
title = "Comments";
|
title = "Comments";
|
||||||
break;
|
break;
|
||||||
|
case "toc":
|
||||||
|
component = <TableOfContents editor={pageEditor} />;
|
||||||
|
title = "Table of contents";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
component = null;
|
component = null;
|
||||||
title = null;
|
title = null;
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ export const asideStateAtom = atom<AsideStateType>({
|
|||||||
isAsideOpen: false,
|
isAsideOpen: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sidebarWidthAtom = atomWithWebStorage<number>('sidebarWidth', 300);
|
export const sidebarWidthAtom = atomWithWebStorage<number>('sidebarWidth', 300);
|
||||||
@@ -35,6 +35,12 @@ export default function AppVersion() {
|
|||||||
position="middle-end"
|
position="middle-end"
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
disabled={!hasUpdate}
|
disabled={!hasUpdate}
|
||||||
|
onClick={() => {
|
||||||
|
window.open(
|
||||||
|
"https://github.com/docmost/docmost/releases",
|
||||||
|
"_blank",
|
||||||
|
);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ export function useCollabToken(): UseQueryResult<ICollabToken, Error> {
|
|||||||
queryKey: ["collab-token"],
|
queryKey: ["collab-token"],
|
||||||
queryFn: () => getCollabToken(),
|
queryFn: () => getCollabToken(),
|
||||||
staleTime: 20 * 60 * 60 * 1000, //20hrs
|
staleTime: 20 * 60 * 60 * 1000, //20hrs
|
||||||
refetchInterval: 12 * 60 * 60 * 1000, // 12hrs
|
//refetchInterval: 12 * 60 * 60 * 1000, // 12hrs
|
||||||
refetchIntervalInBackground: true,
|
//refetchIntervalInBackground: true,
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
retry: (failureCount, error) => {
|
retry: (failureCount, error) => {
|
||||||
|
|||||||
@@ -19,8 +19,7 @@
|
|||||||
box-shadow: 0 0 0 2px var(--mantine-color-blue-3);
|
box-shadow: 0 0 0 2px var(--mantine-color-blue-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror {
|
.ProseMirror :global(.ProseMirror){
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
@@ -29,7 +28,6 @@
|
|||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
font-size: 14px;
|
|
||||||
overflow: hidden auto;
|
overflow: hidden auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
|||||||
searchTerms: ["collapsible", "block", "toggle", "details", "expand"],
|
searchTerms: ["collapsible", "block", "toggle", "details", "expand"],
|
||||||
icon: IconCaretRightFilled,
|
icon: IconCaretRightFilled,
|
||||||
command: ({ editor, range }: CommandProps) =>
|
command: ({ editor, range }: CommandProps) =>
|
||||||
editor.chain().focus().deleteRange(range).toggleDetails().run(),
|
editor.chain().focus().deleteRange(range).setDetails().run(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Callout",
|
title: "Callout",
|
||||||
|
|||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
.headerPadding {
|
||||||
|
display: none;
|
||||||
|
top: calc(
|
||||||
|
var(--app-shell-header-offset, 0rem) + var(--app-shell-header-height, 0rem)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: start;
|
||||||
|
word-wrap: break-word;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--mantine-color-text);
|
||||||
|
font-size: var(--mantine-font-size-sm);
|
||||||
|
line-height: var(--mantine-line-height-sm);
|
||||||
|
padding: 6px;
|
||||||
|
border-top-right-radius: var(--mantine-radius-sm);
|
||||||
|
border-bottom-right-radius: var(--mantine-radius-sm);
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
@mixin hover {
|
||||||
|
background-color: light-dark(
|
||||||
|
var(--mantine-color-gray-2),
|
||||||
|
var(--mantine-color-dark-6)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $mantine-breakpoint-sm) {
|
||||||
|
& {
|
||||||
|
border: none !important;
|
||||||
|
padding-left: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkActive {
|
||||||
|
font-weight: 500;
|
||||||
|
border-left-color: light-dark(
|
||||||
|
var(--mantine-color-grey-5),
|
||||||
|
var(--mantine-color-grey-3)
|
||||||
|
);
|
||||||
|
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
||||||
|
|
||||||
|
&,
|
||||||
|
&:hover {
|
||||||
|
background-color: light-dark(
|
||||||
|
var(--mantine-color-gray-3),
|
||||||
|
var(--mantine-color-dark-5)
|
||||||
|
) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
import { NodePos, useEditor } from "@tiptap/react";
|
||||||
|
import { TextSelection } from "@tiptap/pm/state";
|
||||||
|
import React, { FC, useEffect, useRef, useState } from "react";
|
||||||
|
import classes from "./table-of-contents.module.css";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { Box, Text } from "@mantine/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
type TableOfContentsProps = {
|
||||||
|
editor: ReturnType<typeof useEditor>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HeadingLink = {
|
||||||
|
label: string;
|
||||||
|
level: number;
|
||||||
|
element: HTMLElement;
|
||||||
|
position: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const recalculateLinks = (nodePos: NodePos[]) => {
|
||||||
|
const nodes: HTMLElement[] = [];
|
||||||
|
|
||||||
|
const links: HeadingLink[] = Array.from(nodePos).reduce<HeadingLink[]>(
|
||||||
|
(acc, item) => {
|
||||||
|
const label = item.node.textContent;
|
||||||
|
const level = Number(item.node.attrs.level);
|
||||||
|
if (label.length && level <= 3) {
|
||||||
|
acc.push({
|
||||||
|
label,
|
||||||
|
level,
|
||||||
|
element: item.element,
|
||||||
|
//@ts-ignore
|
||||||
|
position: item.resolvedPos.pos,
|
||||||
|
});
|
||||||
|
nodes.push(item.element);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
return { links, nodes };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TableOfContents: FC<TableOfContentsProps> = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [links, setLinks] = useState<HeadingLink[]>([]);
|
||||||
|
const [headingDOMNodes, setHeadingDOMNodes] = useState<HTMLElement[]>([]);
|
||||||
|
const [activeElement, setActiveElement] = useState<HTMLElement | null>(null);
|
||||||
|
const headerPaddingRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
const handleScrollToHeading = (position: number) => {
|
||||||
|
const { view } = props.editor;
|
||||||
|
|
||||||
|
const headerOffset = parseInt(
|
||||||
|
window.getComputedStyle(headerPaddingRef.current).getPropertyValue("top"),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { node } = view.domAtPos(position);
|
||||||
|
const element = node as HTMLElement;
|
||||||
|
const scrollPosition =
|
||||||
|
element.getBoundingClientRect().top + window.scrollY - headerOffset;
|
||||||
|
|
||||||
|
window.scrollTo({
|
||||||
|
top: scrollPosition,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
|
||||||
|
const tr = view.state.tr;
|
||||||
|
tr.setSelection(new TextSelection(tr.doc.resolve(position)));
|
||||||
|
view.dispatch(tr);
|
||||||
|
view.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdate = () => {
|
||||||
|
const result = recalculateLinks(props.editor?.$nodes("heading"));
|
||||||
|
setLinks(result.links);
|
||||||
|
setHeadingDOMNodes(result.nodes);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
props.editor?.on("update", handleUpdate);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
props.editor?.off("update", handleUpdate);
|
||||||
|
};
|
||||||
|
}, [props.editor]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleUpdate();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
const observeHandler = (entries: IntersectionObserverEntry[]) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setActiveElement(entry.target as HTMLElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let headerOffset = 0;
|
||||||
|
if (headerPaddingRef.current) {
|
||||||
|
headerOffset = parseInt(
|
||||||
|
window
|
||||||
|
.getComputedStyle(headerPaddingRef.current)
|
||||||
|
.getPropertyValue("top"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const observerOptions: IntersectionObserverInit = {
|
||||||
|
rootMargin: `-${headerOffset}px 0px -85% 0px`,
|
||||||
|
threshold: 0,
|
||||||
|
root: null,
|
||||||
|
};
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
observeHandler,
|
||||||
|
observerOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
headingDOMNodes.forEach((heading) => {
|
||||||
|
observer.observe(heading);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
headingDOMNodes.forEach((heading) => {
|
||||||
|
observer.unobserve(heading);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}, [headingDOMNodes, props.editor]);
|
||||||
|
|
||||||
|
if (!links.length) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text size="sm">
|
||||||
|
{t("Add headings (H1, H2, H3) to generate a table of contents.")}
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
{links.map((item, idx) => (
|
||||||
|
<Box<"button">
|
||||||
|
component="button"
|
||||||
|
onClick={() => handleScrollToHeading(item.position)}
|
||||||
|
key={idx}
|
||||||
|
className={clsx(classes.link, {
|
||||||
|
[classes.linkActive]: item.element === activeElement,
|
||||||
|
})}
|
||||||
|
style={{
|
||||||
|
paddingLeft: `calc(${item.level} * var(--mantine-spacing-md))`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div ref={headerPaddingRef} className={classes.headerPadding} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -228,4 +228,4 @@ export const collabExtensions: CollabExtensions = (provider, user) => [
|
|||||||
color: randomElement(userColors),
|
color: randomElement(userColors),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
@@ -52,6 +52,7 @@ import { IPage } from "@/features/page/types/page.types.ts";
|
|||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { extractPageSlugId } from "@/lib";
|
import { extractPageSlugId } from "@/lib";
|
||||||
import { FIVE_MINUTES } from "@/lib/constants.ts";
|
import { FIVE_MINUTES } from "@/lib/constants.ts";
|
||||||
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
|
||||||
interface PageEditorProps {
|
interface PageEditorProps {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
@@ -83,7 +84,6 @@ export default function PageEditor({
|
|||||||
const documentState = useDocumentVisibility();
|
const documentState = useDocumentVisibility();
|
||||||
const [isCollabReady, setIsCollabReady] = useState(false);
|
const [isCollabReady, setIsCollabReady] = useState(false);
|
||||||
const { pageSlug } = useParams();
|
const { pageSlug } = useParams();
|
||||||
const collabRetryCount = useRef(0);
|
|
||||||
const slugId = extractPageSlugId(pageSlug);
|
const slugId = extractPageSlugId(pageSlug);
|
||||||
|
|
||||||
const localProvider = useMemo(() => {
|
const localProvider = useMemo(() => {
|
||||||
@@ -105,13 +105,11 @@ export default function PageEditor({
|
|||||||
connect: false,
|
connect: false,
|
||||||
preserveConnection: false,
|
preserveConnection: false,
|
||||||
onAuthenticationFailed: (auth: onAuthenticationFailedParameters) => {
|
onAuthenticationFailed: (auth: onAuthenticationFailedParameters) => {
|
||||||
collabRetryCount.current = collabRetryCount.current + 1;
|
const payload = jwtDecode(collabQuery?.token);
|
||||||
refetchCollabToken().then(() => {
|
const now = Date.now().valueOf() / 1000;
|
||||||
collabRetryCount.current = 0;
|
const isTokenExpired = now >= payload.exp;
|
||||||
});
|
if (isTokenExpired) {
|
||||||
|
refetchCollabToken();
|
||||||
if (collabRetryCount.current > 20) {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onStatus: (status) => {
|
onStatus: (status) => {
|
||||||
@@ -211,6 +209,7 @@ export default function PageEditor({
|
|||||||
queryClient.setQueryData(["pages", slugId], {
|
queryClient.setQueryData(["pages", slugId], {
|
||||||
...pageData,
|
...pageData,
|
||||||
content: newContent,
|
content: newContent,
|
||||||
|
updatedAt: new Date(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
@@ -265,17 +264,13 @@ export default function PageEditor({
|
|||||||
documentState === "visible" &&
|
documentState === "visible" &&
|
||||||
remoteProvider?.status === WebSocketStatus.Disconnected
|
remoteProvider?.status === WebSocketStatus.Disconnected
|
||||||
) {
|
) {
|
||||||
const reconnectTimeout = setTimeout(
|
resetIdle();
|
||||||
() => {
|
remoteProvider.connect();
|
||||||
remoteProvider.connect();
|
setTimeout(() => {
|
||||||
resetIdle();
|
setIsCollabReady(true);
|
||||||
},
|
}, 600);
|
||||||
collabRetryCount.current > 2 ? 3000 : 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => clearTimeout(reconnectTimeout);
|
|
||||||
}
|
}
|
||||||
}, [isIdle, documentState, remoteProvider?.status]);
|
}, [isIdle, documentState, remoteProvider]);
|
||||||
|
|
||||||
const isSynced = isLocalSynced && isRemoteSynced;
|
const isSynced = isLocalSynced && isRemoteSynced;
|
||||||
|
|
||||||
@@ -284,7 +279,7 @@ export default function PageEditor({
|
|||||||
if (
|
if (
|
||||||
!isCollabReady &&
|
!isCollabReady &&
|
||||||
isSynced &&
|
isSynced &&
|
||||||
remoteProvider.status === WebSocketStatus.Connected
|
remoteProvider?.status === WebSocketStatus.Connected
|
||||||
) {
|
) {
|
||||||
setIsCollabReady(true);
|
setIsCollabReady(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,24 +53,22 @@
|
|||||||
padding: 4px;
|
padding: 4px;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
|
|
||||||
[data-type="detailsContent"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[open] {
|
|
||||||
[data-type="detailsButton"] {
|
|
||||||
.ProseMirror-icon {
|
|
||||||
transform: rotateZ(90deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-type="detailsContainer"] {
|
|
||||||
[data-type="detailsContent"] {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
[data-type="details"] > [data-type="detailsContainer"] > [data-type="detailsContent"]{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-type="details"][open] > [data-type="detailsContainer"] > [data-type="detailsContent"]{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-type="details"][open] > [data-type="detailsButton"] {
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-type="details"][open] > [data-type="detailsButton"] .ProseMirror-icon{
|
||||||
|
transform: rotateZ(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ export async function getGroupMembers(
|
|||||||
groupId: string,
|
groupId: string,
|
||||||
params?: QueryParams,
|
params?: QueryParams,
|
||||||
): Promise<IPagination<IUser>> {
|
): Promise<IPagination<IUser>> {
|
||||||
const req = await api.post("/groups/members", { groupId, params });
|
const req = await api.post("/groups/members", { groupId, ...params });
|
||||||
return req.data;
|
return req.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ import {
|
|||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useSpaceAbility } from "@/features/space/permissions/use-space-ability.ts";
|
||||||
|
import { useSpaceQuery } from "@/features/space/queries/space-query.ts";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
SpaceCaslAction,
|
||||||
|
SpaceCaslSubject,
|
||||||
|
} from "@/features/space/permissions/permissions.type.ts";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
@@ -36,6 +43,11 @@ function HistoryList({ pageId }: Props) {
|
|||||||
const [mainEditorTitle] = useAtom(titleEditorAtom);
|
const [mainEditorTitle] = useAtom(titleEditorAtom);
|
||||||
const [, setHistoryModalOpen] = useAtom(historyAtoms);
|
const [, setHistoryModalOpen] = useAtom(historyAtoms);
|
||||||
|
|
||||||
|
const { spaceSlug } = useParams();
|
||||||
|
const { data: space } = useSpaceQuery(spaceSlug);
|
||||||
|
const spaceRules = space?.membership?.permissions;
|
||||||
|
const spaceAbility = useSpaceAbility(spaceRules);
|
||||||
|
|
||||||
const confirmModal = () =>
|
const confirmModal = () =>
|
||||||
modals.openConfirmModal({
|
modals.openConfirmModal({
|
||||||
title: t("Please confirm your action"),
|
title: t("Please confirm your action"),
|
||||||
@@ -103,20 +115,26 @@ function HistoryList({ pageId }: Props) {
|
|||||||
))}
|
))}
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
||||||
<Divider />
|
{spaceAbility.cannot(
|
||||||
|
SpaceCaslAction.Manage,
|
||||||
<Group p="xs" wrap="nowrap">
|
SpaceCaslSubject.Page,
|
||||||
<Button size="compact-md" onClick={confirmModal}>
|
) ? null : (
|
||||||
{t("Restore")}
|
<>
|
||||||
</Button>
|
<Divider />
|
||||||
<Button
|
<Group p="xs" wrap="nowrap">
|
||||||
variant="default"
|
<Button size="compact-md" onClick={confirmModal}>
|
||||||
size="compact-md"
|
{t("Restore")}
|
||||||
onClick={() => setHistoryModalOpen(false)}
|
</Button>
|
||||||
>
|
<Button
|
||||||
{t("Cancel")}
|
variant="default"
|
||||||
</Button>
|
size="compact-md"
|
||||||
</Group>
|
onClick={() => setHistoryModalOpen(false)}
|
||||||
|
>
|
||||||
|
{t("Cancel")}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import { ActionIcon, Group, Menu, Text, Tooltip } from "@mantine/core";
|
import { ActionIcon, Group, Menu, Text, Tooltip } from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
|
IconArrowRight,
|
||||||
IconArrowsHorizontal,
|
IconArrowsHorizontal,
|
||||||
IconDots,
|
IconDots,
|
||||||
IconFileExport,
|
IconFileExport,
|
||||||
IconHistory,
|
IconHistory,
|
||||||
IconLink,
|
IconLink,
|
||||||
|
IconList,
|
||||||
IconMessage,
|
IconMessage,
|
||||||
IconPrinter,
|
IconPrinter,
|
||||||
IconTrash,
|
IconTrash,
|
||||||
IconWifiOff,
|
IconWifiOff,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import useToggleAside from "@/hooks/use-toggle-aside.tsx";
|
import useToggleAside from "@/hooks/use-toggle-aside.tsx";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { historyAtoms } from "@/features/page-history/atoms/history-atoms.ts";
|
import { historyAtoms } from "@/features/page-history/atoms/history-atoms.ts";
|
||||||
@@ -31,11 +33,14 @@ import {
|
|||||||
yjsConnectionStatusAtom,
|
yjsConnectionStatusAtom,
|
||||||
} from "@/features/editor/atoms/editor-atoms.ts";
|
} from "@/features/editor/atoms/editor-atoms.ts";
|
||||||
import { formattedDate, timeAgo } from "@/lib/time.ts";
|
import { formattedDate, timeAgo } from "@/lib/time.ts";
|
||||||
|
import MovePageModal from "@/features/page/components/move-page-modal.tsx";
|
||||||
|
import { useTimeAgo } from "@/hooks/use-time-ago.tsx";
|
||||||
|
|
||||||
interface PageHeaderMenuProps {
|
interface PageHeaderMenuProps {
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
|
export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const toggleAside = useToggleAside();
|
const toggleAside = useToggleAside();
|
||||||
const [yjsConnectionStatus] = useAtom(yjsConnectionStatusAtom);
|
const [yjsConnectionStatus] = useAtom(yjsConnectionStatusAtom);
|
||||||
|
|
||||||
@@ -43,7 +48,7 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
|
|||||||
<>
|
<>
|
||||||
{yjsConnectionStatus === "disconnected" && (
|
{yjsConnectionStatus === "disconnected" && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label="Real-time editor connection lost. Retrying..."
|
label={t("Real-time editor connection lost. Retrying...")}
|
||||||
openDelay={250}
|
openDelay={250}
|
||||||
withArrow
|
withArrow
|
||||||
>
|
>
|
||||||
@@ -53,7 +58,7 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Tooltip label="Comments" openDelay={250} withArrow>
|
<Tooltip label={t("Comments")} openDelay={250} withArrow>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="default"
|
variant="default"
|
||||||
style={{ border: "none" }}
|
style={{ border: "none" }}
|
||||||
@@ -63,6 +68,16 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label={t("Table of contents")} openDelay={250} withArrow>
|
||||||
|
<ActionIcon
|
||||||
|
variant="default"
|
||||||
|
style={{ border: "none" }}
|
||||||
|
onClick={() => toggleAside("toc")}
|
||||||
|
>
|
||||||
|
<IconList size={20} stroke={2} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<PageActionMenu readOnly={readOnly} />
|
<PageActionMenu readOnly={readOnly} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -83,7 +98,12 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
|
|||||||
const [tree] = useAtom(treeApiAtom);
|
const [tree] = useAtom(treeApiAtom);
|
||||||
const [exportOpened, { open: openExportModal, close: closeExportModal }] =
|
const [exportOpened, { open: openExportModal, close: closeExportModal }] =
|
||||||
useDisclosure(false);
|
useDisclosure(false);
|
||||||
|
const [
|
||||||
|
movePageModalOpened,
|
||||||
|
{ open: openMovePageModal, close: closeMoveSpaceModal },
|
||||||
|
] = useDisclosure(false);
|
||||||
const [pageEditor] = useAtom(pageEditorAtom);
|
const [pageEditor] = useAtom(pageEditorAtom);
|
||||||
|
const pageUpdatedAt = useTimeAgo(page.updatedAt);
|
||||||
|
|
||||||
const handleCopyLink = () => {
|
const handleCopyLink = () => {
|
||||||
const pageUrl =
|
const pageUrl =
|
||||||
@@ -147,6 +167,15 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
|
|||||||
|
|
||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
|
|
||||||
|
{!readOnly && (
|
||||||
|
<Menu.Item
|
||||||
|
leftSection={<IconArrowRight size={16} />}
|
||||||
|
onClick={openMovePageModal}
|
||||||
|
>
|
||||||
|
{t("Move")}
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<IconFileExport size={16} />}
|
leftSection={<IconFileExport size={16} />}
|
||||||
onClick={openExportModal}
|
onClick={openExportModal}
|
||||||
@@ -181,7 +210,7 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
label={t("Edited by {{name}} {{time}}", {
|
label={t("Edited by {{name}} {{time}}", {
|
||||||
name: page.lastUpdatedBy.name,
|
name: page.lastUpdatedBy.name,
|
||||||
time: timeAgo(page.updatedAt),
|
time: pageUpdatedAt,
|
||||||
})}
|
})}
|
||||||
position="left-start"
|
position="left-start"
|
||||||
>
|
>
|
||||||
@@ -217,6 +246,14 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
|
|||||||
open={exportOpened}
|
open={exportOpened}
|
||||||
onClose={closeExportModal}
|
onClose={closeExportModal}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<MovePageModal
|
||||||
|
pageId={page.id}
|
||||||
|
slugId={page.slugId}
|
||||||
|
currentSpaceSlug={spaceSlug}
|
||||||
|
onClose={closeMoveSpaceModal}
|
||||||
|
open={movePageModalOpened}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { Modal, Button, Group, Text } from "@mantine/core";
|
||||||
|
import { movePageToSpace } from "@/features/page/services/page-service.ts";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ISpace } from "@/features/space/types/space.types.ts";
|
||||||
|
import { queryClient } from "@/main.tsx";
|
||||||
|
import { SpaceSelect } from "@/features/space/components/sidebar/space-select.tsx";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { buildPageUrl } from "@/features/page/page.utils.ts";
|
||||||
|
|
||||||
|
interface MovePageModalProps {
|
||||||
|
pageId: string;
|
||||||
|
slugId: string;
|
||||||
|
currentSpaceSlug: string;
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MovePageModal({
|
||||||
|
pageId,
|
||||||
|
slugId,
|
||||||
|
currentSpaceSlug,
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
}: MovePageModalProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [targetSpace, setTargetSpace] = useState<ISpace>(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handlePageMove = async () => {
|
||||||
|
if (!targetSpace) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await movePageToSpace({ pageId, spaceId: targetSpace.id });
|
||||||
|
queryClient.removeQueries({
|
||||||
|
predicate: (item) =>
|
||||||
|
["pages", "sidebar-pages", "root-sidebar-pages"].includes(
|
||||||
|
item.queryKey[0] as string,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const pageUrl = buildPageUrl(targetSpace.slug, slugId, undefined);
|
||||||
|
navigate(pageUrl);
|
||||||
|
notifications.show({
|
||||||
|
message: t("Page moved successfully"),
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
} catch (err) {
|
||||||
|
notifications.show({
|
||||||
|
message: err.response?.data.message || "An error occurred",
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
setTargetSpace(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (space: ISpace) => {
|
||||||
|
setTargetSpace(space);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal.Root
|
||||||
|
opened={open}
|
||||||
|
onClose={onClose}
|
||||||
|
size={500}
|
||||||
|
padding="xl"
|
||||||
|
yOffset="10vh"
|
||||||
|
xOffset={0}
|
||||||
|
mah={400}
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Modal.Overlay />
|
||||||
|
<Modal.Content style={{ overflow: "hidden" }}>
|
||||||
|
<Modal.Header py={0}>
|
||||||
|
<Modal.Title fw={500}>{t("Move page")}</Modal.Title>
|
||||||
|
<Modal.CloseButton />
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<Text mb="xs" c="dimmed" size="sm">{t("Move page to a different space.")}</Text>
|
||||||
|
|
||||||
|
<SpaceSelect
|
||||||
|
value={currentSpaceSlug}
|
||||||
|
clearable={false}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<Group justify="end" mt="md">
|
||||||
|
<Button onClick={onClose} variant="default">
|
||||||
|
{t("Cancel")}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handlePageMove}>{t("Move")}</Button>
|
||||||
|
</Group>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import api from "@/lib/api-client";
|
|||||||
import {
|
import {
|
||||||
IExportPageParams,
|
IExportPageParams,
|
||||||
IMovePage,
|
IMovePage,
|
||||||
|
IMovePageToSpace,
|
||||||
IPage,
|
IPage,
|
||||||
IPageInput,
|
IPageInput,
|
||||||
SidebarPagesParams,
|
SidebarPagesParams,
|
||||||
@@ -34,6 +35,10 @@ export async function movePage(data: IMovePage): Promise<void> {
|
|||||||
await api.post<void>("/pages/move", data);
|
await api.post<void>("/pages/move", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function movePageToSpace(data: IMovePageToSpace): Promise<void> {
|
||||||
|
await api.post<void>("/pages/move-to-space", data);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getSidebarPages(
|
export async function getSidebarPages(
|
||||||
params: SidebarPagesParams,
|
params: SidebarPagesParams,
|
||||||
): Promise<IPagination<IPage>> {
|
): Promise<IPagination<IPage>> {
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ import {
|
|||||||
usePageQuery,
|
usePageQuery,
|
||||||
useUpdatePageMutation,
|
useUpdatePageMutation,
|
||||||
} from "@/features/page/queries/page-query.ts";
|
} from "@/features/page/queries/page-query.ts";
|
||||||
import React, { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import classes from "@/features/page/tree/styles/tree.module.css";
|
import classes from "@/features/page/tree/styles/tree.module.css";
|
||||||
import { ActionIcon, Menu, rem } from "@mantine/core";
|
import { ActionIcon, Menu, rem } from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
|
IconArrowRight,
|
||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconChevronRight,
|
IconChevronRight,
|
||||||
IconDotsVertical,
|
IconDotsVertical,
|
||||||
@@ -56,6 +57,7 @@ import { extractPageSlugId } from "@/lib";
|
|||||||
import { useDeletePageModal } from "@/features/page/hooks/use-delete-page-modal.tsx";
|
import { useDeletePageModal } from "@/features/page/hooks/use-delete-page-modal.tsx";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import ExportModal from "@/components/common/export-modal";
|
import ExportModal from "@/components/common/export-modal";
|
||||||
|
import MovePageModal from "../../components/move-page-modal.tsx";
|
||||||
|
|
||||||
interface SpaceTreeProps {
|
interface SpaceTreeProps {
|
||||||
spaceId: string;
|
spaceId: string;
|
||||||
@@ -234,6 +236,7 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps<any>) {
|
|||||||
const emit = useQueryEmit();
|
const emit = useQueryEmit();
|
||||||
const { spaceSlug } = useParams();
|
const { spaceSlug } = useParams();
|
||||||
const timerRef = useRef(null);
|
const timerRef = useRef(null);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const prefetchPage = () => {
|
const prefetchPage = () => {
|
||||||
timerRef.current = setTimeout(() => {
|
timerRef.current = setTimeout(() => {
|
||||||
@@ -369,7 +372,7 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps<any>) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className={classes.text}>{node.data.name || "untitled"}</span>
|
<span className={classes.text}>{node.data.name || t("untitled")}</span>
|
||||||
|
|
||||||
<div className={classes.actions}>
|
<div className={classes.actions}>
|
||||||
<NodeMenu node={node} treeApi={tree} />
|
<NodeMenu node={node} treeApi={tree} />
|
||||||
@@ -434,6 +437,10 @@ function NodeMenu({ node, treeApi }: NodeMenuProps) {
|
|||||||
const { openDeleteModal } = useDeletePageModal();
|
const { openDeleteModal } = useDeletePageModal();
|
||||||
const [exportOpened, { open: openExportModal, close: closeExportModal }] =
|
const [exportOpened, { open: openExportModal, close: closeExportModal }] =
|
||||||
useDisclosure(false);
|
useDisclosure(false);
|
||||||
|
const [
|
||||||
|
movePageModalOpened,
|
||||||
|
{ open: openMovePageModal, close: closeMoveSpaceModal },
|
||||||
|
] = useDisclosure(false);
|
||||||
|
|
||||||
const handleCopyLink = () => {
|
const handleCopyLink = () => {
|
||||||
const pageUrl =
|
const pageUrl =
|
||||||
@@ -486,8 +493,18 @@ function NodeMenu({ node, treeApi }: NodeMenuProps) {
|
|||||||
|
|
||||||
{!(treeApi.props.disableEdit as boolean) && (
|
{!(treeApi.props.disableEdit as boolean) && (
|
||||||
<>
|
<>
|
||||||
<Menu.Divider />
|
<Menu.Item
|
||||||
|
leftSection={<IconArrowRight size={16} />}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
openMovePageModal();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("Move")}
|
||||||
|
</Menu.Item>
|
||||||
|
|
||||||
|
<Menu.Divider />
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
c="red"
|
c="red"
|
||||||
leftSection={<IconTrash size={16} />}
|
leftSection={<IconTrash size={16} />}
|
||||||
@@ -504,6 +521,14 @@ function NodeMenu({ node, treeApi }: NodeMenuProps) {
|
|||||||
</Menu.Dropdown>
|
</Menu.Dropdown>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
|
<MovePageModal
|
||||||
|
pageId={node.id}
|
||||||
|
slugId={node.data.slugId}
|
||||||
|
currentSpaceSlug={spaceSlug}
|
||||||
|
onClose={closeMoveSpaceModal}
|
||||||
|
open={movePageModalOpened}
|
||||||
|
/>
|
||||||
|
|
||||||
<ExportModal
|
<ExportModal
|
||||||
type="page"
|
type="page"
|
||||||
id={node.id}
|
id={node.id}
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ export interface IMovePage {
|
|||||||
parentPageId?: string;
|
parentPageId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IMovePageToSpace {
|
||||||
|
pageId: string;
|
||||||
|
spaceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SidebarPagesParams {
|
export interface SidebarPagesParams {
|
||||||
spaceId: string;
|
spaceId: string;
|
||||||
pageId?: string;
|
pageId?: string;
|
||||||
|
|||||||
@@ -6,21 +6,33 @@ import { ISpace } from "../../types/space.types";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface SpaceSelectProps {
|
interface SpaceSelectProps {
|
||||||
onChange: (value: string) => void;
|
onChange: (value: ISpace) => void;
|
||||||
value?: string;
|
value?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
width?: number;
|
||||||
|
opened?: boolean;
|
||||||
|
clearable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderSelectOption: SelectProps["renderOption"] = ({ option }) => (
|
const renderSelectOption: SelectProps["renderOption"] = ({ option }) => (
|
||||||
<Group gap="sm" wrap="nowrap">
|
<Group gap="sm" wrap="nowrap">
|
||||||
<Avatar color="initials" variant="filled" name={option.label} size={20} />
|
<Avatar color="initials" variant="filled" name={option.label} size={20} />
|
||||||
<div>
|
<div>
|
||||||
<Text size="sm" lineClamp={1}>{option.label}</Text>
|
<Text size="sm" lineClamp={1}>
|
||||||
|
{option.label}
|
||||||
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
|
|
||||||
export function SpaceSelect({ onChange, label, value }: SpaceSelectProps) {
|
export function SpaceSelect({
|
||||||
|
onChange,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
width,
|
||||||
|
opened,
|
||||||
|
clearable,
|
||||||
|
}: SpaceSelectProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [searchValue, setSearchValue] = useState("");
|
const [searchValue, setSearchValue] = useState("");
|
||||||
const [debouncedQuery] = useDebouncedValue(searchValue, 500);
|
const [debouncedQuery] = useDebouncedValue(searchValue, 500);
|
||||||
@@ -42,8 +54,8 @@ export function SpaceSelect({ onChange, label, value }: SpaceSelectProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const filteredSpaceData = spaceData.filter(
|
const filteredSpaceData = spaceData.filter(
|
||||||
(user) =>
|
(space) =>
|
||||||
!data.find((existingUser) => existingUser.value === user.value),
|
!data.find((existingSpace) => existingSpace.value === space.value),
|
||||||
);
|
);
|
||||||
setData((prevData) => [...prevData, ...filteredSpaceData]);
|
setData((prevData) => [...prevData, ...filteredSpaceData]);
|
||||||
}
|
}
|
||||||
@@ -59,14 +71,18 @@ export function SpaceSelect({ onChange, label, value }: SpaceSelectProps) {
|
|||||||
searchable
|
searchable
|
||||||
searchValue={searchValue}
|
searchValue={searchValue}
|
||||||
onSearchChange={setSearchValue}
|
onSearchChange={setSearchValue}
|
||||||
clearable
|
clearable={clearable}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
onChange={onChange}
|
onChange={(slug) =>
|
||||||
|
onChange(spaces.items?.find((item) => item.slug === slug))
|
||||||
|
}
|
||||||
|
// duct tape
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
nothingFoundMessage={t("No space found")}
|
nothingFoundMessage={t("No space found")}
|
||||||
limit={50}
|
limit={50}
|
||||||
checkIconPosition="right"
|
checkIconPosition="right"
|
||||||
comboboxProps={{ width: 300, withinPortal: false }}
|
comboboxProps={{ width, withinPortal: false }}
|
||||||
dropdownOpened
|
dropdownOpened={opened}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,9 @@ export function SwitchSpace({ spaceName, spaceSlug }: SwitchSpaceProps) {
|
|||||||
<SpaceSelect
|
<SpaceSelect
|
||||||
label={spaceName}
|
label={spaceName}
|
||||||
value={spaceSlug}
|
value={spaceSlug}
|
||||||
onChange={handleSelect}
|
onChange={space => handleSelect(space.slug)}
|
||||||
|
width={300}
|
||||||
|
opened={true}
|
||||||
/>
|
/>
|
||||||
</Popover.Dropdown>
|
</Popover.Dropdown>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { Group, Table, Text, Menu, ActionIcon } from "@mantine/core";
|
import {
|
||||||
|
Group,
|
||||||
|
Table,
|
||||||
|
Text,
|
||||||
|
Menu,
|
||||||
|
ActionIcon,
|
||||||
|
ScrollArea,
|
||||||
|
} from "@mantine/core";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { IconDots } from "@tabler/icons-react";
|
import { IconDots } from "@tabler/icons-react";
|
||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
@@ -106,93 +113,95 @@ export default function SpaceMembersList({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SearchInput onSearch={handleSearch} />
|
<SearchInput onSearch={handleSearch} />
|
||||||
<Table.ScrollContainer minWidth={500}>
|
<ScrollArea h={400}>
|
||||||
<Table highlightOnHover verticalSpacing={8}>
|
<Table.ScrollContainer minWidth={500}>
|
||||||
<Table.Thead>
|
<Table highlightOnHover verticalSpacing={8}>
|
||||||
<Table.Tr>
|
<Table.Thead>
|
||||||
<Table.Th>{t("Member")}</Table.Th>
|
<Table.Tr>
|
||||||
<Table.Th>{t("Role")}</Table.Th>
|
<Table.Th>{t("Member")}</Table.Th>
|
||||||
<Table.Th></Table.Th>
|
<Table.Th>{t("Role")}</Table.Th>
|
||||||
</Table.Tr>
|
<Table.Th></Table.Th>
|
||||||
</Table.Thead>
|
|
||||||
|
|
||||||
<Table.Tbody>
|
|
||||||
{data?.items.map((member, index) => (
|
|
||||||
<Table.Tr key={index}>
|
|
||||||
<Table.Td>
|
|
||||||
<Group gap="sm" wrap="nowrap">
|
|
||||||
{member.type === "user" && (
|
|
||||||
<CustomAvatar
|
|
||||||
avatarUrl={member?.avatarUrl}
|
|
||||||
name={member.name}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{member.type === "group" && <IconGroupCircle />}
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text fz="sm" fw={500} lineClamp={1}>
|
|
||||||
{member?.name}
|
|
||||||
</Text>
|
|
||||||
<Text fz="xs" c="dimmed">
|
|
||||||
{member.type == "user" && member?.email}
|
|
||||||
|
|
||||||
{member.type == "group" &&
|
|
||||||
`${t("Group")} - ${formatMemberCount(member?.memberCount, t)}`}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Table.Td>
|
|
||||||
|
|
||||||
<Table.Td>
|
|
||||||
<RoleSelectMenu
|
|
||||||
roles={spaceRoleData}
|
|
||||||
roleName={getSpaceRoleLabel(member.role)}
|
|
||||||
onChange={(newRole) =>
|
|
||||||
handleRoleChange(
|
|
||||||
member.id,
|
|
||||||
member.type,
|
|
||||||
newRole,
|
|
||||||
member.role,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
disabled={readOnly}
|
|
||||||
/>
|
|
||||||
</Table.Td>
|
|
||||||
|
|
||||||
<Table.Td>
|
|
||||||
{!readOnly && (
|
|
||||||
<Menu
|
|
||||||
shadow="xl"
|
|
||||||
position="bottom-end"
|
|
||||||
offset={20}
|
|
||||||
width={200}
|
|
||||||
withArrow
|
|
||||||
arrowPosition="center"
|
|
||||||
>
|
|
||||||
<Menu.Target>
|
|
||||||
<ActionIcon variant="subtle" c="gray">
|
|
||||||
<IconDots size={20} stroke={2} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Menu.Target>
|
|
||||||
|
|
||||||
<Menu.Dropdown>
|
|
||||||
<Menu.Item
|
|
||||||
onClick={() =>
|
|
||||||
openRemoveModal(member.id, member.type)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t("Remove space member")}
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu.Dropdown>
|
|
||||||
</Menu>
|
|
||||||
)}
|
|
||||||
</Table.Td>
|
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
</Table.Thead>
|
||||||
</Table.Tbody>
|
|
||||||
</Table>
|
<Table.Tbody>
|
||||||
</Table.ScrollContainer>
|
{data?.items.map((member, index) => (
|
||||||
|
<Table.Tr key={index}>
|
||||||
|
<Table.Td>
|
||||||
|
<Group gap="sm" wrap="nowrap">
|
||||||
|
{member.type === "user" && (
|
||||||
|
<CustomAvatar
|
||||||
|
avatarUrl={member?.avatarUrl}
|
||||||
|
name={member.name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{member.type === "group" && <IconGroupCircle />}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text fz="sm" fw={500} lineClamp={1}>
|
||||||
|
{member?.name}
|
||||||
|
</Text>
|
||||||
|
<Text fz="xs" c="dimmed">
|
||||||
|
{member.type == "user" && member?.email}
|
||||||
|
|
||||||
|
{member.type == "group" &&
|
||||||
|
`${t("Group")} - ${formatMemberCount(member?.memberCount, t)}`}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Table.Td>
|
||||||
|
|
||||||
|
<Table.Td>
|
||||||
|
<RoleSelectMenu
|
||||||
|
roles={spaceRoleData}
|
||||||
|
roleName={getSpaceRoleLabel(member.role)}
|
||||||
|
onChange={(newRole) =>
|
||||||
|
handleRoleChange(
|
||||||
|
member.id,
|
||||||
|
member.type,
|
||||||
|
newRole,
|
||||||
|
member.role,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
disabled={readOnly}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
|
||||||
|
<Table.Td>
|
||||||
|
{!readOnly && (
|
||||||
|
<Menu
|
||||||
|
shadow="xl"
|
||||||
|
position="bottom-end"
|
||||||
|
offset={20}
|
||||||
|
width={200}
|
||||||
|
withArrow
|
||||||
|
arrowPosition="center"
|
||||||
|
>
|
||||||
|
<Menu.Target>
|
||||||
|
<ActionIcon variant="subtle" c="gray">
|
||||||
|
<IconDots size={20} stroke={2} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Menu.Target>
|
||||||
|
|
||||||
|
<Menu.Dropdown>
|
||||||
|
<Menu.Item
|
||||||
|
onClick={() =>
|
||||||
|
openRemoveModal(member.id, member.type)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("Remove space member")}
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Table.ScrollContainer>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
{data?.items.length > 0 && (
|
{data?.items.length > 0 && (
|
||||||
<Paginate
|
<Paginate
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ function ChangeEmailForm() {
|
|||||||
|
|
||||||
function handleSubmit(data: FormValues) {
|
function handleSubmit(data: FormValues) {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
console.log(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Group, Text, Switch, MantineSize } from "@mantine/core";
|
|
||||||
import { useAtom } from "jotai/index";
|
|
||||||
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 { useAtom } from "jotai/index";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@@ -26,6 +26,7 @@ interface PageWidthToggleProps {
|
|||||||
size?: MantineSize;
|
size?: MantineSize;
|
||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PageWidthToggle({ size, label }: PageWidthToggleProps) {
|
export function PageWidthToggle({ size, label }: PageWidthToggleProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [user, setUser] = useAtom(userAtom);
|
const [user, setUser] = useAtom(userAtom);
|
||||||
@@ -50,4 +51,4 @@ export function PageWidthToggle({ size, label }: PageWidthToggleProps) {
|
|||||||
aria-label={t("Toggle full page width")}
|
aria-label={t("Toggle full page width")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -30,4 +30,4 @@ export interface IUserSettings {
|
|||||||
preferences: {
|
preferences: {
|
||||||
fullPageWidth: boolean;
|
fullPageWidth: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
import { Menu, ActionIcon, Text } from "@mantine/core";
|
||||||
|
import React from "react";
|
||||||
|
import { IconDots, IconTrash } from "@tabler/icons-react";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import { useDeleteWorkspaceMemberMutation } from "@/features/workspace/queries/workspace-query.ts";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import useUserRole from "@/hooks/use-user-role.tsx";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
export default function MemberActionMenu({ userId }: Props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const deleteWorkspaceMemberMutation = useDeleteWorkspaceMemberMutation();
|
||||||
|
const { isAdmin } = useUserRole();
|
||||||
|
|
||||||
|
const onRevoke = async () => {
|
||||||
|
await deleteWorkspaceMemberMutation.mutateAsync({ userId });
|
||||||
|
};
|
||||||
|
|
||||||
|
const openRevokeModal = () =>
|
||||||
|
modals.openConfirmModal({
|
||||||
|
title: t("Delete member"),
|
||||||
|
children: (
|
||||||
|
<Text size="sm">
|
||||||
|
{t(
|
||||||
|
"Are you sure you want to delete this workspace member? This action is irreversible.",
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
centered: true,
|
||||||
|
labels: { confirm: t("Delete"), cancel: t("Don't") },
|
||||||
|
confirmProps: { color: "red" },
|
||||||
|
onConfirm: onRevoke,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Menu
|
||||||
|
shadow="xl"
|
||||||
|
position="bottom-end"
|
||||||
|
offset={20}
|
||||||
|
width={200}
|
||||||
|
withArrow
|
||||||
|
arrowPosition="center"
|
||||||
|
>
|
||||||
|
<Menu.Target>
|
||||||
|
<ActionIcon variant="subtle" c="gray">
|
||||||
|
<IconDots size={20} stroke={2} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Menu.Target>
|
||||||
|
|
||||||
|
<Menu.Dropdown>
|
||||||
|
<Menu.Item
|
||||||
|
c="red"
|
||||||
|
onClick={openRevokeModal}
|
||||||
|
leftSection={<IconTrash size={16} />}
|
||||||
|
disabled={!isAdmin}
|
||||||
|
>
|
||||||
|
{t("Delete member")}
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
+4
@@ -17,6 +17,7 @@ import Paginate from "@/components/common/paginate.tsx";
|
|||||||
import { SearchInput } from "@/components/common/search-input.tsx";
|
import { SearchInput } from "@/components/common/search-input.tsx";
|
||||||
import NoTableResults from "@/components/common/no-table-results.tsx";
|
import NoTableResults from "@/components/common/no-table-results.tsx";
|
||||||
import { usePaginateAndSearch } from "@/hooks/use-paginate-and-search.tsx";
|
import { usePaginateAndSearch } from "@/hooks/use-paginate-and-search.tsx";
|
||||||
|
import MemberActionMenu from "@/features/workspace/components/members/components/members-action-menu.tsx";
|
||||||
|
|
||||||
export default function WorkspaceMembersTable() {
|
export default function WorkspaceMembersTable() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -96,6 +97,9 @@ export default function WorkspaceMembersTable() {
|
|||||||
disabled={!isAdmin}
|
disabled={!isAdmin}
|
||||||
/>
|
/>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
{isAdmin && <MemberActionMenu userId={user.id} />}
|
||||||
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
getWorkspace,
|
getWorkspace,
|
||||||
getWorkspacePublicData,
|
getWorkspacePublicData,
|
||||||
getAppVersion,
|
getAppVersion,
|
||||||
|
deleteWorkspaceMember,
|
||||||
} from "@/features/workspace/services/workspace-service";
|
} from "@/features/workspace/services/workspace-service";
|
||||||
import { IPagination, QueryParams } from "@/lib/types.ts";
|
import { IPagination, QueryParams } from "@/lib/types.ts";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
@@ -56,6 +57,30 @@ export function useWorkspaceMembersQuery(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useDeleteWorkspaceMemberMutation() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<
|
||||||
|
void,
|
||||||
|
Error,
|
||||||
|
{
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
>({
|
||||||
|
mutationFn: (data) => deleteWorkspaceMember(data),
|
||||||
|
onSuccess: (data, variables) => {
|
||||||
|
notifications.show({ message: "Member deleted successfully" });
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["workspaceMembers"],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
const errorMessage = error["response"]?.data?.message;
|
||||||
|
notifications.show({ message: errorMessage, color: "red" });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function useChangeMemberRoleMutation() {
|
export function useChangeMemberRoleMutation() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ export async function getWorkspaceMembers(
|
|||||||
return req.data;
|
return req.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteWorkspaceMember(data: {
|
||||||
|
userId: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
await api.post("/workspace/members/delete", data);
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateWorkspace(data: Partial<IWorkspace>) {
|
export async function updateWorkspace(data: Partial<IWorkspace>) {
|
||||||
const req = await api.post<IWorkspace>("/workspace/update", data);
|
const req = await api.post<IWorkspace>("/workspace/update", data);
|
||||||
return req.data;
|
return req.data;
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { timeAgo } from "@/lib/time.ts";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export function useTimeAgo(date: Date | string) {
|
||||||
|
const [value, setValue] = useState(() => timeAgo(new Date(date)));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setValue(timeAgo(new Date(date)));
|
||||||
|
}, 5 * 1000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [date]);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
@@ -2,8 +2,8 @@ import SettingsTitle from "@/components/settings/settings-title.tsx";
|
|||||||
import AccountLanguage from "@/features/user/components/account-language.tsx";
|
import AccountLanguage from "@/features/user/components/account-language.tsx";
|
||||||
import AccountTheme from "@/features/user/components/account-theme.tsx";
|
import AccountTheme from "@/features/user/components/account-theme.tsx";
|
||||||
import PageWidthPref from "@/features/user/components/page-width-pref.tsx";
|
import PageWidthPref from "@/features/user/components/page-width-pref.tsx";
|
||||||
import { Divider } from "@mantine/core";
|
|
||||||
import { getAppName } from "@/lib/config.ts";
|
import { getAppName } from "@/lib/config.ts";
|
||||||
|
import { Divider } from "@mantine/core";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.9.0",
|
"version": "0.10.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -59,14 +59,13 @@
|
|||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
"cookie": "^1.0.2",
|
"cookie": "^1.0.2",
|
||||||
"fix-esm": "^1.0.1",
|
|
||||||
"fs-extra": "^11.3.0",
|
"fs-extra": "^11.3.0",
|
||||||
"happy-dom": "^15.11.6",
|
"happy-dom": "^15.11.6",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"kysely": "^0.27.5",
|
"kysely": "^0.27.5",
|
||||||
"kysely-migration-cli": "^0.4.2",
|
"kysely-migration-cli": "^0.4.2",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"nanoid": "^5.1.0",
|
"nanoid": "^5.1.5",
|
||||||
"nestjs-kysely": "^1.1.0",
|
"nestjs-kysely": "^1.1.0",
|
||||||
"nodemailer": "^6.10.0",
|
"nodemailer": "^6.10.0",
|
||||||
"openid-client": "^5.7.1",
|
"openid-client": "^5.7.1",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
|
afterUnloadDocumentPayload,
|
||||||
Extension,
|
Extension,
|
||||||
|
onChangePayload,
|
||||||
onLoadDocumentPayload,
|
onLoadDocumentPayload,
|
||||||
onStoreDocumentPayload,
|
onStoreDocumentPayload,
|
||||||
} from '@hocuspocus/server';
|
} from '@hocuspocus/server';
|
||||||
@@ -26,6 +28,7 @@ import { Page } from '@docmost/db/types/entity.types';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class PersistenceExtension implements Extension {
|
export class PersistenceExtension implements Extension {
|
||||||
private readonly logger = new Logger(PersistenceExtension.name);
|
private readonly logger = new Logger(PersistenceExtension.name);
|
||||||
|
private contributors: Map<string, Set<string>> = new Map();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly pageRepo: PageRepo,
|
private readonly pageRepo: PageRepo,
|
||||||
@@ -116,12 +119,27 @@ export class PersistenceExtension implements Extension {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let contributorIds = undefined;
|
||||||
|
try {
|
||||||
|
const existingContributors = page.contributorIds || [];
|
||||||
|
const contributorSet = this.contributors.get(documentName);
|
||||||
|
contributorSet.add(page.creatorId);
|
||||||
|
const newContributors = [...contributorSet];
|
||||||
|
contributorIds = Array.from(
|
||||||
|
new Set([...existingContributors, ...newContributors]),
|
||||||
|
);
|
||||||
|
this.contributors.delete(documentName);
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.log('Contributors error:' + err?.['message']);
|
||||||
|
}
|
||||||
|
|
||||||
await this.pageRepo.updatePage(
|
await this.pageRepo.updatePage(
|
||||||
{
|
{
|
||||||
content: tiptapJson,
|
content: tiptapJson,
|
||||||
textContent: textContent,
|
textContent: textContent,
|
||||||
ydoc: ydocState,
|
ydoc: ydocState,
|
||||||
lastUpdatedById: context.user.id,
|
lastUpdatedById: context.user.id,
|
||||||
|
contributorIds: contributorIds,
|
||||||
},
|
},
|
||||||
pageId,
|
pageId,
|
||||||
trx,
|
trx,
|
||||||
@@ -152,4 +170,21 @@ export class PersistenceExtension implements Extension {
|
|||||||
} as IPageBacklinkJob);
|
} as IPageBacklinkJob);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onChange(data: onChangePayload) {
|
||||||
|
const documentName = data.documentName;
|
||||||
|
const userId = data.context?.user.id;
|
||||||
|
if (!userId) return;
|
||||||
|
|
||||||
|
if (!this.contributors.has(documentName)) {
|
||||||
|
this.contributors.set(documentName, new Set());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.contributors.get(documentName).add(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async afterUnloadDocument(data: afterUnloadDocumentPayload) {
|
||||||
|
const documentName = data.documentName;
|
||||||
|
this.contributors.delete(documentName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
import { customAlphabet } from 'nanoid';
|
||||||
const { customAlphabet } = require('fix-esm').require('nanoid');
|
|
||||||
|
|
||||||
const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||||
export const nanoIdGen = customAlphabet(alphabet, 10);
|
export const nanoIdGen = customAlphabet(alphabet, 10);
|
||||||
|
|
||||||
const slugIdAlphabet =
|
const slugIdAlphabet =
|
||||||
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||||
export const generateSlugId = customAlphabet(slugIdAlphabet, 10);
|
export const generateSlugId = customAlphabet(slugIdAlphabet, 10);
|
||||||
@@ -17,6 +17,9 @@ export class AttachmentProcessor extends WorkerHost implements OnModuleDestroy {
|
|||||||
if (job.name === QueueJob.DELETE_SPACE_ATTACHMENTS) {
|
if (job.name === QueueJob.DELETE_SPACE_ATTACHMENTS) {
|
||||||
await this.attachmentService.handleDeleteSpaceAttachments(job.data.id);
|
await this.attachmentService.handleDeleteSpaceAttachments(job.data.id);
|
||||||
}
|
}
|
||||||
|
if (job.name === QueueJob.DELETE_USER_AVATARS) {
|
||||||
|
await this.attachmentService.handleDeleteUserAvatars(job.data.id);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,10 +281,42 @@ export class AttachmentService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if(failedDeletions.length === attachments.length){
|
if (failedDeletions.length === attachments.length) {
|
||||||
throw new Error(`Failed to delete any attachments for spaceId: ${spaceId}`);
|
throw new Error(
|
||||||
|
`Failed to delete any attachments for spaceId: ${spaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleDeleteUserAvatars(userId: string) {
|
||||||
|
try {
|
||||||
|
const userAvatars = await this.db
|
||||||
|
.selectFrom('attachments')
|
||||||
|
.select(['id', 'filePath'])
|
||||||
|
.where('creatorId', '=', userId)
|
||||||
|
.where('type', '=', AttachmentType.Avatar)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
if (!userAvatars || userAvatars.length === 0) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
userAvatars.map(async (attachment) => {
|
||||||
|
try {
|
||||||
|
await this.storageService.delete(attachment.filePath);
|
||||||
|
await this.attachmentRepo.deleteAttachmentById(attachment.id);
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.log(
|
||||||
|
`DeleteUserAvatar: failed to delete user avatar ${attachment.id}:`,
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,19 +43,22 @@ export class AuthService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async login(loginDto: LoginDto, workspaceId: string) {
|
async login(loginDto: LoginDto, workspaceId: string) {
|
||||||
const user = await this.userRepo.findByEmail(
|
const user = await this.userRepo.findByEmail(loginDto.email, workspaceId, {
|
||||||
loginDto.email,
|
includePassword: true,
|
||||||
workspaceId,
|
});
|
||||||
{
|
|
||||||
includePassword: true
|
const errorMessage = 'email or password does not match';
|
||||||
}
|
if (!user || user?.deletedAt) {
|
||||||
|
throw new UnauthorizedException(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPasswordMatch = await comparePasswordHash(
|
||||||
|
loginDto.password,
|
||||||
|
user.password,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (!isPasswordMatch) {
|
||||||
!user ||
|
throw new UnauthorizedException(errorMessage);
|
||||||
!(await comparePasswordHash(loginDto.password, user.password))
|
|
||||||
) {
|
|
||||||
throw new UnauthorizedException('email or password does not match');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user.lastLoginAt = new Date();
|
user.lastLoginAt = new Date();
|
||||||
@@ -86,7 +89,7 @@ export class AuthService {
|
|||||||
includePassword: true,
|
includePassword: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user || user.deletedAt) {
|
||||||
throw new NotFoundException('User not found');
|
throw new NotFoundException('User not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +128,7 @@ export class AuthService {
|
|||||||
workspace.id,
|
workspace.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!user) {
|
if (!user || user.deletedAt) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +171,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const user = await this.userRepo.findById(userToken.userId, workspaceId);
|
const user = await this.userRepo.findById(userToken.userId, workspaceId);
|
||||||
if (!user) {
|
if (!user || user.deletedAt) {
|
||||||
throw new NotFoundException('User not found');
|
throw new NotFoundException('User not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
import {
|
||||||
|
ForbiddenException,
|
||||||
|
Injectable,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { EnvironmentService } from '../../../integrations/environment/environment.service';
|
import { EnvironmentService } from '../../../integrations/environment/environment.service';
|
||||||
import {
|
import {
|
||||||
@@ -17,6 +21,10 @@ export class TokenService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async generateAccessToken(user: User): Promise<string> {
|
async generateAccessToken(user: User): Promise<string> {
|
||||||
|
if (user.deletedAt) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
const payload: JwtPayload = {
|
const payload: JwtPayload = {
|
||||||
sub: user.id,
|
sub: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import {
|
import { Injectable, Logger, UnauthorizedException } from '@nestjs/common';
|
||||||
BadRequestException,
|
|
||||||
Injectable,
|
|
||||||
Logger,
|
|
||||||
UnauthorizedException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { Strategy } from 'passport-jwt';
|
import { Strategy } from 'passport-jwt';
|
||||||
import { EnvironmentService } from '../../../integrations/environment/environment.service';
|
import { EnvironmentService } from '../../../integrations/environment/environment.service';
|
||||||
@@ -47,7 +42,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|||||||
}
|
}
|
||||||
const user = await this.userRepo.findById(payload.sub, payload.workspaceId);
|
const user = await this.userRepo.findById(payload.sub, payload.workspaceId);
|
||||||
|
|
||||||
if (!user) {
|
if (!user || user.deletedAt) {
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,3 +13,11 @@ export class MovePageDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
parentPageId?: string | null;
|
parentPageId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class MovePageToSpaceDto {
|
||||||
|
@IsString()
|
||||||
|
pageId: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
spaceId: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ import {
|
|||||||
UseGuards,
|
UseGuards,
|
||||||
ForbiddenException,
|
ForbiddenException,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
|
BadRequestException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { PageService } from './services/page.service';
|
import { PageService } from './services/page.service';
|
||||||
import { CreatePageDto } from './dto/create-page.dto';
|
import { CreatePageDto } from './dto/create-page.dto';
|
||||||
import { UpdatePageDto } from './dto/update-page.dto';
|
import { UpdatePageDto } from './dto/update-page.dto';
|
||||||
import { MovePageDto } from './dto/move-page.dto';
|
import { MovePageDto, MovePageToSpaceDto } from './dto/move-page.dto';
|
||||||
import { PageHistoryIdDto, PageIdDto, PageInfoDto } from './dto/page.dto';
|
import { PageHistoryIdDto, PageIdDto, PageInfoDto } from './dto/page.dto';
|
||||||
import { PageHistoryService } from './services/page-history.service';
|
import { PageHistoryService } from './services/page-history.service';
|
||||||
import { AuthUser } from '../../common/decorators/auth-user.decorator';
|
import { AuthUser } from '../../common/decorators/auth-user.decorator';
|
||||||
@@ -46,6 +47,7 @@ export class PageController {
|
|||||||
includeContent: true,
|
includeContent: true,
|
||||||
includeCreator: true,
|
includeCreator: true,
|
||||||
includeLastUpdatedBy: true,
|
includeLastUpdatedBy: true,
|
||||||
|
includeContributors: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
@@ -92,11 +94,7 @@ export class PageController {
|
|||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.pageService.update(
|
return this.pageService.update(page, updatePageDto, user.id);
|
||||||
updatePageDto.pageId,
|
|
||||||
updatePageDto,
|
|
||||||
user.id,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@@ -209,6 +207,36 @@ export class PageController {
|
|||||||
return this.pageService.getSidebarPages(dto.spaceId, pagination, pageId);
|
return this.pageService.getSidebarPages(dto.spaceId, pagination, pageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post('move-to-space')
|
||||||
|
async movePageToSpace(
|
||||||
|
@Body() dto: MovePageToSpaceDto,
|
||||||
|
@AuthUser() user: User,
|
||||||
|
) {
|
||||||
|
const movedPage = await this.pageRepo.findById(dto.pageId);
|
||||||
|
if (!movedPage) {
|
||||||
|
throw new NotFoundException('Page to move not found');
|
||||||
|
}
|
||||||
|
if (movedPage.spaceId === dto.spaceId) {
|
||||||
|
throw new BadRequestException('Page is already in this space');
|
||||||
|
}
|
||||||
|
|
||||||
|
const abilities = await Promise.all([
|
||||||
|
this.spaceAbility.createForUser(user, movedPage.spaceId),
|
||||||
|
this.spaceAbility.createForUser(user, dto.spaceId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
abilities.some((ability) =>
|
||||||
|
ability.cannot(SpaceCaslAction.Edit, SpaceCaslSubject.Page),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.pageService.movePageToSpace(movedPage, dto.spaceId);
|
||||||
|
}
|
||||||
|
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('move')
|
@Post('move')
|
||||||
async movePage(@Body() dto: MovePageDto, @AuthUser() user: User) {
|
async movePage(@Body() dto: MovePageDto, @AuthUser() user: User) {
|
||||||
|
|||||||
@@ -19,11 +19,14 @@ import { MovePageDto } from '../dto/move-page.dto';
|
|||||||
import { ExpressionBuilder } from 'kysely';
|
import { ExpressionBuilder } from 'kysely';
|
||||||
import { DB } from '@docmost/db/types/db';
|
import { DB } from '@docmost/db/types/db';
|
||||||
import { generateSlugId } from '../../../common/helpers';
|
import { generateSlugId } from '../../../common/helpers';
|
||||||
|
import { executeTx } from '@docmost/db/utils';
|
||||||
|
import { AttachmentRepo } from '@docmost/db/repos/attachment/attachment.repo';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PageService {
|
export class PageService {
|
||||||
constructor(
|
constructor(
|
||||||
private pageRepo: PageRepo,
|
private pageRepo: PageRepo,
|
||||||
|
private attachmentRepo: AttachmentRepo,
|
||||||
@InjectKysely() private readonly db: KyselyDB,
|
@InjectKysely() private readonly db: KyselyDB,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -60,12 +63,31 @@ export class PageService {
|
|||||||
parentPageId = parentPage.id;
|
parentPageId = parentPage.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createdPage = await this.pageRepo.insertPage({
|
||||||
|
slugId: generateSlugId(),
|
||||||
|
title: createPageDto.title,
|
||||||
|
position: await this.nextPagePosition(
|
||||||
|
createPageDto.spaceId,
|
||||||
|
parentPageId,
|
||||||
|
),
|
||||||
|
icon: createPageDto.icon,
|
||||||
|
parentPageId: parentPageId,
|
||||||
|
spaceId: createPageDto.spaceId,
|
||||||
|
creatorId: userId,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
lastUpdatedById: userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return createdPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
async nextPagePosition(spaceId: string, parentPageId?: string) {
|
||||||
let pagePosition: string;
|
let pagePosition: string;
|
||||||
|
|
||||||
const lastPageQuery = this.db
|
const lastPageQuery = this.db
|
||||||
.selectFrom('pages')
|
.selectFrom('pages')
|
||||||
.select(['id', 'position'])
|
.select(['position'])
|
||||||
.where('spaceId', '=', createPageDto.spaceId)
|
.where('spaceId', '=', spaceId)
|
||||||
.orderBy('position', 'desc')
|
.orderBy('position', 'desc')
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
@@ -96,37 +118,36 @@ export class PageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdPage = await this.pageRepo.insertPage({
|
return pagePosition;
|
||||||
slugId: generateSlugId(),
|
|
||||||
title: createPageDto.title,
|
|
||||||
position: pagePosition,
|
|
||||||
icon: createPageDto.icon,
|
|
||||||
parentPageId: parentPageId,
|
|
||||||
spaceId: createPageDto.spaceId,
|
|
||||||
creatorId: userId,
|
|
||||||
workspaceId: workspaceId,
|
|
||||||
lastUpdatedById: userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return createdPage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(
|
async update(
|
||||||
pageId: string,
|
page: Page,
|
||||||
updatePageDto: UpdatePageDto,
|
updatePageDto: UpdatePageDto,
|
||||||
userId: string,
|
userId: string,
|
||||||
): Promise<Page> {
|
): Promise<Page> {
|
||||||
|
const contributors = new Set<string>(page.contributorIds);
|
||||||
|
contributors.add(userId);
|
||||||
|
const contributorIds = Array.from(contributors);
|
||||||
|
|
||||||
await this.pageRepo.updatePage(
|
await this.pageRepo.updatePage(
|
||||||
{
|
{
|
||||||
title: updatePageDto.title,
|
title: updatePageDto.title,
|
||||||
icon: updatePageDto.icon,
|
icon: updatePageDto.icon,
|
||||||
lastUpdatedById: userId,
|
lastUpdatedById: userId,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
|
contributorIds: contributorIds,
|
||||||
},
|
},
|
||||||
pageId,
|
page.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
return await this.pageRepo.findById(pageId);
|
return await this.pageRepo.findById(page.id, {
|
||||||
|
includeSpace: true,
|
||||||
|
includeContent: true,
|
||||||
|
includeCreator: true,
|
||||||
|
includeLastUpdatedBy: true,
|
||||||
|
includeContributors: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
withHasChildren(eb: ExpressionBuilder<DB, 'pages'>) {
|
withHasChildren(eb: ExpressionBuilder<DB, 'pages'>) {
|
||||||
@@ -181,6 +202,36 @@ export class PageService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async movePageToSpace(rootPage: Page, spaceId: string) {
|
||||||
|
await executeTx(this.db, async (trx) => {
|
||||||
|
// Update root page
|
||||||
|
const nextPosition = await this.nextPagePosition(spaceId);
|
||||||
|
await this.pageRepo.updatePage(
|
||||||
|
{ spaceId, parentPageId: null, position: nextPosition },
|
||||||
|
rootPage.id,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
const pageIds = await this.pageRepo
|
||||||
|
.getPageAndDescendants(rootPage.id)
|
||||||
|
.then((pages) => pages.map((page) => page.id));
|
||||||
|
// The first id is the root page id
|
||||||
|
if (pageIds.length > 1) {
|
||||||
|
// Update sub pages
|
||||||
|
await this.pageRepo.updatePages(
|
||||||
|
{ spaceId },
|
||||||
|
pageIds.filter((id) => id !== rootPage.id),
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Update attachments
|
||||||
|
await this.attachmentRepo.updateAttachmentsByPageId(
|
||||||
|
{ spaceId },
|
||||||
|
pageIds,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async movePage(dto: MovePageDto, movedPage: Page) {
|
async movePage(dto: MovePageDto, movedPage: Page) {
|
||||||
// validate position value by attempting to generate a key
|
// validate position value by attempting to generate a key
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export class SearchService {
|
|||||||
.select(['id', 'name', 'avatarUrl'])
|
.select(['id', 'name', 'avatarUrl'])
|
||||||
.where((eb) => eb(sql`LOWER(users.name)`, 'like', `%${query}%`))
|
.where((eb) => eb(sql`LOWER(users.name)`, 'like', `%${query}%`))
|
||||||
.where('workspaceId', '=', workspaceId)
|
.where('workspaceId', '=', workspaceId)
|
||||||
|
.where('deletedAt', 'is', null)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { OmitType, PartialType } from '@nestjs/mapped-types';
|
import { OmitType, PartialType } from '@nestjs/mapped-types';
|
||||||
import { CreateUserDto } from '../../auth/dto/create-user.dto';
|
|
||||||
import { IsBoolean, IsOptional, IsString } from 'class-validator';
|
import { IsBoolean, IsOptional, IsString } from 'class-validator';
|
||||||
|
import { CreateUserDto } from '../../auth/dto/create-user.dto';
|
||||||
|
|
||||||
export class UpdateUserDto extends PartialType(
|
export class UpdateUserDto extends PartialType(
|
||||||
OmitType(CreateUserDto, ['password'] as const),
|
OmitType(CreateUserDto, ['password'] as const),
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { UserRepo } from '@docmost/db/repos/user/user.repo';
|
||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
Injectable,
|
Injectable,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { UpdateUserDto } from './dto/update-user.dto';
|
import { UpdateUserDto } from './dto/update-user.dto';
|
||||||
import { UserRepo } from '@docmost/db/repos/user/user.repo';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
@@ -27,8 +27,9 @@ export class UserService {
|
|||||||
|
|
||||||
// preference update
|
// preference update
|
||||||
if (typeof updateUserDto.fullPageWidth !== 'undefined') {
|
if (typeof updateUserDto.fullPageWidth !== 'undefined') {
|
||||||
return this.updateUserPageWidthPreference(
|
return this.userRepo.updatePreference(
|
||||||
userId,
|
userId,
|
||||||
|
'fullPageWidth',
|
||||||
updateUserDto.fullPageWidth,
|
updateUserDto.fullPageWidth,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -55,12 +56,4 @@ export class UserService {
|
|||||||
await this.userRepo.updateUser(updateUserDto, userId, workspaceId);
|
await this.userRepo.updateUser(updateUserDto, userId, workspaceId);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUserPageWidthPreference(userId: string, fullPageWidth: boolean) {
|
|
||||||
return this.userRepo.updatePreference(
|
|
||||||
userId,
|
|
||||||
'fullPageWidth',
|
|
||||||
fullPageWidth,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { addDays } from 'date-fns';
|
|||||||
import { FastifyReply } from 'fastify';
|
import { FastifyReply } from 'fastify';
|
||||||
import { EnvironmentService } from '../../../integrations/environment/environment.service';
|
import { EnvironmentService } from '../../../integrations/environment/environment.service';
|
||||||
import { CheckHostnameDto } from '../dto/check-hostname.dto';
|
import { CheckHostnameDto } from '../dto/check-hostname.dto';
|
||||||
|
import { RemoveWorkspaceUserDto } from '../dto/remove-workspace-user.dto';
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Controller('workspace')
|
@Controller('workspace')
|
||||||
@@ -120,6 +121,22 @@ export class WorkspaceController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post('members/delete')
|
||||||
|
async deleteWorkspaceMember(
|
||||||
|
@Body() dto: RemoveWorkspaceUserDto,
|
||||||
|
@AuthUser() user: User,
|
||||||
|
@AuthWorkspace() workspace: Workspace,
|
||||||
|
) {
|
||||||
|
const ability = this.workspaceAbility.createForUser(user, workspace);
|
||||||
|
if (
|
||||||
|
ability.cannot(WorkspaceCaslAction.Manage, WorkspaceCaslSubject.Member)
|
||||||
|
) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
await this.workspaceService.deleteUser(user, dto.userId, workspace.id);
|
||||||
|
}
|
||||||
|
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('members/change-role')
|
@Post('members/change-role')
|
||||||
async updateWorkspaceMemberRole(
|
async updateWorkspaceMemberRole(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
BadRequestException,
|
BadRequestException,
|
||||||
ForbiddenException,
|
ForbiddenException,
|
||||||
Injectable,
|
Injectable,
|
||||||
|
Logger,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { CreateWorkspaceDto } from '../dto/create-workspace.dto';
|
import { CreateWorkspaceDto } from '../dto/create-workspace.dto';
|
||||||
@@ -26,9 +27,16 @@ import { DomainService } from '../../../integrations/environment/domain.service'
|
|||||||
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
||||||
import { addDays } from 'date-fns';
|
import { addDays } from 'date-fns';
|
||||||
import { DISALLOWED_HOSTNAMES, WorkspaceStatus } from '../workspace.constants';
|
import { DISALLOWED_HOSTNAMES, WorkspaceStatus } from '../workspace.constants';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
import { AttachmentType } from 'src/core/attachment/attachment.constants';
|
||||||
|
import { InjectQueue } from '@nestjs/bullmq';
|
||||||
|
import { QueueJob, QueueName } from '../../../integrations/queue/constants';
|
||||||
|
import { Queue } from 'bullmq';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceService {
|
export class WorkspaceService {
|
||||||
|
private readonly logger = new Logger(WorkspaceService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private workspaceRepo: WorkspaceRepo,
|
private workspaceRepo: WorkspaceRepo,
|
||||||
private spaceService: SpaceService,
|
private spaceService: SpaceService,
|
||||||
@@ -39,6 +47,8 @@ export class WorkspaceService {
|
|||||||
private environmentService: EnvironmentService,
|
private environmentService: EnvironmentService,
|
||||||
private domainService: DomainService,
|
private domainService: DomainService,
|
||||||
@InjectKysely() private readonly db: KyselyDB,
|
@InjectKysely() private readonly db: KyselyDB,
|
||||||
|
@InjectQueue(QueueName.ATTACHMENT_QUEUE) private attachmentQueue: Queue,
|
||||||
|
@InjectQueue(QueueName.BILLING_QUEUE) private billingQueue: Queue,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async findById(workspaceId: string) {
|
async findById(workspaceId: string) {
|
||||||
@@ -91,13 +101,15 @@ export class WorkspaceService {
|
|||||||
createWorkspaceDto: CreateWorkspaceDto,
|
createWorkspaceDto: CreateWorkspaceDto,
|
||||||
trx?: KyselyTransaction,
|
trx?: KyselyTransaction,
|
||||||
) {
|
) {
|
||||||
return await executeTx(
|
let trialEndAt = undefined;
|
||||||
|
|
||||||
|
const createdWorkspace = await executeTx(
|
||||||
this.db,
|
this.db,
|
||||||
async (trx) => {
|
async (trx) => {
|
||||||
let hostname = undefined;
|
let hostname = undefined;
|
||||||
let trialEndAt = undefined;
|
|
||||||
let status = undefined;
|
let status = undefined;
|
||||||
let plan = undefined;
|
let plan = undefined;
|
||||||
|
let billingEmail = undefined;
|
||||||
|
|
||||||
if (this.environmentService.isCloud()) {
|
if (this.environmentService.isCloud()) {
|
||||||
// generate unique hostname
|
// generate unique hostname
|
||||||
@@ -110,6 +122,7 @@ export class WorkspaceService {
|
|||||||
);
|
);
|
||||||
status = WorkspaceStatus.Active;
|
status = WorkspaceStatus.Active;
|
||||||
plan = 'standard';
|
plan = 'standard';
|
||||||
|
billingEmail = user.email;
|
||||||
}
|
}
|
||||||
|
|
||||||
// create workspace
|
// create workspace
|
||||||
@@ -121,6 +134,7 @@ export class WorkspaceService {
|
|||||||
status,
|
status,
|
||||||
trialEndAt,
|
trialEndAt,
|
||||||
plan,
|
plan,
|
||||||
|
billingEmail,
|
||||||
},
|
},
|
||||||
trx,
|
trx,
|
||||||
);
|
);
|
||||||
@@ -195,6 +209,28 @@ export class WorkspaceService {
|
|||||||
},
|
},
|
||||||
trx,
|
trx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.environmentService.isCloud() && trialEndAt) {
|
||||||
|
try {
|
||||||
|
const delay = trialEndAt.getTime() - Date.now();
|
||||||
|
|
||||||
|
await this.billingQueue.add(
|
||||||
|
QueueJob.TRIAL_ENDED,
|
||||||
|
{ workspaceId: createdWorkspace.id },
|
||||||
|
{ delay },
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.billingQueue.add(
|
||||||
|
QueueJob.WELCOME_EMAIL,
|
||||||
|
{ userId: user.id },
|
||||||
|
{ delay: 60 * 1000 }, // 1m
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return createdWorkspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addUserToWorkspace(
|
async addUserToWorkspace(
|
||||||
@@ -386,4 +422,66 @@ export class WorkspaceService {
|
|||||||
}
|
}
|
||||||
return { hostname: this.domainService.getUrl(hostname) };
|
return { hostname: this.domainService.getUrl(hostname) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteUser(
|
||||||
|
authUser: User,
|
||||||
|
userId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const user = await this.userRepo.findById(userId, workspaceId);
|
||||||
|
|
||||||
|
if (!user || user.deletedAt) {
|
||||||
|
throw new BadRequestException('Workspace member not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceOwnerCount = await this.userRepo.roleCountByWorkspaceId(
|
||||||
|
UserRole.OWNER,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (user.role === UserRole.OWNER && workspaceOwnerCount === 1) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'There must be at least one workspace owner',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authUser.id === userId) {
|
||||||
|
throw new BadRequestException('You cannot delete yourself');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authUser.role === UserRole.ADMIN && user.role === UserRole.OWNER) {
|
||||||
|
throw new BadRequestException('You cannot delete a user with owner role');
|
||||||
|
}
|
||||||
|
|
||||||
|
await executeTx(this.db, async (trx) => {
|
||||||
|
await this.userRepo.updateUser(
|
||||||
|
{
|
||||||
|
name: 'Deleted user',
|
||||||
|
email: v4() + '@deleted.docmost.com',
|
||||||
|
avatarUrl: null,
|
||||||
|
settings: null,
|
||||||
|
deletedAt: new Date(),
|
||||||
|
},
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
|
||||||
|
await trx.deleteFrom('groupUsers').where('userId', '=', userId).execute();
|
||||||
|
await trx
|
||||||
|
.deleteFrom('spaceMembers')
|
||||||
|
.where('userId', '=', userId)
|
||||||
|
.execute();
|
||||||
|
await trx
|
||||||
|
.deleteFrom('authAccounts')
|
||||||
|
.where('userId', '=', userId)
|
||||||
|
.execute();
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.attachmentQueue.add(QueueJob.DELETE_USER_AVATARS, user);
|
||||||
|
} catch (err) {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ types.setTypeParser(types.builtins.INT8, (val) => Number(val));
|
|||||||
log: (event: LogEvent) => {
|
log: (event: LogEvent) => {
|
||||||
if (environmentService.getNodeEnv() !== 'development') return;
|
if (environmentService.getNodeEnv() !== 'development') return;
|
||||||
const logger = new Logger(DatabaseModule.name);
|
const logger = new Logger(DatabaseModule.name);
|
||||||
if (event.level === 'query') {
|
if (event.level) {
|
||||||
if (process.env.DEBUG_DB?.toLowerCase() === 'true') {
|
if (process.env.DEBUG_DB?.toLowerCase() === 'true') {
|
||||||
logger.debug(event.query.sql);
|
logger.debug(event.query.sql);
|
||||||
logger.debug('query time: ' + event.queryDurationMillis + ' ms');
|
logger.debug('query time: ' + event.queryDurationMillis + ' ms');
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { type Kysely, sql } from 'kysely';
|
||||||
|
|
||||||
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
|
await db.schema
|
||||||
|
.alterTable('pages')
|
||||||
|
.addColumn('contributor_ids', sql`uuid[]`, (col) => col.defaultTo("{}"))
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(db: Kysely<any>): Promise<void> {
|
||||||
|
await db.schema.alterTable('pages').dropColumn('contributor_ids').execute();
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ export async function executeWithPagination<O, DB, TB extends keyof DB>(
|
|||||||
perPage: number;
|
perPage: number;
|
||||||
page: number;
|
page: number;
|
||||||
experimental_deferredJoinPrimaryKey?: StringReference<DB, TB>;
|
experimental_deferredJoinPrimaryKey?: StringReference<DB, TB>;
|
||||||
|
hasEmptyIds?: boolean; // in cases where we pass empty whereIn ids
|
||||||
},
|
},
|
||||||
): Promise<PaginationResult<O>> {
|
): Promise<PaginationResult<O>> {
|
||||||
if (opts.page < 1) {
|
if (opts.page < 1) {
|
||||||
@@ -33,21 +34,20 @@ export async function executeWithPagination<O, DB, TB extends keyof DB>(
|
|||||||
.select((eb) => eb.ref(deferredJoinPrimaryKey).as('primaryKey'))
|
.select((eb) => eb.ref(deferredJoinPrimaryKey).as('primaryKey'))
|
||||||
.execute()
|
.execute()
|
||||||
// @ts-expect-error TODO: Fix the type here later
|
// @ts-expect-error TODO: Fix the type here later
|
||||||
|
|
||||||
.then((rows) => rows.map((row) => row.primaryKey));
|
.then((rows) => rows.map((row) => row.primaryKey));
|
||||||
|
|
||||||
qb = qb
|
qb = qb
|
||||||
.where((eb) =>
|
.where((eb) =>
|
||||||
primaryKeys.length > 0
|
primaryKeys.length > 0
|
||||||
?
|
? eb(deferredJoinPrimaryKey, 'in', primaryKeys as any)
|
||||||
eb(deferredJoinPrimaryKey, 'in', primaryKeys as any)
|
|
||||||
: eb(sql`1`, '=', 0),
|
: eb(sql`1`, '=', 0),
|
||||||
)
|
)
|
||||||
.clearOffset()
|
.clearOffset()
|
||||||
.clearLimit();
|
.clearLimit();
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows = await qb.execute();
|
const rows = opts.hasEmptyIds ? [] : await qb.execute();
|
||||||
const hasNextPage = rows.length > 0 ? rows.length > opts.perPage : false;
|
const hasNextPage = rows.length > 0 ? rows.length > opts.perPage : false;
|
||||||
const hasPrevPage = rows.length > 0 ? opts.page > 1 : false;
|
const hasPrevPage = rows.length > 0 ? opts.page > 1 : false;
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,18 @@ export class AttachmentRepo {
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateAttachmentsByPageId(
|
||||||
|
updatableAttachment: UpdatableAttachment,
|
||||||
|
pageIds: string[],
|
||||||
|
trx?: KyselyTransaction,
|
||||||
|
) {
|
||||||
|
return dbOrTx(this.db, trx)
|
||||||
|
.updateTable('attachments')
|
||||||
|
.set(updatableAttachment)
|
||||||
|
.where('pageId', 'in', pageIds)
|
||||||
|
.executeTakeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
async updateAttachment(
|
async updateAttachment(
|
||||||
updatableAttachment: UpdatableAttachment,
|
updatableAttachment: UpdatableAttachment,
|
||||||
attachmentId: string,
|
attachmentId: string,
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import {
|
|||||||
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
|
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
|
||||||
import { executeWithPagination } from '@docmost/db/pagination/pagination';
|
import { executeWithPagination } from '@docmost/db/pagination/pagination';
|
||||||
import { validate as isValidUUID } from 'uuid';
|
import { validate as isValidUUID } from 'uuid';
|
||||||
import { ExpressionBuilder } from 'kysely';
|
import { ExpressionBuilder, sql } from 'kysely';
|
||||||
import { DB } from '@docmost/db/types/db';
|
import { DB } from '@docmost/db/types/db';
|
||||||
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||||
import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo';
|
import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -38,6 +38,7 @@ export class PageRepo {
|
|||||||
'createdAt',
|
'createdAt',
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
'deletedAt',
|
'deletedAt',
|
||||||
|
'contributorIds',
|
||||||
];
|
];
|
||||||
|
|
||||||
async findById(
|
async findById(
|
||||||
@@ -48,6 +49,7 @@ export class PageRepo {
|
|||||||
includeSpace?: boolean;
|
includeSpace?: boolean;
|
||||||
includeCreator?: boolean;
|
includeCreator?: boolean;
|
||||||
includeLastUpdatedBy?: boolean;
|
includeLastUpdatedBy?: boolean;
|
||||||
|
includeContributors?: boolean;
|
||||||
withLock?: boolean;
|
withLock?: boolean;
|
||||||
trx?: KyselyTransaction;
|
trx?: KyselyTransaction;
|
||||||
},
|
},
|
||||||
@@ -68,6 +70,10 @@ export class PageRepo {
|
|||||||
query = query.select((eb) => this.withLastUpdatedBy(eb));
|
query = query.select((eb) => this.withLastUpdatedBy(eb));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts?.includeContributors) {
|
||||||
|
query = query.select((eb) => this.withContributors(eb));
|
||||||
|
}
|
||||||
|
|
||||||
if (opts?.includeSpace) {
|
if (opts?.includeSpace) {
|
||||||
query = query.select((eb) => this.withSpace(eb));
|
query = query.select((eb) => this.withSpace(eb));
|
||||||
}
|
}
|
||||||
@@ -90,18 +96,23 @@ export class PageRepo {
|
|||||||
pageId: string,
|
pageId: string,
|
||||||
trx?: KyselyTransaction,
|
trx?: KyselyTransaction,
|
||||||
) {
|
) {
|
||||||
const db = dbOrTx(this.db, trx);
|
return this.updatePages(updatablePage, [pageId], trx);
|
||||||
let query = db
|
}
|
||||||
|
|
||||||
|
async updatePages(
|
||||||
|
updatePageData: UpdatablePage,
|
||||||
|
pageIds: string[],
|
||||||
|
trx?: KyselyTransaction,
|
||||||
|
) {
|
||||||
|
return dbOrTx(this.db, trx)
|
||||||
.updateTable('pages')
|
.updateTable('pages')
|
||||||
.set({ ...updatablePage, updatedAt: new Date() });
|
.set({ ...updatePageData, updatedAt: new Date() })
|
||||||
|
.where(
|
||||||
if (isValidUUID(pageId)) {
|
pageIds.some((pageId) => !isValidUUID(pageId)) ? 'slugId' : 'id',
|
||||||
query = query.where('id', '=', pageId);
|
'in',
|
||||||
} else {
|
pageIds,
|
||||||
query = query.where('slugId', '=', pageId);
|
)
|
||||||
}
|
.executeTakeFirst();
|
||||||
|
|
||||||
return query.executeTakeFirst();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async insertPage(
|
async insertPage(
|
||||||
@@ -154,9 +165,11 @@ export class PageRepo {
|
|||||||
.where('spaceId', 'in', userSpaceIds)
|
.where('spaceId', 'in', userSpaceIds)
|
||||||
.orderBy('updatedAt', 'desc');
|
.orderBy('updatedAt', 'desc');
|
||||||
|
|
||||||
|
const hasEmptyIds = userSpaceIds.length === 0;
|
||||||
const result = executeWithPagination(query, {
|
const result = executeWithPagination(query, {
|
||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
|
hasEmptyIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -189,6 +202,15 @@ export class PageRepo {
|
|||||||
).as('lastUpdatedBy');
|
).as('lastUpdatedBy');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
withContributors(eb: ExpressionBuilder<DB, 'pages'>) {
|
||||||
|
return jsonArrayFrom(
|
||||||
|
eb
|
||||||
|
.selectFrom('users')
|
||||||
|
.select(['users.id', 'users.name', 'users.avatarUrl'])
|
||||||
|
.whereRef('users.id', '=', sql`ANY(${eb.ref('pages.contributorIds')})`),
|
||||||
|
).as('contributors');
|
||||||
|
}
|
||||||
|
|
||||||
async getPageAndDescendants(parentPageId: string) {
|
async getPageAndDescendants(parentPageId: string) {
|
||||||
return this.db
|
return this.db
|
||||||
.withRecursive('page_hierarchy', (db) =>
|
.withRecursive('page_hierarchy', (db) =>
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ export class SpaceMemberRepo {
|
|||||||
])
|
])
|
||||||
.select((eb) => this.groupRepo.withMemberCount(eb))
|
.select((eb) => this.groupRepo.withMemberCount(eb))
|
||||||
.where('spaceId', '=', spaceId)
|
.where('spaceId', '=', spaceId)
|
||||||
|
.orderBy((eb) => eb('groups.id', 'is not', null), 'desc')
|
||||||
.orderBy('spaceMembers.createdAt', 'asc');
|
.orderBy('spaceMembers.createdAt', 'asc');
|
||||||
|
|
||||||
if (pagination.query) {
|
if (pagination.query) {
|
||||||
@@ -221,7 +222,7 @@ export class SpaceMemberRepo {
|
|||||||
|
|
||||||
let query = this.db
|
let query = this.db
|
||||||
.selectFrom('spaces')
|
.selectFrom('spaces')
|
||||||
.selectAll('spaces')
|
.selectAll()
|
||||||
.select((eb) => [this.spaceRepo.withMemberCount(eb)])
|
.select((eb) => [this.spaceRepo.withMemberCount(eb)])
|
||||||
//.where('workspaceId', '=', workspaceId)
|
//.where('workspaceId', '=', workspaceId)
|
||||||
.where('id', 'in', userSpaceIds)
|
.where('id', 'in', userSpaceIds)
|
||||||
@@ -237,9 +238,12 @@ export class SpaceMemberRepo {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasEmptyIds = userSpaceIds.length === 0;
|
||||||
|
|
||||||
const result = executeWithPagination(query, {
|
const result = executeWithPagination(query, {
|
||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
|
hasEmptyIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ export class UserRepo {
|
|||||||
.selectFrom('users')
|
.selectFrom('users')
|
||||||
.select(this.baseFields)
|
.select(this.baseFields)
|
||||||
.where('workspaceId', '=', workspaceId)
|
.where('workspaceId', '=', workspaceId)
|
||||||
|
.where('deletedAt', 'is', null)
|
||||||
.orderBy('createdAt', 'asc');
|
.orderBy('createdAt', 'asc');
|
||||||
|
|
||||||
if (pagination.query) {
|
if (pagination.query) {
|
||||||
|
|||||||
+1
@@ -161,6 +161,7 @@ export interface PageHistory {
|
|||||||
|
|
||||||
export interface Pages {
|
export interface Pages {
|
||||||
content: Json | null;
|
content: Json | null;
|
||||||
|
contributorIds: Generated<string[] | null>;
|
||||||
coverPhoto: string | null;
|
coverPhoto: string | null;
|
||||||
createdAt: Generated<Timestamp>;
|
createdAt: Generated<Timestamp>;
|
||||||
creatorId: string | null;
|
creatorId: string | null;
|
||||||
|
|||||||
+1
-1
Submodule apps/server/src/ee updated: 337854ce96...d3095f2d8b
@@ -21,13 +21,12 @@ import {
|
|||||||
getProsemirrorContent,
|
getProsemirrorContent,
|
||||||
PageExportTree,
|
PageExportTree,
|
||||||
replaceInternalLinks,
|
replaceInternalLinks,
|
||||||
updateAttachmentUrls,
|
updateAttachmentUrlsToLocalPaths,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
||||||
import { Node } from '@tiptap/pm/model';
|
import { Node } from '@tiptap/pm/model';
|
||||||
import { EditorState } from '@tiptap/pm/state';
|
import { EditorState } from '@tiptap/pm/state';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
import slugify from '@sindresorhus/slugify';
|
||||||
import slugify = require('@sindresorhus/slugify');
|
|
||||||
import { EnvironmentService } from '../environment/environment.service';
|
import { EnvironmentService } from '../environment/environment.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -193,7 +192,7 @@ export class ExportService {
|
|||||||
|
|
||||||
if (includeAttachments) {
|
if (includeAttachments) {
|
||||||
await this.zipAttachments(updatedJsonContent, page.spaceId, folder);
|
await this.zipAttachments(updatedJsonContent, page.spaceId, folder);
|
||||||
updatedJsonContent = updateAttachmentUrls(updatedJsonContent);
|
updatedJsonContent = updateAttachmentUrlsToLocalPaths(updatedJsonContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageTitle = getPageTitle(page.title);
|
const pageTitle = getPageTitle(page.title);
|
||||||
|
|||||||
@@ -79,16 +79,18 @@ function preserveDetail(turndownService: TurndownService) {
|
|||||||
return node.nodeName === 'DETAILS';
|
return node.nodeName === 'DETAILS';
|
||||||
},
|
},
|
||||||
replacement: function (content: any, node: HTMLInputElement) {
|
replacement: function (content: any, node: HTMLInputElement) {
|
||||||
// TODO: preserve summary of nested details
|
|
||||||
const summary = node.querySelector(':scope > summary');
|
const summary = node.querySelector(':scope > summary');
|
||||||
let detailSummary = '';
|
let detailSummary = '';
|
||||||
|
|
||||||
if (summary) {
|
if (summary) {
|
||||||
detailSummary = `<summary>${turndownService.turndown(summary.innerHTML)}</summary>`;
|
detailSummary = `<summary>${turndownService.turndown(summary.innerHTML)}</summary>`;
|
||||||
summary.remove();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const detailsContent = turndownService.turndown(node.innerHTML);
|
const detailsContent = Array.from(node.childNodes)
|
||||||
|
.filter(child => child.nodeName !== 'SUMMARY')
|
||||||
|
.map(child => (child.nodeType === 1 ? turndownService.turndown((child as HTMLElement).outerHTML) : child.textContent))
|
||||||
|
.join('');
|
||||||
|
|
||||||
return `\n<details>\n${detailSummary}\n\n${detailsContent}\n\n</details>\n`;
|
return `\n<details>\n${detailSummary}\n\n${detailsContent}\n\n</details>\n`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -62,17 +62,30 @@ export function isAttachmentNode(nodeType: string) {
|
|||||||
return attachmentNodeTypes.includes(nodeType);
|
return attachmentNodeTypes.includes(nodeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateAttachmentUrls(prosemirrorJson: any) {
|
export function updateAttachmentUrlsToLocalPaths(prosemirrorJson: any) {
|
||||||
const doc = jsonToNode(prosemirrorJson);
|
const doc = jsonToNode(prosemirrorJson);
|
||||||
|
if (!doc) return null;
|
||||||
|
|
||||||
|
// Helper function to replace specific URL prefixes
|
||||||
|
const replacePrefix = (url: string): string => {
|
||||||
|
const prefixes = ['/files', '/api/files'];
|
||||||
|
for (const prefix of prefixes) {
|
||||||
|
if (url.startsWith(prefix)) {
|
||||||
|
return url.replace(prefix, 'files');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
doc?.descendants((node: Node) => {
|
doc?.descendants((node: Node) => {
|
||||||
if (isAttachmentNode(node.type.name)) {
|
if (isAttachmentNode(node.type.name)) {
|
||||||
if (node.attrs.src && node.attrs.src.startsWith('/files')) {
|
if (node.attrs.src) {
|
||||||
//@ts-expect-error
|
// @ts-ignore
|
||||||
node.attrs.src = node.attrs.src.replace('/files', 'files');
|
node.attrs.src = replacePrefix(node.attrs.src);
|
||||||
} else if (node.attrs.url && node.attrs.url.startsWith('/files')) {
|
}
|
||||||
//@ts-expect-error
|
if (node.attrs.url) {
|
||||||
node.attrs.url = node.attrs.url.replace('/files', 'files');
|
// @ts-ignore
|
||||||
|
node.attrs.url = replacePrefix(node.attrs.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,18 +19,27 @@ export class MailService {
|
|||||||
async sendEmail(message: MailMessage): Promise<void> {
|
async sendEmail(message: MailMessage): Promise<void> {
|
||||||
if (message.template) {
|
if (message.template) {
|
||||||
// in case this method is used directly. we do not send the tsx template from queue
|
// in case this method is used directly. we do not send the tsx template from queue
|
||||||
message.html = await render(message.template, { pretty: true });
|
message.html = await render(message.template, {
|
||||||
|
pretty: true,
|
||||||
|
});
|
||||||
message.text = await render(message.template, { plainText: true });
|
message.text = await render(message.template, { plainText: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const sender = `${this.environmentService.getMailFromName()} <${this.environmentService.getMailFromAddress()}> `;
|
let from = this.environmentService.getMailFromAddress();
|
||||||
|
if (message.from) {
|
||||||
|
from = message.from;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sender = `${this.environmentService.getMailFromName()} <${from}> `;
|
||||||
await this.mailDriver.sendMail({ from: sender, ...message });
|
await this.mailDriver.sendMail({ from: sender, ...message });
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendToQueue(message: MailMessage): Promise<void> {
|
async sendToQueue(message: MailMessage): Promise<void> {
|
||||||
if (message.template) {
|
if (message.template) {
|
||||||
// transform the React object because it gets lost when sent via the queue
|
// transform the React object because it gets lost when sent via the queue
|
||||||
message.html = await render(message.template, { pretty: true });
|
message.html = await render(message.template, {
|
||||||
|
pretty: true,
|
||||||
|
});
|
||||||
message.text = await render(message.template, {
|
message.text = await render(message.template, {
|
||||||
plainText: true,
|
plainText: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ export enum QueueJob {
|
|||||||
DELETE_PAGE_ATTACHMENTS = 'delete-page-attachments',
|
DELETE_PAGE_ATTACHMENTS = 'delete-page-attachments',
|
||||||
PAGE_CONTENT_UPDATE = 'page-content-update',
|
PAGE_CONTENT_UPDATE = 'page-content-update',
|
||||||
|
|
||||||
|
DELETE_USER_AVATARS = 'delete-user-avatars',
|
||||||
|
|
||||||
PAGE_BACKLINKS = 'page-backlinks',
|
PAGE_BACKLINKS = 'page-backlinks',
|
||||||
|
|
||||||
STRIPE_SEATS_SYNC = 'sync-stripe-seats',
|
STRIPE_SEATS_SYNC = 'sync-stripe-seats',
|
||||||
|
TRIAL_ENDED = 'trial-ended',
|
||||||
|
WELCOME_EMAIL = 'welcome-email',
|
||||||
|
FIRST_PAYMENT_EMAIL = 'first-payment-email',
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "docmost",
|
"name": "docmost",
|
||||||
"homepage": "https://docmost.com",
|
"homepage": "https://docmost.com",
|
||||||
"version": "0.9.0",
|
"version": "0.10.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nx run-many -t build",
|
"build": "nx run-many -t build",
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"client:dev": "nx run client:dev",
|
"client:dev": "nx run client:dev",
|
||||||
"server:dev": "nx run server:start:dev",
|
"server:dev": "nx run server:start:dev",
|
||||||
"server:start": "nx run server:start:prod",
|
"server:start": "nx run server:start:prod",
|
||||||
"email:dev": "nx run @docmost/transactional:dev",
|
"email:dev": "nx run server:email:dev",
|
||||||
"dev": "pnpm concurrently -n \"frontend,backend\" -c \"cyan,green\" \"pnpm run client:dev\" \"pnpm run server:dev\""
|
"dev": "pnpm concurrently -n \"frontend,backend\" -c \"cyan,green\" \"pnpm run client:dev\" \"pnpm run server:dev\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"@hocuspocus/transformer": "^2.15.2",
|
"@hocuspocus/transformer": "^2.15.2",
|
||||||
"@joplin/turndown": "^4.0.74",
|
"@joplin/turndown": "^4.0.74",
|
||||||
"@joplin/turndown-plugin-gfm": "^1.0.56",
|
"@joplin/turndown-plugin-gfm": "^1.0.56",
|
||||||
"@sindresorhus/slugify": "1.1.0",
|
"@sindresorhus/slugify": "2.2.1",
|
||||||
"@tiptap/core": "^2.10.3",
|
"@tiptap/core": "^2.10.3",
|
||||||
"@tiptap/extension-code-block": "^2.10.3",
|
"@tiptap/extension-code-block": "^2.10.3",
|
||||||
"@tiptap/extension-code-block-lowlight": "^2.10.3",
|
"@tiptap/extension-code-block-lowlight": "^2.10.3",
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ export * from "./lib/drawio";
|
|||||||
export * from "./lib/excalidraw";
|
export * from "./lib/excalidraw";
|
||||||
export * from "./lib/embed";
|
export * from "./lib/embed";
|
||||||
export * from "./lib/mention";
|
export * from "./lib/mention";
|
||||||
export * from "./lib/markdown";
|
export * from "./lib/markdown";
|
||||||
@@ -78,10 +78,13 @@ export const Details = Node.create<DetailsOptions>({
|
|||||||
dom.setAttribute("data-type", this.name);
|
dom.setAttribute("data-type", this.name);
|
||||||
btn.setAttribute("data-type", `${this.name}Button`);
|
btn.setAttribute("data-type", `${this.name}Button`);
|
||||||
div.setAttribute("data-type", `${this.name}Container`);
|
div.setAttribute("data-type", `${this.name}Container`);
|
||||||
if (node.attrs.open) {
|
|
||||||
dom.setAttribute("open", "true");
|
if (editor.isEditable) {
|
||||||
} else {
|
if (node.attrs.open) {
|
||||||
dom.removeAttribute("open");
|
dom.setAttribute("open", "true");
|
||||||
|
} else {
|
||||||
|
dom.removeAttribute("open");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ico.innerHTML = icon("right-line");
|
ico.innerHTML = icon("right-line");
|
||||||
@@ -111,6 +114,7 @@ export const Details = Node.create<DetailsOptions>({
|
|||||||
if (updatedNode.type !== this.type) {
|
if (updatedNode.type !== this.type) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!editor.isEditable) return true;
|
||||||
if (updatedNode.attrs.open) {
|
if (updatedNode.attrs.open) {
|
||||||
dom.setAttribute("open", "true");
|
dom.setAttribute("open", "true");
|
||||||
} else {
|
} else {
|
||||||
@@ -132,6 +136,10 @@ export const Details = Node.create<DetailsOptions>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const slice = state.doc.slice(range.start, range.end);
|
const slice = state.doc.slice(range.start, range.end);
|
||||||
|
|
||||||
|
if (slice.content.firstChild.type.name === "detailsSummary")
|
||||||
|
return false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!state.schema.nodes.detailsContent.contentMatch.matchFragment(
|
!state.schema.nodes.detailsContent.contentMatch.matchFragment(
|
||||||
slice.content,
|
slice.content,
|
||||||
|
|||||||
Generated
+90
-293
@@ -38,8 +38,8 @@ importers:
|
|||||||
specifier: ^1.0.56
|
specifier: ^1.0.56
|
||||||
version: 1.0.56
|
version: 1.0.56
|
||||||
'@sindresorhus/slugify':
|
'@sindresorhus/slugify':
|
||||||
specifier: 1.1.0
|
specifier: 2.2.1
|
||||||
version: 1.1.0
|
version: 2.2.1
|
||||||
'@tiptap/core':
|
'@tiptap/core':
|
||||||
specifier: ^2.10.3
|
specifier: ^2.10.3
|
||||||
version: 2.10.3(@tiptap/pm@2.10.3)
|
version: 2.10.3(@tiptap/pm@2.10.3)
|
||||||
@@ -272,6 +272,9 @@ importers:
|
|||||||
js-cookie:
|
js-cookie:
|
||||||
specifier: ^3.0.5
|
specifier: ^3.0.5
|
||||||
version: 3.0.5
|
version: 3.0.5
|
||||||
|
jwt-decode:
|
||||||
|
specifier: ^4.0.0
|
||||||
|
version: 4.0.0
|
||||||
katex:
|
katex:
|
||||||
specifier: 0.16.21
|
specifier: 0.16.21
|
||||||
version: 0.16.21
|
version: 0.16.21
|
||||||
@@ -483,9 +486,6 @@ importers:
|
|||||||
cookie:
|
cookie:
|
||||||
specifier: ^1.0.2
|
specifier: ^1.0.2
|
||||||
version: 1.0.2
|
version: 1.0.2
|
||||||
fix-esm:
|
|
||||||
specifier: ^1.0.1
|
|
||||||
version: 1.0.1
|
|
||||||
fs-extra:
|
fs-extra:
|
||||||
specifier: ^11.3.0
|
specifier: ^11.3.0
|
||||||
version: 11.3.0
|
version: 11.3.0
|
||||||
@@ -505,8 +505,8 @@ importers:
|
|||||||
specifier: ^2.1.35
|
specifier: ^2.1.35
|
||||||
version: 2.1.35
|
version: 2.1.35
|
||||||
nanoid:
|
nanoid:
|
||||||
specifier: ^5.1.0
|
specifier: ^5.1.5
|
||||||
version: 5.1.0
|
version: 5.1.5
|
||||||
nestjs-kysely:
|
nestjs-kysely:
|
||||||
specifier: ^1.1.0
|
specifier: ^1.1.0
|
||||||
version: 1.1.0(@nestjs/common@11.0.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.10)(kysely@0.27.5)(reflect-metadata@0.2.2)
|
version: 1.1.0(@nestjs/common@11.0.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.10)(kysely@0.27.5)(reflect-metadata@0.2.2)
|
||||||
@@ -630,7 +630,7 @@ importers:
|
|||||||
version: 7.0.0
|
version: 7.0.0
|
||||||
ts-jest:
|
ts-jest:
|
||||||
specifier: ^29.2.5
|
specifier: ^29.2.5
|
||||||
version: 29.2.5(@babel/core@7.24.3)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.3))(jest@29.7.0(@types/node@22.13.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(typescript@5.7.3)))(typescript@5.7.3)
|
version: 29.2.5(@babel/core@7.24.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest@29.7.0(@types/node@22.13.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(typescript@5.7.3)))(typescript@5.7.3)
|
||||||
ts-loader:
|
ts-loader:
|
||||||
specifier: ^9.5.2
|
specifier: ^9.5.2
|
||||||
version: 9.5.2(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.25(@swc/helpers@0.5.5)))
|
version: 9.5.2(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.25(@swc/helpers@0.5.5)))
|
||||||
@@ -872,18 +872,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
|
resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/compat-data@7.23.5':
|
|
||||||
resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/compat-data@7.26.2':
|
'@babel/compat-data@7.26.2':
|
||||||
resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==}
|
resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/core@7.24.3':
|
|
||||||
resolution: {integrity: sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/core@7.24.5':
|
'@babel/core@7.24.5':
|
||||||
resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==}
|
resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -916,10 +908,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==}
|
resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/helper-compilation-targets@7.23.6':
|
|
||||||
resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/helper-compilation-targets@7.25.9':
|
'@babel/helper-compilation-targets@7.25.9':
|
||||||
resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==}
|
resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -949,18 +937,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==}
|
resolution: {integrity: sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/helper-function-name@7.23.0':
|
|
||||||
resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/helper-function-name@7.24.6':
|
'@babel/helper-function-name@7.24.6':
|
||||||
resolution: {integrity: sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==}
|
resolution: {integrity: sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/helper-hoist-variables@7.22.5':
|
|
||||||
resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/helper-hoist-variables@7.24.6':
|
'@babel/helper-hoist-variables@7.24.6':
|
||||||
resolution: {integrity: sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA==}
|
resolution: {integrity: sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -1071,10 +1051,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
|
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/helper-validator-option@7.23.5':
|
|
||||||
resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/helper-validator-option@7.25.9':
|
'@babel/helper-validator-option@7.25.9':
|
||||||
resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
|
resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -1083,10 +1059,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==}
|
resolution: {integrity: sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/helpers@7.24.1':
|
|
||||||
resolution: {integrity: sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/helpers@7.24.6':
|
'@babel/helpers@7.24.6':
|
||||||
resolution: {integrity: sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA==}
|
resolution: {integrity: sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -1099,11 +1071,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==}
|
resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/parser@7.24.1':
|
|
||||||
resolution: {integrity: sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==}
|
|
||||||
engines: {node: '>=6.0.0'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
'@babel/parser@7.24.5':
|
'@babel/parser@7.24.5':
|
||||||
resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==}
|
resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
@@ -1143,13 +1110,6 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@babel/core': ^7.0.0-0
|
'@babel/core': ^7.0.0-0
|
||||||
|
|
||||||
'@babel/plugin-proposal-export-namespace-from@7.18.9':
|
|
||||||
resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.
|
|
||||||
peerDependencies:
|
|
||||||
'@babel/core': ^7.0.0-0
|
|
||||||
|
|
||||||
'@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2':
|
'@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2':
|
||||||
resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==}
|
resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -1615,14 +1575,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==}
|
resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/template@7.22.15':
|
|
||||||
resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/template@7.24.0':
|
|
||||||
resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/template@7.24.6':
|
'@babel/template@7.24.6':
|
||||||
resolution: {integrity: sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==}
|
resolution: {integrity: sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -1631,10 +1583,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==}
|
resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/traverse@7.24.1':
|
|
||||||
resolution: {integrity: sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/traverse@7.25.9':
|
'@babel/traverse@7.25.9':
|
||||||
resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==}
|
resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -3317,13 +3265,13 @@ packages:
|
|||||||
'@sinclair/typebox@0.27.8':
|
'@sinclair/typebox@0.27.8':
|
||||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||||
|
|
||||||
'@sindresorhus/slugify@1.1.0':
|
'@sindresorhus/slugify@2.2.1':
|
||||||
resolution: {integrity: sha512-ujZRbmmizX26yS/HnB3P9QNlNa4+UvHh+rIse3RbOXLp8yl6n1TxB4t7NHggtVgS8QmmOtzXo48kCxZGACpkPw==}
|
resolution: {integrity: sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
'@sindresorhus/transliterate@0.1.2':
|
'@sindresorhus/transliterate@1.6.0':
|
||||||
resolution: {integrity: sha512-5/kmIOY9FF32nicXH+5yLNTX4NJ4atl7jRgqAJuIn/iyDFXBktOKDxCvyGE/EzmF4ngSUvjXxQUQlQiZ5lfw+w==}
|
resolution: {integrity: sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
'@sinonjs/commons@3.0.1':
|
'@sinonjs/commons@3.0.1':
|
||||||
resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==}
|
resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==}
|
||||||
@@ -4753,11 +4701,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
browserslist@4.23.0:
|
|
||||||
resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==}
|
|
||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
browserslist@4.24.2:
|
browserslist@4.24.2:
|
||||||
resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==}
|
resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==}
|
||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
@@ -4819,9 +4762,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001600:
|
|
||||||
resolution: {integrity: sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==}
|
|
||||||
|
|
||||||
caniuse-lite@1.0.30001684:
|
caniuse-lite@1.0.30001684:
|
||||||
resolution: {integrity: sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==}
|
resolution: {integrity: sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==}
|
||||||
|
|
||||||
@@ -5460,9 +5400,6 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
electron-to-chromium@1.4.715:
|
|
||||||
resolution: {integrity: sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==}
|
|
||||||
|
|
||||||
electron-to-chromium@1.5.65:
|
electron-to-chromium@1.5.65:
|
||||||
resolution: {integrity: sha512-PWVzBjghx7/wop6n22vS2MLU8tKGd4Q91aCEGhG/TYmW6PP5OcSXcdnxTe1NNt0T66N8D6jxh4kC8UsdzOGaIw==}
|
resolution: {integrity: sha512-PWVzBjghx7/wop6n22vS2MLU8tKGd4Q91aCEGhG/TYmW6PP5OcSXcdnxTe1NNt0T66N8D6jxh4kC8UsdzOGaIw==}
|
||||||
|
|
||||||
@@ -5588,6 +5525,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
escape-string-regexp@5.0.0:
|
||||||
|
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
eslint-config-prettier@10.0.1:
|
eslint-config-prettier@10.0.1:
|
||||||
resolution: {integrity: sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==}
|
resolution: {integrity: sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -5796,9 +5737,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
fix-esm@1.0.1:
|
|
||||||
resolution: {integrity: sha512-EZtb7wPXZS54GaGxaWxMlhd1DUDCnAg5srlYdu/1ZVeW+7wwR3Tp59nu52dXByFs3MBRq+SByx1wDOJpRvLEXw==}
|
|
||||||
|
|
||||||
flat-cache@4.0.1:
|
flat-cache@4.0.1:
|
||||||
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
|
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
@@ -6612,6 +6550,10 @@ packages:
|
|||||||
jws@3.2.2:
|
jws@3.2.2:
|
||||||
resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
|
resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
|
||||||
|
|
||||||
|
jwt-decode@4.0.0:
|
||||||
|
resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
katex@0.16.21:
|
katex@0.16.21:
|
||||||
resolution: {integrity: sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==}
|
resolution: {integrity: sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -6762,9 +6704,6 @@ packages:
|
|||||||
lodash.debounce@4.0.8:
|
lodash.debounce@4.0.8:
|
||||||
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
||||||
|
|
||||||
lodash.deburr@4.1.0:
|
|
||||||
resolution: {integrity: sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==}
|
|
||||||
|
|
||||||
lodash.defaults@4.2.0:
|
lodash.defaults@4.2.0:
|
||||||
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
|
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
|
||||||
|
|
||||||
@@ -7013,18 +6952,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
|
resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
|
||||||
engines: {node: ^18.17.0 || >=20.5.0}
|
engines: {node: ^18.17.0 || >=20.5.0}
|
||||||
|
|
||||||
|
nanoid@3.3.11:
|
||||||
|
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||||
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
nanoid@3.3.7:
|
nanoid@3.3.7:
|
||||||
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
|
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
|
||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
nanoid@3.3.8:
|
nanoid@5.1.5:
|
||||||
resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
|
resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==}
|
||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
nanoid@5.1.0:
|
|
||||||
resolution: {integrity: sha512-zDAl/llz8Ue/EblwSYwdxGBYfj46IM1dhjVi8dyp9LQffoIGxJEAHj2oeZ4uNcgycSRcQ83CnfcZqEJzVDLcDw==}
|
|
||||||
engines: {node: ^18 || >=20}
|
engines: {node: ^18 || >=20}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -7097,9 +7036,6 @@ packages:
|
|||||||
node-machine-id@1.1.12:
|
node-machine-id@1.1.12:
|
||||||
resolution: {integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==}
|
resolution: {integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==}
|
||||||
|
|
||||||
node-releases@2.0.14:
|
|
||||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
|
||||||
|
|
||||||
node-releases@2.0.18:
|
node-releases@2.0.18:
|
||||||
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
|
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
|
||||||
|
|
||||||
@@ -8646,12 +8582,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
|
|
||||||
update-browserslist-db@1.0.13:
|
|
||||||
resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
|
|
||||||
hasBin: true
|
|
||||||
peerDependencies:
|
|
||||||
browserslist: '>= 4.21.0'
|
|
||||||
|
|
||||||
update-browserslist-db@1.1.1:
|
update-browserslist-db@1.1.1:
|
||||||
resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==}
|
resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -9672,30 +9602,8 @@ snapshots:
|
|||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
picocolors: 1.0.1
|
picocolors: 1.0.1
|
||||||
|
|
||||||
'@babel/compat-data@7.23.5': {}
|
|
||||||
|
|
||||||
'@babel/compat-data@7.26.2': {}
|
'@babel/compat-data@7.26.2': {}
|
||||||
|
|
||||||
'@babel/core@7.24.3':
|
|
||||||
dependencies:
|
|
||||||
'@ampproject/remapping': 2.3.0
|
|
||||||
'@babel/code-frame': 7.24.2
|
|
||||||
'@babel/generator': 7.24.1
|
|
||||||
'@babel/helper-compilation-targets': 7.23.6
|
|
||||||
'@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.3)
|
|
||||||
'@babel/helpers': 7.24.1
|
|
||||||
'@babel/parser': 7.24.1
|
|
||||||
'@babel/template': 7.24.0
|
|
||||||
'@babel/traverse': 7.24.1
|
|
||||||
'@babel/types': 7.24.0
|
|
||||||
convert-source-map: 2.0.0
|
|
||||||
debug: 4.3.4
|
|
||||||
gensync: 1.0.0-beta.2
|
|
||||||
json5: 2.2.3
|
|
||||||
semver: 6.3.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@babel/core@7.24.5':
|
'@babel/core@7.24.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ampproject/remapping': 2.3.0
|
'@ampproject/remapping': 2.3.0
|
||||||
@@ -9786,14 +9694,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.26.0
|
'@babel/types': 7.26.0
|
||||||
|
|
||||||
'@babel/helper-compilation-targets@7.23.6':
|
|
||||||
dependencies:
|
|
||||||
'@babel/compat-data': 7.23.5
|
|
||||||
'@babel/helper-validator-option': 7.23.5
|
|
||||||
browserslist: 4.23.0
|
|
||||||
lru-cache: 5.1.1
|
|
||||||
semver: 6.3.1
|
|
||||||
|
|
||||||
'@babel/helper-compilation-targets@7.25.9':
|
'@babel/helper-compilation-targets@7.25.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/compat-data': 7.26.2
|
'@babel/compat-data': 7.26.2
|
||||||
@@ -9837,20 +9737,11 @@ snapshots:
|
|||||||
|
|
||||||
'@babel/helper-environment-visitor@7.24.6': {}
|
'@babel/helper-environment-visitor@7.24.6': {}
|
||||||
|
|
||||||
'@babel/helper-function-name@7.23.0':
|
|
||||||
dependencies:
|
|
||||||
'@babel/template': 7.22.15
|
|
||||||
'@babel/types': 7.24.0
|
|
||||||
|
|
||||||
'@babel/helper-function-name@7.24.6':
|
'@babel/helper-function-name@7.24.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/template': 7.24.6
|
'@babel/template': 7.24.6
|
||||||
'@babel/types': 7.24.6
|
'@babel/types': 7.24.6
|
||||||
|
|
||||||
'@babel/helper-hoist-variables@7.22.5':
|
|
||||||
dependencies:
|
|
||||||
'@babel/types': 7.24.0
|
|
||||||
|
|
||||||
'@babel/helper-hoist-variables@7.24.6':
|
'@babel/helper-hoist-variables@7.24.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.24.6
|
'@babel/types': 7.24.6
|
||||||
@@ -9874,15 +9765,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@babel/helper-module-transforms@7.23.3(@babel/core@7.24.3)':
|
|
||||||
dependencies:
|
|
||||||
'@babel/core': 7.24.3
|
|
||||||
'@babel/helper-environment-visitor': 7.22.20
|
|
||||||
'@babel/helper-module-imports': 7.22.15
|
|
||||||
'@babel/helper-simple-access': 7.22.5
|
|
||||||
'@babel/helper-split-export-declaration': 7.22.6
|
|
||||||
'@babel/helper-validator-identifier': 7.22.20
|
|
||||||
|
|
||||||
'@babel/helper-module-transforms@7.23.3(@babel/core@7.26.0)':
|
'@babel/helper-module-transforms@7.23.3(@babel/core@7.26.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
@@ -9975,8 +9857,6 @@ snapshots:
|
|||||||
|
|
||||||
'@babel/helper-validator-identifier@7.25.9': {}
|
'@babel/helper-validator-identifier@7.25.9': {}
|
||||||
|
|
||||||
'@babel/helper-validator-option@7.23.5': {}
|
|
||||||
|
|
||||||
'@babel/helper-validator-option@7.25.9': {}
|
'@babel/helper-validator-option@7.25.9': {}
|
||||||
|
|
||||||
'@babel/helper-wrap-function@7.22.20':
|
'@babel/helper-wrap-function@7.22.20':
|
||||||
@@ -9985,14 +9865,6 @@ snapshots:
|
|||||||
'@babel/template': 7.25.9
|
'@babel/template': 7.25.9
|
||||||
'@babel/types': 7.26.0
|
'@babel/types': 7.26.0
|
||||||
|
|
||||||
'@babel/helpers@7.24.1':
|
|
||||||
dependencies:
|
|
||||||
'@babel/template': 7.24.0
|
|
||||||
'@babel/traverse': 7.24.1
|
|
||||||
'@babel/types': 7.24.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@babel/helpers@7.24.6':
|
'@babel/helpers@7.24.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/template': 7.25.9
|
'@babel/template': 7.25.9
|
||||||
@@ -10010,10 +9882,6 @@ snapshots:
|
|||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
|
|
||||||
'@babel/parser@7.24.1':
|
|
||||||
dependencies:
|
|
||||||
'@babel/types': 7.24.0
|
|
||||||
|
|
||||||
'@babel/parser@7.24.5':
|
'@babel/parser@7.24.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.26.0
|
'@babel/types': 7.26.0
|
||||||
@@ -10051,19 +9919,13 @@ snapshots:
|
|||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
'@babel/plugin-syntax-decorators': 7.23.3(@babel/core@7.26.0)
|
'@babel/plugin-syntax-decorators': 7.23.3(@babel/core@7.26.0)
|
||||||
|
|
||||||
'@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.24.3)':
|
|
||||||
dependencies:
|
|
||||||
'@babel/core': 7.24.3
|
|
||||||
'@babel/helper-plugin-utils': 7.24.0
|
|
||||||
'@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.3)
|
|
||||||
|
|
||||||
'@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0)':
|
'@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
|
|
||||||
'@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.3)':
|
'@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10077,9 +9939,9 @@ snapshots:
|
|||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
|
|
||||||
'@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.3)':
|
'@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10088,9 +9950,9 @@ snapshots:
|
|||||||
'@babel/core': 7.24.6
|
'@babel/core': 7.24.6
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
|
|
||||||
'@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.3)':
|
'@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10119,11 +9981,6 @@ snapshots:
|
|||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
|
|
||||||
'@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.3)':
|
|
||||||
dependencies:
|
|
||||||
'@babel/core': 7.24.3
|
|
||||||
'@babel/helper-plugin-utils': 7.24.0
|
|
||||||
|
|
||||||
'@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.26.0)':
|
'@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.26.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
@@ -10139,9 +9996,9 @@ snapshots:
|
|||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
|
|
||||||
'@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.3)':
|
'@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10155,9 +10012,9 @@ snapshots:
|
|||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
|
|
||||||
'@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.3)':
|
'@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10181,9 +10038,9 @@ snapshots:
|
|||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
|
|
||||||
'@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.3)':
|
'@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10197,9 +10054,9 @@ snapshots:
|
|||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
|
|
||||||
'@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.3)':
|
'@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10213,9 +10070,9 @@ snapshots:
|
|||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
|
|
||||||
'@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.3)':
|
'@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10229,9 +10086,9 @@ snapshots:
|
|||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
|
|
||||||
'@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.3)':
|
'@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10245,9 +10102,9 @@ snapshots:
|
|||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
|
|
||||||
'@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.3)':
|
'@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10261,9 +10118,9 @@ snapshots:
|
|||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
|
|
||||||
'@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.3)':
|
'@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10282,9 +10139,9 @@ snapshots:
|
|||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
|
|
||||||
'@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.3)':
|
'@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -10454,13 +10311,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.24.3)':
|
|
||||||
dependencies:
|
|
||||||
'@babel/core': 7.24.3
|
|
||||||
'@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.3)
|
|
||||||
'@babel/helper-plugin-utils': 7.24.0
|
|
||||||
'@babel/helper-simple-access': 7.22.5
|
|
||||||
|
|
||||||
'@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.26.0)':
|
'@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.26.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
@@ -10763,18 +10613,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime: 0.14.1
|
regenerator-runtime: 0.14.1
|
||||||
|
|
||||||
'@babel/template@7.22.15':
|
|
||||||
dependencies:
|
|
||||||
'@babel/code-frame': 7.26.2
|
|
||||||
'@babel/parser': 7.26.2
|
|
||||||
'@babel/types': 7.24.0
|
|
||||||
|
|
||||||
'@babel/template@7.24.0':
|
|
||||||
dependencies:
|
|
||||||
'@babel/code-frame': 7.24.2
|
|
||||||
'@babel/parser': 7.24.1
|
|
||||||
'@babel/types': 7.24.0
|
|
||||||
|
|
||||||
'@babel/template@7.24.6':
|
'@babel/template@7.24.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.26.2
|
'@babel/code-frame': 7.26.2
|
||||||
@@ -10787,21 +10625,6 @@ snapshots:
|
|||||||
'@babel/parser': 7.26.2
|
'@babel/parser': 7.26.2
|
||||||
'@babel/types': 7.26.0
|
'@babel/types': 7.26.0
|
||||||
|
|
||||||
'@babel/traverse@7.24.1':
|
|
||||||
dependencies:
|
|
||||||
'@babel/code-frame': 7.24.2
|
|
||||||
'@babel/generator': 7.24.1
|
|
||||||
'@babel/helper-environment-visitor': 7.22.20
|
|
||||||
'@babel/helper-function-name': 7.23.0
|
|
||||||
'@babel/helper-hoist-variables': 7.22.5
|
|
||||||
'@babel/helper-split-export-declaration': 7.22.6
|
|
||||||
'@babel/parser': 7.24.1
|
|
||||||
'@babel/types': 7.24.0
|
|
||||||
debug: 4.3.7
|
|
||||||
globals: 11.12.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@babel/traverse@7.25.9':
|
'@babel/traverse@7.25.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.26.2
|
'@babel/code-frame': 7.26.2
|
||||||
@@ -12382,15 +12205,14 @@ snapshots:
|
|||||||
|
|
||||||
'@sinclair/typebox@0.27.8': {}
|
'@sinclair/typebox@0.27.8': {}
|
||||||
|
|
||||||
'@sindresorhus/slugify@1.1.0':
|
'@sindresorhus/slugify@2.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sindresorhus/transliterate': 0.1.2
|
'@sindresorhus/transliterate': 1.6.0
|
||||||
escape-string-regexp: 4.0.0
|
escape-string-regexp: 5.0.0
|
||||||
|
|
||||||
'@sindresorhus/transliterate@0.1.2':
|
'@sindresorhus/transliterate@1.6.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
escape-string-regexp: 2.0.0
|
escape-string-regexp: 5.0.0
|
||||||
lodash.deburr: 4.1.0
|
|
||||||
|
|
||||||
'@sinonjs/commons@3.0.1':
|
'@sinonjs/commons@3.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -13996,13 +13818,13 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
|
|
||||||
babel-jest@29.7.0(@babel/core@7.24.3):
|
babel-jest@29.7.0(@babel/core@7.24.5):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@jest/transform': 29.7.0
|
'@jest/transform': 29.7.0
|
||||||
'@types/babel__core': 7.20.5
|
'@types/babel__core': 7.20.5
|
||||||
babel-plugin-istanbul: 6.1.1
|
babel-plugin-istanbul: 6.1.1
|
||||||
babel-preset-jest: 29.6.3(@babel/core@7.24.3)
|
babel-preset-jest: 29.6.3(@babel/core@7.24.5)
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
slash: 3.0.0
|
slash: 3.0.0
|
||||||
@@ -14086,21 +13908,21 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@babel/traverse': 7.25.9
|
'@babel/traverse': 7.25.9
|
||||||
|
|
||||||
babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.3):
|
babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.5):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.3)
|
'@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.5)
|
||||||
'@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.3)
|
'@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.5)
|
||||||
'@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.3)
|
'@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.5)
|
||||||
'@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.3)
|
'@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.5)
|
||||||
'@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.3)
|
'@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.5)
|
||||||
'@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.3)
|
'@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.5)
|
||||||
'@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.3)
|
'@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.5)
|
||||||
'@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.3)
|
'@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.5)
|
||||||
'@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.3)
|
'@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.5)
|
||||||
'@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.3)
|
'@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.5)
|
||||||
'@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.3)
|
'@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.5)
|
||||||
'@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.3)
|
'@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.5)
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.6):
|
babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.6):
|
||||||
@@ -14119,11 +13941,11 @@ snapshots:
|
|||||||
'@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.6)
|
'@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.6)
|
||||||
'@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.6)
|
'@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.6)
|
||||||
|
|
||||||
babel-preset-jest@29.6.3(@babel/core@7.24.3):
|
babel-preset-jest@29.6.3(@babel/core@7.24.5):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
babel-plugin-jest-hoist: 29.6.3
|
babel-plugin-jest-hoist: 29.6.3
|
||||||
babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.3)
|
babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.5)
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
babel-preset-jest@29.6.3(@babel/core@7.24.6):
|
babel-preset-jest@29.6.3(@babel/core@7.24.6):
|
||||||
@@ -14188,13 +14010,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fill-range: 7.1.1
|
fill-range: 7.1.1
|
||||||
|
|
||||||
browserslist@4.23.0:
|
|
||||||
dependencies:
|
|
||||||
caniuse-lite: 1.0.30001600
|
|
||||||
electron-to-chromium: 1.4.715
|
|
||||||
node-releases: 2.0.14
|
|
||||||
update-browserslist-db: 1.0.13(browserslist@4.23.0)
|
|
||||||
|
|
||||||
browserslist@4.24.2:
|
browserslist@4.24.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001684
|
caniuse-lite: 1.0.30001684
|
||||||
@@ -14266,8 +14081,6 @@ snapshots:
|
|||||||
|
|
||||||
camelcase@6.3.0: {}
|
camelcase@6.3.0: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001600: {}
|
|
||||||
|
|
||||||
caniuse-lite@1.0.30001684: {}
|
caniuse-lite@1.0.30001684: {}
|
||||||
|
|
||||||
chalk@2.4.2:
|
chalk@2.4.2:
|
||||||
@@ -14914,8 +14727,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
jake: 10.8.7
|
jake: 10.8.7
|
||||||
|
|
||||||
electron-to-chromium@1.4.715: {}
|
|
||||||
|
|
||||||
electron-to-chromium@1.5.65: {}
|
electron-to-chromium@1.5.65: {}
|
||||||
|
|
||||||
emittery@0.13.1: {}
|
emittery@0.13.1: {}
|
||||||
@@ -15172,6 +14983,8 @@ snapshots:
|
|||||||
|
|
||||||
escape-string-regexp@4.0.0: {}
|
escape-string-regexp@4.0.0: {}
|
||||||
|
|
||||||
|
escape-string-regexp@5.0.0: {}
|
||||||
|
|
||||||
eslint-config-prettier@10.0.1(eslint@9.20.1(jiti@1.21.0)):
|
eslint-config-prettier@10.0.1(eslint@9.20.1(jiti@1.21.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.20.1(jiti@1.21.0)
|
eslint: 9.20.1(jiti@1.21.0)
|
||||||
@@ -15469,14 +15282,6 @@ snapshots:
|
|||||||
locate-path: 6.0.0
|
locate-path: 6.0.0
|
||||||
path-exists: 4.0.0
|
path-exists: 4.0.0
|
||||||
|
|
||||||
fix-esm@1.0.1:
|
|
||||||
dependencies:
|
|
||||||
'@babel/core': 7.24.3
|
|
||||||
'@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.24.3)
|
|
||||||
'@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.24.3)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
flat-cache@4.0.1:
|
flat-cache@4.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
flatted: 3.2.9
|
flatted: 3.2.9
|
||||||
@@ -16534,6 +16339,8 @@ snapshots:
|
|||||||
jwa: 1.4.1
|
jwa: 1.4.1
|
||||||
safe-buffer: 5.2.1
|
safe-buffer: 5.2.1
|
||||||
|
|
||||||
|
jwt-decode@4.0.0: {}
|
||||||
|
|
||||||
katex@0.16.21:
|
katex@0.16.21:
|
||||||
dependencies:
|
dependencies:
|
||||||
commander: 8.3.0
|
commander: 8.3.0
|
||||||
@@ -16661,8 +16468,6 @@ snapshots:
|
|||||||
|
|
||||||
lodash.debounce@4.0.8: {}
|
lodash.debounce@4.0.8: {}
|
||||||
|
|
||||||
lodash.deburr@4.1.0: {}
|
|
||||||
|
|
||||||
lodash.defaults@4.2.0: {}
|
lodash.defaults@4.2.0: {}
|
||||||
|
|
||||||
lodash.flatten@4.4.0: {}
|
lodash.flatten@4.4.0: {}
|
||||||
@@ -16904,11 +16709,11 @@ snapshots:
|
|||||||
|
|
||||||
mute-stream@2.0.0: {}
|
mute-stream@2.0.0: {}
|
||||||
|
|
||||||
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
nanoid@3.3.7: {}
|
nanoid@3.3.7: {}
|
||||||
|
|
||||||
nanoid@3.3.8: {}
|
nanoid@5.1.5: {}
|
||||||
|
|
||||||
nanoid@5.1.0: {}
|
|
||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
@@ -16974,8 +16779,6 @@ snapshots:
|
|||||||
|
|
||||||
node-machine-id@1.1.12: {}
|
node-machine-id@1.1.12: {}
|
||||||
|
|
||||||
node-releases@2.0.14: {}
|
|
||||||
|
|
||||||
node-releases@2.0.18: {}
|
node-releases@2.0.18: {}
|
||||||
|
|
||||||
nodemailer@6.10.0: {}
|
nodemailer@6.10.0: {}
|
||||||
@@ -17415,7 +17218,7 @@ snapshots:
|
|||||||
|
|
||||||
postcss@8.4.31:
|
postcss@8.4.31:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.8
|
nanoid: 3.3.11
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
@@ -17427,7 +17230,7 @@ snapshots:
|
|||||||
|
|
||||||
postcss@8.5.2:
|
postcss@8.5.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.8
|
nanoid: 3.3.11
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
@@ -18477,7 +18280,7 @@ snapshots:
|
|||||||
|
|
||||||
ts-dedent@2.2.0: {}
|
ts-dedent@2.2.0: {}
|
||||||
|
|
||||||
ts-jest@29.2.5(@babel/core@7.24.3)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.3))(jest@29.7.0(@types/node@22.13.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(typescript@5.7.3)))(typescript@5.7.3):
|
ts-jest@29.2.5(@babel/core@7.24.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest@29.7.0(@types/node@22.13.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(typescript@5.7.3)))(typescript@5.7.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
bs-logger: 0.2.6
|
bs-logger: 0.2.6
|
||||||
ejs: 3.1.10
|
ejs: 3.1.10
|
||||||
@@ -18491,10 +18294,10 @@ snapshots:
|
|||||||
typescript: 5.7.3
|
typescript: 5.7.3
|
||||||
yargs-parser: 21.1.1
|
yargs-parser: 21.1.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@babel/core': 7.24.3
|
'@babel/core': 7.24.5
|
||||||
'@jest/transform': 29.7.0
|
'@jest/transform': 29.7.0
|
||||||
'@jest/types': 29.6.3
|
'@jest/types': 29.6.3
|
||||||
babel-jest: 29.7.0(@babel/core@7.24.3)
|
babel-jest: 29.7.0(@babel/core@7.24.5)
|
||||||
|
|
||||||
ts-loader@9.5.2(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.25(@swc/helpers@0.5.5))):
|
ts-loader@9.5.2(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.25(@swc/helpers@0.5.5))):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -18678,12 +18481,6 @@ snapshots:
|
|||||||
|
|
||||||
universalify@2.0.1: {}
|
universalify@2.0.1: {}
|
||||||
|
|
||||||
update-browserslist-db@1.0.13(browserslist@4.23.0):
|
|
||||||
dependencies:
|
|
||||||
browserslist: 4.23.0
|
|
||||||
escalade: 3.1.1
|
|
||||||
picocolors: 1.0.0
|
|
||||||
|
|
||||||
update-browserslist-db@1.1.1(browserslist@4.24.2):
|
update-browserslist-db@1.1.1(browserslist@4.24.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.24.2
|
browserslist: 4.24.2
|
||||||
|
|||||||
Reference in New Issue
Block a user