From fa194a497c43061071aa6949890bb5c0da511933 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:04:43 +0100 Subject: [PATCH 01/63] cleanup --- apps/client/public/locales/zh-CN/translation.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/client/public/locales/zh-CN/translation.json b/apps/client/public/locales/zh-CN/translation.json index 0c45cd50..bbe88fce 100644 --- a/apps/client/public/locales/zh-CN/translation.json +++ b/apps/client/public/locales/zh-CN/translation.json @@ -75,7 +75,6 @@ "Full access": "完全访问", "Full page width": "全页宽度", "Full width": "全宽", - "View headings": "查看标题", "Show article title menu.": "显示文章标题菜单", "General": "常规", "Group": "群组", @@ -172,10 +171,8 @@ "Successfully restored": "恢复成功", "System settings": "系统设置", "Theme": "主题", - "On this page": "他是这个页面", "To change your email, you have to enter your password and new email.": "要更改您的电子邮箱,您需要输入密码和新的电子邮箱地址。", "Toggle full page width": "切换全页宽度", - "Toggle view headings menu": "切换查看广告菜单", "Unable to import pages. Please try again.": "无法导入页面。请重试。", "untitled": "无标题", "Untitled": "无标题", From 276ececbf2ad3d4d0287013278c4039b21bbc6cf Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:06:32 +0100 Subject: [PATCH 02/63] cleanup --- apps/client/public/locales/zh-CN/translation.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/client/public/locales/zh-CN/translation.json b/apps/client/public/locales/zh-CN/translation.json index bbe88fce..31788ae2 100644 --- a/apps/client/public/locales/zh-CN/translation.json +++ b/apps/client/public/locales/zh-CN/translation.json @@ -75,7 +75,6 @@ "Full access": "完全访问", "Full page width": "全页宽度", "Full width": "全宽", - "Show article title menu.": "显示文章标题菜单", "General": "常规", "Group": "群组", "Group description": "群组描述", From ee30d9d0f2b908f09b686d7368df0b2752081231 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:10:08 +0100 Subject: [PATCH 03/63] New Crowdin updates (#1003) * New translations translation.json (French) * New translations translation.json (Italian) * New translations translation.json (Japanese) * New translations translation.json (Korean) * New translations translation.json (Russian) * New translations translation.json (Chinese Simplified) * New translations translation.json (English) * New translations translation.json (Portuguese, Brazilian) --- .../public/locales/en-US/translation.json | 13 +------- .../public/locales/fr-FR/translation.json | 2 +- .../public/locales/it-IT/translation.json | 10 +++--- .../public/locales/ja-JP/translation.json | 10 +++--- .../public/locales/ko-KR/translation.json | 26 +++++++-------- .../public/locales/pt-BR/translation.json | 26 +++++++-------- .../public/locales/ru-RU/translation.json | 32 +++++++++---------- .../public/locales/zh-CN/translation.json | 26 +++++++-------- 8 files changed, 67 insertions(+), 78 deletions(-) diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 3cd9fae6..b69d0b28 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -351,16 +351,5 @@ "Created at: {{time}}": "Created at: {{time}}", "Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}", "Word count: {{wordCount}}": "Word count: {{wordCount}}", - "Character count: {{characterCount}}": "Character count: {{characterCount}}", - "New update": "New update", - "{{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." + "Character count: {{characterCount}}": "Character count: {{characterCount}}" } diff --git a/apps/client/public/locales/fr-FR/translation.json b/apps/client/public/locales/fr-FR/translation.json index 1d750d40..9a5bf561 100644 --- a/apps/client/public/locales/fr-FR/translation.json +++ b/apps/client/public/locales/fr-FR/translation.json @@ -21,7 +21,7 @@ "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.", "Cancel": "Annuler", - "Change email": "Changer l'email", + "Change email": "Changer le courriel", "Change password": "Changer le mot de passe", "Change photo": "Changer la photo", "Choose a role": "Choisir un rôle", diff --git a/apps/client/public/locales/it-IT/translation.json b/apps/client/public/locales/it-IT/translation.json index 5712088f..04e866fb 100644 --- a/apps/client/public/locales/it-IT/translation.json +++ b/apps/client/public/locales/it-IT/translation.json @@ -347,9 +347,9 @@ "Members added successfully": "Membri aggiunti con successo", "Member removed successfully": "Membro rimosso con successo", "Member role updated successfully": "Ruolo del membro aggiornato con successo", - "Created by: {{creatorName}}": "Created by: {{creatorName}}", - "Created at: {{time}}": "Created at: {{time}}", - "Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}", - "Word count: {{wordCount}}": "Word count: {{wordCount}}", - "Character count: {{characterCount}}": "Character count: {{characterCount}}" + "Created by: {{creatorName}}": "Creato da: {{creatorName}}", + "Created at: {{time}}": "Creato il: {{time}}", + "Edited by {{name}} {{time}}": "Modificato da {{name}} il {{time}}", + "Word count: {{wordCount}}": "Conteggio parole: {{wordCount}}", + "Character count: {{characterCount}}": "Conteggio caratteri: {{characterCount}}" } diff --git a/apps/client/public/locales/ja-JP/translation.json b/apps/client/public/locales/ja-JP/translation.json index a4194823..4d066912 100644 --- a/apps/client/public/locales/ja-JP/translation.json +++ b/apps/client/public/locales/ja-JP/translation.json @@ -347,9 +347,9 @@ "Members added successfully": "メンバーを追加しました", "Member removed successfully": "メンバーが削除されました", "Member role updated successfully": "メンバーのロールを更新しました", - "Created by: {{creatorName}}": "Created by: {{creatorName}}", - "Created at: {{time}}": "Created at: {{time}}", - "Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}", - "Word count: {{wordCount}}": "Word count: {{wordCount}}", - "Character count: {{characterCount}}": "Character count: {{characterCount}}" + "Created by: {{creatorName}}": "作成者: {{creatorName}}", + "Created at: {{time}}": "が作成しました:{{time}}", + "Edited by {{name}} {{time}}": "最終編集: {{name}} {{time}}", + "Word count: {{wordCount}}": "ワード数: {{wordCount}}", + "Character count: {{characterCount}}": "文字数: {{characterCount}}" } diff --git a/apps/client/public/locales/ko-KR/translation.json b/apps/client/public/locales/ko-KR/translation.json index fab01a51..9ca4b572 100644 --- a/apps/client/public/locales/ko-KR/translation.json +++ b/apps/client/public/locales/ko-KR/translation.json @@ -148,7 +148,7 @@ "Select role to assign to all invited members": "초대된 모든 사용자에게 할당할 역할 선택", "Select theme": "배경 선택", "Send invitation": "초대 보내기", - "Invitation sent": "Invitation sent", + "Invitation sent": "초대 발송 완료", "Settings": "설정", "Setup workspace": "Workspace 설정", "Sign In": "로그인", @@ -245,7 +245,7 @@ "Align left": "왼쪽 정렬", "Align right": "오른쪽 정렬", "Align center": "가운데 정렬", - "Justify": "Justify", + "Justify": "정렬", "Merge cells": "셀 병합", "Split cell": "셀 분할", "Delete column": "열 삭제", @@ -341,15 +341,15 @@ "Names do not match": "이름이 일치하지 않습니다", "Today, {{time}}": "오늘, {{time}}", "Yesterday, {{time}}": "어제, {{time}}", - "Space created successfully": "Space created successfully", - "Space updated successfully": "Space updated successfully", - "Space deleted successfully": "Space deleted successfully", - "Members added successfully": "Members added successfully", - "Member removed successfully": "Member removed successfully", - "Member role updated successfully": "Member role updated successfully", - "Created by: {{creatorName}}": "Created by: {{creatorName}}", - "Created at: {{time}}": "Created at: {{time}}", - "Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}", - "Word count: {{wordCount}}": "Word count: {{wordCount}}", - "Character count: {{characterCount}}": "Character count: {{characterCount}}" + "Space created successfully": "공간 생성 완료", + "Space updated successfully": "공간이 성공적으로 업데이트되었습니다", + "Space deleted successfully": "스페이스 삭제 완료", + "Members added successfully": "회원 추가 완료", + "Member removed successfully": "멤버가 성공적으로 제거되었습니다", + "Member role updated successfully": "회원 역할이 성공적으로 업데이트되었습니다", + "Created by: {{creatorName}}": "작성자: {{creatorName}}", + "Created at: {{time}}": "생성 날짜: {{time}}", + "Edited by {{name}} {{time}}": "{{name}}님이 편집함 {{time}}", + "Word count: {{wordCount}}": "단어 수: {{wordCount}}", + "Character count: {{characterCount}}": "문자 수: {{characterCount}}" } diff --git a/apps/client/public/locales/pt-BR/translation.json b/apps/client/public/locales/pt-BR/translation.json index 26535914..18a286a7 100644 --- a/apps/client/public/locales/pt-BR/translation.json +++ b/apps/client/public/locales/pt-BR/translation.json @@ -148,7 +148,7 @@ "Select role to assign to all invited members": "Selecione a função para atribuir a todos os membros convidados", "Select theme": "Selecionar tema", "Send invitation": "Enviar convite", - "Invitation sent": "Invitation sent", + "Invitation sent": "Convite enviado", "Settings": "Configurações", "Setup workspace": "Configurar workspace", "Sign In": "Entrar", @@ -245,7 +245,7 @@ "Align left": "Alinhar à esquerda", "Align right": "Alinhar à direita", "Align center": "Alinhar ao centro", - "Justify": "Justify", + "Justify": "Justificar", "Merge cells": "Mesclar células", "Split cell": "Dividir célula", "Delete column": "Excluir coluna", @@ -341,15 +341,15 @@ "Names do not match": "Os nomes não coincidem", "Today, {{time}}": "Hoje, {{time}}", "Yesterday, {{time}}": "Ontem, {{time}}", - "Space created successfully": "Space created successfully", - "Space updated successfully": "Space updated successfully", - "Space deleted successfully": "Space deleted successfully", - "Members added successfully": "Members added successfully", - "Member removed successfully": "Member removed successfully", - "Member role updated successfully": "Member role updated successfully", - "Created by: {{creatorName}}": "Created by: {{creatorName}}", - "Created at: {{time}}": "Created at: {{time}}", - "Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}", - "Word count: {{wordCount}}": "Word count: {{wordCount}}", - "Character count: {{characterCount}}": "Character count: {{characterCount}}" + "Space created successfully": "Espaço criado com sucesso", + "Space updated successfully": "Espaço atualizado com sucesso", + "Space deleted successfully": "Espaço excluído com sucesso", + "Members added successfully": "Membros adicionados com sucesso", + "Member removed successfully": "Membro removido com sucesso", + "Member role updated successfully": "Função do membro atualizada com sucesso", + "Created by: {{creatorName}}": "Criado por: {{creatorName}}", + "Created at: {{time}}": "Criado em: {{time}}", + "Edited by {{name}} {{time}}": "Editado por {{name}} {{time}}", + "Word count: {{wordCount}}": "Contagem de palavras: {{wordCount}}", + "Character count: {{characterCount}}": "Contagem de caracteres: {{characterCount}}" } diff --git a/apps/client/public/locales/ru-RU/translation.json b/apps/client/public/locales/ru-RU/translation.json index 257b64db..8b692812 100644 --- a/apps/client/public/locales/ru-RU/translation.json +++ b/apps/client/public/locales/ru-RU/translation.json @@ -148,7 +148,7 @@ "Select role to assign to all invited members": "Выберите роль для всех приглашённых участников", "Select theme": "Выберите тему", "Send invitation": "Отправить приглашение", - "Invitation sent": "Invitation sent", + "Invitation sent": "Приглашение отправлено", "Settings": "Настройки", "Setup workspace": "Настроить рабочее пространство", "Sign In": "Вход", @@ -245,7 +245,7 @@ "Align left": "По левому краю", "Align right": "По правому краю", "Align center": "По центру", - "Justify": "Justify", + "Justify": "По ширине", "Merge cells": "Объединить ячейки", "Split cell": "Разделить ячейку", "Delete column": "Удалить столбец", @@ -331,25 +331,25 @@ "Insert math equation": "Вставить математическое выражение", "Mermaid diagram": "Диаграмма Mermaid", "Insert mermaid diagram": "Вставить диаграмму Mermaid", - "Insert and design Drawio diagrams": "Вставьте и редактируйте диаграммы Draw.io", + "Insert and design Drawio diagrams": "Вставить и рисовать диаграммы Draw.io", "Insert current date": "Вставить текущую дату", - "Draw and sketch excalidraw diagrams": "Создайте и рисуйте диаграммы Excalidraw", + "Draw and sketch excalidraw diagrams": "Вставить и рисовать диаграммы Excalidraw", "Multiple": "Несколько", "Heading {{level}}": "Заголовок {{level}}", "Toggle title": "Переключить заголовок", - "Write anything. Enter \"/\" for commands": "Пишите что угодно. Введите \"/\" для выбора команд", + "Write anything. Enter \"/\" for commands": "Начните писать. Введите \"/\" для списка команд", "Names do not match": "Названия не совпадают", "Today, {{time}}": "Сегодня, {{time}}", "Yesterday, {{time}}": "Вчера, {{time}}", - "Space created successfully": "Space created successfully", - "Space updated successfully": "Space updated successfully", - "Space deleted successfully": "Space deleted successfully", - "Members added successfully": "Members added successfully", - "Member removed successfully": "Member removed successfully", - "Member role updated successfully": "Member role updated successfully", - "Created by: {{creatorName}}": "Created by: {{creatorName}}", - "Created at: {{time}}": "Created at: {{time}}", - "Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}", - "Word count: {{wordCount}}": "Word count: {{wordCount}}", - "Character count: {{characterCount}}": "Character count: {{characterCount}}" + "Space created successfully": "Пространство успешно создано", + "Space updated successfully": "Пространство успешно обновлено", + "Space deleted successfully": "Пространство успешно удалено", + "Members added successfully": "Участники успешно добавлены", + "Member removed successfully": "Участник успешно удален", + "Member role updated successfully": "Роль участника успешно обновлена", + "Created by: {{creatorName}}": "Автор: {{creatorName}}", + "Created at: {{time}}": "Дата создания: {{time}}", + "Edited by {{name}} {{time}}": "Изменено {{name}} {{time}}", + "Word count: {{wordCount}}": "Количество слов: {{wordCount}}", + "Character count: {{characterCount}}": "Количество символов: {{characterCount}}" } diff --git a/apps/client/public/locales/zh-CN/translation.json b/apps/client/public/locales/zh-CN/translation.json index 31788ae2..fb8211ea 100644 --- a/apps/client/public/locales/zh-CN/translation.json +++ b/apps/client/public/locales/zh-CN/translation.json @@ -148,7 +148,7 @@ "Select role to assign to all invited members": "选择要分配给所有被邀请成员的角色", "Select theme": "选择主题", "Send invitation": "发送邀请", - "Invitation sent": "Invitation sent", + "Invitation sent": "邀请邮件已发送", "Settings": "设置", "Setup workspace": "设置工作空间", "Sign In": "登录", @@ -245,7 +245,7 @@ "Align left": "靠左对齐", "Align right": "靠右对齐", "Align center": "居中对齐", - "Justify": "Justify", + "Justify": "两端对齐", "Merge cells": "合并单元格", "Split cell": "分割单元格", "Delete column": "删除整列", @@ -341,15 +341,15 @@ "Names do not match": "名称不匹配", "Today, {{time}}": "今天,{{time}}", "Yesterday, {{time}}": "昨天,{{time}}", - "Space created successfully": "Space created successfully", - "Space updated successfully": "Space updated successfully", - "Space deleted successfully": "Space deleted successfully", - "Members added successfully": "Members added successfully", - "Member removed successfully": "Member removed successfully", - "Member role updated successfully": "Member role updated successfully", - "Created by: {{creatorName}}": "Created by: {{creatorName}}", - "Created at: {{time}}": "Created at: {{time}}", - "Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}", - "Word count: {{wordCount}}": "Word count: {{wordCount}}", - "Character count: {{characterCount}}": "Character count: {{characterCount}}" + "Space created successfully": "空间创建成功", + "Space updated successfully": "空间更新成功", + "Space deleted successfully": "空间已成功删除", + "Members added successfully": "成员添加成功", + "Member removed successfully": "成员移除成功", + "Member role updated successfully": "成员角色更新成功", + "Created by: {{creatorName}}": "创建者:{{creatorName}}", + "Created at: {{time}}": "创建于:{{time}}", + "Edited by {{name}} {{time}}": "由{{name}} 编辑于 {{time}}", + "Word count: {{wordCount}}": "字数:{{wordCount}}", + "Character count: {{characterCount}}": "字符数:{{characterCount}}" } From cd10365f716f582ab885b1bbf167fabe268cb6f0 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:10:48 +0100 Subject: [PATCH 04/63] new translations --- apps/client/public/locales/en-US/translation.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index b69d0b28..3cd9fae6 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -351,5 +351,16 @@ "Created at: {{time}}": "Created at: {{time}}", "Edited by {{name}} {{time}}": "Edited by {{name}} {{time}}", "Word count: {{wordCount}}": "Word count: {{wordCount}}", - "Character count: {{characterCount}}": "Character count: {{characterCount}}" + "Character count: {{characterCount}}": "Character count: {{characterCount}}", + "New update": "New update", + "{{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." } From 117c7049ff57fb64bff1ef62c5871f12cab31fb8 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:15:09 +0100 Subject: [PATCH 05/63] fix --- apps/client/public/locales/en-US/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 3cd9fae6..bbc6a702 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -356,7 +356,7 @@ "{{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." + "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.", From a9f370660b4488d48fdeffdeadf885d21a936346 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:28:33 +0100 Subject: [PATCH 06/63] New Crowdin updates (#1005) * New translations translation.json (French) * New translations translation.json (Spanish) * New translations translation.json (German) * New translations translation.json (Italian) * New translations translation.json (Japanese) * New translations translation.json (Korean) * New translations translation.json (Dutch) * New translations translation.json (Russian) * New translations translation.json (Chinese Simplified) * New translations translation.json (Portuguese, Brazilian) * New translations translation.json (French) * New translations translation.json (Spanish) * New translations translation.json (German) * New translations translation.json (Italian) * New translations translation.json (Japanese) * New translations translation.json (Korean) * New translations translation.json (Dutch) * New translations translation.json (Russian) * New translations translation.json (Chinese Simplified) * New translations translation.json (Portuguese, Brazilian) --- apps/client/public/locales/de-DE/translation.json | 13 ++++++++++++- apps/client/public/locales/es-ES/translation.json | 13 ++++++++++++- apps/client/public/locales/fr-FR/translation.json | 13 ++++++++++++- apps/client/public/locales/it-IT/translation.json | 13 ++++++++++++- apps/client/public/locales/ja-JP/translation.json | 13 ++++++++++++- apps/client/public/locales/ko-KR/translation.json | 13 ++++++++++++- apps/client/public/locales/nl-NL/translation.json | 13 ++++++++++++- apps/client/public/locales/pt-BR/translation.json | 13 ++++++++++++- apps/client/public/locales/ru-RU/translation.json | 13 ++++++++++++- apps/client/public/locales/zh-CN/translation.json | 13 ++++++++++++- 10 files changed, 120 insertions(+), 10 deletions(-) diff --git a/apps/client/public/locales/de-DE/translation.json b/apps/client/public/locales/de-DE/translation.json index 787d1ee5..294bfa79 100644 --- a/apps/client/public/locales/de-DE/translation.json +++ b/apps/client/public/locales/de-DE/translation.json @@ -351,5 +351,16 @@ "Created at: {{time}}": "Erstellt am: {{time}}", "Edited by {{name}} {{time}}": "Bearbeitet von {{name}} {{time}}", "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." } diff --git a/apps/client/public/locales/es-ES/translation.json b/apps/client/public/locales/es-ES/translation.json index 0bc40f49..f17d13d9 100644 --- a/apps/client/public/locales/es-ES/translation.json +++ b/apps/client/public/locales/es-ES/translation.json @@ -351,5 +351,16 @@ "Created at: {{time}}": "Creado a: {{time}}", "Edited by {{name}} {{time}}": "Editado por {{name}} {{time}}", "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." } diff --git a/apps/client/public/locales/fr-FR/translation.json b/apps/client/public/locales/fr-FR/translation.json index 9a5bf561..408ff420 100644 --- a/apps/client/public/locales/fr-FR/translation.json +++ b/apps/client/public/locales/fr-FR/translation.json @@ -351,5 +351,16 @@ "Created at: {{time}}": "Créé à : {{time}}", "Edited by {{name}} {{time}}": "Modifié par {{name}} {{time}}", "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." } diff --git a/apps/client/public/locales/it-IT/translation.json b/apps/client/public/locales/it-IT/translation.json index 04e866fb..b92377c7 100644 --- a/apps/client/public/locales/it-IT/translation.json +++ b/apps/client/public/locales/it-IT/translation.json @@ -351,5 +351,16 @@ "Created at: {{time}}": "Creato il: {{time}}", "Edited by {{name}} {{time}}": "Modificato da {{name}} il {{time}}", "Word count: {{wordCount}}": "Conteggio parole: {{wordCount}}", - "Character count: {{characterCount}}": "Conteggio caratteri: {{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." } diff --git a/apps/client/public/locales/ja-JP/translation.json b/apps/client/public/locales/ja-JP/translation.json index 4d066912..cc6141c4 100644 --- a/apps/client/public/locales/ja-JP/translation.json +++ b/apps/client/public/locales/ja-JP/translation.json @@ -351,5 +351,16 @@ "Created at: {{time}}": "が作成しました:{{time}}", "Edited by {{name}} {{time}}": "最終編集: {{name}} {{time}}", "Word count: {{wordCount}}": "ワード数: {{wordCount}}", - "Character count: {{characterCount}}": "文字数: {{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)を追加して目次を生成します。" } diff --git a/apps/client/public/locales/ko-KR/translation.json b/apps/client/public/locales/ko-KR/translation.json index 9ca4b572..1e87c62e 100644 --- a/apps/client/public/locales/ko-KR/translation.json +++ b/apps/client/public/locales/ko-KR/translation.json @@ -351,5 +351,16 @@ "Created at: {{time}}": "생성 날짜: {{time}}", "Edited by {{name}} {{time}}": "{{name}}님이 편집함 {{time}}", "Word count: {{wordCount}}": "단어 수: {{wordCount}}", - "Character count: {{characterCount}}": "문자 수: {{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)을 추가하세요." } diff --git a/apps/client/public/locales/nl-NL/translation.json b/apps/client/public/locales/nl-NL/translation.json index 27e1ba84..dba2ee9a 100644 --- a/apps/client/public/locales/nl-NL/translation.json +++ b/apps/client/public/locales/nl-NL/translation.json @@ -351,5 +351,16 @@ "Created at: {{time}}": "Aangemaakt op: {{time}}", "Edited by {{name}} {{time}}": "Bewerkt door {{name}} {{time}}", "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." } diff --git a/apps/client/public/locales/pt-BR/translation.json b/apps/client/public/locales/pt-BR/translation.json index 18a286a7..526d298f 100644 --- a/apps/client/public/locales/pt-BR/translation.json +++ b/apps/client/public/locales/pt-BR/translation.json @@ -351,5 +351,16 @@ "Created at: {{time}}": "Criado em: {{time}}", "Edited by {{name}} {{time}}": "Editado por {{name}} {{time}}", "Word count: {{wordCount}}": "Contagem de palavras: {{wordCount}}", - "Character count: {{characterCount}}": "Contagem de caracteres: {{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." } diff --git a/apps/client/public/locales/ru-RU/translation.json b/apps/client/public/locales/ru-RU/translation.json index 8b692812..dce96d80 100644 --- a/apps/client/public/locales/ru-RU/translation.json +++ b/apps/client/public/locales/ru-RU/translation.json @@ -351,5 +351,16 @@ "Created at: {{time}}": "Дата создания: {{time}}", "Edited by {{name}} {{time}}": "Изменено {{name}} {{time}}", "Word count: {{wordCount}}": "Количество слов: {{wordCount}}", - "Character count: {{characterCount}}": "Количество символов: {{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), чтобы создать оглавление." } diff --git a/apps/client/public/locales/zh-CN/translation.json b/apps/client/public/locales/zh-CN/translation.json index fb8211ea..9763c602 100644 --- a/apps/client/public/locales/zh-CN/translation.json +++ b/apps/client/public/locales/zh-CN/translation.json @@ -351,5 +351,16 @@ "Created at: {{time}}": "创建于:{{time}}", "Edited by {{name}} {{time}}": "由{{name}} 编辑于 {{time}}", "Word count: {{wordCount}}": "字数:{{wordCount}}", - "Character count: {{characterCount}}": "字符数:{{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)以生成目录。" } From 7f7f2bccd032a08e796492cbf70692684502c3ae Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Wed, 9 Apr 2025 15:37:18 +0100 Subject: [PATCH 07/63] fix toggle node in non-edit mode --- packages/editor-ext/src/lib/details/details.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/editor-ext/src/lib/details/details.ts b/packages/editor-ext/src/lib/details/details.ts index 8a05f21c..1be9accf 100644 --- a/packages/editor-ext/src/lib/details/details.ts +++ b/packages/editor-ext/src/lib/details/details.ts @@ -111,6 +111,7 @@ export const Details = Node.create({ if (updatedNode.type !== this.type) { return false; } + if (!editor.isEditable) return true; if (updatedNode.attrs.open) { dom.setAttribute("open", "true"); } else { @@ -132,9 +133,10 @@ export const Details = Node.create({ } const slice = state.doc.slice(range.start, range.end); - - if(slice.content.firstChild.type.name === "detailsSummary") return false; - + + if (slice.content.firstChild.type.name === "detailsSummary") + return false; + if ( !state.schema.nodes.detailsContent.contentMatch.matchFragment( slice.content, From 4aa5d7e326f4c986369c5914a939694b30e23ba2 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Wed, 9 Apr 2025 15:42:29 +0100 Subject: [PATCH 08/63] hide history action menu for can-view role (#1001) --- .../page-history/components/history-list.tsx | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/apps/client/src/features/page-history/components/history-list.tsx b/apps/client/src/features/page-history/components/history-list.tsx index c21fb3b7..af178eac 100644 --- a/apps/client/src/features/page-history/components/history-list.tsx +++ b/apps/client/src/features/page-history/components/history-list.tsx @@ -17,6 +17,13 @@ import { import { modals } from "@mantine/modals"; import { notifications } from "@mantine/notifications"; 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 { pageId: string; @@ -36,6 +43,11 @@ function HistoryList({ pageId }: Props) { const [mainEditorTitle] = useAtom(titleEditorAtom); const [, setHistoryModalOpen] = useAtom(historyAtoms); + const { spaceSlug } = useParams(); + const { data: space } = useSpaceQuery(spaceSlug); + const spaceRules = space?.membership?.permissions; + const spaceAbility = useSpaceAbility(spaceRules); + const confirmModal = () => modals.openConfirmModal({ title: t("Please confirm your action"), @@ -103,20 +115,26 @@ function HistoryList({ pageId }: Props) { ))} - - - - - - + {spaceAbility.cannot( + SpaceCaslAction.Manage, + SpaceCaslSubject.Page, + ) ? null : ( + <> + + + + + + + )} ); } From 5fd477d07457047f3d90a81b4dac2785950581ff Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Wed, 9 Apr 2025 15:46:29 +0100 Subject: [PATCH 09/63] collapse by default in node-edit mode --- packages/editor-ext/src/lib/details/details.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/editor-ext/src/lib/details/details.ts b/packages/editor-ext/src/lib/details/details.ts index 1be9accf..b28c4de7 100644 --- a/packages/editor-ext/src/lib/details/details.ts +++ b/packages/editor-ext/src/lib/details/details.ts @@ -78,10 +78,13 @@ export const Details = Node.create({ dom.setAttribute("data-type", this.name); btn.setAttribute("data-type", `${this.name}Button`); div.setAttribute("data-type", `${this.name}Container`); - if (node.attrs.open) { - dom.setAttribute("open", "true"); - } else { - dom.removeAttribute("open"); + + if (editor.isEditable) { + if (node.attrs.open) { + dom.setAttribute("open", "true"); + } else { + dom.removeAttribute("open"); + } } ico.innerHTML = icon("right-line"); From 7fe3c5f17777021f043bb8b53c1b863201f5d522 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Wed, 9 Apr 2025 18:47:39 +0100 Subject: [PATCH 10/63] * time ago hook --- apps/client/src/features/editor/page-editor.tsx | 1 + .../page/components/header/page-header-menu.tsx | 6 ++++-- apps/client/src/hooks/use-time-ago.tsx | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 apps/client/src/hooks/use-time-ago.tsx diff --git a/apps/client/src/features/editor/page-editor.tsx b/apps/client/src/features/editor/page-editor.tsx index 2853875e..32845c6d 100644 --- a/apps/client/src/features/editor/page-editor.tsx +++ b/apps/client/src/features/editor/page-editor.tsx @@ -209,6 +209,7 @@ export default function PageEditor({ queryClient.setQueryData(["pages", slugId], { ...pageData, content: newContent, + updatedAt: new Date(), }); } }, 3000); diff --git a/apps/client/src/features/page/components/header/page-header-menu.tsx b/apps/client/src/features/page/components/header/page-header-menu.tsx index fefd3e28..93d10520 100644 --- a/apps/client/src/features/page/components/header/page-header-menu.tsx +++ b/apps/client/src/features/page/components/header/page-header-menu.tsx @@ -12,7 +12,7 @@ import { IconTrash, IconWifiOff, } from "@tabler/icons-react"; -import React from "react"; +import React, { useEffect } from "react"; import useToggleAside from "@/hooks/use-toggle-aside.tsx"; import { useAtom } from "jotai"; import { historyAtoms } from "@/features/page-history/atoms/history-atoms.ts"; @@ -34,6 +34,7 @@ import { } from "@/features/editor/atoms/editor-atoms.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 { readOnly?: boolean; @@ -102,6 +103,7 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) { { open: openMovePageModal, close: closeMoveSpaceModal }, ] = useDisclosure(false); const [pageEditor] = useAtom(pageEditorAtom); + const pageUpdatedAt = useTimeAgo(page.updatedAt); const handleCopyLink = () => { const pageUrl = @@ -208,7 +210,7 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) { diff --git a/apps/client/src/hooks/use-time-ago.tsx b/apps/client/src/hooks/use-time-ago.tsx new file mode 100644 index 00000000..03fa9eda --- /dev/null +++ b/apps/client/src/hooks/use-time-ago.tsx @@ -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; +} From 58d1855a360091ae175c75a97c572da934dd3f21 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Wed, 9 Apr 2025 19:03:27 +0100 Subject: [PATCH 11/63] fix hash check --- apps/server/src/core/auth/services/auth.service.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/server/src/core/auth/services/auth.service.ts b/apps/server/src/core/auth/services/auth.service.ts index 19f02230..9c761ef3 100644 --- a/apps/server/src/core/auth/services/auth.service.ts +++ b/apps/server/src/core/auth/services/auth.service.ts @@ -47,13 +47,18 @@ export class AuthService { 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, + user.password, ); - if (!user || !isPasswordMatch || user.deletedAt) { - throw new UnauthorizedException('email or password does not match'); + if (!isPasswordMatch) { + throw new UnauthorizedException(errorMessage); } user.lastLoginAt = new Date(); From f2241db5ee845f326b17b767c18f24422d85ecc5 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Wed, 9 Apr 2025 19:14:33 +0100 Subject: [PATCH 12/63] remove beta message --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 0f1b647a..1b7b161a 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,6 @@
-> [!NOTE] -> Docmost is currently in **beta**. We value your feedback as we progress towards a stable release. - ## Getting started To get started with Docmost, please refer to our [documentation](https://docmost.com/docs). From 29bb52db0ca874a497b31706700673006a055d99 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Wed, 9 Apr 2025 19:14:51 +0100 Subject: [PATCH 13/63] v0.10.0 --- apps/client/package.json | 2 +- apps/server/package.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index b64d910b..5b6ee046 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "0.9.0", + "version": "0.10.0", "scripts": { "dev": "vite", "build": "tsc && vite build", diff --git a/apps/server/package.json b/apps/server/package.json index a44cf59e..83a2e85f 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.9.0", + "version": "0.10.0", "description": "", "author": "", "private": true, diff --git a/package.json b/package.json index 64cf9267..290e09a3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "docmost", "homepage": "https://docmost.com", - "version": "0.9.0", + "version": "0.10.0", "private": true, "scripts": { "build": "nx run-many -t build", From f9711918a370e2363bb9c8d273e2225bdc0d9ca8 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Fri, 11 Apr 2025 12:32:54 +0100 Subject: [PATCH 14/63] fix comment editor padding --- .../client/src/features/comment/components/comment.module.css | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/client/src/features/comment/components/comment.module.css b/apps/client/src/features/comment/components/comment.module.css index 87dc5994..b3d0a261 100644 --- a/apps/client/src/features/comment/components/comment.module.css +++ b/apps/client/src/features/comment/components/comment.module.css @@ -19,8 +19,7 @@ box-shadow: 0 0 0 2px var(--mantine-color-blue-3); } - .ProseMirror { - width: 100%; + .ProseMirror :global(.ProseMirror){ max-width: 100%; white-space: pre-wrap; word-break: break-word; @@ -29,7 +28,6 @@ padding-right: 6px; margin-top: 2px; margin-bottom: 2px; - font-size: 14px; overflow: hidden auto; } From 27fa45a76998e32c4a90fb2cdc56d3eabda9bbb3 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Fri, 11 Apr 2025 13:18:44 +0100 Subject: [PATCH 15/63] fix local attachment paths in exports (#1013) --- .../src/integrations/export/export.service.ts | 4 +-- apps/server/src/integrations/export/utils.ts | 27 ++++++++++++++----- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/apps/server/src/integrations/export/export.service.ts b/apps/server/src/integrations/export/export.service.ts index 9cf33234..09fdb5fd 100644 --- a/apps/server/src/integrations/export/export.service.ts +++ b/apps/server/src/integrations/export/export.service.ts @@ -21,7 +21,7 @@ import { getProsemirrorContent, PageExportTree, replaceInternalLinks, - updateAttachmentUrls, + updateAttachmentUrlsToLocalPaths, } from './utils'; import { PageRepo } from '@docmost/db/repos/page/page.repo'; import { Node } from '@tiptap/pm/model'; @@ -193,7 +193,7 @@ export class ExportService { if (includeAttachments) { await this.zipAttachments(updatedJsonContent, page.spaceId, folder); - updatedJsonContent = updateAttachmentUrls(updatedJsonContent); + updatedJsonContent = updateAttachmentUrlsToLocalPaths(updatedJsonContent); } const pageTitle = getPageTitle(page.title); diff --git a/apps/server/src/integrations/export/utils.ts b/apps/server/src/integrations/export/utils.ts index e296f194..f99f337a 100644 --- a/apps/server/src/integrations/export/utils.ts +++ b/apps/server/src/integrations/export/utils.ts @@ -62,17 +62,30 @@ export function isAttachmentNode(nodeType: string) { return attachmentNodeTypes.includes(nodeType); } -export function updateAttachmentUrls(prosemirrorJson: any) { +export function updateAttachmentUrlsToLocalPaths(prosemirrorJson: any) { 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) => { if (isAttachmentNode(node.type.name)) { - if (node.attrs.src && node.attrs.src.startsWith('/files')) { - //@ts-expect-error - node.attrs.src = node.attrs.src.replace('/files', 'files'); - } else if (node.attrs.url && node.attrs.url.startsWith('/files')) { - //@ts-expect-error - node.attrs.url = node.attrs.url.replace('/files', 'files'); + if (node.attrs.src) { + // @ts-ignore + node.attrs.src = replacePrefix(node.attrs.src); + } + if (node.attrs.url) { + // @ts-ignore + node.attrs.url = replacePrefix(node.attrs.url); } } }); From 268001ae26dc8f647fcb3f96dbc3d51ef87c569b Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Fri, 11 Apr 2025 13:23:42 +0100 Subject: [PATCH 16/63] v0.10.1 --- apps/client/package.json | 2 +- apps/server/package.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index 5b6ee046..c88a2860 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "0.10.0", + "version": "0.10.1", "scripts": { "dev": "vite", "build": "tsc && vite build", diff --git a/apps/server/package.json b/apps/server/package.json index 83a2e85f..54cd1661 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.10.0", + "version": "0.10.1", "description": "", "author": "", "private": true, diff --git a/package.json b/package.json index 290e09a3..d67cc4cd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "docmost", "homepage": "https://docmost.com", - "version": "0.10.0", + "version": "0.10.1", "private": true, "scripts": { "build": "nx run-many -t build", From 49d0f1cc9a105e6488be20a7138dde3a69819ac0 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Fri, 11 Apr 2025 13:41:43 +0100 Subject: [PATCH 17/63] Add click handler --- apps/client/src/components/settings/app-version.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/client/src/components/settings/app-version.tsx b/apps/client/src/components/settings/app-version.tsx index f5097d51..cb332478 100644 --- a/apps/client/src/components/settings/app-version.tsx +++ b/apps/client/src/components/settings/app-version.tsx @@ -35,6 +35,12 @@ export default function AppVersion() { position="middle-end" style={{ cursor: "pointer" }} disabled={!hasUpdate} + onClick={() => { + window.open( + "https://github.com/docmost/docmost/releases", + "_blank", + ); + }} > Date: Tue, 15 Apr 2025 12:46:28 +0100 Subject: [PATCH 18/63] sync ee --- apps/server/src/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/ee b/apps/server/src/ee index a04fcc22..d3095f2d 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit a04fcc224e36514741f064d83a3c39df31766b65 +Subproject commit d3095f2d8bd2870da7f3b534c83c84e8fb3099bc From de57d051998d39780b0cb8e1a755d7c8b256549e Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 15 Apr 2025 12:48:40 +0100 Subject: [PATCH 19/63] 0.10.2 --- apps/client/package.json | 2 +- apps/server/package.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index c88a2860..5901a920 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "0.10.1", + "version": "0.10.2", "scripts": { "dev": "vite", "build": "tsc && vite build", diff --git a/apps/server/package.json b/apps/server/package.json index 54cd1661..0ef03d7c 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.10.1", + "version": "0.10.2", "description": "", "author": "", "private": true, diff --git a/package.json b/package.json index d67cc4cd..d84046e5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "docmost", "homepage": "https://docmost.com", - "version": "0.10.1", + "version": "0.10.2", "private": true, "scripts": { "build": "nx run-many -t build", From 862f6d4820cfdf15f466dd0fc7671b5fa8d96bdb Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Sat, 19 Apr 2025 19:45:09 +0100 Subject: [PATCH 20/63] use non-esm nanoid version (#1040) --- apps/server/package.json | 3 +- .../server/src/common/helpers/nanoid.utils.ts | 5 +- pnpm-lock.yaml | 337 +++--------------- 3 files changed, 62 insertions(+), 283 deletions(-) diff --git a/apps/server/package.json b/apps/server/package.json index 0ef03d7c..fced5e33 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -59,14 +59,13 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "cookie": "^1.0.2", - "fix-esm": "^1.0.1", "fs-extra": "^11.3.0", "happy-dom": "^15.11.6", "jsonwebtoken": "^9.0.2", "kysely": "^0.27.5", "kysely-migration-cli": "^0.4.2", "mime-types": "^2.1.35", - "nanoid": "^5.1.0", + "nanoid": "3.3.11", "nestjs-kysely": "^1.1.0", "nodemailer": "^6.10.0", "openid-client": "^5.7.1", diff --git a/apps/server/src/common/helpers/nanoid.utils.ts b/apps/server/src/common/helpers/nanoid.utils.ts index 016b922f..71234659 100644 --- a/apps/server/src/common/helpers/nanoid.utils.ts +++ b/apps/server/src/common/helpers/nanoid.utils.ts @@ -1,9 +1,8 @@ -// eslint-disable-next-line @typescript-eslint/no-require-imports -const { customAlphabet } = require('fix-esm').require('nanoid'); +import { customAlphabet } from 'nanoid'; const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz'; export const nanoIdGen = customAlphabet(alphabet, 10); const slugIdAlphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; -export const generateSlugId = customAlphabet(slugIdAlphabet, 10); +export const generateSlugId = customAlphabet(slugIdAlphabet, 10); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70f5088b..f9cc1623 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -486,9 +486,6 @@ importers: cookie: specifier: ^1.0.2 version: 1.0.2 - fix-esm: - specifier: ^1.0.1 - version: 1.0.1 fs-extra: specifier: ^11.3.0 version: 11.3.0 @@ -508,8 +505,8 @@ importers: specifier: ^2.1.35 version: 2.1.35 nanoid: - specifier: ^5.1.0 - version: 5.1.0 + specifier: 3.3.11 + version: 3.3.11 nestjs-kysely: 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) @@ -633,7 +630,7 @@ importers: version: 7.0.0 ts-jest: 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: 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))) @@ -875,18 +872,10 @@ packages: resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} 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': resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} engines: {node: '>=6.9.0'} - '@babel/core@7.24.3': - resolution: {integrity: sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==} - engines: {node: '>=6.9.0'} - '@babel/core@7.24.5': resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==} engines: {node: '>=6.9.0'} @@ -919,10 +908,6 @@ packages: resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==} 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': resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} @@ -952,18 +937,10 @@ packages: resolution: {integrity: sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==} 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': resolution: {integrity: sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==} 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': resolution: {integrity: sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA==} engines: {node: '>=6.9.0'} @@ -1074,10 +1051,6 @@ packages: resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} 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': resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} @@ -1086,10 +1059,6 @@ packages: resolution: {integrity: sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.24.1': - resolution: {integrity: sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==} - engines: {node: '>=6.9.0'} - '@babel/helpers@7.24.6': resolution: {integrity: sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA==} engines: {node: '>=6.9.0'} @@ -1102,11 +1071,6 @@ packages: resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} 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': resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==} engines: {node: '>=6.0.0'} @@ -1146,13 +1110,6 @@ packages: peerDependencies: '@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': resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} engines: {node: '>=6.9.0'} @@ -1618,14 +1575,6 @@ packages: resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} 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': resolution: {integrity: sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==} engines: {node: '>=6.9.0'} @@ -1634,10 +1583,6 @@ packages: resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} 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': resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} engines: {node: '>=6.9.0'} @@ -4756,11 +4701,6 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 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: resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -4822,9 +4762,6 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001600: - resolution: {integrity: sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==} - caniuse-lite@1.0.30001684: resolution: {integrity: sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==} @@ -5463,9 +5400,6 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.4.715: - resolution: {integrity: sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==} - electron-to-chromium@1.5.65: resolution: {integrity: sha512-PWVzBjghx7/wop6n22vS2MLU8tKGd4Q91aCEGhG/TYmW6PP5OcSXcdnxTe1NNt0T66N8D6jxh4kC8UsdzOGaIw==} @@ -5799,9 +5733,6 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - fix-esm@1.0.1: - resolution: {integrity: sha512-EZtb7wPXZS54GaGxaWxMlhd1DUDCnAg5srlYdu/1ZVeW+7wwR3Tp59nu52dXByFs3MBRq+SByx1wDOJpRvLEXw==} - flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} @@ -7020,21 +6951,16 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} 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: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} - 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} - hasBin: true - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -7104,9 +7030,6 @@ packages: node-machine-id@1.1.12: resolution: {integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==} - node-releases@2.0.14: - resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} @@ -8653,12 +8576,6 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} 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: resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true @@ -9679,30 +9596,8 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.0.1 - '@babel/compat-data@7.23.5': {} - '@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': dependencies: '@ampproject/remapping': 2.3.0 @@ -9793,14 +9688,6 @@ snapshots: dependencies: '@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': dependencies: '@babel/compat-data': 7.26.2 @@ -9844,20 +9731,11 @@ snapshots: '@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': dependencies: '@babel/template': 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': dependencies: '@babel/types': 7.24.6 @@ -9881,15 +9759,6 @@ snapshots: transitivePeerDependencies: - 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)': dependencies: '@babel/core': 7.26.0 @@ -9982,8 +9851,6 @@ snapshots: '@babel/helper-validator-identifier@7.25.9': {} - '@babel/helper-validator-option@7.23.5': {} - '@babel/helper-validator-option@7.25.9': {} '@babel/helper-wrap-function@7.22.20': @@ -9992,14 +9859,6 @@ snapshots: '@babel/template': 7.25.9 '@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': dependencies: '@babel/template': 7.25.9 @@ -10017,10 +9876,6 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.0.0 - '@babel/parser@7.24.1': - dependencies: - '@babel/types': 7.24.0 - '@babel/parser@7.24.5': dependencies: '@babel/types': 7.26.0 @@ -10058,19 +9913,13 @@ snapshots: '@babel/helper-plugin-utils': 7.25.9 '@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)': dependencies: '@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: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.25.9 optional: true @@ -10084,9 +9933,9 @@ snapshots: '@babel/core': 7.26.0 '@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: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.25.9 optional: true @@ -10095,9 +9944,9 @@ snapshots: '@babel/core': 7.24.6 '@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: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.25.9 optional: true @@ -10126,11 +9975,6 @@ snapshots: '@babel/core': 7.26.0 '@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)': dependencies: '@babel/core': 7.26.0 @@ -10146,9 +9990,9 @@ snapshots: '@babel/core': 7.26.0 '@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: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.25.9 optional: true @@ -10162,9 +10006,9 @@ snapshots: '@babel/core': 7.26.0 '@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: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.25.9 optional: true @@ -10188,9 +10032,9 @@ snapshots: '@babel/core': 7.26.0 '@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: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.25.9 optional: true @@ -10204,9 +10048,9 @@ snapshots: '@babel/core': 7.26.0 '@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: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.25.9 optional: true @@ -10220,9 +10064,9 @@ snapshots: '@babel/core': 7.26.0 '@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: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.25.9 optional: true @@ -10236,9 +10080,9 @@ snapshots: '@babel/core': 7.26.0 '@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: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.25.9 optional: true @@ -10252,9 +10096,9 @@ snapshots: '@babel/core': 7.26.0 '@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: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.25.9 optional: true @@ -10268,9 +10112,9 @@ snapshots: '@babel/core': 7.26.0 '@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: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.25.9 optional: true @@ -10289,9 +10133,9 @@ snapshots: '@babel/core': 7.26.0 '@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: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.25.9 optional: true @@ -10461,13 +10305,6 @@ snapshots: transitivePeerDependencies: - 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)': dependencies: '@babel/core': 7.26.0 @@ -10770,18 +10607,6 @@ snapshots: dependencies: 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': dependencies: '@babel/code-frame': 7.26.2 @@ -10794,21 +10619,6 @@ snapshots: '@babel/parser': 7.26.2 '@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': dependencies: '@babel/code-frame': 7.26.2 @@ -14003,13 +13813,13 @@ snapshots: transitivePeerDependencies: - debug - babel-jest@29.7.0(@babel/core@7.24.3): + babel-jest@29.7.0(@babel/core@7.24.5): dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 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 graceful-fs: 4.2.11 slash: 3.0.0 @@ -14093,21 +13903,21 @@ snapshots: optionalDependencies: '@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: - '@babel/core': 7.24.3 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.3) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.3) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.3) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.3) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.3) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.3) + '@babel/core': 7.24.5 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.5) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.5) + '@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.5) + '@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.5) + '@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.5) + '@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.5) + '@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.5) optional: true babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.6): @@ -14126,11 +13936,11 @@ snapshots: '@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-preset-jest@29.6.3(@babel/core@7.24.3): + babel-preset-jest@29.6.3(@babel/core@7.24.5): dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 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 babel-preset-jest@29.6.3(@babel/core@7.24.6): @@ -14195,13 +14005,6 @@ snapshots: dependencies: 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: dependencies: caniuse-lite: 1.0.30001684 @@ -14273,8 +14076,6 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001600: {} - caniuse-lite@1.0.30001684: {} chalk@2.4.2: @@ -14921,8 +14722,6 @@ snapshots: dependencies: jake: 10.8.7 - electron-to-chromium@1.4.715: {} - electron-to-chromium@1.5.65: {} emittery@0.13.1: {} @@ -15476,14 +15275,6 @@ snapshots: locate-path: 6.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: dependencies: flatted: 3.2.9 @@ -16913,12 +16704,10 @@ snapshots: mute-stream@2.0.0: {} + nanoid@3.3.11: {} + nanoid@3.3.7: {} - nanoid@3.3.8: {} - - nanoid@5.1.0: {} - natural-compare@1.4.0: {} needle@3.3.1: @@ -16983,8 +16772,6 @@ snapshots: node-machine-id@1.1.12: {} - node-releases@2.0.14: {} - node-releases@2.0.18: {} nodemailer@6.10.0: {} @@ -17424,7 +17211,7 @@ snapshots: postcss@8.4.31: dependencies: - nanoid: 3.3.8 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -17436,7 +17223,7 @@ snapshots: postcss@8.5.2: dependencies: - nanoid: 3.3.8 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -18486,7 +18273,7 @@ snapshots: 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: bs-logger: 0.2.6 ejs: 3.1.10 @@ -18500,10 +18287,10 @@ snapshots: typescript: 5.7.3 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.5 '@jest/transform': 29.7.0 '@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))): dependencies: @@ -18687,12 +18474,6 @@ snapshots: 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): dependencies: browserslist: 4.24.2 From 5c957fda8dafa44b92e91b04590955eeacf6a25f Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Mon, 21 Apr 2025 19:24:25 +0100 Subject: [PATCH 21/63] fix: nested tree open state --- .../src/features/page/tree/components/space-tree.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/client/src/features/page/tree/components/space-tree.tsx b/apps/client/src/features/page/tree/components/space-tree.tsx index c099691b..7b2f2f7d 100644 --- a/apps/client/src/features/page/tree/components/space-tree.tsx +++ b/apps/client/src/features/page/tree/components/space-tree.tsx @@ -7,7 +7,7 @@ import { usePageQuery, useUpdatePageMutation, } from "@/features/page/queries/page-query.ts"; -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import classes from "@/features/page/tree/styles/tree.module.css"; import { ActionIcon, Menu, rem } from "@mantine/core"; @@ -84,7 +84,7 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) { const rootElement = useRef(); const { ref: sizeRef, width, height } = useElementSize(); const mergedRef = useMergedRef(rootElement, sizeRef); - const isDataLoaded = useRef(false); + const [isDataLoaded, setIsDataLoaded] = useState(false); const { data: currentPage } = usePageQuery({ pageId: extractPageSlugId(pageSlug), }); @@ -108,7 +108,7 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) { // and append root pages instead of resetting the entire tree // which looses async loaded children too setData(treeData); - isDataLoaded.current = true; + setIsDataLoaded(true); setOpenTreeNodes({}); } } @@ -116,7 +116,7 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) { useEffect(() => { const fetchData = async () => { - if (isDataLoaded.current && currentPage) { + if (isDataLoaded && currentPage) { // check if pageId node is present in the tree const node = dfs(treeApiRef.current?.root, currentPage.id); if (node) { @@ -178,7 +178,7 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) { }; fetchData(); - }, [isDataLoaded.current, currentPage?.id]); + }, [isDataLoaded, currentPage?.id]); useEffect(() => { if (currentPage?.id) { From 10b67929ea9ef74f0a8b4a21d149b9d4bfeb1ec0 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Mon, 21 Apr 2025 21:50:21 +0100 Subject: [PATCH 22/63] Update README.md --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b7b161a..f09680e1 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,15 @@ Open-source collaborative wiki and documentation software.
Website | - Documentation + Documentation | + Twitter / X


## 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) or try our [cloud version](https://docmost.com/pricing) . ## Features @@ -46,3 +47,16 @@ All files in the following directories are licensed under the Docmost Enterprise ### Contributing See the [development documentation](https://docmost.com/docs/self-hosting/development) + +## Thanks +Special thanks to; + +Crowdin + +[Crowdin](https://crowdin.com/) for providing access to their localization platform. + + +Algolia-mark-square-white + +[Algolia](https://www.algolia.com/) for providing full-text search to the docs. + From 1a1b2c8682ccd08062d8486d3ddd0b60989a9dd5 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Tue, 22 Apr 2025 15:52:44 +0100 Subject: [PATCH 23/63] switch to vite rolldown (#1048) * switch to vite rolldown * update --- apps/client/package.json | 6 +- pnpm-lock.yaml | 929 ++++++++++++++++++--------------------- 2 files changed, 435 insertions(+), 500 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index 5901a920..74e3c21e 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -25,7 +25,7 @@ "@tabler/icons-react": "^3.22.0", "@tanstack/react-query": "^5.61.4", "@tiptap/extension-character-count": "^2.11.5", - "axios": "^1.7.9", + "axios": "^1.8.4", "clsx": "^2.1.1", "emoji-mart": "^5.6.0", "file-saver": "^2.0.5", @@ -63,7 +63,7 @@ "@types/node": "22.10.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.3.4", + "@vitejs/plugin-react": "^4.4.1", "eslint": "^9.15.0", "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.1.0", @@ -76,6 +76,6 @@ "prettier": "^3.4.1", "typescript": "^5.7.2", "typescript-eslint": "^8.17.0", - "vite": "^6.1.0" + "vite": "npm:rolldown-vite@latest" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9cc1623..d5eaf2ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -181,7 +181,7 @@ importers: devDependencies: '@nx/js': specifier: 20.4.5 - version: 20.4.5(@babel/traverse@7.25.9)(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(nx@20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)))(typescript@5.7.3) + version: 20.4.5(@babel/traverse@7.27.0)(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(nx@20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)))(typescript@5.7.3) '@types/bytes': specifier: ^3.1.5 version: 3.1.5 @@ -246,8 +246,8 @@ importers: specifier: ^2.11.5 version: 2.11.5(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) axios: - specifier: ^1.7.9 - version: 1.7.9 + specifier: ^1.8.4 + version: 1.8.4 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -355,8 +355,8 @@ importers: specifier: ^18.3.1 version: 18.3.1 '@vitejs/plugin-react': - specifier: ^4.3.4 - version: 4.3.4(vite@6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.4.1 + version: 4.4.1(rolldown-vite@6.3.3(@types/node@22.10.0)(esbuild@0.25.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(typescript@5.7.2)(yaml@2.7.0)) eslint: specifier: ^9.15.0 version: 9.15.0(jiti@1.21.0) @@ -394,8 +394,8 @@ importers: specifier: ^8.17.0 version: 8.17.0(eslint@9.15.0(jiti@1.21.0))(typescript@5.7.2) vite: - specifier: ^6.1.0 - version: 6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) + specifier: npm:rolldown-vite@latest + version: rolldown-vite@6.3.3(@types/node@22.10.0)(esbuild@0.25.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(typescript@5.7.2)(yaml@2.7.0) apps/server: dependencies: @@ -876,6 +876,10 @@ packages: resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.26.8': + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + engines: {node: '>=6.9.0'} + '@babel/core@7.24.5': resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==} engines: {node: '>=6.9.0'} @@ -888,6 +892,10 @@ packages: resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} engines: {node: '>=6.9.0'} + '@babel/core@7.26.10': + resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.24.1': resolution: {integrity: sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==} engines: {node: '>=6.9.0'} @@ -900,6 +908,10 @@ packages: resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.27.0': + resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.22.5': resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} engines: {node: '>=6.9.0'} @@ -912,6 +924,10 @@ packages: resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.27.0': + resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} + engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.23.7': resolution: {integrity: sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g==} engines: {node: '>=6.9.0'} @@ -1067,6 +1083,10 @@ packages: resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.27.0': + resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} + engines: {node: '>=6.9.0'} + '@babel/highlight@7.24.2': resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} engines: {node: '>=6.9.0'} @@ -1076,13 +1096,13 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.24.6': - resolution: {integrity: sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==} + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.26.2': - resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + '@babel/parser@7.27.0': + resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} engines: {node: '>=6.0.0'} hasBin: true @@ -1583,10 +1603,18 @@ packages: resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} + '@babel/template@7.27.0': + resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.25.9': resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.27.0': + resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} + engines: {node: '>=6.9.0'} + '@babel/types@7.23.6': resolution: {integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==} engines: {node: '>=6.9.0'} @@ -1603,6 +1631,10 @@ packages: resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} engines: {node: '>=6.9.0'} + '@babel/types@7.27.0': + resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -1701,12 +1733,6 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.24.2': - resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.25.0': resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} engines: {node: '>=18'} @@ -1719,12 +1745,6 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.24.2': - resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.25.0': resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} engines: {node: '>=18'} @@ -1737,12 +1757,6 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.24.2': - resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.25.0': resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} engines: {node: '>=18'} @@ -1755,12 +1769,6 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.24.2': - resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.25.0': resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} engines: {node: '>=18'} @@ -1773,12 +1781,6 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.24.2': - resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.25.0': resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} engines: {node: '>=18'} @@ -1791,12 +1793,6 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.24.2': - resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.25.0': resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} engines: {node: '>=18'} @@ -1809,12 +1805,6 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.24.2': - resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.25.0': resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} engines: {node: '>=18'} @@ -1827,12 +1817,6 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.24.2': - resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.25.0': resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} engines: {node: '>=18'} @@ -1845,12 +1829,6 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.24.2': - resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.25.0': resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} engines: {node: '>=18'} @@ -1863,12 +1841,6 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.24.2': - resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.25.0': resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} engines: {node: '>=18'} @@ -1881,12 +1853,6 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.24.2': - resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.25.0': resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} engines: {node: '>=18'} @@ -1899,12 +1865,6 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.24.2': - resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.25.0': resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} engines: {node: '>=18'} @@ -1917,12 +1877,6 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.24.2': - resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.25.0': resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} engines: {node: '>=18'} @@ -1935,12 +1889,6 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.24.2': - resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.25.0': resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} engines: {node: '>=18'} @@ -1953,12 +1901,6 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.24.2': - resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.25.0': resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} engines: {node: '>=18'} @@ -1971,12 +1913,6 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.24.2': - resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.25.0': resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} engines: {node: '>=18'} @@ -1989,24 +1925,12 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.24.2': - resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.25.0': resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.24.2': - resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.25.0': resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} engines: {node: '>=18'} @@ -2019,24 +1943,12 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.24.2': - resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.25.0': resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.24.2': - resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.25.0': resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} engines: {node: '>=18'} @@ -2049,12 +1961,6 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.24.2': - resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.25.0': resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} engines: {node: '>=18'} @@ -2067,12 +1973,6 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.24.2': - resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.25.0': resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} engines: {node: '>=18'} @@ -2085,12 +1985,6 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.24.2': - resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.25.0': resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} engines: {node: '>=18'} @@ -2103,12 +1997,6 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.24.2': - resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.25.0': resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} engines: {node: '>=18'} @@ -2121,12 +2009,6 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.24.2': - resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.25.0': resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} engines: {node: '>=18'} @@ -3020,6 +2902,13 @@ packages: resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} + '@oxc-project/runtime@0.65.0': + resolution: {integrity: sha512-qn70kbkGtJ3uWz+HXha+kufRXkT+pZWckJKL8jUPzXH5UNszSSwADkNQhb7/uit3tC70wFm9qPRlLHnJcjSGuA==} + engines: {node: '>=6.9.0'} + + '@oxc-project/types@0.65.0': + resolution: {integrity: sha512-7MpMzyXCcwxrTxJ4L0siy63Ds/LA8LAM4szumTFiynxTJkfrIZEV4PyR4Th0CqFZQ+oNi8WvW3Dr1MLy7o9qPQ==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -3164,98 +3053,63 @@ packages: '@remirror/core-constants@3.0.0': resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} - '@rollup/rollup-android-arm-eabi@4.34.7': - resolution: {integrity: sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.34.7': - resolution: {integrity: sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.34.7': - resolution: {integrity: sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA==} + '@rolldown/binding-darwin-arm64@1.0.0-beta.7-commit.30e0395': + resolution: {integrity: sha512-2o/ZF2nt59xOksx7WKxYcnuysv9SaMfCgIAUUltbCriZwhfu02Q2I71SNp/Uh/wJ/7zKbIiEEvunTkkEjWUqow==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.34.7': - resolution: {integrity: sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA==} + '@rolldown/binding-darwin-x64@1.0.0-beta.7-commit.30e0395': + resolution: {integrity: sha512-SxlqmJ0dRHwPDRTUnZldVR3wtt1yZUKjMuorgoElFpLDpxGRN9C7wMUB88uw2R+LUkXHmGhgbQZc5TIWz+gi2A==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.34.7': - resolution: {integrity: sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.34.7': - resolution: {integrity: sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q==} + '@rolldown/binding-freebsd-x64@1.0.0-beta.7-commit.30e0395': + resolution: {integrity: sha512-XiBQRJbJmihXHruDUFloWG284y8ZLgUQrlnEOw5Kdz+wxvq1Kxz5aKf62Zrw3lvY8m6F8hBeE93ne2ZDHngQOA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.34.7': - resolution: {integrity: sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.7-commit.30e0395': + resolution: {integrity: sha512-tfVAzmJ/nkMSK2o4tDf/pLSuaXieOw+XjWkVqJUHoxt5wufno9tPApOBGEbjbnENkXdR4+1dlZfE0ZmCpdUdvw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.34.7': - resolution: {integrity: sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.34.7': - resolution: {integrity: sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.7-commit.30e0395': + resolution: {integrity: sha512-6myOJPi7rr29FPU0BNrNufYDCHR//JsFbZEgj4ykx/22TUUZaOKJoiLNBREplO7b4YCJ9+0pTvvuGTkvmp5esg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.34.7': - resolution: {integrity: sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw==} + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.7-commit.30e0395': + resolution: {integrity: sha512-VAJlT86fnUJBOwIpZ6Y9DQQDVZ6bYVJPWhGp4EVs3aQfyub/hOvd4RXiLSjaCIL3BafNZhZ+HtqHjIngWaiLwA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.34.7': - resolution: {integrity: sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.34.7': - resolution: {integrity: sha512-vrDk9JDa/BFkxcS2PbWpr0C/LiiSLxFbNOBgfbW6P8TBe9PPHx9Wqbvx2xgNi1TOAyQHQJ7RZFqBiEohm79r0w==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.34.7': - resolution: {integrity: sha512-rB+ejFyjtmSo+g/a4eovDD1lHWHVqizN8P0Hm0RElkINpS0XOdpaXloqM4FBkF9ZWEzg6bezymbpLmeMldfLTw==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.34.7': - resolution: {integrity: sha512-nNXNjo4As6dNqRn7OrsnHzwTgtypfRA3u3AKr0B3sOOo+HkedIbn8ZtFnB+4XyKJojIfqDKmbIzO1QydQ8c+Pw==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.34.7': - resolution: {integrity: sha512-9kPVf9ahnpOMSGlCxXGv980wXD0zRR3wyk8+33/MXQIpQEOpaNe7dEHm5LMfyRZRNt9lMEQuH0jUKj15MkM7QA==} + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.7-commit.30e0395': + resolution: {integrity: sha512-myi+HoUkWSUqMLhVGf8pD6lsfx25TBm6mQcw7qIwXKcX6a0I1SFCHFL3qlqQtXhAdwGKi18M3e5Bz67PdQV5Dg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.34.7': - resolution: {integrity: sha512-7wJPXRWTTPtTFDFezA8sle/1sdgxDjuMoRXEKtx97ViRxGGkVQYovem+Q8Pr/2HxiHp74SSRG+o6R0Yq0shPwQ==} + '@rolldown/binding-linux-x64-musl@1.0.0-beta.7-commit.30e0395': + resolution: {integrity: sha512-CMfInIUDGY4y9JOfn30U6o7V9faT2zJs3kMshyaX3GSSmOg0QmvQlzAxWWMyIQKMXHIN0mnYQxPq9AhW2Yz1dg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.34.7': - resolution: {integrity: sha512-MN7aaBC7mAjsiMEZcsJvwNsQVNZShgES/9SzWp1HC9Yjqb5OpexYnRjF7RmE4itbeesHMYYQiAtUAQaSKs2Rfw==} + '@rolldown/binding-wasm32-wasi@1.0.0-beta.7-commit.30e0395': + resolution: {integrity: sha512-EkjaSUFAAPBld7ojUmMKiSB1A7iWgLaAeSyJaO4m4oIHvzZLsTKmtFnJodT7TMl5IBtO4kpBeWc4v9J+A8MZGQ==} + engines: {node: '>=14.21.3'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.7-commit.30e0395': + resolution: {integrity: sha512-It1vFY6GMg3Yx+rhYzuUa2nq5BY/z5hRT2NtjAOcp+DLOi5swWLoCNozA0QhGhESMnq9Nune71AKImCyIg8Peg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.34.7': - resolution: {integrity: sha512-aeawEKYswsFu1LhDM9RIgToobquzdtSc4jSVqHV8uApz4FVvhFl/mKh92wc8WpFc6aYCothV/03UjY6y7yLgbg==} + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.7-commit.30e0395': + resolution: {integrity: sha512-YSvkr4kb9RoiqObJZGEyYDB01Lf9jpqibFuFb/X9U/UAF/8lyYsjP18M/WD4inyoUBrDbh3wN0f38Una9FGKhQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.34.7': - resolution: {integrity: sha512-4ZedScpxxIrVO7otcZ8kCX1mZArtH2Wfj3uFCxRJ9NO80gg1XV0U/b2f/MKaGwj2X3QopHfoWiDQ917FRpwY3w==} + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.7-commit.30e0395': + resolution: {integrity: sha512-znpr7YScTNBki1u+/vk1jLl59/x+q2rrGCJYNm9o/KCOPZPIqCFy4+/y/SJo1eVa7QJdellTi5Z07Z/Bx/iViQ==} cpu: [x64] os: [win32] @@ -4314,8 +4168,13 @@ packages: '@ucast/mongo@2.4.3': resolution: {integrity: sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==} - '@vitejs/plugin-react@4.3.4': - resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + '@valibot/to-json-schema@1.0.0': + resolution: {integrity: sha512-/9crJgPptVsGCL6X+JPDQyaJwkalSZ/52WuF8DiRUxJgcmpNdzYRfZ+gqMEP8W3CTVfuMWPqqvIgfwJ97f9Etw==} + peerDependencies: + valibot: ^1.0.0 + + '@vitejs/plugin-react@4.4.1': + resolution: {integrity: sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 @@ -4513,6 +4372,10 @@ packages: resolution: {integrity: sha512-zIcWDJ+Kwqxfdnogx66Gxzr0kVmCcRAdat9nlY2IHsshqTN4fBH6tMeRMPA/2w0rpBayIJvjQAaa2/4RDrNqwg==} engines: {node: '>=14'} + ansis@3.17.0: + resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} + engines: {node: '>=14'} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -4588,11 +4451,8 @@ packages: avvio@9.1.0: resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} - axios@1.7.8: - resolution: {integrity: sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==} - - axios@1.7.9: - resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + axios@1.8.4: + resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} @@ -5492,11 +5352,6 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.24.2: - resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.25.0: resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} engines: {node: '>=18'} @@ -6665,6 +6520,70 @@ packages: light-my-request@6.6.0: resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} + lightningcss-darwin-arm64@1.29.3: + resolution: {integrity: sha512-fb7raKO3pXtlNbQbiMeEu8RbBVHnpyqAoxTyTRMEWFQWmscGC2wZxoHzZ+YKAepUuKT9uIW5vL2QbFivTgprZg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.29.3: + resolution: {integrity: sha512-KF2XZ4ZdmDGGtEYmx5wpzn6u8vg7AdBHaEOvDKu8GOs7xDL/vcU2vMKtTeNe1d4dogkDdi3B9zC77jkatWBwEQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.29.3: + resolution: {integrity: sha512-VUWeVf+V1UM54jv9M4wen9vMlIAyT69Krl9XjI8SsRxz4tdNV/7QEPlW6JASev/pYdiynUCW0pwaFquDRYdxMw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.29.3: + resolution: {integrity: sha512-UhgZ/XVNfXQVEJrMIWeK1Laj8KbhjbIz7F4znUk7G4zeGw7TRoJxhb66uWrEsonn1+O45w//0i0Fu0wIovYdYg==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.29.3: + resolution: {integrity: sha512-Pqau7jtgJNmQ/esugfmAT1aCFy/Gxc92FOxI+3n+LbMHBheBnk41xHDhc0HeYlx9G0xP5tK4t0Koy3QGGNqypw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.29.3: + resolution: {integrity: sha512-dxakOk66pf7KLS7VRYFO7B8WOJLecE5OPL2YOk52eriFd/yeyxt2Km5H0BjLfElokIaR+qWi33gB8MQLrdAY3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.29.3: + resolution: {integrity: sha512-ySZTNCpbfbK8rqpKJeJR2S0g/8UqqV3QnzcuWvpI60LWxnFN91nxpSSwCbzfOXkzKfar9j5eOuOplf+klKtINg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.29.3: + resolution: {integrity: sha512-3pVZhIzW09nzi10usAXfIGTTSTYQ141dk88vGFNCgawIzayiIzZQxEcxVtIkdvlEq2YuFsL9Wcj/h61JHHzuFQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.29.3: + resolution: {integrity: sha512-VRnkAvtIkeWuoBJeGOTrZxsNp4HogXtcaaLm8agmbYtLDOhQdpgxW6NjZZjDXbvGF+eOehGulXZ3C1TiwHY4QQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.29.3: + resolution: {integrity: sha512-IszwRPu2cPnDQsZpd7/EAr0x2W7jkaWqQ1SwCVIZ/tSbZVXPLt6k8s6FkcyBjViCzvB5CW0We0QbbP7zp2aBjQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.29.3: + resolution: {integrity: sha512-GlOJwTIP6TMIlrTFsxTerwC0W6OpQpCGuX1ECRLBUVRh6fpJH3xTqjCjRgQHTb4ZXexH9rtHou1Lf03GKzmhhQ==} + engines: {node: '>= 12.0.0'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -7332,9 +7251,6 @@ packages: pgpass@1.0.5: resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} - picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} @@ -7429,8 +7345,8 @@ packages: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.2: - resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==} + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -7698,8 +7614,8 @@ packages: react-promise-suspense@0.3.4: resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==} - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} react-remove-scroll-bar@2.3.8: @@ -7918,10 +7834,54 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rollup@4.34.7: - resolution: {integrity: sha512-8qhyN0oZ4x0H6wmBgfKxJtxM7qS98YJ0k0kNh5ECVtuchIJ7z9IVVvzpmtQyT10PXKMtBxYr1wQ5Apg8RS8kXQ==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} + rolldown-vite@6.3.3: + resolution: {integrity: sha512-ZmIaiGPNC7bSnc8FOETiesfGtyBSYGy+m7FM6EhcjciBggv6OHd28HSS3NItosI1AXfgVHdrHpyPPy/YrfmkdA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + esbuild: ^0.25.0 + jiti: '>=1.21.0' + less: '*' + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + rolldown@1.0.0-beta.7-commit.30e0395: + resolution: {integrity: sha512-io3+hz8Eh9wdbX8SaybkPWwo17PDVezp8qEjldCAqfVZ/jXM1xF3lTnwvgMiW0rgsZuPdPSZA7hhtKJ/x6aq8g==} + hasBin: true + peerDependencies: + '@oxc-project/runtime': 0.65.0 + peerDependenciesMeta: + '@oxc-project/runtime': + optional: true rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} @@ -8658,6 +8618,14 @@ packages: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} + valibot@1.0.0: + resolution: {integrity: sha512-1Hc0ihzWxBar6NGeZv7fPLY0QuxFMyxwYR2sF1Blu7Wq7EnremwY2W02tit2ij2VJT8HcSkHAQqmFfl77f73Yw==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + validate-npm-package-name@5.0.0: resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -8670,46 +8638,6 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite@6.1.0: - resolution: {integrity: sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} @@ -9588,7 +9516,7 @@ snapshots: '@babel/code-frame@7.24.2': dependencies: '@babel/highlight': 7.24.2 - picocolors: 1.0.0 + picocolors: 1.1.1 '@babel/code-frame@7.26.2': dependencies: @@ -9598,6 +9526,8 @@ snapshots: '@babel/compat-data@7.26.2': {} + '@babel/compat-data@7.26.8': {} + '@babel/core@7.24.5': dependencies: '@ampproject/remapping': 2.3.0 @@ -9658,6 +9588,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.26.10': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helpers': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + convert-source-map: 2.0.0 + debug: 4.3.7 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/generator@7.24.1': dependencies: '@babel/types': 7.24.0 @@ -9680,6 +9630,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.0.2 + '@babel/generator@7.27.0': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + '@babel/helper-annotate-as-pure@7.22.5': dependencies: '@babel/types': 7.26.0 @@ -9696,6 +9654,14 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-compilation-targets@7.27.0': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.2 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-create-class-features-plugin@7.23.7(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -9795,6 +9761,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + '@babel/helper-optimise-call-expression@7.22.5': dependencies: '@babel/types': 7.26.0 @@ -9869,25 +9844,30 @@ snapshots: '@babel/template': 7.25.9 '@babel/types': 7.26.0 + '@babel/helpers@7.27.0': + dependencies: + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + '@babel/highlight@7.24.2': dependencies: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.0 + picocolors: 1.1.1 '@babel/parser@7.24.5': dependencies: '@babel/types': 7.26.0 - '@babel/parser@7.24.6': - dependencies: - '@babel/types': 7.24.6 - '@babel/parser@7.26.2': dependencies: '@babel/types': 7.26.0 + '@babel/parser@7.27.0': + dependencies: + '@babel/types': 7.27.0 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -10405,14 +10385,14 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.26.0)': @@ -10619,6 +10599,12 @@ snapshots: '@babel/parser': 7.26.2 '@babel/types': 7.26.0 + '@babel/template@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@babel/traverse@7.25.9': dependencies: '@babel/code-frame': 7.26.2 @@ -10631,6 +10617,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + debug: 4.3.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + '@babel/types@7.23.6': dependencies: '@babel/helper-string-parser': 7.23.4 @@ -10654,6 +10652,11 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@babel/types@7.27.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@bcoe/v8-coverage@0.2.3': {} '@braintree/sanitize-url@7.1.0': {} @@ -10742,219 +10745,144 @@ snapshots: '@esbuild/aix-ppc64@0.19.11': optional: true - '@esbuild/aix-ppc64@0.24.2': - optional: true - '@esbuild/aix-ppc64@0.25.0': optional: true '@esbuild/android-arm64@0.19.11': optional: true - '@esbuild/android-arm64@0.24.2': - optional: true - '@esbuild/android-arm64@0.25.0': optional: true '@esbuild/android-arm@0.19.11': optional: true - '@esbuild/android-arm@0.24.2': - optional: true - '@esbuild/android-arm@0.25.0': optional: true '@esbuild/android-x64@0.19.11': optional: true - '@esbuild/android-x64@0.24.2': - optional: true - '@esbuild/android-x64@0.25.0': optional: true '@esbuild/darwin-arm64@0.19.11': optional: true - '@esbuild/darwin-arm64@0.24.2': - optional: true - '@esbuild/darwin-arm64@0.25.0': optional: true '@esbuild/darwin-x64@0.19.11': optional: true - '@esbuild/darwin-x64@0.24.2': - optional: true - '@esbuild/darwin-x64@0.25.0': optional: true '@esbuild/freebsd-arm64@0.19.11': optional: true - '@esbuild/freebsd-arm64@0.24.2': - optional: true - '@esbuild/freebsd-arm64@0.25.0': optional: true '@esbuild/freebsd-x64@0.19.11': optional: true - '@esbuild/freebsd-x64@0.24.2': - optional: true - '@esbuild/freebsd-x64@0.25.0': optional: true '@esbuild/linux-arm64@0.19.11': optional: true - '@esbuild/linux-arm64@0.24.2': - optional: true - '@esbuild/linux-arm64@0.25.0': optional: true '@esbuild/linux-arm@0.19.11': optional: true - '@esbuild/linux-arm@0.24.2': - optional: true - '@esbuild/linux-arm@0.25.0': optional: true '@esbuild/linux-ia32@0.19.11': optional: true - '@esbuild/linux-ia32@0.24.2': - optional: true - '@esbuild/linux-ia32@0.25.0': optional: true '@esbuild/linux-loong64@0.19.11': optional: true - '@esbuild/linux-loong64@0.24.2': - optional: true - '@esbuild/linux-loong64@0.25.0': optional: true '@esbuild/linux-mips64el@0.19.11': optional: true - '@esbuild/linux-mips64el@0.24.2': - optional: true - '@esbuild/linux-mips64el@0.25.0': optional: true '@esbuild/linux-ppc64@0.19.11': optional: true - '@esbuild/linux-ppc64@0.24.2': - optional: true - '@esbuild/linux-ppc64@0.25.0': optional: true '@esbuild/linux-riscv64@0.19.11': optional: true - '@esbuild/linux-riscv64@0.24.2': - optional: true - '@esbuild/linux-riscv64@0.25.0': optional: true '@esbuild/linux-s390x@0.19.11': optional: true - '@esbuild/linux-s390x@0.24.2': - optional: true - '@esbuild/linux-s390x@0.25.0': optional: true '@esbuild/linux-x64@0.19.11': optional: true - '@esbuild/linux-x64@0.24.2': - optional: true - '@esbuild/linux-x64@0.25.0': optional: true - '@esbuild/netbsd-arm64@0.24.2': - optional: true - '@esbuild/netbsd-arm64@0.25.0': optional: true '@esbuild/netbsd-x64@0.19.11': optional: true - '@esbuild/netbsd-x64@0.24.2': - optional: true - '@esbuild/netbsd-x64@0.25.0': optional: true - '@esbuild/openbsd-arm64@0.24.2': - optional: true - '@esbuild/openbsd-arm64@0.25.0': optional: true '@esbuild/openbsd-x64@0.19.11': optional: true - '@esbuild/openbsd-x64@0.24.2': - optional: true - '@esbuild/openbsd-x64@0.25.0': optional: true '@esbuild/sunos-x64@0.19.11': optional: true - '@esbuild/sunos-x64@0.24.2': - optional: true - '@esbuild/sunos-x64@0.25.0': optional: true '@esbuild/win32-arm64@0.19.11': optional: true - '@esbuild/win32-arm64@0.24.2': - optional: true - '@esbuild/win32-arm64@0.25.0': optional: true '@esbuild/win32-ia32@0.19.11': optional: true - '@esbuild/win32-ia32@0.24.2': - optional: true - '@esbuild/win32-ia32@0.25.0': optional: true '@esbuild/win32-x64@0.19.11': optional: true - '@esbuild/win32-x64@0.24.2': - optional: true - '@esbuild/win32-x64@0.25.0': optional: true @@ -11919,7 +11847,7 @@ snapshots: tslib: 2.8.0 yargs-parser: 21.1.1 - '@nx/js@20.4.5(@babel/traverse@7.25.9)(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(nx@20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)))(typescript@5.7.3)': + '@nx/js@20.4.5(@babel/traverse@7.27.0)(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(nx@20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)))(typescript@5.7.3)': dependencies: '@babel/core': 7.26.0 '@babel/plugin-proposal-decorators': 7.23.7(@babel/core@7.26.0) @@ -11933,7 +11861,7 @@ snapshots: '@zkochan/js-yaml': 0.0.7 babel-plugin-const-enum: 1.2.0(@babel/core@7.26.0) babel-plugin-macros: 3.1.0 - babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.26.0)(@babel/traverse@7.25.9) + babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.26.0)(@babel/traverse@7.27.0) chalk: 4.1.2 columnify: 1.6.0 detect-port: 1.5.1 @@ -12010,6 +11938,10 @@ snapshots: '@opentelemetry/api@1.9.0': optional: true + '@oxc-project/runtime@0.65.0': {} + + '@oxc-project/types@0.65.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -12135,61 +12067,42 @@ snapshots: '@remirror/core-constants@3.0.0': {} - '@rollup/rollup-android-arm-eabi@4.34.7': + '@rolldown/binding-darwin-arm64@1.0.0-beta.7-commit.30e0395': optional: true - '@rollup/rollup-android-arm64@4.34.7': + '@rolldown/binding-darwin-x64@1.0.0-beta.7-commit.30e0395': optional: true - '@rollup/rollup-darwin-arm64@4.34.7': + '@rolldown/binding-freebsd-x64@1.0.0-beta.7-commit.30e0395': optional: true - '@rollup/rollup-darwin-x64@4.34.7': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.7-commit.30e0395': optional: true - '@rollup/rollup-freebsd-arm64@4.34.7': + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.7-commit.30e0395': optional: true - '@rollup/rollup-freebsd-x64@4.34.7': + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.7-commit.30e0395': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.34.7': + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.7-commit.30e0395': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.34.7': + '@rolldown/binding-linux-x64-musl@1.0.0-beta.7-commit.30e0395': optional: true - '@rollup/rollup-linux-arm64-gnu@4.34.7': + '@rolldown/binding-wasm32-wasi@1.0.0-beta.7-commit.30e0395': + dependencies: + '@napi-rs/wasm-runtime': 0.2.4 optional: true - '@rollup/rollup-linux-arm64-musl@4.34.7': + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.7-commit.30e0395': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.34.7': + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.7-commit.30e0395': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.34.7': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.34.7': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.34.7': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.34.7': - optional: true - - '@rollup/rollup-linux-x64-musl@4.34.7': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.34.7': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.34.7': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.34.7': + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.7-commit.30e0395': optional: true '@selderee/plugin-htmlparser2@0.11.0': @@ -12926,20 +12839,20 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.24.6 - '@babel/types': 7.24.6 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.5 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.24.6 + '@babel/types': 7.26.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.24.6 - '@babel/types': 7.24.6 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 '@types/babel__traverse@7.20.5': dependencies: @@ -13485,14 +13398,18 @@ snapshots: dependencies: '@ucast/core': 1.10.2 - '@vitejs/plugin-react@4.3.4(vite@6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))': + '@valibot/to-json-schema@1.0.0(valibot@1.0.0(typescript@5.7.2))': dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) + valibot: 1.0.0(typescript@5.7.2) + + '@vitejs/plugin-react@4.4.1(rolldown-vite@6.3.3(@types/node@22.10.0)(esbuild@0.25.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(typescript@5.7.2)(yaml@2.7.0))': + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) + react-refresh: 0.17.0 + vite: rolldown-vite@6.3.3(@types/node@22.10.0)(esbuild@0.25.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(typescript@5.7.2)(yaml@2.7.0) transitivePeerDependencies: - supports-color @@ -13700,6 +13617,8 @@ snapshots: ansis@3.15.0: {} + ansis@3.17.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -13797,15 +13716,7 @@ snapshots: '@fastify/error': 4.0.0 fastq: 1.17.1 - axios@1.7.8: - dependencies: - follow-redirects: 1.15.6 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - axios@1.7.9: + axios@1.8.4: dependencies: follow-redirects: 1.15.6 form-data: 4.0.0 @@ -13896,12 +13807,12 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.26.0)(@babel/traverse@7.25.9): + babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.26.0)(@babel/traverse@7.27.0): dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 optionalDependencies: - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.27.0 babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.5): dependencies: @@ -14910,34 +14821,6 @@ snapshots: '@esbuild/win32-ia32': 0.19.11 '@esbuild/win32-x64': 0.19.11 - esbuild@0.24.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.24.2 - '@esbuild/android-arm': 0.24.2 - '@esbuild/android-arm64': 0.24.2 - '@esbuild/android-x64': 0.24.2 - '@esbuild/darwin-arm64': 0.24.2 - '@esbuild/darwin-x64': 0.24.2 - '@esbuild/freebsd-arm64': 0.24.2 - '@esbuild/freebsd-x64': 0.24.2 - '@esbuild/linux-arm': 0.24.2 - '@esbuild/linux-arm64': 0.24.2 - '@esbuild/linux-ia32': 0.24.2 - '@esbuild/linux-loong64': 0.24.2 - '@esbuild/linux-mips64el': 0.24.2 - '@esbuild/linux-ppc64': 0.24.2 - '@esbuild/linux-riscv64': 0.24.2 - '@esbuild/linux-s390x': 0.24.2 - '@esbuild/linux-x64': 0.24.2 - '@esbuild/netbsd-arm64': 0.24.2 - '@esbuild/netbsd-x64': 0.24.2 - '@esbuild/openbsd-arm64': 0.24.2 - '@esbuild/openbsd-x64': 0.24.2 - '@esbuild/sunos-x64': 0.24.2 - '@esbuild/win32-arm64': 0.24.2 - '@esbuild/win32-ia32': 0.24.2 - '@esbuild/win32-x64': 0.24.2 - esbuild@0.25.0: optionalDependencies: '@esbuild/aix-ppc64': 0.25.0 @@ -16432,6 +16315,51 @@ snapshots: process-warning: 4.0.0 set-cookie-parser: 2.6.0 + lightningcss-darwin-arm64@1.29.3: + optional: true + + lightningcss-darwin-x64@1.29.3: + optional: true + + lightningcss-freebsd-x64@1.29.3: + optional: true + + lightningcss-linux-arm-gnueabihf@1.29.3: + optional: true + + lightningcss-linux-arm64-gnu@1.29.3: + optional: true + + lightningcss-linux-arm64-musl@1.29.3: + optional: true + + lightningcss-linux-x64-gnu@1.29.3: + optional: true + + lightningcss-linux-x64-musl@1.29.3: + optional: true + + lightningcss-win32-arm64-msvc@1.29.3: + optional: true + + lightningcss-win32-x64-msvc@1.29.3: + optional: true + + lightningcss@1.29.3: + dependencies: + detect-libc: 2.0.3 + optionalDependencies: + lightningcss-darwin-arm64: 1.29.3 + lightningcss-darwin-x64: 1.29.3 + lightningcss-freebsd-x64: 1.29.3 + lightningcss-linux-arm-gnueabihf: 1.29.3 + lightningcss-linux-arm64-gnu: 1.29.3 + lightningcss-linux-arm64-musl: 1.29.3 + lightningcss-linux-x64-gnu: 1.29.3 + lightningcss-linux-x64-musl: 1.29.3 + lightningcss-win32-arm64-msvc: 1.29.3 + lightningcss-win32-x64-msvc: 1.29.3 + lines-and-columns@1.2.4: {} lines-and-columns@2.0.3: {} @@ -16814,7 +16742,7 @@ snapshots: '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.2 '@zkochan/js-yaml': 0.0.7 - axios: 1.7.9 + axios: 1.8.4 chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 @@ -17119,8 +17047,6 @@ snapshots: dependencies: split2: 4.2.0 - picocolors@1.0.0: {} - picocolors@1.0.1: {} picocolors@1.1.1: {} @@ -17221,7 +17147,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.2: + postcss@8.5.3: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -17251,7 +17177,7 @@ snapshots: postmark@4.0.5: dependencies: - axios: 1.7.8 + axios: 1.8.4 transitivePeerDependencies: - debug @@ -17528,7 +17454,7 @@ snapshots: dependencies: fast-deep-equal: 2.0.1 - react-refresh@0.14.2: {} + react-refresh@0.17.0: {} react-remove-scroll-bar@2.3.8(@types/react@18.3.12)(react@18.3.1): dependencies: @@ -17749,30 +17675,50 @@ snapshots: robust-predicates@3.0.2: {} - rollup@4.34.7: + rolldown-vite@6.3.3(@types/node@22.10.0)(esbuild@0.25.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(typescript@5.7.2)(yaml@2.7.0): dependencies: - '@types/estree': 1.0.6 + '@oxc-project/runtime': 0.65.0 + fdir: 6.4.3(picomatch@4.0.2) + lightningcss: 1.29.3 + picomatch: 4.0.2 + postcss: 8.5.3 + rolldown: 1.0.0-beta.7-commit.30e0395(@oxc-project/runtime@0.65.0)(typescript@5.7.2) + tinyglobby: 0.2.12 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.34.7 - '@rollup/rollup-android-arm64': 4.34.7 - '@rollup/rollup-darwin-arm64': 4.34.7 - '@rollup/rollup-darwin-x64': 4.34.7 - '@rollup/rollup-freebsd-arm64': 4.34.7 - '@rollup/rollup-freebsd-x64': 4.34.7 - '@rollup/rollup-linux-arm-gnueabihf': 4.34.7 - '@rollup/rollup-linux-arm-musleabihf': 4.34.7 - '@rollup/rollup-linux-arm64-gnu': 4.34.7 - '@rollup/rollup-linux-arm64-musl': 4.34.7 - '@rollup/rollup-linux-loongarch64-gnu': 4.34.7 - '@rollup/rollup-linux-powerpc64le-gnu': 4.34.7 - '@rollup/rollup-linux-riscv64-gnu': 4.34.7 - '@rollup/rollup-linux-s390x-gnu': 4.34.7 - '@rollup/rollup-linux-x64-gnu': 4.34.7 - '@rollup/rollup-linux-x64-musl': 4.34.7 - '@rollup/rollup-win32-arm64-msvc': 4.34.7 - '@rollup/rollup-win32-ia32-msvc': 4.34.7 - '@rollup/rollup-win32-x64-msvc': 4.34.7 + '@types/node': 22.10.0 + esbuild: 0.25.0 fsevents: 2.3.3 + jiti: 1.21.0 + less: 4.2.0 + sugarss: 4.0.1(postcss@8.4.49) + terser: 5.39.0 + tsx: 4.19.3 + yaml: 2.7.0 + transitivePeerDependencies: + - typescript + + rolldown@1.0.0-beta.7-commit.30e0395(@oxc-project/runtime@0.65.0)(typescript@5.7.2): + dependencies: + '@oxc-project/types': 0.65.0 + '@valibot/to-json-schema': 1.0.0(valibot@1.0.0(typescript@5.7.2)) + ansis: 3.17.0 + valibot: 1.0.0(typescript@5.7.2) + optionalDependencies: + '@oxc-project/runtime': 0.65.0 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.7-commit.30e0395 + '@rolldown/binding-darwin-x64': 1.0.0-beta.7-commit.30e0395 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.7-commit.30e0395 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.7-commit.30e0395 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.7-commit.30e0395 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.7-commit.30e0395 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.7-commit.30e0395 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.7-commit.30e0395 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.7-commit.30e0395 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.7-commit.30e0395 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.7-commit.30e0395 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.7-commit.30e0395 + transitivePeerDependencies: + - typescript rope-sequence@1.3.4: {} @@ -18538,6 +18484,10 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + valibot@1.0.0(typescript@5.7.2): + optionalDependencies: + typescript: 5.7.2 + validate-npm-package-name@5.0.0: dependencies: builtins: 5.0.1 @@ -18546,21 +18496,6 @@ snapshots: vary@1.1.2: {} - vite@6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): - dependencies: - esbuild: 0.24.2 - postcss: 8.5.2 - rollup: 4.34.7 - optionalDependencies: - '@types/node': 22.10.0 - fsevents: 2.3.3 - jiti: 1.21.0 - less: 4.2.0 - sugarss: 4.0.1(postcss@8.4.49) - terser: 5.39.0 - tsx: 4.19.3 - yaml: 2.7.0 - void-elements@3.1.0: {} vscode-jsonrpc@8.2.0: {} From 882f3093bda7d0ef3587bf7a006f0801bc1c1a03 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Tue, 22 Apr 2025 19:37:06 +0100 Subject: [PATCH 24/63] search space members by email (#1049) --- apps/server/src/database/repos/space/space-member.repo.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/server/src/database/repos/space/space-member.repo.ts b/apps/server/src/database/repos/space/space-member.repo.ts index 67275034..b9c4fbf9 100644 --- a/apps/server/src/database/repos/space/space-member.repo.ts +++ b/apps/server/src/database/repos/space/space-member.repo.ts @@ -119,11 +119,9 @@ export class SpaceMemberRepo { if (pagination.query) { query = query.where((eb) => - eb('users.name', 'ilike', `%${pagination.query}%`).or( - 'groups.name', - 'ilike', - `%${pagination.query}%`, - ), + eb('users.name', 'ilike', `%${pagination.query}%`) + .or('users.email', 'ilike', `%${pagination.query}%`) + .or('groups.name', 'ilike', `%${pagination.query}%`), ); } From 37a1804db9f4996951fee7c38bd3ea66e8dca3a3 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Tue, 22 Apr 2025 20:00:36 +0100 Subject: [PATCH 25/63] Revert "switch to vite rolldown (#1048)" (#1050) This reverts commit 1a1b2c8682ccd08062d8486d3ddd0b60989a9dd5. --- apps/client/package.json | 6 +- pnpm-lock.yaml | 929 +++++++++++++++++++++------------------ 2 files changed, 500 insertions(+), 435 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index 74e3c21e..5901a920 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -25,7 +25,7 @@ "@tabler/icons-react": "^3.22.0", "@tanstack/react-query": "^5.61.4", "@tiptap/extension-character-count": "^2.11.5", - "axios": "^1.8.4", + "axios": "^1.7.9", "clsx": "^2.1.1", "emoji-mart": "^5.6.0", "file-saver": "^2.0.5", @@ -63,7 +63,7 @@ "@types/node": "22.10.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.4.1", + "@vitejs/plugin-react": "^4.3.4", "eslint": "^9.15.0", "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.1.0", @@ -76,6 +76,6 @@ "prettier": "^3.4.1", "typescript": "^5.7.2", "typescript-eslint": "^8.17.0", - "vite": "npm:rolldown-vite@latest" + "vite": "^6.1.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d5eaf2ae..f9cc1623 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -181,7 +181,7 @@ importers: devDependencies: '@nx/js': specifier: 20.4.5 - version: 20.4.5(@babel/traverse@7.27.0)(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(nx@20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)))(typescript@5.7.3) + version: 20.4.5(@babel/traverse@7.25.9)(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(nx@20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)))(typescript@5.7.3) '@types/bytes': specifier: ^3.1.5 version: 3.1.5 @@ -246,8 +246,8 @@ importers: specifier: ^2.11.5 version: 2.11.5(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) axios: - specifier: ^1.8.4 - version: 1.8.4 + specifier: ^1.7.9 + version: 1.7.9 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -355,8 +355,8 @@ importers: specifier: ^18.3.1 version: 18.3.1 '@vitejs/plugin-react': - specifier: ^4.4.1 - version: 4.4.1(rolldown-vite@6.3.3(@types/node@22.10.0)(esbuild@0.25.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(typescript@5.7.2)(yaml@2.7.0)) + specifier: ^4.3.4 + version: 4.3.4(vite@6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) eslint: specifier: ^9.15.0 version: 9.15.0(jiti@1.21.0) @@ -394,8 +394,8 @@ importers: specifier: ^8.17.0 version: 8.17.0(eslint@9.15.0(jiti@1.21.0))(typescript@5.7.2) vite: - specifier: npm:rolldown-vite@latest - version: rolldown-vite@6.3.3(@types/node@22.10.0)(esbuild@0.25.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(typescript@5.7.2)(yaml@2.7.0) + specifier: ^6.1.0 + version: 6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) apps/server: dependencies: @@ -876,10 +876,6 @@ packages: resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.26.8': - resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} - engines: {node: '>=6.9.0'} - '@babel/core@7.24.5': resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==} engines: {node: '>=6.9.0'} @@ -892,10 +888,6 @@ packages: resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} engines: {node: '>=6.9.0'} - '@babel/core@7.26.10': - resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.24.1': resolution: {integrity: sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==} engines: {node: '>=6.9.0'} @@ -908,10 +900,6 @@ packages: resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.27.0': - resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} - engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.22.5': resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} engines: {node: '>=6.9.0'} @@ -924,10 +912,6 @@ packages: resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.0': - resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} - engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.23.7': resolution: {integrity: sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g==} engines: {node: '>=6.9.0'} @@ -1083,10 +1067,6 @@ packages: resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.27.0': - resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} - engines: {node: '>=6.9.0'} - '@babel/highlight@7.24.2': resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} engines: {node: '>=6.9.0'} @@ -1096,13 +1076,13 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.26.2': - resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + '@babel/parser@7.24.6': + resolution: {integrity: sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.27.0': - resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -1603,18 +1583,10 @@ packages: resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} - '@babel/template@7.27.0': - resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.25.9': resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.27.0': - resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} - engines: {node: '>=6.9.0'} - '@babel/types@7.23.6': resolution: {integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==} engines: {node: '>=6.9.0'} @@ -1631,10 +1603,6 @@ packages: resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} engines: {node: '>=6.9.0'} - '@babel/types@7.27.0': - resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} - engines: {node: '>=6.9.0'} - '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -1733,6 +1701,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.0': resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} engines: {node: '>=18'} @@ -1745,6 +1719,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.0': resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} engines: {node: '>=18'} @@ -1757,6 +1737,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.0': resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} engines: {node: '>=18'} @@ -1769,6 +1755,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.0': resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} engines: {node: '>=18'} @@ -1781,6 +1773,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.0': resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} engines: {node: '>=18'} @@ -1793,6 +1791,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.0': resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} engines: {node: '>=18'} @@ -1805,6 +1809,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.0': resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} engines: {node: '>=18'} @@ -1817,6 +1827,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.0': resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} engines: {node: '>=18'} @@ -1829,6 +1845,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.0': resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} engines: {node: '>=18'} @@ -1841,6 +1863,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.0': resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} engines: {node: '>=18'} @@ -1853,6 +1881,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.0': resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} engines: {node: '>=18'} @@ -1865,6 +1899,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.0': resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} engines: {node: '>=18'} @@ -1877,6 +1917,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.0': resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} engines: {node: '>=18'} @@ -1889,6 +1935,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.0': resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} engines: {node: '>=18'} @@ -1901,6 +1953,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.0': resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} engines: {node: '>=18'} @@ -1913,6 +1971,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.0': resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} engines: {node: '>=18'} @@ -1925,12 +1989,24 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.0': resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.25.0': resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} engines: {node: '>=18'} @@ -1943,12 +2019,24 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.0': resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.25.0': resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} engines: {node: '>=18'} @@ -1961,6 +2049,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.0': resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} engines: {node: '>=18'} @@ -1973,6 +2067,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.0': resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} engines: {node: '>=18'} @@ -1985,6 +2085,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.0': resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} engines: {node: '>=18'} @@ -1997,6 +2103,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.0': resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} engines: {node: '>=18'} @@ -2009,6 +2121,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.0': resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} engines: {node: '>=18'} @@ -2902,13 +3020,6 @@ packages: resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} - '@oxc-project/runtime@0.65.0': - resolution: {integrity: sha512-qn70kbkGtJ3uWz+HXha+kufRXkT+pZWckJKL8jUPzXH5UNszSSwADkNQhb7/uit3tC70wFm9qPRlLHnJcjSGuA==} - engines: {node: '>=6.9.0'} - - '@oxc-project/types@0.65.0': - resolution: {integrity: sha512-7MpMzyXCcwxrTxJ4L0siy63Ds/LA8LAM4szumTFiynxTJkfrIZEV4PyR4Th0CqFZQ+oNi8WvW3Dr1MLy7o9qPQ==} - '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -3053,63 +3164,98 @@ packages: '@remirror/core-constants@3.0.0': resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} - '@rolldown/binding-darwin-arm64@1.0.0-beta.7-commit.30e0395': - resolution: {integrity: sha512-2o/ZF2nt59xOksx7WKxYcnuysv9SaMfCgIAUUltbCriZwhfu02Q2I71SNp/Uh/wJ/7zKbIiEEvunTkkEjWUqow==} + '@rollup/rollup-android-arm-eabi@4.34.7': + resolution: {integrity: sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.34.7': + resolution: {integrity: sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.34.7': + resolution: {integrity: sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA==} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-beta.7-commit.30e0395': - resolution: {integrity: sha512-SxlqmJ0dRHwPDRTUnZldVR3wtt1yZUKjMuorgoElFpLDpxGRN9C7wMUB88uw2R+LUkXHmGhgbQZc5TIWz+gi2A==} + '@rollup/rollup-darwin-x64@4.34.7': + resolution: {integrity: sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA==} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-beta.7-commit.30e0395': - resolution: {integrity: sha512-XiBQRJbJmihXHruDUFloWG284y8ZLgUQrlnEOw5Kdz+wxvq1Kxz5aKf62Zrw3lvY8m6F8hBeE93ne2ZDHngQOA==} + '@rollup/rollup-freebsd-arm64@4.34.7': + resolution: {integrity: sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.34.7': + resolution: {integrity: sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q==} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.7-commit.30e0395': - resolution: {integrity: sha512-tfVAzmJ/nkMSK2o4tDf/pLSuaXieOw+XjWkVqJUHoxt5wufno9tPApOBGEbjbnENkXdR4+1dlZfE0ZmCpdUdvw==} + '@rollup/rollup-linux-arm-gnueabihf@4.34.7': + resolution: {integrity: sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ==} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.7-commit.30e0395': - resolution: {integrity: sha512-6myOJPi7rr29FPU0BNrNufYDCHR//JsFbZEgj4ykx/22TUUZaOKJoiLNBREplO7b4YCJ9+0pTvvuGTkvmp5esg==} + '@rollup/rollup-linux-arm-musleabihf@4.34.7': + resolution: {integrity: sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.34.7': + resolution: {integrity: sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA==} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.7-commit.30e0395': - resolution: {integrity: sha512-VAJlT86fnUJBOwIpZ6Y9DQQDVZ6bYVJPWhGp4EVs3aQfyub/hOvd4RXiLSjaCIL3BafNZhZ+HtqHjIngWaiLwA==} + '@rollup/rollup-linux-arm64-musl@4.34.7': + resolution: {integrity: sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw==} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.7-commit.30e0395': - resolution: {integrity: sha512-myi+HoUkWSUqMLhVGf8pD6lsfx25TBm6mQcw7qIwXKcX6a0I1SFCHFL3qlqQtXhAdwGKi18M3e5Bz67PdQV5Dg==} + '@rollup/rollup-linux-loongarch64-gnu@4.34.7': + resolution: {integrity: sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.7': + resolution: {integrity: sha512-vrDk9JDa/BFkxcS2PbWpr0C/LiiSLxFbNOBgfbW6P8TBe9PPHx9Wqbvx2xgNi1TOAyQHQJ7RZFqBiEohm79r0w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.34.7': + resolution: {integrity: sha512-rB+ejFyjtmSo+g/a4eovDD1lHWHVqizN8P0Hm0RElkINpS0XOdpaXloqM4FBkF9ZWEzg6bezymbpLmeMldfLTw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.34.7': + resolution: {integrity: sha512-nNXNjo4As6dNqRn7OrsnHzwTgtypfRA3u3AKr0B3sOOo+HkedIbn8ZtFnB+4XyKJojIfqDKmbIzO1QydQ8c+Pw==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.34.7': + resolution: {integrity: sha512-9kPVf9ahnpOMSGlCxXGv980wXD0zRR3wyk8+33/MXQIpQEOpaNe7dEHm5LMfyRZRNt9lMEQuH0jUKj15MkM7QA==} cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-musl@1.0.0-beta.7-commit.30e0395': - resolution: {integrity: sha512-CMfInIUDGY4y9JOfn30U6o7V9faT2zJs3kMshyaX3GSSmOg0QmvQlzAxWWMyIQKMXHIN0mnYQxPq9AhW2Yz1dg==} + '@rollup/rollup-linux-x64-musl@4.34.7': + resolution: {integrity: sha512-7wJPXRWTTPtTFDFezA8sle/1sdgxDjuMoRXEKtx97ViRxGGkVQYovem+Q8Pr/2HxiHp74SSRG+o6R0Yq0shPwQ==} cpu: [x64] os: [linux] - '@rolldown/binding-wasm32-wasi@1.0.0-beta.7-commit.30e0395': - resolution: {integrity: sha512-EkjaSUFAAPBld7ojUmMKiSB1A7iWgLaAeSyJaO4m4oIHvzZLsTKmtFnJodT7TMl5IBtO4kpBeWc4v9J+A8MZGQ==} - engines: {node: '>=14.21.3'} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.7-commit.30e0395': - resolution: {integrity: sha512-It1vFY6GMg3Yx+rhYzuUa2nq5BY/z5hRT2NtjAOcp+DLOi5swWLoCNozA0QhGhESMnq9Nune71AKImCyIg8Peg==} + '@rollup/rollup-win32-arm64-msvc@4.34.7': + resolution: {integrity: sha512-MN7aaBC7mAjsiMEZcsJvwNsQVNZShgES/9SzWp1HC9Yjqb5OpexYnRjF7RmE4itbeesHMYYQiAtUAQaSKs2Rfw==} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.7-commit.30e0395': - resolution: {integrity: sha512-YSvkr4kb9RoiqObJZGEyYDB01Lf9jpqibFuFb/X9U/UAF/8lyYsjP18M/WD4inyoUBrDbh3wN0f38Una9FGKhQ==} + '@rollup/rollup-win32-ia32-msvc@4.34.7': + resolution: {integrity: sha512-aeawEKYswsFu1LhDM9RIgToobquzdtSc4jSVqHV8uApz4FVvhFl/mKh92wc8WpFc6aYCothV/03UjY6y7yLgbg==} cpu: [ia32] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.7-commit.30e0395': - resolution: {integrity: sha512-znpr7YScTNBki1u+/vk1jLl59/x+q2rrGCJYNm9o/KCOPZPIqCFy4+/y/SJo1eVa7QJdellTi5Z07Z/Bx/iViQ==} + '@rollup/rollup-win32-x64-msvc@4.34.7': + resolution: {integrity: sha512-4ZedScpxxIrVO7otcZ8kCX1mZArtH2Wfj3uFCxRJ9NO80gg1XV0U/b2f/MKaGwj2X3QopHfoWiDQ917FRpwY3w==} cpu: [x64] os: [win32] @@ -4168,13 +4314,8 @@ packages: '@ucast/mongo@2.4.3': resolution: {integrity: sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==} - '@valibot/to-json-schema@1.0.0': - resolution: {integrity: sha512-/9crJgPptVsGCL6X+JPDQyaJwkalSZ/52WuF8DiRUxJgcmpNdzYRfZ+gqMEP8W3CTVfuMWPqqvIgfwJ97f9Etw==} - peerDependencies: - valibot: ^1.0.0 - - '@vitejs/plugin-react@4.4.1': - resolution: {integrity: sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==} + '@vitejs/plugin-react@4.3.4': + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 @@ -4372,10 +4513,6 @@ packages: resolution: {integrity: sha512-zIcWDJ+Kwqxfdnogx66Gxzr0kVmCcRAdat9nlY2IHsshqTN4fBH6tMeRMPA/2w0rpBayIJvjQAaa2/4RDrNqwg==} engines: {node: '>=14'} - ansis@3.17.0: - resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} - engines: {node: '>=14'} - anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -4451,8 +4588,11 @@ packages: avvio@9.1.0: resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} - axios@1.8.4: - resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} + axios@1.7.8: + resolution: {integrity: sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==} + + axios@1.7.9: + resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} @@ -5352,6 +5492,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.25.0: resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} engines: {node: '>=18'} @@ -6520,70 +6665,6 @@ packages: light-my-request@6.6.0: resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} - lightningcss-darwin-arm64@1.29.3: - resolution: {integrity: sha512-fb7raKO3pXtlNbQbiMeEu8RbBVHnpyqAoxTyTRMEWFQWmscGC2wZxoHzZ+YKAepUuKT9uIW5vL2QbFivTgprZg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.29.3: - resolution: {integrity: sha512-KF2XZ4ZdmDGGtEYmx5wpzn6u8vg7AdBHaEOvDKu8GOs7xDL/vcU2vMKtTeNe1d4dogkDdi3B9zC77jkatWBwEQ==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.29.3: - resolution: {integrity: sha512-VUWeVf+V1UM54jv9M4wen9vMlIAyT69Krl9XjI8SsRxz4tdNV/7QEPlW6JASev/pYdiynUCW0pwaFquDRYdxMw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.29.3: - resolution: {integrity: sha512-UhgZ/XVNfXQVEJrMIWeK1Laj8KbhjbIz7F4znUk7G4zeGw7TRoJxhb66uWrEsonn1+O45w//0i0Fu0wIovYdYg==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.29.3: - resolution: {integrity: sha512-Pqau7jtgJNmQ/esugfmAT1aCFy/Gxc92FOxI+3n+LbMHBheBnk41xHDhc0HeYlx9G0xP5tK4t0Koy3QGGNqypw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-arm64-musl@1.29.3: - resolution: {integrity: sha512-dxakOk66pf7KLS7VRYFO7B8WOJLecE5OPL2YOk52eriFd/yeyxt2Km5H0BjLfElokIaR+qWi33gB8MQLrdAY3A==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-x64-gnu@1.29.3: - resolution: {integrity: sha512-ySZTNCpbfbK8rqpKJeJR2S0g/8UqqV3QnzcuWvpI60LWxnFN91nxpSSwCbzfOXkzKfar9j5eOuOplf+klKtINg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-linux-x64-musl@1.29.3: - resolution: {integrity: sha512-3pVZhIzW09nzi10usAXfIGTTSTYQ141dk88vGFNCgawIzayiIzZQxEcxVtIkdvlEq2YuFsL9Wcj/h61JHHzuFQ==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-win32-arm64-msvc@1.29.3: - resolution: {integrity: sha512-VRnkAvtIkeWuoBJeGOTrZxsNp4HogXtcaaLm8agmbYtLDOhQdpgxW6NjZZjDXbvGF+eOehGulXZ3C1TiwHY4QQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.29.3: - resolution: {integrity: sha512-IszwRPu2cPnDQsZpd7/EAr0x2W7jkaWqQ1SwCVIZ/tSbZVXPLt6k8s6FkcyBjViCzvB5CW0We0QbbP7zp2aBjQ==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.29.3: - resolution: {integrity: sha512-GlOJwTIP6TMIlrTFsxTerwC0W6OpQpCGuX1ECRLBUVRh6fpJH3xTqjCjRgQHTb4ZXexH9rtHou1Lf03GKzmhhQ==} - engines: {node: '>= 12.0.0'} - lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -7251,6 +7332,9 @@ packages: pgpass@1.0.5: resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} @@ -7345,8 +7429,8 @@ packages: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.3: - resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + postcss@8.5.2: + resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -7614,8 +7698,8 @@ packages: react-promise-suspense@0.3.4: resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==} - react-refresh@0.17.0: - resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} react-remove-scroll-bar@2.3.8: @@ -7834,54 +7918,10 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rolldown-vite@6.3.3: - resolution: {integrity: sha512-ZmIaiGPNC7bSnc8FOETiesfGtyBSYGy+m7FM6EhcjciBggv6OHd28HSS3NItosI1AXfgVHdrHpyPPy/YrfmkdA==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + rollup@4.34.7: + resolution: {integrity: sha512-8qhyN0oZ4x0H6wmBgfKxJtxM7qS98YJ0k0kNh5ECVtuchIJ7z9IVVvzpmtQyT10PXKMtBxYr1wQ5Apg8RS8kXQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - esbuild: ^0.25.0 - jiti: '>=1.21.0' - less: '*' - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - rolldown@1.0.0-beta.7-commit.30e0395: - resolution: {integrity: sha512-io3+hz8Eh9wdbX8SaybkPWwo17PDVezp8qEjldCAqfVZ/jXM1xF3lTnwvgMiW0rgsZuPdPSZA7hhtKJ/x6aq8g==} - hasBin: true - peerDependencies: - '@oxc-project/runtime': 0.65.0 - peerDependenciesMeta: - '@oxc-project/runtime': - optional: true rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} @@ -8618,14 +8658,6 @@ packages: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} - valibot@1.0.0: - resolution: {integrity: sha512-1Hc0ihzWxBar6NGeZv7fPLY0QuxFMyxwYR2sF1Blu7Wq7EnremwY2W02tit2ij2VJT8HcSkHAQqmFfl77f73Yw==} - peerDependencies: - typescript: '>=5' - peerDependenciesMeta: - typescript: - optional: true - validate-npm-package-name@5.0.0: resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -8638,6 +8670,46 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite@6.1.0: + resolution: {integrity: sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} @@ -9516,7 +9588,7 @@ snapshots: '@babel/code-frame@7.24.2': dependencies: '@babel/highlight': 7.24.2 - picocolors: 1.1.1 + picocolors: 1.0.0 '@babel/code-frame@7.26.2': dependencies: @@ -9526,8 +9598,6 @@ snapshots: '@babel/compat-data@7.26.2': {} - '@babel/compat-data@7.26.8': {} - '@babel/core@7.24.5': dependencies: '@ampproject/remapping': 2.3.0 @@ -9588,26 +9658,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/core@7.26.10': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.27.0 - '@babel/helper-compilation-targets': 7.27.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) - '@babel/helpers': 7.27.0 - '@babel/parser': 7.27.0 - '@babel/template': 7.27.0 - '@babel/traverse': 7.27.0 - '@babel/types': 7.27.0 - convert-source-map: 2.0.0 - debug: 4.3.7 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/generator@7.24.1': dependencies: '@babel/types': 7.24.0 @@ -9630,14 +9680,6 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.0.2 - '@babel/generator@7.27.0': - dependencies: - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.0.2 - '@babel/helper-annotate-as-pure@7.22.5': dependencies: '@babel/types': 7.26.0 @@ -9654,14 +9696,6 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-compilation-targets@7.27.0': - dependencies: - '@babel/compat-data': 7.26.8 - '@babel/helper-validator-option': 7.25.9 - browserslist: 4.24.2 - lru-cache: 5.1.1 - semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.23.7(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -9761,15 +9795,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': - dependencies: - '@babel/core': 7.26.10 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.25.9 - transitivePeerDependencies: - - supports-color - '@babel/helper-optimise-call-expression@7.22.5': dependencies: '@babel/types': 7.26.0 @@ -9844,30 +9869,25 @@ snapshots: '@babel/template': 7.25.9 '@babel/types': 7.26.0 - '@babel/helpers@7.27.0': - dependencies: - '@babel/template': 7.27.0 - '@babel/types': 7.27.0 - '@babel/highlight@7.24.2': dependencies: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.1.1 + picocolors: 1.0.0 '@babel/parser@7.24.5': dependencies: '@babel/types': 7.26.0 + '@babel/parser@7.24.6': + dependencies: + '@babel/types': 7.24.6 + '@babel/parser@7.26.2': dependencies: '@babel/types': 7.26.0 - '@babel/parser@7.27.0': - dependencies: - '@babel/types': 7.27.0 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -10385,14 +10405,14 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.10)': + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.26.10 + '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.10)': + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.26.10 + '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.26.0)': @@ -10599,12 +10619,6 @@ snapshots: '@babel/parser': 7.26.2 '@babel/types': 7.26.0 - '@babel/template@7.27.0': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 - '@babel/traverse@7.25.9': dependencies: '@babel/code-frame': 7.26.2 @@ -10617,18 +10631,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/traverse@7.27.0': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.27.0 - '@babel/parser': 7.27.0 - '@babel/template': 7.27.0 - '@babel/types': 7.27.0 - debug: 4.3.7 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - '@babel/types@7.23.6': dependencies: '@babel/helper-string-parser': 7.23.4 @@ -10652,11 +10654,6 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/types@7.27.0': - dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - '@bcoe/v8-coverage@0.2.3': {} '@braintree/sanitize-url@7.1.0': {} @@ -10745,144 +10742,219 @@ snapshots: '@esbuild/aix-ppc64@0.19.11': optional: true + '@esbuild/aix-ppc64@0.24.2': + optional: true + '@esbuild/aix-ppc64@0.25.0': optional: true '@esbuild/android-arm64@0.19.11': optional: true + '@esbuild/android-arm64@0.24.2': + optional: true + '@esbuild/android-arm64@0.25.0': optional: true '@esbuild/android-arm@0.19.11': optional: true + '@esbuild/android-arm@0.24.2': + optional: true + '@esbuild/android-arm@0.25.0': optional: true '@esbuild/android-x64@0.19.11': optional: true + '@esbuild/android-x64@0.24.2': + optional: true + '@esbuild/android-x64@0.25.0': optional: true '@esbuild/darwin-arm64@0.19.11': optional: true + '@esbuild/darwin-arm64@0.24.2': + optional: true + '@esbuild/darwin-arm64@0.25.0': optional: true '@esbuild/darwin-x64@0.19.11': optional: true + '@esbuild/darwin-x64@0.24.2': + optional: true + '@esbuild/darwin-x64@0.25.0': optional: true '@esbuild/freebsd-arm64@0.19.11': optional: true + '@esbuild/freebsd-arm64@0.24.2': + optional: true + '@esbuild/freebsd-arm64@0.25.0': optional: true '@esbuild/freebsd-x64@0.19.11': optional: true + '@esbuild/freebsd-x64@0.24.2': + optional: true + '@esbuild/freebsd-x64@0.25.0': optional: true '@esbuild/linux-arm64@0.19.11': optional: true + '@esbuild/linux-arm64@0.24.2': + optional: true + '@esbuild/linux-arm64@0.25.0': optional: true '@esbuild/linux-arm@0.19.11': optional: true + '@esbuild/linux-arm@0.24.2': + optional: true + '@esbuild/linux-arm@0.25.0': optional: true '@esbuild/linux-ia32@0.19.11': optional: true + '@esbuild/linux-ia32@0.24.2': + optional: true + '@esbuild/linux-ia32@0.25.0': optional: true '@esbuild/linux-loong64@0.19.11': optional: true + '@esbuild/linux-loong64@0.24.2': + optional: true + '@esbuild/linux-loong64@0.25.0': optional: true '@esbuild/linux-mips64el@0.19.11': optional: true + '@esbuild/linux-mips64el@0.24.2': + optional: true + '@esbuild/linux-mips64el@0.25.0': optional: true '@esbuild/linux-ppc64@0.19.11': optional: true + '@esbuild/linux-ppc64@0.24.2': + optional: true + '@esbuild/linux-ppc64@0.25.0': optional: true '@esbuild/linux-riscv64@0.19.11': optional: true + '@esbuild/linux-riscv64@0.24.2': + optional: true + '@esbuild/linux-riscv64@0.25.0': optional: true '@esbuild/linux-s390x@0.19.11': optional: true + '@esbuild/linux-s390x@0.24.2': + optional: true + '@esbuild/linux-s390x@0.25.0': optional: true '@esbuild/linux-x64@0.19.11': optional: true + '@esbuild/linux-x64@0.24.2': + optional: true + '@esbuild/linux-x64@0.25.0': optional: true + '@esbuild/netbsd-arm64@0.24.2': + optional: true + '@esbuild/netbsd-arm64@0.25.0': optional: true '@esbuild/netbsd-x64@0.19.11': optional: true + '@esbuild/netbsd-x64@0.24.2': + optional: true + '@esbuild/netbsd-x64@0.25.0': optional: true + '@esbuild/openbsd-arm64@0.24.2': + optional: true + '@esbuild/openbsd-arm64@0.25.0': optional: true '@esbuild/openbsd-x64@0.19.11': optional: true + '@esbuild/openbsd-x64@0.24.2': + optional: true + '@esbuild/openbsd-x64@0.25.0': optional: true '@esbuild/sunos-x64@0.19.11': optional: true + '@esbuild/sunos-x64@0.24.2': + optional: true + '@esbuild/sunos-x64@0.25.0': optional: true '@esbuild/win32-arm64@0.19.11': optional: true + '@esbuild/win32-arm64@0.24.2': + optional: true + '@esbuild/win32-arm64@0.25.0': optional: true '@esbuild/win32-ia32@0.19.11': optional: true + '@esbuild/win32-ia32@0.24.2': + optional: true + '@esbuild/win32-ia32@0.25.0': optional: true '@esbuild/win32-x64@0.19.11': optional: true + '@esbuild/win32-x64@0.24.2': + optional: true + '@esbuild/win32-x64@0.25.0': optional: true @@ -11847,7 +11919,7 @@ snapshots: tslib: 2.8.0 yargs-parser: 21.1.1 - '@nx/js@20.4.5(@babel/traverse@7.27.0)(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(nx@20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)))(typescript@5.7.3)': + '@nx/js@20.4.5(@babel/traverse@7.25.9)(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(nx@20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)))(typescript@5.7.3)': dependencies: '@babel/core': 7.26.0 '@babel/plugin-proposal-decorators': 7.23.7(@babel/core@7.26.0) @@ -11861,7 +11933,7 @@ snapshots: '@zkochan/js-yaml': 0.0.7 babel-plugin-const-enum: 1.2.0(@babel/core@7.26.0) babel-plugin-macros: 3.1.0 - babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.26.0)(@babel/traverse@7.27.0) + babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.26.0)(@babel/traverse@7.25.9) chalk: 4.1.2 columnify: 1.6.0 detect-port: 1.5.1 @@ -11938,10 +12010,6 @@ snapshots: '@opentelemetry/api@1.9.0': optional: true - '@oxc-project/runtime@0.65.0': {} - - '@oxc-project/types@0.65.0': {} - '@pkgjs/parseargs@0.11.0': optional: true @@ -12067,42 +12135,61 @@ snapshots: '@remirror/core-constants@3.0.0': {} - '@rolldown/binding-darwin-arm64@1.0.0-beta.7-commit.30e0395': + '@rollup/rollup-android-arm-eabi@4.34.7': optional: true - '@rolldown/binding-darwin-x64@1.0.0-beta.7-commit.30e0395': + '@rollup/rollup-android-arm64@4.34.7': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-beta.7-commit.30e0395': + '@rollup/rollup-darwin-arm64@4.34.7': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.7-commit.30e0395': + '@rollup/rollup-darwin-x64@4.34.7': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.7-commit.30e0395': + '@rollup/rollup-freebsd-arm64@4.34.7': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.7-commit.30e0395': + '@rollup/rollup-freebsd-x64@4.34.7': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.7-commit.30e0395': + '@rollup/rollup-linux-arm-gnueabihf@4.34.7': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-beta.7-commit.30e0395': + '@rollup/rollup-linux-arm-musleabihf@4.34.7': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-beta.7-commit.30e0395': - dependencies: - '@napi-rs/wasm-runtime': 0.2.4 + '@rollup/rollup-linux-arm64-gnu@4.34.7': optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.7-commit.30e0395': + '@rollup/rollup-linux-arm64-musl@4.34.7': optional: true - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.7-commit.30e0395': + '@rollup/rollup-linux-loongarch64-gnu@4.34.7': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.7-commit.30e0395': + '@rollup/rollup-linux-powerpc64le-gnu@4.34.7': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.34.7': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.34.7': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.34.7': + optional: true + + '@rollup/rollup-linux-x64-musl@4.34.7': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.34.7': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.34.7': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.34.7': optional: true '@selderee/plugin-htmlparser2@0.11.0': @@ -12839,20 +12926,20 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.26.2 - '@babel/types': 7.26.0 + '@babel/parser': 7.24.6 + '@babel/types': 7.24.6 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.5 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.26.0 + '@babel/types': 7.24.6 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.26.2 - '@babel/types': 7.26.0 + '@babel/parser': 7.24.6 + '@babel/types': 7.24.6 '@types/babel__traverse@7.20.5': dependencies: @@ -13398,18 +13485,14 @@ snapshots: dependencies: '@ucast/core': 1.10.2 - '@valibot/to-json-schema@1.0.0(valibot@1.0.0(typescript@5.7.2))': + '@vitejs/plugin-react@4.3.4(vite@6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))': dependencies: - valibot: 1.0.0(typescript@5.7.2) - - '@vitejs/plugin-react@4.4.1(rolldown-vite@6.3.3(@types/node@22.10.0)(esbuild@0.25.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(typescript@5.7.2)(yaml@2.7.0))': - dependencies: - '@babel/core': 7.26.10 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) + '@babel/core': 7.26.0 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) '@types/babel__core': 7.20.5 - react-refresh: 0.17.0 - vite: rolldown-vite@6.3.3(@types/node@22.10.0)(esbuild@0.25.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(typescript@5.7.2)(yaml@2.7.0) + react-refresh: 0.14.2 + vite: 6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - supports-color @@ -13617,8 +13700,6 @@ snapshots: ansis@3.15.0: {} - ansis@3.17.0: {} - anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -13716,7 +13797,15 @@ snapshots: '@fastify/error': 4.0.0 fastq: 1.17.1 - axios@1.8.4: + axios@1.7.8: + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + axios@1.7.9: dependencies: follow-redirects: 1.15.6 form-data: 4.0.0 @@ -13807,12 +13896,12 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.26.0)(@babel/traverse@7.27.0): + babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.26.0)(@babel/traverse@7.25.9): dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 optionalDependencies: - '@babel/traverse': 7.27.0 + '@babel/traverse': 7.25.9 babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.5): dependencies: @@ -14821,6 +14910,34 @@ snapshots: '@esbuild/win32-ia32': 0.19.11 '@esbuild/win32-x64': 0.19.11 + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + esbuild@0.25.0: optionalDependencies: '@esbuild/aix-ppc64': 0.25.0 @@ -16315,51 +16432,6 @@ snapshots: process-warning: 4.0.0 set-cookie-parser: 2.6.0 - lightningcss-darwin-arm64@1.29.3: - optional: true - - lightningcss-darwin-x64@1.29.3: - optional: true - - lightningcss-freebsd-x64@1.29.3: - optional: true - - lightningcss-linux-arm-gnueabihf@1.29.3: - optional: true - - lightningcss-linux-arm64-gnu@1.29.3: - optional: true - - lightningcss-linux-arm64-musl@1.29.3: - optional: true - - lightningcss-linux-x64-gnu@1.29.3: - optional: true - - lightningcss-linux-x64-musl@1.29.3: - optional: true - - lightningcss-win32-arm64-msvc@1.29.3: - optional: true - - lightningcss-win32-x64-msvc@1.29.3: - optional: true - - lightningcss@1.29.3: - dependencies: - detect-libc: 2.0.3 - optionalDependencies: - lightningcss-darwin-arm64: 1.29.3 - lightningcss-darwin-x64: 1.29.3 - lightningcss-freebsd-x64: 1.29.3 - lightningcss-linux-arm-gnueabihf: 1.29.3 - lightningcss-linux-arm64-gnu: 1.29.3 - lightningcss-linux-arm64-musl: 1.29.3 - lightningcss-linux-x64-gnu: 1.29.3 - lightningcss-linux-x64-musl: 1.29.3 - lightningcss-win32-arm64-msvc: 1.29.3 - lightningcss-win32-x64-msvc: 1.29.3 - lines-and-columns@1.2.4: {} lines-and-columns@2.0.3: {} @@ -16742,7 +16814,7 @@ snapshots: '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.2 '@zkochan/js-yaml': 0.0.7 - axios: 1.8.4 + axios: 1.7.9 chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 @@ -17047,6 +17119,8 @@ snapshots: dependencies: split2: 4.2.0 + picocolors@1.0.0: {} + picocolors@1.0.1: {} picocolors@1.1.1: {} @@ -17147,7 +17221,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.3: + postcss@8.5.2: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -17177,7 +17251,7 @@ snapshots: postmark@4.0.5: dependencies: - axios: 1.8.4 + axios: 1.7.8 transitivePeerDependencies: - debug @@ -17454,7 +17528,7 @@ snapshots: dependencies: fast-deep-equal: 2.0.1 - react-refresh@0.17.0: {} + react-refresh@0.14.2: {} react-remove-scroll-bar@2.3.8(@types/react@18.3.12)(react@18.3.1): dependencies: @@ -17675,50 +17749,30 @@ snapshots: robust-predicates@3.0.2: {} - rolldown-vite@6.3.3(@types/node@22.10.0)(esbuild@0.25.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(typescript@5.7.2)(yaml@2.7.0): + rollup@4.34.7: dependencies: - '@oxc-project/runtime': 0.65.0 - fdir: 6.4.3(picomatch@4.0.2) - lightningcss: 1.29.3 - picomatch: 4.0.2 - postcss: 8.5.3 - rolldown: 1.0.0-beta.7-commit.30e0395(@oxc-project/runtime@0.65.0)(typescript@5.7.2) - tinyglobby: 0.2.12 + '@types/estree': 1.0.6 optionalDependencies: - '@types/node': 22.10.0 - esbuild: 0.25.0 + '@rollup/rollup-android-arm-eabi': 4.34.7 + '@rollup/rollup-android-arm64': 4.34.7 + '@rollup/rollup-darwin-arm64': 4.34.7 + '@rollup/rollup-darwin-x64': 4.34.7 + '@rollup/rollup-freebsd-arm64': 4.34.7 + '@rollup/rollup-freebsd-x64': 4.34.7 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.7 + '@rollup/rollup-linux-arm-musleabihf': 4.34.7 + '@rollup/rollup-linux-arm64-gnu': 4.34.7 + '@rollup/rollup-linux-arm64-musl': 4.34.7 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.7 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.7 + '@rollup/rollup-linux-riscv64-gnu': 4.34.7 + '@rollup/rollup-linux-s390x-gnu': 4.34.7 + '@rollup/rollup-linux-x64-gnu': 4.34.7 + '@rollup/rollup-linux-x64-musl': 4.34.7 + '@rollup/rollup-win32-arm64-msvc': 4.34.7 + '@rollup/rollup-win32-ia32-msvc': 4.34.7 + '@rollup/rollup-win32-x64-msvc': 4.34.7 fsevents: 2.3.3 - jiti: 1.21.0 - less: 4.2.0 - sugarss: 4.0.1(postcss@8.4.49) - terser: 5.39.0 - tsx: 4.19.3 - yaml: 2.7.0 - transitivePeerDependencies: - - typescript - - rolldown@1.0.0-beta.7-commit.30e0395(@oxc-project/runtime@0.65.0)(typescript@5.7.2): - dependencies: - '@oxc-project/types': 0.65.0 - '@valibot/to-json-schema': 1.0.0(valibot@1.0.0(typescript@5.7.2)) - ansis: 3.17.0 - valibot: 1.0.0(typescript@5.7.2) - optionalDependencies: - '@oxc-project/runtime': 0.65.0 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.7-commit.30e0395 - '@rolldown/binding-darwin-x64': 1.0.0-beta.7-commit.30e0395 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.7-commit.30e0395 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.7-commit.30e0395 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.7-commit.30e0395 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.7-commit.30e0395 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.7-commit.30e0395 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.7-commit.30e0395 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.7-commit.30e0395 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.7-commit.30e0395 - '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.7-commit.30e0395 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.7-commit.30e0395 - transitivePeerDependencies: - - typescript rope-sequence@1.3.4: {} @@ -18484,10 +18538,6 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 - valibot@1.0.0(typescript@5.7.2): - optionalDependencies: - typescript: 5.7.2 - validate-npm-package-name@5.0.0: dependencies: builtins: 5.0.1 @@ -18496,6 +18546,21 @@ snapshots: vary@1.1.2: {} + vite@6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): + dependencies: + esbuild: 0.24.2 + postcss: 8.5.2 + rollup: 4.34.7 + optionalDependencies: + '@types/node': 22.10.0 + fsevents: 2.3.3 + jiti: 1.21.0 + less: 4.2.0 + sugarss: 4.0.1(postcss@8.4.49) + terser: 5.39.0 + tsx: 4.19.3 + yaml: 2.7.0 + void-elements@3.1.0: {} vscode-jsonrpc@8.2.0: {} From 3e8824435d2bd8af54b3594beeec6277cf86ff7b Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 22 Apr 2025 20:28:27 +0100 Subject: [PATCH 26/63] update vite and axios --- apps/client/package.json | 6 +- pnpm-lock.yaml | 659 +++++++++++++++------------------------ 2 files changed, 260 insertions(+), 405 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index 5901a920..ffd94cae 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -25,7 +25,7 @@ "@tabler/icons-react": "^3.22.0", "@tanstack/react-query": "^5.61.4", "@tiptap/extension-character-count": "^2.11.5", - "axios": "^1.7.9", + "axios": "^1.8.4", "clsx": "^2.1.1", "emoji-mart": "^5.6.0", "file-saver": "^2.0.5", @@ -63,7 +63,7 @@ "@types/node": "22.10.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.3.4", + "@vitejs/plugin-react": "^4.4.1", "eslint": "^9.15.0", "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.1.0", @@ -76,6 +76,6 @@ "prettier": "^3.4.1", "typescript": "^5.7.2", "typescript-eslint": "^8.17.0", - "vite": "^6.1.0" + "vite": "^6.3.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9cc1623..8772d574 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -181,7 +181,7 @@ importers: devDependencies: '@nx/js': specifier: 20.4.5 - version: 20.4.5(@babel/traverse@7.25.9)(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(nx@20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)))(typescript@5.7.3) + version: 20.4.5(@babel/traverse@7.27.0)(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(nx@20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)))(typescript@5.7.3) '@types/bytes': specifier: ^3.1.5 version: 3.1.5 @@ -246,8 +246,8 @@ importers: specifier: ^2.11.5 version: 2.11.5(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))(@tiptap/pm@2.10.3) axios: - specifier: ^1.7.9 - version: 1.7.9 + specifier: ^1.8.4 + version: 1.8.4 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -355,8 +355,8 @@ importers: specifier: ^18.3.1 version: 18.3.1 '@vitejs/plugin-react': - specifier: ^4.3.4 - version: 4.3.4(vite@6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.4.1 + version: 4.4.1(vite@6.3.2(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) eslint: specifier: ^9.15.0 version: 9.15.0(jiti@1.21.0) @@ -394,8 +394,8 @@ importers: specifier: ^8.17.0 version: 8.17.0(eslint@9.15.0(jiti@1.21.0))(typescript@5.7.2) vite: - specifier: ^6.1.0 - version: 6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^6.3.2 + version: 6.3.2(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) apps/server: dependencies: @@ -876,6 +876,10 @@ packages: resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.26.8': + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + engines: {node: '>=6.9.0'} + '@babel/core@7.24.5': resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==} engines: {node: '>=6.9.0'} @@ -888,6 +892,10 @@ packages: resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} engines: {node: '>=6.9.0'} + '@babel/core@7.26.10': + resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.24.1': resolution: {integrity: sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==} engines: {node: '>=6.9.0'} @@ -900,6 +908,10 @@ packages: resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.27.0': + resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.22.5': resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} engines: {node: '>=6.9.0'} @@ -912,6 +924,10 @@ packages: resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.27.0': + resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} + engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.23.7': resolution: {integrity: sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g==} engines: {node: '>=6.9.0'} @@ -1067,6 +1083,10 @@ packages: resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.27.0': + resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} + engines: {node: '>=6.9.0'} + '@babel/highlight@7.24.2': resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} engines: {node: '>=6.9.0'} @@ -1076,13 +1096,13 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.24.6': - resolution: {integrity: sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==} + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.26.2': - resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + '@babel/parser@7.27.0': + resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} engines: {node: '>=6.0.0'} hasBin: true @@ -1583,10 +1603,18 @@ packages: resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} + '@babel/template@7.27.0': + resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.25.9': resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.27.0': + resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} + engines: {node: '>=6.9.0'} + '@babel/types@7.23.6': resolution: {integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==} engines: {node: '>=6.9.0'} @@ -1603,6 +1631,10 @@ packages: resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} engines: {node: '>=6.9.0'} + '@babel/types@7.27.0': + resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -1701,12 +1733,6 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.24.2': - resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.25.0': resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} engines: {node: '>=18'} @@ -1719,12 +1745,6 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.24.2': - resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.25.0': resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} engines: {node: '>=18'} @@ -1737,12 +1757,6 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.24.2': - resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.25.0': resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} engines: {node: '>=18'} @@ -1755,12 +1769,6 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.24.2': - resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.25.0': resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} engines: {node: '>=18'} @@ -1773,12 +1781,6 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.24.2': - resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.25.0': resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} engines: {node: '>=18'} @@ -1791,12 +1793,6 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.24.2': - resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.25.0': resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} engines: {node: '>=18'} @@ -1809,12 +1805,6 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.24.2': - resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.25.0': resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} engines: {node: '>=18'} @@ -1827,12 +1817,6 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.24.2': - resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.25.0': resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} engines: {node: '>=18'} @@ -1845,12 +1829,6 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.24.2': - resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.25.0': resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} engines: {node: '>=18'} @@ -1863,12 +1841,6 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.24.2': - resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.25.0': resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} engines: {node: '>=18'} @@ -1881,12 +1853,6 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.24.2': - resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.25.0': resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} engines: {node: '>=18'} @@ -1899,12 +1865,6 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.24.2': - resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.25.0': resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} engines: {node: '>=18'} @@ -1917,12 +1877,6 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.24.2': - resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.25.0': resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} engines: {node: '>=18'} @@ -1935,12 +1889,6 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.24.2': - resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.25.0': resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} engines: {node: '>=18'} @@ -1953,12 +1901,6 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.24.2': - resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.25.0': resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} engines: {node: '>=18'} @@ -1971,12 +1913,6 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.24.2': - resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.25.0': resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} engines: {node: '>=18'} @@ -1989,24 +1925,12 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.24.2': - resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.25.0': resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.24.2': - resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.25.0': resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} engines: {node: '>=18'} @@ -2019,24 +1943,12 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.24.2': - resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.25.0': resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.24.2': - resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.25.0': resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} engines: {node: '>=18'} @@ -2049,12 +1961,6 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.24.2': - resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.25.0': resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} engines: {node: '>=18'} @@ -2067,12 +1973,6 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.24.2': - resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.25.0': resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} engines: {node: '>=18'} @@ -2085,12 +1985,6 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.24.2': - resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.25.0': resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} engines: {node: '>=18'} @@ -2103,12 +1997,6 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.24.2': - resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.25.0': resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} engines: {node: '>=18'} @@ -2121,12 +2009,6 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.24.2': - resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.25.0': resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} engines: {node: '>=18'} @@ -3164,98 +3046,103 @@ packages: '@remirror/core-constants@3.0.0': resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} - '@rollup/rollup-android-arm-eabi@4.34.7': - resolution: {integrity: sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw==} + '@rollup/rollup-android-arm-eabi@4.40.0': + resolution: {integrity: sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.34.7': - resolution: {integrity: sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg==} + '@rollup/rollup-android-arm64@4.40.0': + resolution: {integrity: sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.34.7': - resolution: {integrity: sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA==} + '@rollup/rollup-darwin-arm64@4.40.0': + resolution: {integrity: sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.34.7': - resolution: {integrity: sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA==} + '@rollup/rollup-darwin-x64@4.40.0': + resolution: {integrity: sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.34.7': - resolution: {integrity: sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA==} + '@rollup/rollup-freebsd-arm64@4.40.0': + resolution: {integrity: sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.34.7': - resolution: {integrity: sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q==} + '@rollup/rollup-freebsd-x64@4.40.0': + resolution: {integrity: sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.34.7': - resolution: {integrity: sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.40.0': + resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.34.7': - resolution: {integrity: sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw==} + '@rollup/rollup-linux-arm-musleabihf@4.40.0': + resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.34.7': - resolution: {integrity: sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA==} + '@rollup/rollup-linux-arm64-gnu@4.40.0': + resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.34.7': - resolution: {integrity: sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw==} + '@rollup/rollup-linux-arm64-musl@4.40.0': + resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.34.7': - resolution: {integrity: sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw==} + '@rollup/rollup-linux-loongarch64-gnu@4.40.0': + resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.34.7': - resolution: {integrity: sha512-vrDk9JDa/BFkxcS2PbWpr0C/LiiSLxFbNOBgfbW6P8TBe9PPHx9Wqbvx2xgNi1TOAyQHQJ7RZFqBiEohm79r0w==} + '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': + resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.34.7': - resolution: {integrity: sha512-rB+ejFyjtmSo+g/a4eovDD1lHWHVqizN8P0Hm0RElkINpS0XOdpaXloqM4FBkF9ZWEzg6bezymbpLmeMldfLTw==} + '@rollup/rollup-linux-riscv64-gnu@4.40.0': + resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.34.7': - resolution: {integrity: sha512-nNXNjo4As6dNqRn7OrsnHzwTgtypfRA3u3AKr0B3sOOo+HkedIbn8ZtFnB+4XyKJojIfqDKmbIzO1QydQ8c+Pw==} + '@rollup/rollup-linux-riscv64-musl@4.40.0': + resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.40.0': + resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.34.7': - resolution: {integrity: sha512-9kPVf9ahnpOMSGlCxXGv980wXD0zRR3wyk8+33/MXQIpQEOpaNe7dEHm5LMfyRZRNt9lMEQuH0jUKj15MkM7QA==} + '@rollup/rollup-linux-x64-gnu@4.40.0': + resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.34.7': - resolution: {integrity: sha512-7wJPXRWTTPtTFDFezA8sle/1sdgxDjuMoRXEKtx97ViRxGGkVQYovem+Q8Pr/2HxiHp74SSRG+o6R0Yq0shPwQ==} + '@rollup/rollup-linux-x64-musl@4.40.0': + resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.34.7': - resolution: {integrity: sha512-MN7aaBC7mAjsiMEZcsJvwNsQVNZShgES/9SzWp1HC9Yjqb5OpexYnRjF7RmE4itbeesHMYYQiAtUAQaSKs2Rfw==} + '@rollup/rollup-win32-arm64-msvc@4.40.0': + resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.34.7': - resolution: {integrity: sha512-aeawEKYswsFu1LhDM9RIgToobquzdtSc4jSVqHV8uApz4FVvhFl/mKh92wc8WpFc6aYCothV/03UjY6y7yLgbg==} + '@rollup/rollup-win32-ia32-msvc@4.40.0': + resolution: {integrity: sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.34.7': - resolution: {integrity: sha512-4ZedScpxxIrVO7otcZ8kCX1mZArtH2Wfj3uFCxRJ9NO80gg1XV0U/b2f/MKaGwj2X3QopHfoWiDQ917FRpwY3w==} + '@rollup/rollup-win32-x64-msvc@4.40.0': + resolution: {integrity: sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==} cpu: [x64] os: [win32] @@ -4019,6 +3906,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/express-serve-static-core@4.17.43': resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==} @@ -4314,8 +4204,8 @@ packages: '@ucast/mongo@2.4.3': resolution: {integrity: sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==} - '@vitejs/plugin-react@4.3.4': - resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + '@vitejs/plugin-react@4.4.1': + resolution: {integrity: sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 @@ -4588,11 +4478,8 @@ packages: avvio@9.1.0: resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} - axios@1.7.8: - resolution: {integrity: sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==} - - axios@1.7.9: - resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + axios@1.8.4: + resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} @@ -5492,11 +5379,6 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.24.2: - resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.25.0: resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} engines: {node: '>=18'} @@ -7429,8 +7311,8 @@ packages: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.2: - resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==} + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -7698,8 +7580,8 @@ packages: react-promise-suspense@0.3.4: resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==} - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} react-remove-scroll-bar@2.3.8: @@ -7918,8 +7800,8 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rollup@4.34.7: - resolution: {integrity: sha512-8qhyN0oZ4x0H6wmBgfKxJtxM7qS98YJ0k0kNh5ECVtuchIJ7z9IVVvzpmtQyT10PXKMtBxYr1wQ5Apg8RS8kXQ==} + rollup@4.40.0: + resolution: {integrity: sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -8670,8 +8552,8 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite@6.1.0: - resolution: {integrity: sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==} + vite@6.3.2: + resolution: {integrity: sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -9598,6 +9480,8 @@ snapshots: '@babel/compat-data@7.26.2': {} + '@babel/compat-data@7.26.8': {} + '@babel/core@7.24.5': dependencies: '@ampproject/remapping': 2.3.0 @@ -9658,6 +9542,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.26.10': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helpers': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + convert-source-map: 2.0.0 + debug: 4.3.7 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/generator@7.24.1': dependencies: '@babel/types': 7.24.0 @@ -9680,6 +9584,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.0.2 + '@babel/generator@7.27.0': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + '@babel/helper-annotate-as-pure@7.22.5': dependencies: '@babel/types': 7.26.0 @@ -9696,6 +9608,14 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-compilation-targets@7.27.0': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.2 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-create-class-features-plugin@7.23.7(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -9795,6 +9715,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + '@babel/helper-optimise-call-expression@7.22.5': dependencies: '@babel/types': 7.26.0 @@ -9869,6 +9798,11 @@ snapshots: '@babel/template': 7.25.9 '@babel/types': 7.26.0 + '@babel/helpers@7.27.0': + dependencies: + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + '@babel/highlight@7.24.2': dependencies: '@babel/helper-validator-identifier': 7.22.20 @@ -9880,14 +9814,14 @@ snapshots: dependencies: '@babel/types': 7.26.0 - '@babel/parser@7.24.6': - dependencies: - '@babel/types': 7.24.6 - '@babel/parser@7.26.2': dependencies: '@babel/types': 7.26.0 + '@babel/parser@7.27.0': + dependencies: + '@babel/types': 7.27.0 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -10405,14 +10339,14 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.26.0)': @@ -10619,6 +10553,12 @@ snapshots: '@babel/parser': 7.26.2 '@babel/types': 7.26.0 + '@babel/template@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@babel/traverse@7.25.9': dependencies: '@babel/code-frame': 7.26.2 @@ -10631,6 +10571,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + debug: 4.3.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + '@babel/types@7.23.6': dependencies: '@babel/helper-string-parser': 7.23.4 @@ -10654,6 +10606,11 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@babel/types@7.27.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@bcoe/v8-coverage@0.2.3': {} '@braintree/sanitize-url@7.1.0': {} @@ -10742,219 +10699,144 @@ snapshots: '@esbuild/aix-ppc64@0.19.11': optional: true - '@esbuild/aix-ppc64@0.24.2': - optional: true - '@esbuild/aix-ppc64@0.25.0': optional: true '@esbuild/android-arm64@0.19.11': optional: true - '@esbuild/android-arm64@0.24.2': - optional: true - '@esbuild/android-arm64@0.25.0': optional: true '@esbuild/android-arm@0.19.11': optional: true - '@esbuild/android-arm@0.24.2': - optional: true - '@esbuild/android-arm@0.25.0': optional: true '@esbuild/android-x64@0.19.11': optional: true - '@esbuild/android-x64@0.24.2': - optional: true - '@esbuild/android-x64@0.25.0': optional: true '@esbuild/darwin-arm64@0.19.11': optional: true - '@esbuild/darwin-arm64@0.24.2': - optional: true - '@esbuild/darwin-arm64@0.25.0': optional: true '@esbuild/darwin-x64@0.19.11': optional: true - '@esbuild/darwin-x64@0.24.2': - optional: true - '@esbuild/darwin-x64@0.25.0': optional: true '@esbuild/freebsd-arm64@0.19.11': optional: true - '@esbuild/freebsd-arm64@0.24.2': - optional: true - '@esbuild/freebsd-arm64@0.25.0': optional: true '@esbuild/freebsd-x64@0.19.11': optional: true - '@esbuild/freebsd-x64@0.24.2': - optional: true - '@esbuild/freebsd-x64@0.25.0': optional: true '@esbuild/linux-arm64@0.19.11': optional: true - '@esbuild/linux-arm64@0.24.2': - optional: true - '@esbuild/linux-arm64@0.25.0': optional: true '@esbuild/linux-arm@0.19.11': optional: true - '@esbuild/linux-arm@0.24.2': - optional: true - '@esbuild/linux-arm@0.25.0': optional: true '@esbuild/linux-ia32@0.19.11': optional: true - '@esbuild/linux-ia32@0.24.2': - optional: true - '@esbuild/linux-ia32@0.25.0': optional: true '@esbuild/linux-loong64@0.19.11': optional: true - '@esbuild/linux-loong64@0.24.2': - optional: true - '@esbuild/linux-loong64@0.25.0': optional: true '@esbuild/linux-mips64el@0.19.11': optional: true - '@esbuild/linux-mips64el@0.24.2': - optional: true - '@esbuild/linux-mips64el@0.25.0': optional: true '@esbuild/linux-ppc64@0.19.11': optional: true - '@esbuild/linux-ppc64@0.24.2': - optional: true - '@esbuild/linux-ppc64@0.25.0': optional: true '@esbuild/linux-riscv64@0.19.11': optional: true - '@esbuild/linux-riscv64@0.24.2': - optional: true - '@esbuild/linux-riscv64@0.25.0': optional: true '@esbuild/linux-s390x@0.19.11': optional: true - '@esbuild/linux-s390x@0.24.2': - optional: true - '@esbuild/linux-s390x@0.25.0': optional: true '@esbuild/linux-x64@0.19.11': optional: true - '@esbuild/linux-x64@0.24.2': - optional: true - '@esbuild/linux-x64@0.25.0': optional: true - '@esbuild/netbsd-arm64@0.24.2': - optional: true - '@esbuild/netbsd-arm64@0.25.0': optional: true '@esbuild/netbsd-x64@0.19.11': optional: true - '@esbuild/netbsd-x64@0.24.2': - optional: true - '@esbuild/netbsd-x64@0.25.0': optional: true - '@esbuild/openbsd-arm64@0.24.2': - optional: true - '@esbuild/openbsd-arm64@0.25.0': optional: true '@esbuild/openbsd-x64@0.19.11': optional: true - '@esbuild/openbsd-x64@0.24.2': - optional: true - '@esbuild/openbsd-x64@0.25.0': optional: true '@esbuild/sunos-x64@0.19.11': optional: true - '@esbuild/sunos-x64@0.24.2': - optional: true - '@esbuild/sunos-x64@0.25.0': optional: true '@esbuild/win32-arm64@0.19.11': optional: true - '@esbuild/win32-arm64@0.24.2': - optional: true - '@esbuild/win32-arm64@0.25.0': optional: true '@esbuild/win32-ia32@0.19.11': optional: true - '@esbuild/win32-ia32@0.24.2': - optional: true - '@esbuild/win32-ia32@0.25.0': optional: true '@esbuild/win32-x64@0.19.11': optional: true - '@esbuild/win32-x64@0.24.2': - optional: true - '@esbuild/win32-x64@0.25.0': optional: true @@ -11919,7 +11801,7 @@ snapshots: tslib: 2.8.0 yargs-parser: 21.1.1 - '@nx/js@20.4.5(@babel/traverse@7.25.9)(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(nx@20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)))(typescript@5.7.3)': + '@nx/js@20.4.5(@babel/traverse@7.27.0)(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(nx@20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)))(typescript@5.7.3)': dependencies: '@babel/core': 7.26.0 '@babel/plugin-proposal-decorators': 7.23.7(@babel/core@7.26.0) @@ -11933,7 +11815,7 @@ snapshots: '@zkochan/js-yaml': 0.0.7 babel-plugin-const-enum: 1.2.0(@babel/core@7.26.0) babel-plugin-macros: 3.1.0 - babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.26.0)(@babel/traverse@7.25.9) + babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.26.0)(@babel/traverse@7.27.0) chalk: 4.1.2 columnify: 1.6.0 detect-port: 1.5.1 @@ -12135,61 +12017,64 @@ snapshots: '@remirror/core-constants@3.0.0': {} - '@rollup/rollup-android-arm-eabi@4.34.7': + '@rollup/rollup-android-arm-eabi@4.40.0': optional: true - '@rollup/rollup-android-arm64@4.34.7': + '@rollup/rollup-android-arm64@4.40.0': optional: true - '@rollup/rollup-darwin-arm64@4.34.7': + '@rollup/rollup-darwin-arm64@4.40.0': optional: true - '@rollup/rollup-darwin-x64@4.34.7': + '@rollup/rollup-darwin-x64@4.40.0': optional: true - '@rollup/rollup-freebsd-arm64@4.34.7': + '@rollup/rollup-freebsd-arm64@4.40.0': optional: true - '@rollup/rollup-freebsd-x64@4.34.7': + '@rollup/rollup-freebsd-x64@4.40.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.34.7': + '@rollup/rollup-linux-arm-gnueabihf@4.40.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.34.7': + '@rollup/rollup-linux-arm-musleabihf@4.40.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.34.7': + '@rollup/rollup-linux-arm64-gnu@4.40.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.34.7': + '@rollup/rollup-linux-arm64-musl@4.40.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.34.7': + '@rollup/rollup-linux-loongarch64-gnu@4.40.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.34.7': + '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.34.7': + '@rollup/rollup-linux-riscv64-gnu@4.40.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.34.7': + '@rollup/rollup-linux-riscv64-musl@4.40.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.34.7': + '@rollup/rollup-linux-s390x-gnu@4.40.0': optional: true - '@rollup/rollup-linux-x64-musl@4.34.7': + '@rollup/rollup-linux-x64-gnu@4.40.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.34.7': + '@rollup/rollup-linux-x64-musl@4.40.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.34.7': + '@rollup/rollup-win32-arm64-msvc@4.40.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.34.7': + '@rollup/rollup-win32-ia32-msvc@4.40.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.40.0': optional: true '@selderee/plugin-htmlparser2@0.11.0': @@ -12926,20 +12811,20 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.24.6 - '@babel/types': 7.24.6 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.5 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.24.6 + '@babel/types': 7.26.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.24.6 - '@babel/types': 7.24.6 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 '@types/babel__traverse@7.20.5': dependencies: @@ -13105,6 +12990,8 @@ snapshots: '@types/estree@1.0.6': {} + '@types/estree@1.0.7': {} + '@types/express-serve-static-core@4.17.43': dependencies: '@types/node': 22.13.4 @@ -13485,14 +13372,14 @@ snapshots: dependencies: '@ucast/core': 1.10.2 - '@vitejs/plugin-react@4.3.4(vite@6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))': + '@vitejs/plugin-react@4.4.1(vite@6.3.2(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))': dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.26.10 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) + react-refresh: 0.17.0 + vite: 6.3.2(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - supports-color @@ -13797,15 +13684,7 @@ snapshots: '@fastify/error': 4.0.0 fastq: 1.17.1 - axios@1.7.8: - dependencies: - follow-redirects: 1.15.6 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - axios@1.7.9: + axios@1.8.4: dependencies: follow-redirects: 1.15.6 form-data: 4.0.0 @@ -13896,12 +13775,12 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.26.0)(@babel/traverse@7.25.9): + babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.26.0)(@babel/traverse@7.27.0): dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 optionalDependencies: - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.27.0 babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.5): dependencies: @@ -14910,34 +14789,6 @@ snapshots: '@esbuild/win32-ia32': 0.19.11 '@esbuild/win32-x64': 0.19.11 - esbuild@0.24.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.24.2 - '@esbuild/android-arm': 0.24.2 - '@esbuild/android-arm64': 0.24.2 - '@esbuild/android-x64': 0.24.2 - '@esbuild/darwin-arm64': 0.24.2 - '@esbuild/darwin-x64': 0.24.2 - '@esbuild/freebsd-arm64': 0.24.2 - '@esbuild/freebsd-x64': 0.24.2 - '@esbuild/linux-arm': 0.24.2 - '@esbuild/linux-arm64': 0.24.2 - '@esbuild/linux-ia32': 0.24.2 - '@esbuild/linux-loong64': 0.24.2 - '@esbuild/linux-mips64el': 0.24.2 - '@esbuild/linux-ppc64': 0.24.2 - '@esbuild/linux-riscv64': 0.24.2 - '@esbuild/linux-s390x': 0.24.2 - '@esbuild/linux-x64': 0.24.2 - '@esbuild/netbsd-arm64': 0.24.2 - '@esbuild/netbsd-x64': 0.24.2 - '@esbuild/openbsd-arm64': 0.24.2 - '@esbuild/openbsd-x64': 0.24.2 - '@esbuild/sunos-x64': 0.24.2 - '@esbuild/win32-arm64': 0.24.2 - '@esbuild/win32-ia32': 0.24.2 - '@esbuild/win32-x64': 0.24.2 - esbuild@0.25.0: optionalDependencies: '@esbuild/aix-ppc64': 0.25.0 @@ -16814,7 +16665,7 @@ snapshots: '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.2 '@zkochan/js-yaml': 0.0.7 - axios: 1.7.9 + axios: 1.8.4 chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 @@ -17221,7 +17072,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.2: + postcss@8.5.3: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -17251,7 +17102,7 @@ snapshots: postmark@4.0.5: dependencies: - axios: 1.7.8 + axios: 1.8.4 transitivePeerDependencies: - debug @@ -17528,7 +17379,7 @@ snapshots: dependencies: fast-deep-equal: 2.0.1 - react-refresh@0.14.2: {} + react-refresh@0.17.0: {} react-remove-scroll-bar@2.3.8(@types/react@18.3.12)(react@18.3.1): dependencies: @@ -17749,29 +17600,30 @@ snapshots: robust-predicates@3.0.2: {} - rollup@4.34.7: + rollup@4.40.0: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.34.7 - '@rollup/rollup-android-arm64': 4.34.7 - '@rollup/rollup-darwin-arm64': 4.34.7 - '@rollup/rollup-darwin-x64': 4.34.7 - '@rollup/rollup-freebsd-arm64': 4.34.7 - '@rollup/rollup-freebsd-x64': 4.34.7 - '@rollup/rollup-linux-arm-gnueabihf': 4.34.7 - '@rollup/rollup-linux-arm-musleabihf': 4.34.7 - '@rollup/rollup-linux-arm64-gnu': 4.34.7 - '@rollup/rollup-linux-arm64-musl': 4.34.7 - '@rollup/rollup-linux-loongarch64-gnu': 4.34.7 - '@rollup/rollup-linux-powerpc64le-gnu': 4.34.7 - '@rollup/rollup-linux-riscv64-gnu': 4.34.7 - '@rollup/rollup-linux-s390x-gnu': 4.34.7 - '@rollup/rollup-linux-x64-gnu': 4.34.7 - '@rollup/rollup-linux-x64-musl': 4.34.7 - '@rollup/rollup-win32-arm64-msvc': 4.34.7 - '@rollup/rollup-win32-ia32-msvc': 4.34.7 - '@rollup/rollup-win32-x64-msvc': 4.34.7 + '@rollup/rollup-android-arm-eabi': 4.40.0 + '@rollup/rollup-android-arm64': 4.40.0 + '@rollup/rollup-darwin-arm64': 4.40.0 + '@rollup/rollup-darwin-x64': 4.40.0 + '@rollup/rollup-freebsd-arm64': 4.40.0 + '@rollup/rollup-freebsd-x64': 4.40.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.40.0 + '@rollup/rollup-linux-arm-musleabihf': 4.40.0 + '@rollup/rollup-linux-arm64-gnu': 4.40.0 + '@rollup/rollup-linux-arm64-musl': 4.40.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.40.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.40.0 + '@rollup/rollup-linux-riscv64-gnu': 4.40.0 + '@rollup/rollup-linux-riscv64-musl': 4.40.0 + '@rollup/rollup-linux-s390x-gnu': 4.40.0 + '@rollup/rollup-linux-x64-gnu': 4.40.0 + '@rollup/rollup-linux-x64-musl': 4.40.0 + '@rollup/rollup-win32-arm64-msvc': 4.40.0 + '@rollup/rollup-win32-ia32-msvc': 4.40.0 + '@rollup/rollup-win32-x64-msvc': 4.40.0 fsevents: 2.3.3 rope-sequence@1.3.4: {} @@ -18546,11 +18398,14 @@ snapshots: vary@1.1.2: {} - vite@6.1.0(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): + vite@6.3.2(@types/node@22.10.0)(jiti@1.21.0)(less@4.2.0)(sugarss@4.0.1(postcss@8.4.49))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): dependencies: - esbuild: 0.24.2 - postcss: 8.5.2 - rollup: 4.34.7 + esbuild: 0.25.0 + fdir: 6.4.3(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.3 + rollup: 4.40.0 + tinyglobby: 0.2.12 optionalDependencies: '@types/node': 22.10.0 fsevents: 2.3.3 From 6c422011ac26c49978b5526cf8d3d644db9ca510 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Tue, 22 Apr 2025 20:37:32 +0100 Subject: [PATCH 27/63] feat: public page sharing (#1012) * Share - WIP * - public attachment links - WIP * WIP * WIP * Share - WIP * WIP * WIP * include userRole in space object * WIP * Server render shared page meta tags * disable user select * Close Navbar on outside click on mobile * update shared page spaceId * WIP * fix * close sidebar on click * close sidebar * defaults * update copy * Store share key in lowercase * refactor page breadcrumbs * Change copy * add link ref * open link button * add meta og:title * add twitter tags * WIP * make shares/info endpoint public * fix * * add /p/ segment to share urls * minore fixes * change mobile breadcrumb icon --- apps/client/index.html | 1 + .../public/locales/en-US/translation.json | 23 +- apps/client/src/App.tsx | 11 + .../layouts/global/global-app-shell.tsx | 14 +- .../components/settings/settings-queries.tsx | 12 +- .../components/settings/settings-sidebar.tsx | 15 + .../src/components/theme-toggle.module.css | 19 + apps/client/src/components/theme-toggle.tsx | 33 +- .../src/features/editor/atoms/editor-atoms.ts | 2 + .../editor/components/drawio/drawio-view.tsx | 2 +- .../components/excalidraw/excalidraw-view.tsx | 2 +- .../components/mention/mention-view.tsx | 21 +- .../table-of-contents.module.css | 5 + .../table-of-contents/table-of-contents.tsx | 26 +- .../features/editor/readonly-page-editor.tsx | 67 ++ .../breadcrumbs/breadcrumb.module.css | 1 + .../components/breadcrumbs/breadcrumb.tsx | 88 ++- .../components/header/page-header-menu.tsx | 3 + apps/client/src/features/page/page.utils.ts | 19 +- .../page/tree/components/space-tree.tsx | 31 +- .../features/page/tree/styles/tree.module.css | 6 +- .../src/features/page/tree/utils/utils.ts | 2 +- .../src/features/share/atoms/sidebar-atom.ts | 9 + .../share/components/share-action-menu.tsx | 106 +++ .../share/components/share-layout.tsx | 10 + .../features/share/components/share-list.tsx | 97 +++ .../features/share/components/share-modal.tsx | 227 +++++++ .../features/share/components/share-shell.tsx | 196 ++++++ .../share/components/share.module.css | 20 + .../features/share/components/shared-tree.tsx | 179 +++++ .../features/share/hooks/use-toggle-toc.ts | 8 + .../src/features/share/queries/share-query.ts | 179 +++++ .../features/share/services/share-service.ts | 59 ++ .../src/features/share/types/share.types.ts | 73 +++ apps/client/src/features/share/utils.ts | 60 ++ .../components/sidebar/space-sidebar.tsx | 12 +- apps/client/src/lib/api-client.ts | 1 + .../src/pages/settings/shares/shares.tsx | 31 + .../client/src/pages/share/share-redirect.tsx | 35 + apps/client/src/pages/share/shared-page.tsx | 58 ++ apps/server/package.json | 12 +- .../src/common/helpers/prosemirror/utils.ts | 39 ++ .../core/attachment/attachment.controller.ts | 611 ++++++++++-------- .../src/core/attachment/attachment.module.ts | 3 +- apps/server/src/core/auth/dto/jwt-payload.ts | 9 + .../src/core/auth/services/token.service.ts | 16 + .../casl/abilities/space-ability.factory.ts | 3 + .../casl/interfaces/space-ability.type.ts | 4 +- apps/server/src/core/core.module.ts | 2 + .../src/core/page/services/page.service.ts | 12 +- apps/server/src/core/share/dto/share.dto.ts | 58 ++ .../src/core/share/share-seo.controller.ts | 109 ++++ .../server/src/core/share/share.controller.ts | 171 +++++ apps/server/src/core/share/share.module.ts | 13 + apps/server/src/core/share/share.service.ts | 297 +++++++++ apps/server/src/core/share/share.util.ts | 22 + apps/server/src/database/database.module.ts | 3 + .../migrations/20250408T191830-shares.ts | 38 ++ .../src/database/repos/page/page.repo.ts | 11 +- .../src/database/repos/share/share.repo.ts | 242 +++++++ apps/server/src/database/types/db.d.ts | 15 + .../server/src/database/types/entity.types.ts | 6 + .../src/integrations/export/export.service.ts | 26 +- apps/server/src/integrations/export/utils.ts | 38 +- apps/server/src/main.ts | 11 +- pnpm-lock.yaml | 309 +++++---- 66 files changed, 3331 insertions(+), 512 deletions(-) create mode 100644 apps/client/src/components/theme-toggle.module.css create mode 100644 apps/client/src/features/editor/readonly-page-editor.tsx create mode 100644 apps/client/src/features/share/atoms/sidebar-atom.ts create mode 100644 apps/client/src/features/share/components/share-action-menu.tsx create mode 100644 apps/client/src/features/share/components/share-layout.tsx create mode 100644 apps/client/src/features/share/components/share-list.tsx create mode 100644 apps/client/src/features/share/components/share-modal.tsx create mode 100644 apps/client/src/features/share/components/share-shell.tsx create mode 100644 apps/client/src/features/share/components/share.module.css create mode 100644 apps/client/src/features/share/components/shared-tree.tsx create mode 100644 apps/client/src/features/share/hooks/use-toggle-toc.ts create mode 100644 apps/client/src/features/share/queries/share-query.ts create mode 100644 apps/client/src/features/share/services/share-service.ts create mode 100644 apps/client/src/features/share/types/share.types.ts create mode 100644 apps/client/src/features/share/utils.ts create mode 100644 apps/client/src/pages/settings/shares/shares.tsx create mode 100644 apps/client/src/pages/share/share-redirect.tsx create mode 100644 apps/client/src/pages/share/shared-page.tsx create mode 100644 apps/server/src/core/share/dto/share.dto.ts create mode 100644 apps/server/src/core/share/share-seo.controller.ts create mode 100644 apps/server/src/core/share/share.controller.ts create mode 100644 apps/server/src/core/share/share.module.ts create mode 100644 apps/server/src/core/share/share.service.ts create mode 100644 apps/server/src/core/share/share.util.ts create mode 100644 apps/server/src/database/migrations/20250408T191830-shares.ts create mode 100644 apps/server/src/database/repos/share/share.repo.ts diff --git a/apps/client/index.html b/apps/client/index.html index 98e02c8b..c96058cb 100644 --- a/apps/client/index.html +++ b/apps/client/index.html @@ -6,6 +6,7 @@ Docmost +
diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index bbc6a702..0746ed15 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -362,5 +362,26 @@ "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." + "Add headings (H1, H2, H3) to generate a table of contents.": "Add headings (H1, H2, H3) to generate a table of contents.", + "Share": "Share", + "Public sharing": "Public sharing", + "Shared by": "Shared by", + "Shared at": "Shared at", + "Inherits public sharing from": "Inherits public sharing from", + "Share to web": "Share to web", + "Shared to web": "Shared to web", + "Anyone with the link can view this page": "Anyone with the link can view this page", + "Make this page publicly accessible": "Make this page publicly accessible", + "Include sub-pages": "Include sub-pages", + "Make sub-pages public too": "Make sub-pages public too", + "Allow search engines to index page": "Allow search engines to index page", + "Open page": "Open page", + "Page": "Page", + "Delete public share link": "Delete public share link", + "Delete share": "Delete share", + "Are you sure you want to delete this shared link?": "Are you sure you want to delete this shared link?", + "Publicly shared pages from spaces you are a member of will appear here": "Publicly shared pages from spaces you are a member of will appear here", + "Share deleted successfully": "Share deleted successfully", + "Share not found": "Share not found", + "Failed to share page": "Failed to share page" } diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index c806f852..4399062f 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -26,6 +26,10 @@ import { useTranslation } from "react-i18next"; import Security from "@/ee/security/pages/security.tsx"; import License from "@/ee/licence/pages/license.tsx"; import { useRedirectToCloudSelect } from "@/ee/hooks/use-redirect-to-cloud-select.tsx"; +import SharedPage from "@/pages/share/shared-page.tsx"; +import Shares from "@/pages/settings/shares/shares.tsx"; +import ShareLayout from "@/features/share/components/share-layout.tsx"; +import ShareRedirect from '@/pages/share/share-redirect.tsx'; export default function App() { const { t } = useTranslation(); @@ -51,6 +55,12 @@ export default function App() { )} + }> + } /> + } /> + + + } /> } /> }> @@ -78,6 +88,7 @@ export default function App() { } /> } /> } /> + } /> } /> {!isCloud() && } />} {isCloud() && } />} diff --git a/apps/client/src/components/layouts/global/global-app-shell.tsx b/apps/client/src/components/layouts/global/global-app-shell.tsx index 4b5c0269..c8aff4cc 100644 --- a/apps/client/src/components/layouts/global/global-app-shell.tsx +++ b/apps/client/src/components/layouts/global/global-app-shell.tsx @@ -14,6 +14,8 @@ import { AppHeader } from "@/components/layouts/global/app-header.tsx"; import Aside from "@/components/layouts/global/aside.tsx"; import classes from "./app-shell.module.css"; import { useTrialEndAction } from "@/ee/hooks/use-trial-end-action.tsx"; +import { useClickOutside, useMergedRef } from "@mantine/hooks"; +import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts"; export default function GlobalAppShell({ children, @@ -22,11 +24,19 @@ export default function GlobalAppShell({ }) { useTrialEndAction(); const [mobileOpened] = useAtom(mobileSidebarAtom); + const toggleMobile = useToggleSidebar(mobileSidebarAtom); const [desktopOpened] = useAtom(desktopSidebarAtom); const [{ isAsideOpen }] = useAtom(asideStateAtom); const [sidebarWidth, setSidebarWidth] = useAtom(sidebarWidthAtom); const [isResizing, setIsResizing] = useState(false); const sidebarRef = useRef(null); + const navbarOutsideRef = useClickOutside(() => { + if (mobileOpened) { + toggleMobile(); + } + }); + + const mergedRef = useMergedRef(sidebarRef, navbarOutsideRef); const startResizing = React.useCallback((mouseDownEvent) => { mouseDownEvent.preventDefault(); @@ -102,7 +112,7 @@ export default function GlobalAppShell({
{isSpaceRoute && } @@ -111,7 +121,7 @@ export default function GlobalAppShell({ )} {isSettingsRoute ? ( - {children} + {children} ) : ( children )} diff --git a/apps/client/src/components/settings/settings-queries.tsx b/apps/client/src/components/settings/settings-queries.tsx index f4cddfaa..2f3b46bd 100644 --- a/apps/client/src/components/settings/settings-queries.tsx +++ b/apps/client/src/components/settings/settings-queries.tsx @@ -8,7 +8,8 @@ import { getGroups } from "@/features/group/services/group-service.ts"; import { QueryParams } from "@/lib/types.ts"; import { getWorkspaceMembers } from "@/features/workspace/services/workspace-service.ts"; import { getLicenseInfo } from "@/ee/licence/services/license-service.ts"; -import { getSsoProviders } from '@/ee/security/services/security-service.ts'; +import { getSsoProviders } from "@/ee/security/services/security-service.ts"; +import { getShares } from "@/features/share/services/share-service.ts"; export const prefetchWorkspaceMembers = () => { const params = { limit: 100, page: 1, query: "" } as QueryParams; @@ -56,4 +57,11 @@ export const prefetchSsoProviders = () => { queryKey: ["sso-providers"], queryFn: () => getSsoProviders(), }); -}; \ No newline at end of file +}; + +export const prefetchShares = () => { + queryClient.prefetchQuery({ + queryKey: ["share-list", { page: 1 }], + queryFn: () => getShares({ page: 1, limit: 100 }), + }); +}; diff --git a/apps/client/src/components/settings/settings-sidebar.tsx b/apps/client/src/components/settings/settings-sidebar.tsx index 16d94c64..483c0026 100644 --- a/apps/client/src/components/settings/settings-sidebar.tsx +++ b/apps/client/src/components/settings/settings-sidebar.tsx @@ -11,6 +11,7 @@ import { IconCoin, IconLock, IconKey, + IconWorld, } from "@tabler/icons-react"; import { Link, useLocation, useNavigate } from "react-router-dom"; import classes from "./settings.module.css"; @@ -23,11 +24,14 @@ import { prefetchBilling, prefetchGroups, prefetchLicense, + prefetchShares, prefetchSpaces, prefetchSsoProviders, prefetchWorkspaceMembers, } from "@/components/settings/settings-queries.tsx"; import AppVersion from "@/components/settings/app-version.tsx"; +import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; +import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts"; interface DataItem { label: string; @@ -82,6 +86,7 @@ const groupedData: DataGroup[] = [ }, { label: "Groups", icon: IconUsersGroup, path: "/settings/groups" }, { label: "Spaces", icon: IconSpaces, path: "/settings/spaces" }, + { label: "Public sharing", icon: IconWorld, path: "/settings/sharing" }, ], }, { @@ -103,6 +108,8 @@ export default function SettingsSidebar() { const navigate = useNavigate(); const { isAdmin } = useUserRole(); const [workspace] = useAtom(workspaceAtom); + const [mobileSidebarOpened] = useAtom(mobileSidebarAtom); + const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom); useEffect(() => { setActive(location.pathname); @@ -170,6 +177,9 @@ export default function SettingsSidebar() { case "Security & SSO": prefetchHandler = prefetchSsoProviders; break; + case "Public sharing": + prefetchHandler = prefetchShares; + break; default: break; } @@ -181,6 +191,11 @@ export default function SettingsSidebar() { data-active={active.startsWith(item.path) || undefined} key={item.label} to={item.path} + onClick={() => { + if (mobileSidebarOpened) { + toggleMobileSidebar(); + } + }} > {t(item.label)} diff --git a/apps/client/src/components/theme-toggle.module.css b/apps/client/src/components/theme-toggle.module.css new file mode 100644 index 00000000..936c5983 --- /dev/null +++ b/apps/client/src/components/theme-toggle.module.css @@ -0,0 +1,19 @@ +.dark { + @mixin dark { + display: none; + } + + @mixin light { + display: block; + } +} + +.light { + @mixin light { + display: none; + } + + @mixin dark { + display: block; + } +} diff --git a/apps/client/src/components/theme-toggle.tsx b/apps/client/src/components/theme-toggle.tsx index a988db6f..bf9245a2 100644 --- a/apps/client/src/components/theme-toggle.tsx +++ b/apps/client/src/components/theme-toggle.tsx @@ -1,13 +1,28 @@ -import { Button, Group, useMantineColorScheme } from '@mantine/core'; +import { + ActionIcon, + Tooltip, + useComputedColorScheme, + useMantineColorScheme, +} from "@mantine/core"; +import { IconMoon, IconSun } from "@tabler/icons-react"; +import classes from "./theme-toggle.module.css"; export function ThemeToggle() { - const { setColorScheme } = useMantineColorScheme(); + const { setColorScheme } = useMantineColorScheme(); + const computedColorScheme = useComputedColorScheme(); - return ( - - - - - - ); + return ( + + { + setColorScheme(computedColorScheme === "light" ? "dark" : "light"); + }} + aria-label="Toggle color scheme" + > + + + + + ); } diff --git a/apps/client/src/features/editor/atoms/editor-atoms.ts b/apps/client/src/features/editor/atoms/editor-atoms.ts index 6f54c057..d4f133f7 100644 --- a/apps/client/src/features/editor/atoms/editor-atoms.ts +++ b/apps/client/src/features/editor/atoms/editor-atoms.ts @@ -5,4 +5,6 @@ export const pageEditorAtom = atom(null); export const titleEditorAtom = atom(null); +export const readOnlyEditorAtom = atom(null); + export const yjsConnectionStatusAtom = atom(""); diff --git a/apps/client/src/features/editor/components/drawio/drawio-view.tsx b/apps/client/src/features/editor/components/drawio/drawio-view.tsx index 16e6dc97..468e26a0 100644 --- a/apps/client/src/features/editor/components/drawio/drawio-view.tsx +++ b/apps/client/src/features/editor/components/drawio/drawio-view.tsx @@ -139,7 +139,7 @@ export default function DrawioView(props: NodeViewProps) { )} /> - {selected && ( + {selected && editor.isEditable && ( - {selected && ( + {selected && editor.isEditable && ( {entityType === "user" && ( @@ -28,7 +41,9 @@ export default function MentionView(props: NodeViewProps) { diff --git a/apps/client/src/features/editor/components/table-of-contents/table-of-contents.module.css b/apps/client/src/features/editor/components/table-of-contents/table-of-contents.module.css index 9554a84d..739cc0d1 100644 --- a/apps/client/src/features/editor/components/table-of-contents/table-of-contents.module.css +++ b/apps/client/src/features/editor/components/table-of-contents/table-of-contents.module.css @@ -52,3 +52,8 @@ ) !important; } } + + +.leftBorder { + border-left: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4)); +} diff --git a/apps/client/src/features/editor/components/table-of-contents/table-of-contents.tsx b/apps/client/src/features/editor/components/table-of-contents/table-of-contents.tsx index 6945a29b..b309d67d 100644 --- a/apps/client/src/features/editor/components/table-of-contents/table-of-contents.tsx +++ b/apps/client/src/features/editor/components/table-of-contents/table-of-contents.tsx @@ -8,6 +8,7 @@ import { useTranslation } from "react-i18next"; type TableOfContentsProps = { editor: ReturnType; + isShare?: boolean; }; export type HeadingLink = { @@ -73,6 +74,7 @@ export const TableOfContents: FC = (props) => { const handleUpdate = () => { const result = recalculateLinks(props.editor?.$nodes("heading")); + setLinks(result.links); setHeadingDOMNodes(result.nodes); }; @@ -85,9 +87,12 @@ export const TableOfContents: FC = (props) => { }; }, [props.editor]); - useEffect(() => { - handleUpdate(); - }, []); + useEffect( + () => { + handleUpdate(); + }, + props.isShare ? [props.editor] : [], + ); useEffect(() => { try { @@ -133,16 +138,23 @@ export const TableOfContents: FC = (props) => { if (!links.length) { return ( <> - - {t("Add headings (H1, H2, H3) to generate a table of contents.")} - + {!props.isShare && ( + + {t("Add headings (H1, H2, H3) to generate a table of contents.")} + + )} ); } return ( <> -
+ {props.isShare && ( + + {t("Table of contents")} + + )} +
{links.map((item, idx) => ( component="button" diff --git a/apps/client/src/features/editor/readonly-page-editor.tsx b/apps/client/src/features/editor/readonly-page-editor.tsx new file mode 100644 index 00000000..bd6b9f6b --- /dev/null +++ b/apps/client/src/features/editor/readonly-page-editor.tsx @@ -0,0 +1,67 @@ +import "@/features/editor/styles/index.css"; +import React, { useMemo } from "react"; +import { EditorProvider } from "@tiptap/react"; +import { mainExtensions } from "@/features/editor/extensions/extensions"; +import { Document } from "@tiptap/extension-document"; +import { Heading } from "@tiptap/extension-heading"; +import { Text } from "@tiptap/extension-text"; +import { Placeholder } from "@tiptap/extension-placeholder"; +import { useAtom } from "jotai/index"; +import { + pageEditorAtom, + readOnlyEditorAtom, +} from "@/features/editor/atoms/editor-atoms.ts"; +import { Editor } from "@tiptap/core"; + +interface PageEditorProps { + title: string; + content: any; +} + +export default function ReadonlyPageEditor({ + title, + content, +}: PageEditorProps) { + const [, setReadOnlyEditor] = useAtom(readOnlyEditorAtom); + + const extensions = useMemo(() => { + return [...mainExtensions]; + }, []); + + const titleExtensions = [ + Document.extend({ + content: "heading", + }), + Heading, + Text, + Placeholder.configure({ + placeholder: "Untitled", + showOnlyWhenEditable: false, + }), + ]; + + return ( + <> + + + { + if (editor) { + // @ts-ignore + setReadOnlyEditor(editor); + } + }} + > +
+ + ); +} diff --git a/apps/client/src/features/page/components/breadcrumbs/breadcrumb.module.css b/apps/client/src/features/page/components/breadcrumbs/breadcrumb.module.css index cf3637b2..cebee031 100644 --- a/apps/client/src/features/page/components/breadcrumbs/breadcrumb.module.css +++ b/apps/client/src/features/page/components/breadcrumbs/breadcrumb.module.css @@ -2,6 +2,7 @@ display: flex; align-items: center; overflow: hidden; + flex-wrap: nowrap; a { color: var(--mantine-color-default-color); diff --git a/apps/client/src/features/page/components/breadcrumbs/breadcrumb.tsx b/apps/client/src/features/page/components/breadcrumbs/breadcrumb.tsx index 367b2682..9d78f38c 100644 --- a/apps/client/src/features/page/components/breadcrumbs/breadcrumb.tsx +++ b/apps/client/src/features/page/components/breadcrumbs/breadcrumb.tsx @@ -1,6 +1,6 @@ import { useAtomValue } from "jotai"; import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts"; -import React, { useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { findBreadcrumbPath } from "@/features/page/tree/utils"; import { Button, @@ -9,14 +9,16 @@ import { Breadcrumbs, ActionIcon, Text, + Tooltip, } from "@mantine/core"; -import { IconDots } from "@tabler/icons-react"; +import { IconCornerDownRightDouble, IconDots } from "@tabler/icons-react"; import { Link, useParams } from "react-router-dom"; import classes from "./breadcrumb.module.css"; import { SpaceTreeNode } from "@/features/page/tree/types.ts"; import { buildPageUrl } from "@/features/page/page.utils.ts"; import { usePageQuery } from "@/features/page/queries/page-query.ts"; import { extractPageSlugId } from "@/lib"; +import { useMediaQuery } from "@mantine/hooks"; function getTitle(name: string, icon: string) { if (icon) { @@ -34,6 +36,7 @@ export default function Breadcrumb() { const { data: currentPage } = usePageQuery({ pageId: extractPageSlugId(pageSlug), }); + const isMobile = useMediaQuery("(max-width: 48em)"); useEffect(() => { if (treeData?.length > 0 && currentPage) { @@ -43,7 +46,7 @@ export default function Breadcrumb() { }, [currentPage?.id, treeData]); const HiddenNodesTooltipContent = () => - breadcrumbNodes?.slice(1, -2).map((node) => ( + breadcrumbNodes?.slice(1, -1).map((node) => ( + + )); + + const renderAnchor = useCallback( + (node: SpaceTreeNode) => ( + + + {getTitle(node.name, node.icon)} + + + ), + [spaceSlug], ); const getBreadcrumbItems = () => { @@ -77,7 +102,7 @@ export default function Breadcrumb() { if (breadcrumbNodes.length > 3) { const firstNode = breadcrumbNodes[0]; - const secondLastNode = breadcrumbNodes[breadcrumbNodes.length - 2]; + //const secondLastNode = breadcrumbNodes[breadcrumbNodes.length - 2]; const lastNode = breadcrumbNodes[breadcrumbNodes.length - 1]; return [ @@ -98,7 +123,7 @@ export default function Breadcrumb() { , - renderAnchor(secondLastNode), + //renderAnchor(secondLastNode), renderAnchor(lastNode), ]; } @@ -106,11 +131,40 @@ export default function Breadcrumb() { return breadcrumbNodes.map(renderAnchor); }; + const getMobileBreadcrumbItems = () => { + if (!breadcrumbNodes) return []; + + if (breadcrumbNodes.length > 0) { + return [ + + + + + + + + + + + + , + ]; + } + + return breadcrumbNodes.map(renderAnchor); + }; + return (
{breadcrumbNodes && ( - {getBreadcrumbItems()} + {isMobile ? getMobileBreadcrumbItems() : getBreadcrumbItems()} )}
diff --git a/apps/client/src/features/page/components/header/page-header-menu.tsx b/apps/client/src/features/page/components/header/page-header-menu.tsx index 93d10520..9267ad9e 100644 --- a/apps/client/src/features/page/components/header/page-header-menu.tsx +++ b/apps/client/src/features/page/components/header/page-header-menu.tsx @@ -35,6 +35,7 @@ import { 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"; +import ShareModal from '@/features/share/components/share-modal.tsx'; interface PageHeaderMenuProps { readOnly?: boolean; @@ -58,6 +59,8 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) { )} + + { ], }); - return `p/${titleSlug}-${pageSlugId}`; + return `${titleSlug}-${pageSlugId}`; }; export const buildPageUrl = ( @@ -17,7 +17,20 @@ export const buildPageUrl = ( pageTitle?: string, ): string => { if (spaceName === undefined) { - return `/${buildPageSlug(pageSlugId, pageTitle)}`; + return `/p/${buildPageSlug(pageSlugId, pageTitle)}`; } - return `/s/${spaceName}/${buildPageSlug(pageSlugId, pageTitle)}`; + return `/s/${spaceName}/p/${buildPageSlug(pageSlugId, pageTitle)}`; +}; + +export const buildSharedPageUrl = (opts: { + shareId: string; + pageSlugId: string; + pageTitle?: string; +}): string => { + const { shareId, pageSlugId, pageTitle } = opts; + if (!shareId) { + return `/share/p/${buildPageSlug(pageSlugId, pageTitle)}`; + } + + return `/share/${shareId}/p/${buildPageSlug(pageSlugId, pageTitle)}`; }; diff --git a/apps/client/src/features/page/tree/components/space-tree.tsx b/apps/client/src/features/page/tree/components/space-tree.tsx index 7b2f2f7d..5a00f258 100644 --- a/apps/client/src/features/page/tree/components/space-tree.tsx +++ b/apps/client/src/features/page/tree/components/space-tree.tsx @@ -8,9 +8,9 @@ import { useUpdatePageMutation, } from "@/features/page/queries/page-query.ts"; import { useEffect, useRef, useState } from "react"; -import { useNavigate, useParams } from "react-router-dom"; +import { Link, useParams } from "react-router-dom"; import classes from "@/features/page/tree/styles/tree.module.css"; -import { ActionIcon, Menu, rem } from "@mantine/core"; +import { ActionIcon, Box, Menu, rem } from "@mantine/core"; import { IconArrowRight, IconChevronDown, @@ -58,6 +58,8 @@ import { useDeletePageModal } from "@/features/page/hooks/use-delete-page-modal. import { useTranslation } from "react-i18next"; import ExportModal from "@/components/common/export-modal"; import MovePageModal from "../../components/move-page-modal.tsx"; +import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; +import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts"; interface SpaceTreeProps { spaceId: string; @@ -230,13 +232,14 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) { } function Node({ node, style, dragHandle, tree }: NodeRendererProps) { - const navigate = useNavigate(); + const { t } = useTranslation(); const updatePageMutation = useUpdatePageMutation(); const [treeData, setTreeData] = useAtom(treeDataAtom); const emit = useQueryEmit(); const { spaceSlug } = useParams(); const timerRef = useRef(null); - const { t } = useTranslation(); + const [mobileSidebarOpened] = useAtom(mobileSidebarAtom); + const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom); const prefetchPage = () => { timerRef.current = setTimeout(() => { @@ -287,11 +290,6 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps) { } } - const handleClick = () => { - const pageUrl = buildPageUrl(spaceSlug, node.data.slugId, node.data.name); - navigate(pageUrl); - }; - const handleUpdateNodeIcon = (nodeId: string, newIcon: string) => { const updatedTree = updateTreeNodeIcon(treeData, nodeId, newIcon); setTreeData(updatedTree); @@ -345,13 +343,22 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps) { }, 650); } + const pageUrl = buildPageUrl(spaceSlug, node.data.slugId, node.data.name); + return ( <> -
{ + if (mobileSidebarOpened) { + toggleMobileSidebar(); + } + }} onMouseEnter={prefetchPage} onMouseLeave={cancelPagePrefetch} > @@ -385,7 +392,7 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps) { /> )}
-
+ ); } diff --git a/apps/client/src/features/page/tree/styles/tree.module.css b/apps/client/src/features/page/tree/styles/tree.module.css index 0a258fb5..716101e0 100644 --- a/apps/client/src/features/page/tree/styles/tree.module.css +++ b/apps/client/src/features/page/tree/styles/tree.module.css @@ -18,7 +18,7 @@ align-items: center; height: 100%; width: 93%; /* not to overlap with scroll bar */ - + text-decoration: none; color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0)); &:hover { @@ -70,6 +70,10 @@ background-color: light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5)); } +.row:focus .node:global(.isFocused) { + background-color: light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5)); +} + .row { white-space: nowrap; cursor: pointer; diff --git a/apps/client/src/features/page/tree/utils/utils.ts b/apps/client/src/features/page/tree/utils/utils.ts index 0dfe8ed4..7ae84e38 100644 --- a/apps/client/src/features/page/tree/utils/utils.ts +++ b/apps/client/src/features/page/tree/utils/utils.ts @@ -1,7 +1,7 @@ import { IPage } from "@/features/page/types/page.types.ts"; import { SpaceTreeNode } from "@/features/page/tree/types.ts"; -function sortPositionKeys(keys: any[]) { +export function sortPositionKeys(keys: any[]) { return keys.sort((a, b) => { if (a.position < b.position) return -1; if (a.position > b.position) return 1; diff --git a/apps/client/src/features/share/atoms/sidebar-atom.ts b/apps/client/src/features/share/atoms/sidebar-atom.ts new file mode 100644 index 00000000..0bc9d681 --- /dev/null +++ b/apps/client/src/features/share/atoms/sidebar-atom.ts @@ -0,0 +1,9 @@ +import { atomWithWebStorage } from "@/lib/jotai-helper.ts"; +import { atom } from 'jotai'; + +export const tableOfContentAsideAtom = atomWithWebStorage( + "showTOC", + true, +); + +export const mobileTableOfContentAsideAtom = atom(false); \ No newline at end of file diff --git a/apps/client/src/features/share/components/share-action-menu.tsx b/apps/client/src/features/share/components/share-action-menu.tsx new file mode 100644 index 00000000..398e25e9 --- /dev/null +++ b/apps/client/src/features/share/components/share-action-menu.tsx @@ -0,0 +1,106 @@ +import { Menu, ActionIcon, Text } from "@mantine/core"; +import React from "react"; +import { + IconCopy, + IconDots, + IconFileDescription, + IconTrash, +} from "@tabler/icons-react"; +import { modals } from "@mantine/modals"; +import { useTranslation } from "react-i18next"; +import { ISharedItem } from "@/features/share/types/share.types.ts"; +import { + buildPageUrl, + buildSharedPageUrl, +} from "@/features/page/page.utils.ts"; +import { useClipboard } from "@mantine/hooks"; +import { notifications } from "@mantine/notifications"; +import { useNavigate } from "react-router-dom"; +import { useDeleteShareMutation } from "@/features/share/queries/share-query.ts"; + +interface Props { + share: ISharedItem; +} +export default function ShareActionMenu({ share }: Props) { + const { t } = useTranslation(); + const navigate = useNavigate(); + const clipboard = useClipboard(); + const deleteShareMutation = useDeleteShareMutation(); + + const openPage = () => { + const pageLink = buildPageUrl( + share.space.slug, + share.page.slugId, + share.page.title, + ); + navigate(pageLink); + }; + + const copyLink = () => { + const shareLink = buildSharedPageUrl({ + shareId: share.key, + pageTitle: share.page.title, + pageSlugId: share.page.slugId, + }); + + clipboard.copy(shareLink); + notifications.show({ message: t("Link copied") }); + }; + const onDelete = async () => { + deleteShareMutation.mutateAsync(share.key); + }; + + const openDeleteModal = () => + modals.openConfirmModal({ + title: t("Delete public share link"), + children: ( + + {t("Are you sure you want to delete this shared link?")} + + ), + centered: true, + labels: { confirm: t("Delete"), cancel: t("Don't") }, + confirmProps: { color: "red" }, + onConfirm: onDelete, + }); + + return ( + <> + + + + + + + + + }> + {t("Copy link")} + + + } + > + {t("Open page")} + + } + disabled={share.space?.userRole === "reader"} + > + {t("Delete share")} + + + + + ); +} diff --git a/apps/client/src/features/share/components/share-layout.tsx b/apps/client/src/features/share/components/share-layout.tsx new file mode 100644 index 00000000..e3b2eb17 --- /dev/null +++ b/apps/client/src/features/share/components/share-layout.tsx @@ -0,0 +1,10 @@ +import { Outlet } from "react-router-dom"; +import ShareShell from "@/features/share/components/share-shell.tsx"; + +export default function ShareLayout() { + return ( + + + + ); +} diff --git a/apps/client/src/features/share/components/share-list.tsx b/apps/client/src/features/share/components/share-list.tsx new file mode 100644 index 00000000..d5acbbd6 --- /dev/null +++ b/apps/client/src/features/share/components/share-list.tsx @@ -0,0 +1,97 @@ +import { Table, Group, Text, Anchor } from "@mantine/core"; +import React, { useState } from "react"; +import { Link } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import Paginate from "@/components/common/paginate.tsx"; +import { useGetSharesQuery } from "@/features/share/queries/share-query.ts"; +import { ISharedItem } from "@/features/share/types/share.types.ts"; +import { format } from "date-fns"; +import ShareActionMenu from "@/features/share/components/share-action-menu.tsx"; +import { buildSharedPageUrl } from "@/features/page/page.utils.ts"; +import { getPageIcon } from "@/lib"; +import { CustomAvatar } from "@/components/ui/custom-avatar.tsx"; +import classes from "./share.module.css"; + +export default function ShareList() { + const { t } = useTranslation(); + const [page, setPage] = useState(1); + const { data, isLoading } = useGetSharesQuery({ page }); + + return ( + <> + + + + + {t("Page")} + {t("Shared by")} + {t("Shared at")} + + + + + {data?.items.map((share: ISharedItem, index: number) => ( + + + + + {getPageIcon(share.page.icon)} +
+ + {share.page.title || t("untitled")} + +
+
+
+
+ + + + + {share.creator.name} + + + + + + {format(new Date(share.createdAt), "MMM dd, yyyy")} + + + + + +
+ ))} +
+
+
+ + {data?.items.length > 0 && ( + + )} + + ); +} diff --git a/apps/client/src/features/share/components/share-modal.tsx b/apps/client/src/features/share/components/share-modal.tsx new file mode 100644 index 00000000..20dcc518 --- /dev/null +++ b/apps/client/src/features/share/components/share-modal.tsx @@ -0,0 +1,227 @@ +import { + ActionIcon, + Anchor, + Button, + Group, + Indicator, + Popover, + Switch, + Text, + TextInput, + Tooltip, +} from "@mantine/core"; +import { IconExternalLink, IconWorld } from "@tabler/icons-react"; +import React, { useEffect, useMemo, useState } from "react"; +import { + useCreateShareMutation, + useDeleteShareMutation, + useShareForPageQuery, + useUpdateShareMutation, +} from "@/features/share/queries/share-query.ts"; +import { Link, useParams } from "react-router-dom"; +import { extractPageSlugId, getPageIcon } from "@/lib"; +import { useTranslation } from "react-i18next"; +import CopyTextButton from "@/components/common/copy.tsx"; +import { getAppUrl } from "@/lib/config.ts"; +import { buildPageUrl } from "@/features/page/page.utils.ts"; +import classes from "@/features/share/components/share.module.css"; + +interface ShareModalProps { + readOnly: boolean; +} +export default function ShareModal({ readOnly }: ShareModalProps) { + const { t } = useTranslation(); + const { pageSlug } = useParams(); + const pageId = extractPageSlugId(pageSlug); + const { data: share } = useShareForPageQuery(pageId); + const { spaceSlug } = useParams(); + const createShareMutation = useCreateShareMutation(); + const updateShareMutation = useUpdateShareMutation(); + const deleteShareMutation = useDeleteShareMutation(); + // pageIsShared means that the share exists and its level equals zero. + const pageIsShared = share && share.level === 0; + // if level is greater than zero, then it is a descendant page from a shared page + const isDescendantShared = share && share.level > 0; + + const publicLink = `${getAppUrl()}/share/${share?.key}/p/${pageSlug}`; + + const [isPagePublic, setIsPagePublic] = useState(false); + useEffect(() => { + if (share) { + setIsPagePublic(true); + } else { + setIsPagePublic(false); + } + }, [share, pageId]); + + const handleChange = async (event: React.ChangeEvent) => { + const value = event.currentTarget.checked; + + if (value) { + createShareMutation.mutateAsync({ + pageId: pageId, + includeSubPages: true, + searchIndexing: true, + }); + setIsPagePublic(value); + } else { + if (share && share.id) { + deleteShareMutation.mutateAsync(share.id); + setIsPagePublic(value); + } + } + }; + + const handleSubPagesChange = async ( + event: React.ChangeEvent, + ) => { + const value = event.currentTarget.checked; + updateShareMutation.mutateAsync({ + shareId: share.id, + includeSubPages: value, + }); + }; + + const handleIndexSearchChange = async ( + event: React.ChangeEvent, + ) => { + const value = event.currentTarget.checked; + updateShareMutation.mutateAsync({ + shareId: share.id, + searchIndexing: value, + }); + }; + + const shareLink = useMemo(() => ( + + } + style={{ width: "100%" }} + /> + + + + + ), [publicLink]); + + return ( + + + + + + {isDescendantShared ? ( + <> + {t("Inherits public sharing from")} + + + {getPageIcon(share.sharedPage.icon)} +
+ + {share.sharedPage.title || t("untitled")} + +
+
+
+ + {shareLink} + + ) : ( + <> + +
+ + {isPagePublic ? t("Shared to web") : t("Share to web")} + + + {isPagePublic + ? t("Anyone with the link can view this page") + : t("Make this page publicly accessible")} + +
+ +
+ + {pageIsShared && ( + <> + {shareLink} + +
+ {t("Include sub-pages")} + + {t("Make sub-pages public too")} + +
+ + +
+ +
+ {t("Search engine indexing")} + + {t("Allow search engines to index page")} + +
+ +
+ + )} + + )} +
+
+ ); +} diff --git a/apps/client/src/features/share/components/share-shell.tsx b/apps/client/src/features/share/components/share-shell.tsx new file mode 100644 index 00000000..82863b34 --- /dev/null +++ b/apps/client/src/features/share/components/share-shell.tsx @@ -0,0 +1,196 @@ +import React, { useState } from "react"; +import { + ActionIcon, + Affix, + AppShell, + Button, + Group, + ScrollArea, + Tooltip, +} from "@mantine/core"; +import { useGetSharedPageTreeQuery } from "@/features/share/queries/share-query.ts"; +import { useParams } from "react-router-dom"; +import SharedTree from "@/features/share/components/shared-tree.tsx"; +import { TableOfContents } from "@/features/editor/components/table-of-contents/table-of-contents.tsx"; +import { readOnlyEditorAtom } from "@/features/editor/atoms/editor-atoms.ts"; +import { ThemeToggle } from "@/components/theme-toggle.tsx"; +import { useAtomValue } from "jotai"; +import { useAtom } from "jotai"; +import { + desktopSidebarAtom, + mobileSidebarAtom, +} from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; +import SidebarToggle from "@/components/ui/sidebar-toggle-button.tsx"; +import { useTranslation } from "react-i18next"; +import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts"; +import { + mobileTableOfContentAsideAtom, + tableOfContentAsideAtom, +} from "@/features/share/atoms/sidebar-atom.ts"; +import { IconList } from "@tabler/icons-react"; +import { useToggleToc } from "@/features/share/hooks/use-toggle-toc.ts"; +import classes from "./share.module.css"; +import { useClickOutside } from "@mantine/hooks"; + +const MemoizedSharedTree = React.memo(SharedTree); + +export default function ShareShell({ + children, +}: { + children: React.ReactNode; +}) { + const { t } = useTranslation(); + const [mobileOpened] = useAtom(mobileSidebarAtom); + const [desktopOpened] = useAtom(desktopSidebarAtom); + const toggleMobile = useToggleSidebar(mobileSidebarAtom); + const toggleDesktop = useToggleSidebar(desktopSidebarAtom); + + const [tocOpened] = useAtom(tableOfContentAsideAtom); + const [mobileTocOpened] = useAtom(mobileTableOfContentAsideAtom); + const toggleTocMobile = useToggleToc(mobileTableOfContentAsideAtom); + const toggleToc = useToggleToc(tableOfContentAsideAtom); + + const { shareId } = useParams(); + const { data } = useGetSharedPageTreeQuery(shareId); + const readOnlyEditor = useAtomValue(readOnlyEditorAtom); + + const [navbarOutside, setNavbarOutside] = useState(null); + const [asideOutside, setAsideOutside] = useState(null); + + useClickOutside( + () => { + if (mobileOpened) { + toggleMobile(); + } + if (mobileTocOpened) { + toggleTocMobile(); + } + }, + null, + [navbarOutside, asideOutside], + ); + + return ( + 1 && { + navbar: { + width: 300, + breakpoint: "sm", + collapsed: { + mobile: !mobileOpened, + desktop: !desktopOpened, + }, + }, + })} + aside={{ + width: 300, + breakpoint: "sm", + collapsed: { + mobile: !mobileTocOpened, + desktop: !tocOpened, + }, + }} + padding="md" + > + + + + {data?.pageTree?.length > 1 && ( + <> + + + + + + + + + )} + + + <> + + + + + + + + + + + + + + + + + + + {data?.pageTree?.length > 1 && ( + + + + )} + + + {children} + + + + + + + + +
+ {readOnlyEditor && ( + + )} +
+
+
+
+ ); +} diff --git a/apps/client/src/features/share/components/share.module.css b/apps/client/src/features/share/components/share.module.css new file mode 100644 index 00000000..617768ff --- /dev/null +++ b/apps/client/src/features/share/components/share.module.css @@ -0,0 +1,20 @@ +.shareLinkText { + @mixin light { + border-bottom: 0.05em solid var(--mantine-color-dark-0); + } + @mixin dark { + border-bottom: 0.05em solid var(--mantine-color-dark-2); + } +} + +.treeNode { + text-decoration: none; + user-select: none; +} + +.navbar, +.aside { + @media (max-width: $mantine-breakpoint-sm) { + width: 350px; + } +} diff --git a/apps/client/src/features/share/components/shared-tree.tsx b/apps/client/src/features/share/components/shared-tree.tsx new file mode 100644 index 00000000..5e85ab57 --- /dev/null +++ b/apps/client/src/features/share/components/shared-tree.tsx @@ -0,0 +1,179 @@ +import { ISharedPageTree } from "@/features/share/types/share.types.ts"; +import { NodeApi, NodeRendererProps, Tree, TreeApi } from "react-arborist"; +import { + buildSharedPageTree, + SharedPageTreeNode, +} from "@/features/share/utils.ts"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { useElementSize, useMergedRef } from "@mantine/hooks"; +import { SpaceTreeNode } from "@/features/page/tree/types.ts"; +import { Link, useParams } from "react-router-dom"; +import { atom, useAtom } from "jotai/index"; +import { useTranslation } from "react-i18next"; +import { buildSharedPageUrl } from "@/features/page/page.utils.ts"; +import clsx from "clsx"; +import { + IconChevronDown, + IconChevronRight, + IconPointFilled, +} from "@tabler/icons-react"; +import { ActionIcon, Box } from "@mantine/core"; +import { extractPageSlugId } from "@/lib"; +import { OpenMap } from "react-arborist/dist/main/state/open-slice"; +import classes from "@/features/page/tree/styles/tree.module.css"; +import styles from "./share.module.css"; +import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; + +interface SharedTree { + sharedPageTree: ISharedPageTree; +} + +const openSharedTreeNodesAtom = atom({}); + +export default function SharedTree({ sharedPageTree }: SharedTree) { + const [tree, setTree] = useState< + TreeApi | null | undefined + >(null); + const rootElement = useRef(); + const { ref: sizeRef, width, height } = useElementSize(); + const mergedRef = useMergedRef(rootElement, sizeRef); + const { pageSlug } = useParams(); + const [openTreeNodes, setOpenTreeNodes] = useAtom( + openSharedTreeNodesAtom, + ); + + const currentNodeId = extractPageSlugId(pageSlug); + + const treeData: SharedPageTreeNode[] = useMemo(() => { + if (!sharedPageTree?.pageTree) return; + return buildSharedPageTree(sharedPageTree.pageTree); + }, [sharedPageTree?.pageTree]); + + useEffect(() => { + const parentNodeId = treeData?.[0]?.slugId; + + if (parentNodeId && tree) { + const parentNode = tree.get(parentNodeId); + + setTimeout(() => { + if (parentNode) { + tree.openSiblings(parentNode); + } + }); + + // open direct children of parent node + parentNode?.children.forEach((node) => { + tree.openSiblings(node); + }); + } + }, [treeData, tree]); + + useEffect(() => { + if (currentNodeId && tree) { + setTimeout(() => { + // focus on node and open all parents + tree?.select(currentNodeId, { align: "auto" }); + }, 200); + } else { + tree?.deselectAll(); + } + }, [currentNodeId, tree]); + + if (!sharedPageTree || !sharedPageTree?.pageTree) { + return null; + } + + return ( +
+ {rootElement.current && ( + setTree(t)} + openByDefault={false} + disableMultiSelection={true} + className={classes.tree} + rowClassName={classes.row} + rowHeight={30} + overscanCount={10} + dndRootElement={rootElement.current} + onToggle={() => { + setOpenTreeNodes(tree?.openState); + }} + initialOpenState={openTreeNodes} + onClick={(e) => { + if (tree && tree.focusedNode) { + tree.select(tree.focusedNode); + } + }} + > + {Node} + + )} +
+ ); +} + +function Node({ node, style, tree }: NodeRendererProps) { + const { shareId } = useParams(); + const { t } = useTranslation(); + const [, setMobileSidebarState] = useAtom(mobileSidebarAtom); + + const pageUrl = buildSharedPageUrl({ + shareId: shareId, + pageSlugId: node.data.slugId, + pageTitle: node.data.name, + }); + + return ( + <> + { + setMobileSidebarState(false); + }} + > + + {node.data.name || t("untitled")} + + + ); +} + +interface PageArrowProps { + node: NodeApi; +} + +function PageArrow({ node }: PageArrowProps) { + return ( + { + e.preventDefault(); + e.stopPropagation(); + node.toggle(); + }} + > + {node.isInternal ? ( + node.children && (node.children.length > 0 || node.data.hasChildren) ? ( + node.isOpen ? ( + + ) : ( + + ) + ) : ( + + ) + ) : null} + + ); +} diff --git a/apps/client/src/features/share/hooks/use-toggle-toc.ts b/apps/client/src/features/share/hooks/use-toggle-toc.ts new file mode 100644 index 00000000..ec43086a --- /dev/null +++ b/apps/client/src/features/share/hooks/use-toggle-toc.ts @@ -0,0 +1,8 @@ +import { useAtom } from "jotai"; + +export function useToggleToc(tocAtom: any) { + const [tocState, setTocState] = useAtom(tocAtom); + return () => { + setTocState(!tocState); + } +} diff --git a/apps/client/src/features/share/queries/share-query.ts b/apps/client/src/features/share/queries/share-query.ts new file mode 100644 index 00000000..dea047bf --- /dev/null +++ b/apps/client/src/features/share/queries/share-query.ts @@ -0,0 +1,179 @@ +import { + keepPreviousData, + useMutation, + useQuery, + useQueryClient, + UseQueryResult, +} from "@tanstack/react-query"; +import { notifications } from "@mantine/notifications"; +import { useTranslation } from "react-i18next"; +import { + ICreateShare, + IShare, + ISharedItem, + ISharedPage, + ISharedPageTree, + IShareForPage, + IShareInfoInput, + IUpdateShare, +} from "@/features/share/types/share.types.ts"; +import { + createShare, + deleteShare, + getSharedPageTree, + getShareForPage, + getShareInfo, + getSharePageInfo, + getShares, + updateShare, +} from "@/features/share/services/share-service.ts"; +import { IPage } from "@/features/page/types/page.types.ts"; +import { IPagination, QueryParams } from "@/lib/types.ts"; +import { useEffect } from "react"; + +export function useGetSharesQuery( + params?: QueryParams, +): UseQueryResult, Error> { + return useQuery({ + queryKey: ["share-list"], + queryFn: () => getShares(params), + placeholderData: keepPreviousData, + }); +} + +export function useGetShareByIdQuery( + shareId: string, +): UseQueryResult { + const query = useQuery({ + queryKey: ["share-by-id", shareId], + queryFn: () => getShareInfo(shareId), + enabled: !!shareId, + }); + + return query; +} + +export function useSharePageQuery( + shareInput: Partial, +): UseQueryResult { + const query = useQuery({ + queryKey: ["shares", shareInput], + queryFn: () => getSharePageInfo(shareInput), + enabled: !!shareInput.pageId, + }); + + return query; +} + +export function useShareForPageQuery( + pageId: string, +): UseQueryResult { + const query = useQuery({ + queryKey: ["share-for-page", pageId], + queryFn: () => getShareForPage(pageId), + enabled: !!pageId, + staleTime: 0, + retry: false, + }); + + return query; +} + +export function useCreateShareMutation() { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data) => createShare(data), + onSuccess: (data) => { + queryClient.invalidateQueries({ + predicate: (item) => + ["share-for-page", "share-list"].includes(item.queryKey[0] as string), + }); + }, + onError: (error) => { + notifications.show({ message: t("Failed to share page"), color: "red" }); + }, + }); +} + +export function useUpdateShareMutation() { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data) => updateShare(data), + onSuccess: (data) => { + queryClient.invalidateQueries({ + predicate: (item) => + ["share-for-page", "share-list"].includes(item.queryKey[0] as string), + }); + }, + onError: (error, params) => { + if (error?.["status"] === 404) { + queryClient.removeQueries({ + predicate: (item) => + ["share-for-page"].includes(item.queryKey[0] as string), + }); + + notifications.show({ + message: t("Share not found"), + color: "red", + }); + return; + } + + notifications.show({ + message: error?.["response"]?.data?.message || "Share not found", + color: "red", + }); + }, + }); +} + +export function useDeleteShareMutation() { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (shareId: string) => deleteShare(shareId), + onSuccess: (data) => { + queryClient.removeQueries({ + predicate: (item) => + ["share-for-page"].includes(item.queryKey[0] as string), + }); + + queryClient.invalidateQueries({ + predicate: (item) => + ["share-list"].includes(item.queryKey[0] as string), + }); + + notifications.show({ message: t("Share deleted successfully") }); + }, + onError: (error) => { + if (error?.["status"] === 404) { + queryClient.removeQueries({ + predicate: (item) => + ["share-for-page"].includes(item.queryKey[0] as string), + }); + } + + notifications.show({ + message: error?.["response"]?.data?.message || "Failed to delete share", + color: "red", + }); + }, + }); +} + +export function useGetSharedPageTreeQuery( + shareId: string, +): UseQueryResult { + return useQuery({ + queryKey: ["shared-page-tree", shareId], + queryFn: () => getSharedPageTree(shareId), + enabled: !!shareId, + placeholderData: keepPreviousData, + staleTime: 60 * 60 * 1000, + }); +} diff --git a/apps/client/src/features/share/services/share-service.ts b/apps/client/src/features/share/services/share-service.ts new file mode 100644 index 00000000..2f43ba20 --- /dev/null +++ b/apps/client/src/features/share/services/share-service.ts @@ -0,0 +1,59 @@ +import api from "@/lib/api-client"; +import { IPage } from "@/features/page/types/page.types"; + +import { + ICreateShare, + IShare, + ISharedItem, + ISharedPage, + ISharedPageTree, + IShareForPage, + IShareInfoInput, + IUpdateShare, +} from "@/features/share/types/share.types.ts"; +import { IPagination, QueryParams } from "@/lib/types.ts"; + +export async function getShares( + params?: QueryParams, +): Promise> { + const req = await api.post("/shares", params); + return req.data; +} + +export async function createShare(data: ICreateShare): Promise { + const req = await api.post("/shares/create", data); + return req.data; +} + +export async function getShareInfo(shareId: string): Promise { + const req = await api.post("/shares/info", { shareId }); + return req.data; +} + +export async function updateShare(data: IUpdateShare): Promise { + const req = await api.post("/shares/update", data); + return req.data; +} + +export async function getShareForPage(pageId: string): Promise { + const req = await api.post("/shares/for-page", { pageId }); + return req.data; +} + +export async function getSharePageInfo( + shareInput: Partial, +): Promise { + const req = await api.post("/shares/page-info", shareInput); + return req.data; +} + +export async function deleteShare(shareId: string): Promise { + await api.post("/shares/delete", { shareId }); +} + +export async function getSharedPageTree( + shareId: string, +): Promise { + const req = await api.post("/shares/tree", { shareId }); + return req.data; +} diff --git a/apps/client/src/features/share/types/share.types.ts b/apps/client/src/features/share/types/share.types.ts new file mode 100644 index 00000000..c40801e8 --- /dev/null +++ b/apps/client/src/features/share/types/share.types.ts @@ -0,0 +1,73 @@ +import { IPage } from "@/features/page/types/page.types.ts"; + +export interface IShare { + id: string; + key: string; + pageId: string; + includeSubPages: boolean; + searchIndexing: boolean; + creatorId: string; + spaceId: string; + workspaceId: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + sharedPage?: ISharePage; +} + +export interface ISharedItem extends IShare { + page: { + id: string; + title: string; + slugId: string; + icon: string | null; + }; + space: { + id: string; + name: string; + slug: string; + userRole: string; + }; + creator: { + id: string; + name: string; + avatarUrl: string | null; + }; +} + +export interface ISharedPage extends IShare { + page: IPage; + share: IShare & { + level: number; + sharedPage: { id: string; slugId: string; title: string; icon: string }; + }; +} + +export interface IShareForPage extends IShare { + level: number; + sharedPage: ISharePage; +} + +interface ISharePage { + id: string; + slugId: string; + title: string; + icon: string; +} + +export interface ICreateShare { + pageId?: string; + includeSubPages?: boolean; + searchIndexing?: boolean; +} + +export type IUpdateShare = ICreateShare & { shareId: string; pageId?: string }; + +export interface IShareInfoInput { + pageId: string; +} + +export interface ISharedPageTree { + share: IShare; + pageTree: Partial; +} diff --git a/apps/client/src/features/share/utils.ts b/apps/client/src/features/share/utils.ts new file mode 100644 index 00000000..74ec349f --- /dev/null +++ b/apps/client/src/features/share/utils.ts @@ -0,0 +1,60 @@ +import { IPage } from "@/features/page/types/page.types.ts"; +import { sortPositionKeys } from "@/features/page/tree/utils"; + +export type SharedPageTreeNode = { + id: string; + slugId: string; + name: string; + icon?: string; + position: string; + spaceId: string; + parentPageId: string; + hasChildren: boolean; + children: SharedPageTreeNode[]; + label: string, + value: string, +}; + +export function buildSharedPageTree(pages: Partial): SharedPageTreeNode[] { + const pageMap: Record = {}; + + // Initialize each page as a tree node and store it in a map. + pages.forEach((page) => { + pageMap[page.id] = { + id: page.slugId, + slugId: page.slugId, + name: page.title, + icon: page.icon, + position: page.position, + // Initially assume a page has no children. + hasChildren: false, + spaceId: page.spaceId, + parentPageId: page.parentPageId, + label: page.title || 'untitled', + value: page.id, + children: [], + }; + }); + + // Build the tree structure. + const tree: SharedPageTreeNode[] = []; + pages.forEach((page) => { + if (page.parentPageId) { + // If the page has a parent, add it as a child of the parent node. + const parentNode = pageMap[page.parentPageId]; + if (parentNode) { + parentNode.children.push(pageMap[page.id]); + parentNode.hasChildren = true; + } else { + // Parent not found – treat this page as a top-level node. + tree.push(pageMap[page.id]); + } + } else { + // No parentPageId indicates a top-level page. + tree.push(pageMap[page.id]); + } + }); + + // Return the sorted tree. + return sortPositionKeys(tree); +} diff --git a/apps/client/src/features/space/components/sidebar/space-sidebar.tsx b/apps/client/src/features/space/components/sidebar/space-sidebar.tsx index 1df7a2c1..528e8051 100644 --- a/apps/client/src/features/space/components/sidebar/space-sidebar.tsx +++ b/apps/client/src/features/space/components/sidebar/space-sidebar.tsx @@ -38,6 +38,8 @@ import PageImportModal from "@/features/page/components/page-import-modal.tsx"; import { useTranslation } from "react-i18next"; import { SwitchSpace } from "./switch-space"; import ExportModal from "@/components/common/export-modal"; +import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; +import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts"; export function SpaceSidebar() { const { t } = useTranslation(); @@ -45,6 +47,9 @@ export function SpaceSidebar() { const location = useLocation(); const [opened, { open: openSettings, close: closeSettings }] = useDisclosure(false); + const [mobileSidebarOpened] = useAtom(mobileSidebarAtom); + const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom); + const { spaceSlug } = useParams(); const { data: space, isLoading, isError } = useGetSpaceBySlugQuery(spaceSlug); @@ -123,7 +128,12 @@ export function SpaceSidebar() { ) && ( { + handleCreatePage(); + if (mobileSidebarOpened) { + toggleMobileSidebar(); + } + }} >
+ + + {t("Public sharing")} - {getAppName()} + + + + + }> + {t( + "Publicly shared pages from spaces you are a member of will appear here", + )} + + + + + ); +} diff --git a/apps/client/src/pages/share/share-redirect.tsx b/apps/client/src/pages/share/share-redirect.tsx new file mode 100644 index 00000000..5653e83f --- /dev/null +++ b/apps/client/src/pages/share/share-redirect.tsx @@ -0,0 +1,35 @@ +import { useNavigate, useParams } from "react-router-dom"; +import { useEffect } from "react"; +import { buildSharedPageUrl } from "@/features/page/page.utils.ts"; +import { Error404 } from "@/components/ui/error-404.tsx"; +import { useGetShareByIdQuery } from "@/features/share/queries/share-query.ts"; + +export default function ShareRedirect() { + const { shareId } = useParams(); + const navigate = useNavigate(); + + const { data: share, isLoading, isError } = useGetShareByIdQuery(shareId); + + useEffect(() => { + if (share) { + navigate( + buildSharedPageUrl({ + shareId: share.key, + pageSlugId: share?.sharedPage.slugId, + pageTitle: share?.sharedPage.title, + }), + { replace: true }, + ); + } + }, [isLoading, share]); + + if (isError) { + return ; + } + + if (isLoading) { + return <>; + } + + return null; +} diff --git a/apps/client/src/pages/share/shared-page.tsx b/apps/client/src/pages/share/shared-page.tsx new file mode 100644 index 00000000..a574a614 --- /dev/null +++ b/apps/client/src/pages/share/shared-page.tsx @@ -0,0 +1,58 @@ +import { useNavigate, useParams } from "react-router-dom"; +import { Helmet } from "react-helmet-async"; +import { useTranslation } from "react-i18next"; +import { useSharePageQuery } from "@/features/share/queries/share-query.ts"; +import { Container } from "@mantine/core"; +import React, { useEffect } from "react"; +import ReadonlyPageEditor from "@/features/editor/readonly-page-editor.tsx"; +import { extractPageSlugId } from "@/lib"; +import { Error404 } from "@/components/ui/error-404.tsx"; + +export default function SingleSharedPage() { + const { t } = useTranslation(); + const { pageSlug } = useParams(); + const { shareId } = useParams(); + const navigate = useNavigate(); + + const { data, isLoading, isError, error } = useSharePageQuery({ + pageId: extractPageSlugId(pageSlug), + }); + + useEffect(() => { + if (shareId && data) { + if (data.share.key !== shareId) { + navigate(`/share/${data.share.key}/p/${pageSlug}`, { replace: true }); + } + } + }, [shareId, data]); + + if (isLoading) { + return <>; + } + + if (isError || !data) { + if ([401, 403, 404].includes(error?.["status"])) { + return ; + } + return
{t("Error fetching page data.")}
; + } + + return ( +
+ + {`${data?.page?.title || t("untitled")}`} + {!data?.share.searchIndexing && ( + + )} + + + + + +
+ ); +} diff --git a/apps/server/package.json b/apps/server/package.json index fced5e33..efd8d8ca 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -37,18 +37,18 @@ "@fastify/multipart": "^9.0.3", "@fastify/static": "^8.1.1", "@nestjs/bullmq": "^11.0.2", - "@nestjs/common": "^11.0.10", - "@nestjs/config": "^4.0.0", - "@nestjs/core": "^11.0.10", + "@nestjs/common": "^11.0.20", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.0.20", "@nestjs/event-emitter": "^3.0.0", "@nestjs/jwt": "^11.0.0", "@nestjs/mapped-types": "^2.1.0", "@nestjs/passport": "^11.0.5", - "@nestjs/platform-fastify": "^11.0.10", - "@nestjs/platform-socket.io": "^11.0.10", + "@nestjs/platform-fastify": "^11.0.20", + "@nestjs/platform-socket.io": "^11.0.20", "@nestjs/schedule": "^5.0.1", "@nestjs/terminus": "^11.0.0", - "@nestjs/websockets": "^11.0.10", + "@nestjs/websockets": "^11.0.20", "@node-saml/passport-saml": "^5.0.1", "@react-email/components": "0.0.28", "@react-email/render": "1.0.2", diff --git a/apps/server/src/common/helpers/prosemirror/utils.ts b/apps/server/src/common/helpers/prosemirror/utils.ts index 9d9b5ebe..aaadcd56 100644 --- a/apps/server/src/common/helpers/prosemirror/utils.ts +++ b/apps/server/src/common/helpers/prosemirror/utils.ts @@ -1,5 +1,6 @@ import { Node } from '@tiptap/pm/model'; import { jsonToNode } from '../../../collaboration/collaboration.util'; +import { validate as isValidUUID } from 'uuid'; export interface MentionNode { id: string; @@ -56,3 +57,41 @@ export function extractPageMentions(mentionList: MentionNode[]): MentionNode[] { } return pageMentionList as MentionNode[]; } + + +export function getProsemirrorContent(content: any) { + return ( + content ?? { + type: 'doc', + content: [{ type: 'paragraph', attrs: { textAlign: 'left' } }], + } + ); +} + +export function isAttachmentNode(nodeType: string) { + const attachmentNodeTypes = [ + 'attachment', + 'image', + 'video', + 'excalidraw', + 'drawio', + ]; + return attachmentNodeTypes.includes(nodeType); +} + +export function getAttachmentIds(prosemirrorJson: any) { + const doc = jsonToNode(prosemirrorJson); + const attachmentIds = []; + + doc?.descendants((node: Node) => { + if (isAttachmentNode(node.type.name)) { + if (node.attrs.attachmentId && isValidUUID(node.attrs.attachmentId)) { + if (!attachmentIds.includes(node.attrs.attachmentId)) { + attachmentIds.push(node.attrs.attachmentId); + } + } + } + }); + + return attachmentIds; +} \ No newline at end of file diff --git a/apps/server/src/core/attachment/attachment.controller.ts b/apps/server/src/core/attachment/attachment.controller.ts index 4804fce6..160d950b 100644 --- a/apps/server/src/core/attachment/attachment.controller.ts +++ b/apps/server/src/core/attachment/attachment.controller.ts @@ -1,310 +1,373 @@ import { - BadRequestException, - Controller, - ForbiddenException, - Get, - HttpCode, - HttpStatus, - Logger, - NotFoundException, - Param, - Post, - Req, - Res, - UseGuards, - UseInterceptors, + BadRequestException, + Controller, + ForbiddenException, + Get, + HttpCode, + HttpStatus, + Logger, + NotFoundException, + Param, + Post, + Query, + Req, + Res, + UseGuards, + UseInterceptors, } from '@nestjs/common'; -import {AttachmentService} from './services/attachment.service'; -import {FastifyReply} from 'fastify'; -import {FileInterceptor} from '../../common/interceptors/file.interceptor'; +import { AttachmentService } from './services/attachment.service'; +import { FastifyReply } from 'fastify'; +import { FileInterceptor } from '../../common/interceptors/file.interceptor'; import * as bytes from 'bytes'; -import {AuthUser} from '../../common/decorators/auth-user.decorator'; -import {AuthWorkspace} from '../../common/decorators/auth-workspace.decorator'; -import {JwtAuthGuard} from '../../common/guards/jwt-auth.guard'; -import {User, Workspace} from '@docmost/db/types/entity.types'; -import {StorageService} from '../../integrations/storage/storage.service'; +import { AuthUser } from '../../common/decorators/auth-user.decorator'; +import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator'; +import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; +import { User, Workspace } from '@docmost/db/types/entity.types'; +import { StorageService } from '../../integrations/storage/storage.service'; import { - getAttachmentFolderPath, - validAttachmentTypes, + getAttachmentFolderPath, + validAttachmentTypes, } from './attachment.utils'; -import {getMimeType} from '../../common/helpers'; +import { getMimeType } from '../../common/helpers'; import { - AttachmentType, - inlineFileExtensions, - MAX_AVATAR_SIZE, + AttachmentType, + inlineFileExtensions, + MAX_AVATAR_SIZE, } from './attachment.constants'; import { - SpaceCaslAction, - SpaceCaslSubject, + SpaceCaslAction, + SpaceCaslSubject, } from '../casl/interfaces/space-ability.type'; import SpaceAbilityFactory from '../casl/abilities/space-ability.factory'; import { - WorkspaceCaslAction, - WorkspaceCaslSubject, + WorkspaceCaslAction, + WorkspaceCaslSubject, } from '../casl/interfaces/workspace-ability.type'; import WorkspaceAbilityFactory from '../casl/abilities/workspace-ability.factory'; -import {PageRepo} from '@docmost/db/repos/page/page.repo'; -import {AttachmentRepo} from '@docmost/db/repos/attachment/attachment.repo'; -import {validate as isValidUUID} from 'uuid'; -import {EnvironmentService} from "../../integrations/environment/environment.service"; +import { PageRepo } from '@docmost/db/repos/page/page.repo'; +import { AttachmentRepo } from '@docmost/db/repos/attachment/attachment.repo'; +import { validate as isValidUUID } from 'uuid'; +import { EnvironmentService } from '../../integrations/environment/environment.service'; +import { TokenService } from '../auth/services/token.service'; +import { JwtAttachmentPayload, JwtType } from '../auth/dto/jwt-payload'; @Controller() export class AttachmentController { - private readonly logger = new Logger(AttachmentController.name); + private readonly logger = new Logger(AttachmentController.name); - constructor( - private readonly attachmentService: AttachmentService, - private readonly storageService: StorageService, - private readonly workspaceAbility: WorkspaceAbilityFactory, - private readonly spaceAbility: SpaceAbilityFactory, - private readonly pageRepo: PageRepo, - private readonly attachmentRepo: AttachmentRepo, - private readonly environmentService: EnvironmentService, - ) { - } + constructor( + private readonly attachmentService: AttachmentService, + private readonly storageService: StorageService, + private readonly workspaceAbility: WorkspaceAbilityFactory, + private readonly spaceAbility: SpaceAbilityFactory, + private readonly pageRepo: PageRepo, + private readonly attachmentRepo: AttachmentRepo, + private readonly environmentService: EnvironmentService, + private readonly tokenService: TokenService, + ) {} - @UseGuards(JwtAuthGuard) - @HttpCode(HttpStatus.OK) - @Post('files/upload') - @UseInterceptors(FileInterceptor) - async uploadFile( - @Req() req: any, - @Res() res: FastifyReply, - @AuthUser() user: User, - @AuthWorkspace() workspace: Workspace, - ) { - const maxFileSize = bytes(this.environmentService.getFileUploadSizeLimit()); + @UseGuards(JwtAuthGuard) + @HttpCode(HttpStatus.OK) + @Post('files/upload') + @UseInterceptors(FileInterceptor) + async uploadFile( + @Req() req: any, + @Res() res: FastifyReply, + @AuthUser() user: User, + @AuthWorkspace() workspace: Workspace, + ) { + const maxFileSize = bytes(this.environmentService.getFileUploadSizeLimit()); - let file = null; - try { - file = await req.file({ - limits: {fileSize: maxFileSize, fields: 3, files: 1}, - }); - } catch (err: any) { - this.logger.error(err.message); - if (err?.statusCode === 413) { - throw new BadRequestException( - `File too large. Exceeds the ${this.environmentService.getFileUploadSizeLimit()} limit`, - ); - } - } - - if (!file) { - throw new BadRequestException('Failed to upload file'); - } - - const pageId = file.fields?.pageId?.value; - - if (!pageId) { - throw new BadRequestException('PageId is required'); - } - - const page = await this.pageRepo.findById(pageId); - - if (!page) { - throw new NotFoundException('Page not found'); - } - - const spaceAbility = await this.spaceAbility.createForUser( - user, - page.spaceId, + let file = null; + try { + file = await req.file({ + limits: { fileSize: maxFileSize, fields: 3, files: 1 }, + }); + } catch (err: any) { + this.logger.error(err.message); + if (err?.statusCode === 413) { + throw new BadRequestException( + `File too large. Exceeds the ${this.environmentService.getFileUploadSizeLimit()} limit`, ); - if (spaceAbility.cannot(SpaceCaslAction.Manage, SpaceCaslSubject.Page)) { - throw new ForbiddenException(); - } - - const spaceId = page.spaceId; - - const attachmentId = file.fields?.attachmentId?.value; - if (attachmentId && !isValidUUID(attachmentId)) { - throw new BadRequestException('Invalid attachment id'); - } - - try { - const fileResponse = await this.attachmentService.uploadFile({ - filePromise: file, - pageId: pageId, - spaceId: spaceId, - userId: user.id, - workspaceId: workspace.id, - attachmentId: attachmentId, - }); - - return res.send(fileResponse); - } catch (err: any) { - if (err?.statusCode === 413) { - const errMessage = `File too large. Exceeds the ${this.environmentService.getFileUploadSizeLimit()} limit`; - this.logger.error(errMessage); - throw new BadRequestException(errMessage); - } - this.logger.error(err); - throw new BadRequestException('Error processing file upload.'); - } + } } - @UseGuards(JwtAuthGuard) - @Get('/files/:fileId/:fileName') - async getFile( - @Res() res: FastifyReply, - @AuthUser() user: User, - @AuthWorkspace() workspace: Workspace, - @Param('fileId') fileId: string, - @Param('fileName') fileName?: string, + if (!file) { + throw new BadRequestException('Failed to upload file'); + } + + const pageId = file.fields?.pageId?.value; + + if (!pageId) { + throw new BadRequestException('PageId is required'); + } + + const page = await this.pageRepo.findById(pageId); + + if (!page) { + throw new NotFoundException('Page not found'); + } + + const spaceAbility = await this.spaceAbility.createForUser( + user, + page.spaceId, + ); + if (spaceAbility.cannot(SpaceCaslAction.Manage, SpaceCaslSubject.Page)) { + throw new ForbiddenException(); + } + + const spaceId = page.spaceId; + + const attachmentId = file.fields?.attachmentId?.value; + if (attachmentId && !isValidUUID(attachmentId)) { + throw new BadRequestException('Invalid attachment id'); + } + + try { + const fileResponse = await this.attachmentService.uploadFile({ + filePromise: file, + pageId: pageId, + spaceId: spaceId, + userId: user.id, + workspaceId: workspace.id, + attachmentId: attachmentId, + }); + + return res.send(fileResponse); + } catch (err: any) { + if (err?.statusCode === 413) { + const errMessage = `File too large. Exceeds the ${this.environmentService.getFileUploadSizeLimit()} limit`; + this.logger.error(errMessage); + throw new BadRequestException(errMessage); + } + this.logger.error(err); + throw new BadRequestException('Error processing file upload.'); + } + } + + @UseGuards(JwtAuthGuard) + @Get('/files/:fileId/:fileName') + async getFile( + @Res() res: FastifyReply, + @AuthUser() user: User, + @AuthWorkspace() workspace: Workspace, + @Param('fileId') fileId: string, + @Param('fileName') fileName?: string, + ) { + if (!isValidUUID(fileId)) { + throw new NotFoundException('Invalid file id'); + } + + const attachment = await this.attachmentRepo.findById(fileId); + if ( + !attachment || + attachment.workspaceId !== workspace.id || + !attachment.pageId || + !attachment.spaceId ) { - if (!isValidUUID(fileId)) { - throw new NotFoundException('Invalid file id'); - } + throw new NotFoundException(); + } - const attachment = await this.attachmentRepo.findById(fileId); - if ( - !attachment || - attachment.workspaceId !== workspace.id || - !attachment.pageId || - !attachment.spaceId - ) { - throw new NotFoundException(); - } + const spaceAbility = await this.spaceAbility.createForUser( + user, + attachment.spaceId, + ); - const spaceAbility = await this.spaceAbility.createForUser( - user, - attachment.spaceId, + if (spaceAbility.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) { + throw new ForbiddenException(); + } + + try { + const fileStream = await this.storageService.read(attachment.filePath); + res.headers({ + 'Content-Type': attachment.mimeType, + 'Cache-Control': 'private, max-age=3600', + }); + + if (!inlineFileExtensions.includes(attachment.fileExt)) { + res.header( + 'Content-Disposition', + `attachment; filename="${encodeURIComponent(attachment.fileName)}"`, ); + } - if (spaceAbility.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) { - throw new ForbiddenException(); - } + return res.send(fileStream); + } catch (err) { + this.logger.error(err); + throw new NotFoundException('File not found'); + } + } - try { - const fileStream = await this.storageService.read(attachment.filePath); - res.headers({ - 'Content-Type': attachment.mimeType, - 'Cache-Control': 'private, max-age=3600', - }); - - if (!inlineFileExtensions.includes(attachment.fileExt)) { - res.header( - 'Content-Disposition', - `attachment; filename="${encodeURIComponent(attachment.fileName)}"`, - ); - } - - return res.send(fileStream); - } catch (err) { - this.logger.error(err); - throw new NotFoundException('File not found'); - } + @Get('/files/public/:fileId/:fileName') + async getPublicFile( + @Res() res: FastifyReply, + @AuthWorkspace() workspace: Workspace, + @Param('fileId') fileId: string, + @Param('fileName') fileName?: string, + @Query('jwt') jwtToken?: string, + ) { + let jwtPayload: JwtAttachmentPayload = null; + try { + jwtPayload = await this.tokenService.verifyJwt( + jwtToken, + JwtType.ATTACHMENT, + ); + } catch (err) { + throw new BadRequestException( + 'Expired or invalid attachment access token', + ); } - @UseGuards(JwtAuthGuard) - @HttpCode(HttpStatus.OK) - @Post('attachments/upload-image') - @UseInterceptors(FileInterceptor) - async uploadAvatarOrLogo( - @Req() req: any, - @Res() res: FastifyReply, - @AuthUser() user: User, - @AuthWorkspace() workspace: Workspace, + if ( + !isValidUUID(fileId) || + fileId !== jwtPayload.attachmentId || + jwtPayload.workspaceId !== workspace.id ) { - const maxFileSize = bytes(MAX_AVATAR_SIZE); - - let file = null; - try { - file = await req.file({ - limits: {fileSize: maxFileSize, fields: 3, files: 1}, - }); - } catch (err: any) { - if (err?.statusCode === 413) { - throw new BadRequestException( - `File too large. Exceeds the ${MAX_AVATAR_SIZE} limit`, - ); - } - } - - if (!file) { - throw new BadRequestException('Invalid file upload'); - } - - const attachmentType = file.fields?.type?.value; - const spaceId = file.fields?.spaceId?.value; - - if (!attachmentType) { - throw new BadRequestException('attachment type is required'); - } - - if ( - !validAttachmentTypes.includes(attachmentType) || - attachmentType === AttachmentType.File - ) { - throw new BadRequestException('Invalid image attachment type'); - } - - if (attachmentType === AttachmentType.WorkspaceLogo) { - const ability = this.workspaceAbility.createForUser(user, workspace); - if ( - ability.cannot( - WorkspaceCaslAction.Manage, - WorkspaceCaslSubject.Settings, - ) - ) { - throw new ForbiddenException(); - } - } - - if (attachmentType === AttachmentType.SpaceLogo) { - if (!spaceId) { - throw new BadRequestException('spaceId is required'); - } - - const spaceAbility = await this.spaceAbility.createForUser(user, spaceId); - if ( - spaceAbility.cannot(SpaceCaslAction.Manage, SpaceCaslSubject.Settings) - ) { - throw new ForbiddenException(); - } - } - - try { - const fileResponse = await this.attachmentService.uploadImage( - file, - attachmentType, - user.id, - workspace.id, - spaceId, - ); - - return res.send(fileResponse); - } catch (err: any) { - this.logger.error(err); - throw new BadRequestException('Error processing file upload.'); - } + throw new NotFoundException('File not found'); } - @Get('attachments/img/:attachmentType/:fileName') - async getLogoOrAvatar( - @Res() res: FastifyReply, - @AuthWorkspace() workspace: Workspace, - @Param('attachmentType') attachmentType: AttachmentType, - @Param('fileName') fileName?: string, + const attachment = await this.attachmentRepo.findById(fileId); + if ( + !attachment || + attachment.workspaceId !== workspace.id || + !attachment.pageId || + !attachment.spaceId || + jwtPayload.pageId !== attachment.pageId ) { - if ( - !validAttachmentTypes.includes(attachmentType) || - attachmentType === AttachmentType.File - ) { - throw new BadRequestException('Invalid image attachment type'); - } - - const filePath = `${getAttachmentFolderPath(attachmentType, workspace.id)}/${fileName}`; - - try { - const fileStream = await this.storageService.read(filePath); - res.headers({ - 'Content-Type': getMimeType(filePath), - 'Cache-Control': 'private, max-age=86400', - }); - return res.send(fileStream); - } catch (err) { - this.logger.error(err); - throw new NotFoundException('File not found'); - } + throw new NotFoundException('File not found'); } + + try { + const fileStream = await this.storageService.read(attachment.filePath); + res.headers({ + 'Content-Type': attachment.mimeType, + 'Cache-Control': 'public, max-age=3600', + }); + + if (!inlineFileExtensions.includes(attachment.fileExt)) { + res.header( + 'Content-Disposition', + `attachment; filename="${encodeURIComponent(attachment.fileName)}"`, + ); + } + + return res.send(fileStream); + } catch (err) { + this.logger.error(err); + throw new NotFoundException('File not found'); + } + } + + @UseGuards(JwtAuthGuard) + @HttpCode(HttpStatus.OK) + @Post('attachments/upload-image') + @UseInterceptors(FileInterceptor) + async uploadAvatarOrLogo( + @Req() req: any, + @Res() res: FastifyReply, + @AuthUser() user: User, + @AuthWorkspace() workspace: Workspace, + ) { + const maxFileSize = bytes(MAX_AVATAR_SIZE); + + let file = null; + try { + file = await req.file({ + limits: { fileSize: maxFileSize, fields: 3, files: 1 }, + }); + } catch (err: any) { + if (err?.statusCode === 413) { + throw new BadRequestException( + `File too large. Exceeds the ${MAX_AVATAR_SIZE} limit`, + ); + } + } + + if (!file) { + throw new BadRequestException('Invalid file upload'); + } + + const attachmentType = file.fields?.type?.value; + const spaceId = file.fields?.spaceId?.value; + + if (!attachmentType) { + throw new BadRequestException('attachment type is required'); + } + + if ( + !validAttachmentTypes.includes(attachmentType) || + attachmentType === AttachmentType.File + ) { + throw new BadRequestException('Invalid image attachment type'); + } + + if (attachmentType === AttachmentType.WorkspaceLogo) { + const ability = this.workspaceAbility.createForUser(user, workspace); + if ( + ability.cannot( + WorkspaceCaslAction.Manage, + WorkspaceCaslSubject.Settings, + ) + ) { + throw new ForbiddenException(); + } + } + + if (attachmentType === AttachmentType.SpaceLogo) { + if (!spaceId) { + throw new BadRequestException('spaceId is required'); + } + + const spaceAbility = await this.spaceAbility.createForUser(user, spaceId); + if ( + spaceAbility.cannot(SpaceCaslAction.Manage, SpaceCaslSubject.Settings) + ) { + throw new ForbiddenException(); + } + } + + try { + const fileResponse = await this.attachmentService.uploadImage( + file, + attachmentType, + user.id, + workspace.id, + spaceId, + ); + + return res.send(fileResponse); + } catch (err: any) { + this.logger.error(err); + throw new BadRequestException('Error processing file upload.'); + } + } + + @Get('attachments/img/:attachmentType/:fileName') + async getLogoOrAvatar( + @Res() res: FastifyReply, + @AuthWorkspace() workspace: Workspace, + @Param('attachmentType') attachmentType: AttachmentType, + @Param('fileName') fileName?: string, + ) { + if ( + !validAttachmentTypes.includes(attachmentType) || + attachmentType === AttachmentType.File + ) { + throw new BadRequestException('Invalid image attachment type'); + } + + const filePath = `${getAttachmentFolderPath(attachmentType, workspace.id)}/${fileName}`; + + try { + const fileStream = await this.storageService.read(filePath); + res.headers({ + 'Content-Type': getMimeType(filePath), + 'Cache-Control': 'private, max-age=86400', + }); + return res.send(fileStream); + } catch (err) { + this.logger.error(err); + throw new NotFoundException('File not found'); + } + } } diff --git a/apps/server/src/core/attachment/attachment.module.ts b/apps/server/src/core/attachment/attachment.module.ts index 7dc47ed8..f80a2eb7 100644 --- a/apps/server/src/core/attachment/attachment.module.ts +++ b/apps/server/src/core/attachment/attachment.module.ts @@ -5,9 +5,10 @@ import { StorageModule } from '../../integrations/storage/storage.module'; import { UserModule } from '../user/user.module'; import { WorkspaceModule } from '../workspace/workspace.module'; import { AttachmentProcessor } from './processors/attachment.processor'; +import { TokenModule } from '../auth/token.module'; @Module({ - imports: [StorageModule, UserModule, WorkspaceModule], + imports: [StorageModule, UserModule, WorkspaceModule, TokenModule], controllers: [AttachmentController], providers: [AttachmentService, AttachmentProcessor], }) diff --git a/apps/server/src/core/auth/dto/jwt-payload.ts b/apps/server/src/core/auth/dto/jwt-payload.ts index ad172b78..b9ce13c4 100644 --- a/apps/server/src/core/auth/dto/jwt-payload.ts +++ b/apps/server/src/core/auth/dto/jwt-payload.ts @@ -2,6 +2,7 @@ export enum JwtType { ACCESS = 'access', COLLAB = 'collab', EXCHANGE = 'exchange', + ATTACHMENT = 'attachment', } export type JwtPayload = { sub: string; @@ -21,3 +22,11 @@ export type JwtExchangePayload = { workspaceId: string; type: 'exchange'; }; + +export type JwtAttachmentPayload = { + attachmentId: string; + pageId: string; + workspaceId: string; + type: 'attachment'; +}; + diff --git a/apps/server/src/core/auth/services/token.service.ts b/apps/server/src/core/auth/services/token.service.ts index ad745290..963e8e65 100644 --- a/apps/server/src/core/auth/services/token.service.ts +++ b/apps/server/src/core/auth/services/token.service.ts @@ -6,6 +6,7 @@ import { import { JwtService } from '@nestjs/jwt'; import { EnvironmentService } from '../../../integrations/environment/environment.service'; import { + JwtAttachmentPayload, JwtCollabPayload, JwtExchangePayload, JwtPayload, @@ -59,6 +60,21 @@ export class TokenService { return this.jwtService.sign(payload, { expiresIn: '10s' }); } + async generateAttachmentToken(opts: { + attachmentId: string; + pageId: string; + workspaceId: string; + }): Promise { + const { attachmentId, pageId, workspaceId } = opts; + const payload: JwtAttachmentPayload = { + attachmentId: attachmentId, + pageId: pageId, + workspaceId: workspaceId, + type: JwtType.ATTACHMENT, + }; + return this.jwtService.sign(payload, { expiresIn: '1h' }); + } + async verifyJwt(token: string, tokenType: string) { const payload = await this.jwtService.verifyAsync(token, { secret: this.environmentService.getAppSecret(), diff --git a/apps/server/src/core/casl/abilities/space-ability.factory.ts b/apps/server/src/core/casl/abilities/space-ability.factory.ts index d2173383..53a57a0c 100644 --- a/apps/server/src/core/casl/abilities/space-ability.factory.ts +++ b/apps/server/src/core/casl/abilities/space-ability.factory.ts @@ -45,6 +45,7 @@ function buildSpaceAdminAbility() { can(SpaceCaslAction.Manage, SpaceCaslSubject.Settings); can(SpaceCaslAction.Manage, SpaceCaslSubject.Member); can(SpaceCaslAction.Manage, SpaceCaslSubject.Page); + can(SpaceCaslAction.Manage, SpaceCaslSubject.Share); return build(); } @@ -55,6 +56,7 @@ function buildSpaceWriterAbility() { can(SpaceCaslAction.Read, SpaceCaslSubject.Settings); can(SpaceCaslAction.Read, SpaceCaslSubject.Member); can(SpaceCaslAction.Manage, SpaceCaslSubject.Page); + can(SpaceCaslAction.Manage, SpaceCaslSubject.Share); return build(); } @@ -65,5 +67,6 @@ function buildSpaceReaderAbility() { can(SpaceCaslAction.Read, SpaceCaslSubject.Settings); can(SpaceCaslAction.Read, SpaceCaslSubject.Member); can(SpaceCaslAction.Read, SpaceCaslSubject.Page); + can(SpaceCaslAction.Read, SpaceCaslSubject.Share); return build(); } diff --git a/apps/server/src/core/casl/interfaces/space-ability.type.ts b/apps/server/src/core/casl/interfaces/space-ability.type.ts index c927229b..d7801cab 100644 --- a/apps/server/src/core/casl/interfaces/space-ability.type.ts +++ b/apps/server/src/core/casl/interfaces/space-ability.type.ts @@ -9,9 +9,11 @@ export enum SpaceCaslSubject { Settings = 'settings', Member = 'member', Page = 'page', + Share = 'share', } export type ISpaceAbility = | [SpaceCaslAction, SpaceCaslSubject.Settings] | [SpaceCaslAction, SpaceCaslSubject.Member] - | [SpaceCaslAction, SpaceCaslSubject.Page]; + | [SpaceCaslAction, SpaceCaslSubject.Page] + | [SpaceCaslAction, SpaceCaslSubject.Share]; diff --git a/apps/server/src/core/core.module.ts b/apps/server/src/core/core.module.ts index 182a1420..f7f4f785 100644 --- a/apps/server/src/core/core.module.ts +++ b/apps/server/src/core/core.module.ts @@ -15,6 +15,7 @@ import { SpaceModule } from './space/space.module'; import { GroupModule } from './group/group.module'; import { CaslModule } from './casl/casl.module'; import { DomainMiddleware } from '../common/middlewares/domain.middleware'; +import { ShareModule } from './share/share.module'; @Module({ imports: [ @@ -28,6 +29,7 @@ import { DomainMiddleware } from '../common/middlewares/domain.middleware'; SpaceModule, GroupModule, CaslModule, + ShareModule, ], }) export class CoreModule implements NestModule { diff --git a/apps/server/src/core/page/services/page.service.ts b/apps/server/src/core/page/services/page.service.ts index 43d8f1d2..5e4553c6 100644 --- a/apps/server/src/core/page/services/page.service.ts +++ b/apps/server/src/core/page/services/page.service.ts @@ -212,7 +212,7 @@ export class PageService { trx, ); const pageIds = await this.pageRepo - .getPageAndDescendants(rootPage.id) + .getPageAndDescendants(rootPage.id, { includeContent: false }) .then((pages) => pages.map((page) => page.id)); // The first id is the root page id if (pageIds.length > 1) { @@ -223,6 +223,16 @@ export class PageService { trx, ); } + + // update spaceId in shares + if (pageIds.length > 0) { + await trx + .updateTable('shares') + .set({ spaceId: spaceId }) + .where('pageId', 'in', pageIds) + .execute(); + } + // Update attachments await this.attachmentRepo.updateAttachmentsByPageId( { spaceId }, diff --git a/apps/server/src/core/share/dto/share.dto.ts b/apps/server/src/core/share/dto/share.dto.ts new file mode 100644 index 00000000..b6e789ec --- /dev/null +++ b/apps/server/src/core/share/dto/share.dto.ts @@ -0,0 +1,58 @@ +import { + IsBoolean, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, +} from 'class-validator'; + +export class CreateShareDto { + @IsString() + @IsNotEmpty() + pageId: string; + + @IsBoolean() + @IsOptional() + includeSubPages: boolean; + + @IsOptional() + @IsBoolean() + searchIndexing: boolean; +} + +export class UpdateShareDto extends CreateShareDto { + @IsString() + @IsNotEmpty() + shareId: string; + + @IsString() + @IsOptional() + pageId: string; +} + +export class ShareIdDto { + @IsString() + @IsNotEmpty() + shareId: string; +} + +export class SpaceIdDto { + @IsUUID() + spaceId: string; +} + +export class ShareInfoDto { + @IsString() + @IsOptional() + shareId?: string; + + @IsString() + @IsOptional() + pageId: string; +} + +export class SharePageIdDto { + @IsString() + @IsNotEmpty() + pageId: string; +} diff --git a/apps/server/src/core/share/share-seo.controller.ts b/apps/server/src/core/share/share-seo.controller.ts new file mode 100644 index 00000000..ecacecf0 --- /dev/null +++ b/apps/server/src/core/share/share-seo.controller.ts @@ -0,0 +1,109 @@ +import { Controller, Get, Param, Req, Res } from '@nestjs/common'; +import { ShareService } from './share.service'; +import { FastifyReply, FastifyRequest } from 'fastify'; +import { join } from 'path'; +import * as fs from 'node:fs'; +import { validate as isValidUUID } from 'uuid'; +import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo'; +import { EnvironmentService } from '../../integrations/environment/environment.service'; +import { Workspace } from '@docmost/db/types/entity.types'; + +@Controller('share') +export class ShareSeoController { + constructor( + private readonly shareService: ShareService, + private workspaceRepo: WorkspaceRepo, + private environmentService: EnvironmentService, + ) {} + + /* + * add meta tags to publicly shared pages + */ + @Get([':shareId/p/:pageSlug', 'p/:pageSlug']) + async getShare( + @Res({ passthrough: false }) res: FastifyReply, + @Req() req: FastifyRequest, + @Param('shareId') shareId: string, + @Param('pageSlug') pageSlug: string, + ) { + // Nestjs does not to apply middlewares to paths excluded from the global /api prefix + // https://github.com/nestjs/nest/issues/9124 + // https://github.com/nestjs/nest/issues/11572 + // https://github.com/nestjs/nest/issues/13401 + // we have to duplicate the DomainMiddleware code here as a workaround + + let workspace: Workspace = null; + if (this.environmentService.isSelfHosted()) { + workspace = await this.workspaceRepo.findFirst(); + } else { + const header = req.raw.headers.host; + const subdomain = header.split('.')[0]; + workspace = await this.workspaceRepo.findByHostname(subdomain); + } + + const clientDistPath = join( + __dirname, + '..', + '..', + '..', + '..', + 'client/dist', + ); + + if (fs.existsSync(clientDistPath)) { + const indexFilePath = join(clientDistPath, 'index.html'); + + if (!workspace) { + return this.sendIndex(indexFilePath, res); + } + + const pageId = this.extractPageSlugId(pageSlug); + + const share = await this.shareService.getShareForPage( + pageId, + workspace.id, + ); + + if (!share) { + return this.sendIndex(indexFilePath, res); + } + + const rawTitle = share.sharedPage.title ?? 'untitled'; + const metaTitle = + rawTitle.length > 80 ? `${rawTitle.slice(0, 77)}…` : rawTitle; + + const metaTagVar = ''; + + const metaTags = [ + ``, + ``, + !share.searchIndexing ? `` : '', + ] + .filter(Boolean) + .join('\n '); + + const html = fs.readFileSync(indexFilePath, 'utf8'); + const transformedHtml = html + .replace(/[\s\S]*?<\/title>/i, `<title>${metaTitle}`) + .replace(metaTagVar, metaTags); + + res.type('text/html').send(transformedHtml); + } + } + + sendIndex(indexFilePath: string, res: FastifyReply) { + const stream = fs.createReadStream(indexFilePath); + res.type('text/html').send(stream); + } + + extractPageSlugId(slug: string): string { + if (!slug) { + return undefined; + } + if (isValidUUID(slug)) { + return slug; + } + const parts = slug.split('-'); + return parts.length > 1 ? parts[parts.length - 1] : slug; + } +} diff --git a/apps/server/src/core/share/share.controller.ts b/apps/server/src/core/share/share.controller.ts new file mode 100644 index 00000000..5e8debe0 --- /dev/null +++ b/apps/server/src/core/share/share.controller.ts @@ -0,0 +1,171 @@ +import { + BadRequestException, + Body, + Controller, + ForbiddenException, + HttpCode, + HttpStatus, + NotFoundException, + Post, + UseGuards, +} from '@nestjs/common'; +import { AuthUser } from '../../common/decorators/auth-user.decorator'; +import { User, Workspace } from '@docmost/db/types/entity.types'; +import { + SpaceCaslAction, + SpaceCaslSubject, +} from '../casl/interfaces/space-ability.type'; +import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator'; +import SpaceAbilityFactory from '../casl/abilities/space-ability.factory'; +import { ShareService } from './share.service'; +import { + CreateShareDto, + ShareIdDto, + ShareInfoDto, + SharePageIdDto, + UpdateShareDto, +} from './dto/share.dto'; +import { PageRepo } from '@docmost/db/repos/page/page.repo'; +import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; +import { Public } from '../../common/decorators/public.decorator'; +import { ShareRepo } from '@docmost/db/repos/share/share.repo'; +import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; + +@UseGuards(JwtAuthGuard) +@Controller('shares') +export class ShareController { + constructor( + private readonly shareService: ShareService, + private readonly spaceAbility: SpaceAbilityFactory, + private readonly shareRepo: ShareRepo, + private readonly pageRepo: PageRepo, + ) {} + + @HttpCode(HttpStatus.OK) + @Post('/') + async getShares( + @AuthUser() user: User, + @Body() pagination: PaginationOptions, + ) { + return this.shareRepo.getShares(user.id, pagination); + } + + @Public() + @HttpCode(HttpStatus.OK) + @Post('/page-info') + async getSharedPageInfo( + @Body() dto: ShareInfoDto, + @AuthWorkspace() workspace: Workspace, + ) { + if (!dto.pageId && !dto.shareId) { + throw new BadRequestException(); + } + + return this.shareService.getSharedPage(dto, workspace.id); + } + + @Public() + @HttpCode(HttpStatus.OK) + @Post('/info') + async getShare(@Body() dto: ShareIdDto) { + const share = await this.shareRepo.findById(dto.shareId, { + includeSharedPage: true, + }); + + if (!share) { + throw new NotFoundException('Share not found'); + } + + return share; + } + + @HttpCode(HttpStatus.OK) + @Post('/for-page') + async getShareForPage( + @Body() dto: SharePageIdDto, + @AuthUser() user: User, + @AuthWorkspace() workspace: Workspace, + ) { + const page = await this.pageRepo.findById(dto.pageId); + if (!page) { + throw new NotFoundException('Shared page not found'); + } + + const ability = await this.spaceAbility.createForUser(user, page.spaceId); + if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Share)) { + throw new ForbiddenException(); + } + + return this.shareService.getShareForPage(page.id, workspace.id); + } + + @HttpCode(HttpStatus.OK) + @Post('create') + async create( + @Body() createShareDto: CreateShareDto, + @AuthUser() user: User, + @AuthWorkspace() workspace: Workspace, + ) { + const page = await this.pageRepo.findById(createShareDto.pageId); + + if (!page || workspace.id !== page.workspaceId) { + throw new NotFoundException('Page not found'); + } + + const ability = await this.spaceAbility.createForUser(user, page.spaceId); + if (ability.cannot(SpaceCaslAction.Create, SpaceCaslSubject.Share)) { + throw new ForbiddenException(); + } + + return this.shareService.createShare({ + page, + authUserId: user.id, + workspaceId: workspace.id, + createShareDto, + }); + } + + @HttpCode(HttpStatus.OK) + @Post('update') + async update(@Body() updateShareDto: UpdateShareDto, @AuthUser() user: User) { + const share = await this.shareRepo.findById(updateShareDto.shareId); + + if (!share) { + throw new NotFoundException('Share not found'); + } + + const ability = await this.spaceAbility.createForUser(user, share.spaceId); + if (ability.cannot(SpaceCaslAction.Edit, SpaceCaslSubject.Share)) { + throw new ForbiddenException(); + } + + return this.shareService.updateShare(share.id, updateShareDto); + } + + @HttpCode(HttpStatus.OK) + @Post('delete') + async delete(@Body() shareIdDto: ShareIdDto, @AuthUser() user: User) { + const share = await this.shareRepo.findById(shareIdDto.shareId); + + if (!share) { + throw new NotFoundException('Share not found'); + } + + const ability = await this.spaceAbility.createForUser(user, share.spaceId); + if (ability.cannot(SpaceCaslAction.Manage, SpaceCaslSubject.Share)) { + throw new ForbiddenException(); + } + + await this.shareRepo.deleteShare(share.id); + } + + @Public() + @HttpCode(HttpStatus.OK) + @Post('/tree') + async getSharePageTree( + @Body() dto: ShareIdDto, + @AuthWorkspace() workspace: Workspace, + ) { + return this.shareService.getShareTree(dto.shareId, workspace.id); + } +} diff --git a/apps/server/src/core/share/share.module.ts b/apps/server/src/core/share/share.module.ts new file mode 100644 index 00000000..2ba9764e --- /dev/null +++ b/apps/server/src/core/share/share.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { ShareController } from './share.controller'; +import { ShareService } from './share.service'; +import { TokenModule } from '../auth/token.module'; +import { ShareSeoController } from './share-seo.controller'; + +@Module({ + imports: [TokenModule], + controllers: [ShareController, ShareSeoController], + providers: [ShareService], + exports: [ShareService], +}) +export class ShareModule {} diff --git a/apps/server/src/core/share/share.service.ts b/apps/server/src/core/share/share.service.ts new file mode 100644 index 00000000..a9140c0b --- /dev/null +++ b/apps/server/src/core/share/share.service.ts @@ -0,0 +1,297 @@ +import { + BadRequestException, + Injectable, + Logger, + NotFoundException, +} from '@nestjs/common'; +import { CreateShareDto, ShareInfoDto, UpdateShareDto } from './dto/share.dto'; +import { InjectKysely } from 'nestjs-kysely'; +import { KyselyDB } from '@docmost/db/types/kysely.types'; +import { nanoIdGen } from '../../common/helpers'; +import { PageRepo } from '@docmost/db/repos/page/page.repo'; +import { TokenService } from '../auth/services/token.service'; +import { jsonToNode } from '../../collaboration/collaboration.util'; +import { + getAttachmentIds, + getProsemirrorContent, + isAttachmentNode, +} from '../../common/helpers/prosemirror/utils'; +import { Node } from '@tiptap/pm/model'; +import { ShareRepo } from '@docmost/db/repos/share/share.repo'; +import { updateAttachmentAttr } from './share.util'; +import { Page } from '@docmost/db/types/entity.types'; +import { validate as isValidUUID } from 'uuid'; +import { sql } from 'kysely'; + +@Injectable() +export class ShareService { + private readonly logger = new Logger(ShareService.name); + + constructor( + private readonly shareRepo: ShareRepo, + private readonly pageRepo: PageRepo, + @InjectKysely() private readonly db: KyselyDB, + private readonly tokenService: TokenService, + ) {} + + async getShareTree(shareId: string, workspaceId: string) { + const share = await this.shareRepo.findById(shareId); + if (!share || share.workspaceId !== workspaceId) { + throw new NotFoundException('Share not found'); + } + + if (share.includeSubPages) { + const pageList = await this.pageRepo.getPageAndDescendants(share.pageId, { + includeContent: false, + }); + + return { share, pageTree: pageList }; + } else { + return { share, pageTree: [] }; + } + } + + async createShare(opts: { + authUserId: string; + workspaceId: string; + page: Page; + createShareDto: CreateShareDto; + }) { + const { authUserId, workspaceId, page, createShareDto } = opts; + + try { + const shares = await this.shareRepo.findByPageId(page.id); + if (shares) { + return shares; + } + + return await this.shareRepo.insertShare({ + key: nanoIdGen().toLowerCase(), + pageId: page.id, + includeSubPages: createShareDto.includeSubPages || true, + searchIndexing: createShareDto.searchIndexing || true, + creatorId: authUserId, + spaceId: page.spaceId, + workspaceId, + }); + } catch (err) { + this.logger.error(err); + throw new BadRequestException('Failed to share page'); + } + } + + async updateShare(shareId: string, updateShareDto: UpdateShareDto) { + try { + return this.shareRepo.updateShare( + { + includeSubPages: updateShareDto.includeSubPages, + searchIndexing: updateShareDto.searchIndexing, + }, + shareId, + ); + } catch (err) { + this.logger.error(err); + throw new BadRequestException('Failed to update share'); + } + } + + async getSharedPage(dto: ShareInfoDto, workspaceId: string) { + const share = await this.getShareForPage(dto.pageId, workspaceId); + + if (!share) { + throw new NotFoundException('Shared page not found'); + } + + const page = await this.pageRepo.findById(dto.pageId, { + includeContent: true, + includeCreator: true, + }); + + page.content = await this.updatePublicAttachments(page); + + if (!page) { + throw new NotFoundException('Shared page not found'); + } + + return { page, share }; + } + + async getShareForPage(pageId: string, workspaceId: string) { + // here we try to check if a page was shared directly or if it inherits the share from its closest shared ancestor + const share = await this.db + .withRecursive('page_hierarchy', (cte) => + cte + .selectFrom('pages') + .select([ + 'id', + 'slugId', + 'pages.title', + 'pages.icon', + 'parentPageId', + sql`0`.as('level'), + ]) + .where(isValidUUID(pageId) ? 'id' : 'slugId', '=', pageId) + .unionAll((union) => + union + .selectFrom('pages as p') + .select([ + 'p.id', + 'p.slugId', + 'p.title', + 'p.icon', + 'p.parentPageId', + // Increase the level by 1 for each ancestor. + sql`ph.level + 1`.as('level'), + ]) + .innerJoin('page_hierarchy as ph', 'ph.parentPageId', 'p.id'), + ), + ) + .selectFrom('page_hierarchy') + .leftJoin('shares', 'shares.pageId', 'page_hierarchy.id') + .select([ + 'page_hierarchy.id as sharedPageId', + 'page_hierarchy.slugId as sharedPageSlugId', + 'page_hierarchy.title as sharedPageTitle', + 'page_hierarchy.icon as sharedPageIcon', + 'page_hierarchy.level as level', + 'shares.id', + 'shares.key', + 'shares.pageId', + 'shares.includeSubPages', + 'shares.searchIndexing', + 'shares.creatorId', + 'shares.spaceId', + 'shares.workspaceId', + 'shares.createdAt', + 'shares.updatedAt', + ]) + .where('shares.id', 'is not', null) + .orderBy('page_hierarchy.level', 'asc') + .executeTakeFirst(); + + if (!share || share.workspaceId != workspaceId) { + return undefined; + } + + if (share.level === 1 && !share.includeSubPages) { + // we can only show a page if its shared ancestor permits it + return undefined; + } + + return { + id: share.id, + key: share.key, + includeSubPages: share.includeSubPages, + searchIndexing: share.searchIndexing, + pageId: share.pageId, + creatorId: share.creatorId, + spaceId: share.spaceId, + workspaceId: share.workspaceId, + createdAt: share.createdAt, + level: share.level, + sharedPage: { + id: share.sharedPageId, + slugId: share.sharedPageSlugId, + title: share.sharedPageTitle, + icon: share.sharedPageIcon, + }, + }; + } + + async getShareAncestorPage( + ancestorPageId: string, + childPageId: string, + ): Promise { + let ancestor = null; + try { + ancestor = await this.db + .withRecursive('page_ancestors', (db) => + db + .selectFrom('pages') + .select([ + 'id', + 'slugId', + 'title', + 'parentPageId', + 'spaceId', + (eb) => + eb + .case() + .when(eb.ref('id'), '=', ancestorPageId) + .then(true) + .else(false) + .end() + .as('found'), + ]) + .where( + isValidUUID(childPageId) ? 'id' : 'slugId', + '=', + childPageId, + ) + .unionAll((exp) => + exp + .selectFrom('pages as p') + .select([ + 'p.id', + 'p.slugId', + 'p.title', + 'p.parentPageId', + 'p.spaceId', + (eb) => + eb + .case() + .when(eb.ref('p.id'), '=', ancestorPageId) + .then(true) + .else(false) + .end() + .as('found'), + ]) + .innerJoin('page_ancestors as pa', 'pa.parentPageId', 'p.id') + // Continue recursing only when the target ancestor hasn't been found on that branch. + .where('pa.found', '=', false), + ), + ) + .selectFrom('page_ancestors') + .selectAll() + .where('found', '=', true) + .limit(1) + .executeTakeFirst(); + } catch (err) { + // empty + } + + return ancestor; + } + + async updatePublicAttachments(page: Page): Promise { + const prosemirrorJson = getProsemirrorContent(page.content); + const attachmentIds = getAttachmentIds(prosemirrorJson); + const attachmentMap = new Map(); + + await Promise.all( + attachmentIds.map(async (attachmentId: string) => { + const token = await this.tokenService.generateAttachmentToken({ + attachmentId, + pageId: page.id, + workspaceId: page.workspaceId, + }); + attachmentMap.set(attachmentId, token); + }), + ); + + const doc = jsonToNode(prosemirrorJson); + + doc?.descendants((node: Node) => { + if (!isAttachmentNode(node.type.name)) return; + + const attachmentId = node.attrs.attachmentId; + const token = attachmentMap.get(attachmentId); + if (!token) return; + + updateAttachmentAttr(node, 'src', token); + updateAttachmentAttr(node, 'url', token); + }); + + return doc.toJSON(); + } +} diff --git a/apps/server/src/core/share/share.util.ts b/apps/server/src/core/share/share.util.ts new file mode 100644 index 00000000..e21f55aa --- /dev/null +++ b/apps/server/src/core/share/share.util.ts @@ -0,0 +1,22 @@ +import { Node } from '@tiptap/pm/model'; + +export function updateAttachmentAttr( + node: Node, + attr: 'src' | 'url', + token: string, +) { + const attrVal = node.attrs[attr]; + if ( + attrVal && + (attrVal.startsWith('/files') || attrVal.startsWith('/api/files')) + ) { + // @ts-ignore + node.attrs[attr] = updateAttachmentUrl(attrVal, token); + } +} + +function updateAttachmentUrl(src: string, jwtToken: string) { + const updatedSrc = src.replace('/files/', '/files/public/'); + const separator = updatedSrc.includes('?') ? '&' : '?'; + return `${updatedSrc}${separator}jwt=${jwtToken}`; +} diff --git a/apps/server/src/database/database.module.ts b/apps/server/src/database/database.module.ts index 930bb59b..68c35dd3 100644 --- a/apps/server/src/database/database.module.ts +++ b/apps/server/src/database/database.module.ts @@ -24,6 +24,7 @@ import * as process from 'node:process'; import { MigrationService } from '@docmost/db/services/migration.service'; import { UserTokenRepo } from './repos/user-token/user-token.repo'; import { BacklinkRepo } from '@docmost/db/repos/backlink/backlink.repo'; +import { ShareRepo } from '@docmost/db/repos/share/share.repo'; // https://github.com/brianc/node-postgres/issues/811 types.setTypeParser(types.builtins.INT8, (val) => Number(val)); @@ -74,6 +75,7 @@ types.setTypeParser(types.builtins.INT8, (val) => Number(val)); AttachmentRepo, UserTokenRepo, BacklinkRepo, + ShareRepo ], exports: [ WorkspaceRepo, @@ -88,6 +90,7 @@ types.setTypeParser(types.builtins.INT8, (val) => Number(val)); AttachmentRepo, UserTokenRepo, BacklinkRepo, + ShareRepo ], }) export class DatabaseModule diff --git a/apps/server/src/database/migrations/20250408T191830-shares.ts b/apps/server/src/database/migrations/20250408T191830-shares.ts new file mode 100644 index 00000000..39d91454 --- /dev/null +++ b/apps/server/src/database/migrations/20250408T191830-shares.ts @@ -0,0 +1,38 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('shares') + .addColumn('id', 'uuid', (col) => + col.primaryKey().defaultTo(sql`gen_uuid_v7()`), + ) + .addColumn('key', 'varchar', (col) => col.notNull()) + .addColumn('page_id', 'uuid', (col) => + col.references('pages.id').onDelete('cascade'), + ) + .addColumn('include_sub_pages', 'boolean', (col) => col.defaultTo(false)) + .addColumn('search_indexing', 'boolean', (col) => col.defaultTo(false)) + .addColumn('creator_id', 'uuid', (col) => col.references('users.id')) + .addColumn('space_id', 'uuid', (col) => + col.references('spaces.id').onDelete('cascade').notNull(), + ) + .addColumn('workspace_id', 'uuid', (col) => + col.references('workspaces.id').onDelete('cascade').notNull(), + ) + .addColumn('created_at', 'timestamptz', (col) => + col.notNull().defaultTo(sql`now()`), + ) + .addColumn('updated_at', 'timestamptz', (col) => + col.notNull().defaultTo(sql`now()`), + ) + .addColumn('deleted_at', 'timestamptz', (col) => col) + .addUniqueConstraint('shares_key_workspace_id_unique', [ + 'key', + 'workspace_id', + ]) + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('shares').execute(); +} diff --git a/apps/server/src/database/repos/page/page.repo.ts b/apps/server/src/database/repos/page/page.repo.ts index 850fb2d1..8f06c4d4 100644 --- a/apps/server/src/database/repos/page/page.repo.ts +++ b/apps/server/src/database/repos/page/page.repo.ts @@ -211,7 +211,10 @@ export class PageRepo { ).as('contributors'); } - async getPageAndDescendants(parentPageId: string) { + async getPageAndDescendants( + parentPageId: string, + opts: { includeContent: boolean }, + ) { return this.db .withRecursive('page_hierarchy', (db) => db @@ -221,11 +224,12 @@ export class PageRepo { 'slugId', 'title', 'icon', - 'content', + 'position', 'parentPageId', 'spaceId', 'workspaceId', ]) + .$if(opts?.includeContent, (qb) => qb.select('content')) .where('id', '=', parentPageId) .unionAll((exp) => exp @@ -235,11 +239,12 @@ export class PageRepo { 'p.slugId', 'p.title', 'p.icon', - 'p.content', + 'p.position', 'p.parentPageId', 'p.spaceId', 'p.workspaceId', ]) + .$if(opts?.includeContent, (qb) => qb.select('content')) .innerJoin('page_hierarchy as ph', 'p.parentPageId', 'ph.id'), ), ) diff --git a/apps/server/src/database/repos/share/share.repo.ts b/apps/server/src/database/repos/share/share.repo.ts new file mode 100644 index 00000000..c2943c07 --- /dev/null +++ b/apps/server/src/database/repos/share/share.repo.ts @@ -0,0 +1,242 @@ +import { Injectable } from '@nestjs/common'; +import { InjectKysely } from 'nestjs-kysely'; +import { KyselyDB, KyselyTransaction } from '../../types/kysely.types'; +import { dbOrTx } from '../../utils'; +import { + InsertableShare, + Share, + UpdatableShare, +} from '@docmost/db/types/entity.types'; +import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; +import { executeWithPagination } from '@docmost/db/pagination/pagination'; +import { validate as isValidUUID } from 'uuid'; +import { ExpressionBuilder, sql } from 'kysely'; +import { DB } from '@docmost/db/types/db'; +import { jsonObjectFrom } from 'kysely/helpers/postgres'; +import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo'; + +@Injectable() +export class ShareRepo { + constructor( + @InjectKysely() private readonly db: KyselyDB, + private spaceMemberRepo: SpaceMemberRepo, + ) {} + + private baseFields: Array = [ + 'id', + 'key', + 'pageId', + 'includeSubPages', + 'searchIndexing', + 'creatorId', + 'spaceId', + 'workspaceId', + 'createdAt', + 'updatedAt', + 'deletedAt', + ]; + + async findById( + shareId: string, + opts?: { + includeSharedPage?: boolean; + includeCreator?: boolean; + withLock?: boolean; + trx?: KyselyTransaction; + }, + ): Promise { + const db = dbOrTx(this.db, opts?.trx); + + let query = db.selectFrom('shares').select(this.baseFields); + + if (opts?.includeSharedPage) { + query = query.select((eb) => this.withSharedPage(eb)); + } + + if (opts?.includeCreator) { + query = query.select((eb) => this.withCreator(eb)); + } + + if (opts?.withLock && opts?.trx) { + query = query.forUpdate(); + } + + if (isValidUUID(shareId)) { + query = query.where('id', '=', shareId); + } else { + query = query.where(sql`LOWER(key)`, '=', shareId.toLowerCase()); + } + + return query.executeTakeFirst(); + } + + async findByPageId( + pageId: string, + opts?: { + includeCreator?: boolean; + withLock?: boolean; + trx?: KyselyTransaction; + }, + ): Promise { + const db = dbOrTx(this.db, opts?.trx); + + let query = db + .selectFrom('shares') + .select(this.baseFields) + .where('pageId', '=', pageId); + + if (opts?.includeCreator) { + query = query.select((eb) => this.withCreator(eb)); + } + + if (opts?.withLock && opts?.trx) { + query = query.forUpdate(); + } + return query.executeTakeFirst(); + } + + async updateShare( + updatableShare: UpdatableShare, + shareId: string, + trx?: KyselyTransaction, + ) { + return dbOrTx(this.db, trx) + .updateTable('shares') + .set({ ...updatableShare, updatedAt: new Date() }) + .where( + isValidUUID(shareId) ? 'id' : sql`LOWER(key)`, + '=', + shareId.toLowerCase(), + ) + .returning(this.baseFields) + .executeTakeFirst(); + } + + async insertShare( + insertableShare: InsertableShare, + trx?: KyselyTransaction, + ): Promise { + const db = dbOrTx(this.db, trx); + return db + .insertInto('shares') + .values(insertableShare) + .returning(this.baseFields) + .executeTakeFirst(); + } + + async deleteShare(shareId: string): Promise { + let query = this.db.deleteFrom('shares'); + + if (isValidUUID(shareId)) { + query = query.where('id', '=', shareId); + } else { + query = query.where(sql`LOWER(key)`, '=', shareId.toLowerCase()); + } + + await query.execute(); + } + + async getShares(userId: string, pagination: PaginationOptions) { + const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(userId); + + const query = this.db + .selectFrom('shares') + .select(this.baseFields) + .select((eb) => this.withPage(eb)) + .select((eb) => this.withSpace(eb, userId)) + .select((eb) => this.withCreator(eb)) + .where('spaceId', 'in', userSpaceIds) + .orderBy('updatedAt', 'desc'); + + const hasEmptyIds = userSpaceIds.length === 0; + const result = executeWithPagination(query, { + page: pagination.page, + perPage: pagination.limit, + hasEmptyIds, + }); + + return result; + } + + withPage(eb: ExpressionBuilder) { + return jsonObjectFrom( + eb + .selectFrom('pages') + .select(['pages.id', 'pages.title', 'pages.slugId', 'pages.icon']) + .whereRef('pages.id', '=', 'shares.pageId'), + ).as('page'); + } + + withSpace(eb: ExpressionBuilder, userId?: string) { + return jsonObjectFrom( + eb + .selectFrom('spaces') + .select(['spaces.id', 'spaces.name', 'spaces.slug']) + .$if(Boolean(userId), (qb) => + qb.select((eb) => this.withUserSpaceRole(eb, userId)), + ) + .whereRef('spaces.id', '=', 'shares.spaceId'), + ).as('space'); + } + + withUserSpaceRole(eb: ExpressionBuilder, userId: string) { + return eb + .selectFrom( + eb + .selectFrom('spaceMembers') + .select(['spaceMembers.role']) + .whereRef('spaceMembers.spaceId', '=', 'spaces.id') + .where('spaceMembers.userId', '=', userId) + .unionAll( + eb + .selectFrom('spaceMembers') + .innerJoin( + 'groupUsers', + 'groupUsers.groupId', + 'spaceMembers.groupId', + ) + .select(['spaceMembers.role']) + .whereRef('spaceMembers.spaceId', '=', 'spaces.id') + .where('groupUsers.userId', '=', userId), + ) + .as('roles_union'), + ) + .select('roles_union.role') + .orderBy( + sql`CASE roles_union.role + WHEN 'admin' THEN 3 + WHEN 'writer' THEN 2 + WHEN 'reader' THEN 1 + ELSE 0 + END`, + + 'desc', + ) + .limit(1) + .as('userRole'); + } + + withCreator(eb: ExpressionBuilder) { + return jsonObjectFrom( + eb + .selectFrom('users') + .select(['users.id', 'users.name', 'users.avatarUrl']) + .whereRef('users.id', '=', 'shares.creatorId'), + ).as('creator'); + } + + withSharedPage(eb: ExpressionBuilder) { + return jsonObjectFrom( + eb + .selectFrom('pages') + .select([ + 'pages.id', + 'pages.slugId', + 'pages.title', + 'pages.icon', + 'pages.parentPageId', + ]) + .whereRef('pages.id', '=', 'shares.pageId'), + ).as('sharedPage'); + } +} diff --git a/apps/server/src/database/types/db.d.ts b/apps/server/src/database/types/db.d.ts index eae94943..8c4cbd57 100644 --- a/apps/server/src/database/types/db.d.ts +++ b/apps/server/src/database/types/db.d.ts @@ -183,6 +183,20 @@ export interface Pages { ydoc: Buffer | null; } +export interface Shares { + createdAt: Generated; + creatorId: string | null; + deletedAt: Timestamp | null; + id: Generated; + includeSubPages: Generated; + key: string; + pageId: string | null; + searchIndexing: Generated; + spaceId: string; + updatedAt: Generated; + workspaceId: string; +} + export interface SpaceMembers { addedById: string | null; createdAt: Generated; @@ -288,6 +302,7 @@ export interface DB { groupUsers: GroupUsers; pageHistory: PageHistory; pages: Pages; + shares: Shares; spaceMembers: SpaceMembers; spaces: Spaces; users: Users; diff --git a/apps/server/src/database/types/entity.types.ts b/apps/server/src/database/types/entity.types.ts index 8abd9f98..6cb55a11 100644 --- a/apps/server/src/database/types/entity.types.ts +++ b/apps/server/src/database/types/entity.types.ts @@ -16,6 +16,7 @@ import { Billing as BillingSubscription, AuthProviders, AuthAccounts, + Shares, } from './db'; // Workspace @@ -101,3 +102,8 @@ export type UpdatableAuthProvider = Updateable>; export type AuthAccount = Selectable; export type InsertableAuthAccount = Insertable; export type UpdatableAuthAccount = Updateable>; + +// Share +export type Share = Selectable; +export type InsertableShare = Insertable; +export type UpdatableShare = Updateable>; diff --git a/apps/server/src/integrations/export/export.service.ts b/apps/server/src/integrations/export/export.service.ts index 09fdb5fd..4bb5146f 100644 --- a/apps/server/src/integrations/export/export.service.ts +++ b/apps/server/src/integrations/export/export.service.ts @@ -15,10 +15,8 @@ import { StorageService } from '../storage/storage.service'; import { buildTree, computeLocalPath, - getAttachmentIds, getExportExtension, getPageTitle, - getProsemirrorContent, PageExportTree, replaceInternalLinks, updateAttachmentUrlsToLocalPaths, @@ -29,6 +27,10 @@ import { EditorState } from '@tiptap/pm/state'; // eslint-disable-next-line @typescript-eslint/no-require-imports import slugify = require('@sindresorhus/slugify'); import { EnvironmentService } from '../environment/environment.service'; +import { + getAttachmentIds, + getProsemirrorContent, +} from '../../common/helpers/prosemirror/utils'; @Injectable() export class ExportService { @@ -76,8 +78,11 @@ export class ExportService { `; } - if (format === ExportFormat.Markdown) { - const newPageHtml = pageHtml.replace(/]*>[\s\S]*?<\/colgroup>/gmi, ''); + if (format === ExportFormat.Markdown) { + const newPageHtml = pageHtml.replace( + /]*>[\s\S]*?<\/colgroup>/gim, + '', + ); return turndown(newPageHtml); } @@ -85,7 +90,9 @@ export class ExportService { } async exportPageWithChildren(pageId: string, format: string) { - const pages = await this.pageRepo.getPageAndDescendants(pageId); + const pages = await this.pageRepo.getPageAndDescendants(pageId, { + includeContent: true, + }); if (!pages || pages.length === 0) { throw new BadRequestException('No pages to export'); @@ -260,14 +267,7 @@ export class ExportService { const pages = await this.db .selectFrom('pages') - .select([ - 'id', - 'slugId', - 'title', - 'creatorId', - 'spaceId', - 'workspaceId', - ]) + .select(['id', 'slugId', 'title', 'creatorId', 'spaceId', 'workspaceId']) .select((eb) => this.pageRepo.withSpace(eb)) .where('id', 'in', pageMentionIds) .where('workspaceId', '=', workspaceId) diff --git a/apps/server/src/integrations/export/utils.ts b/apps/server/src/integrations/export/utils.ts index f99f337a..fe1815b0 100644 --- a/apps/server/src/integrations/export/utils.ts +++ b/apps/server/src/integrations/export/utils.ts @@ -4,6 +4,7 @@ import { Node } from '@tiptap/pm/model'; import { validate as isValidUUID } from 'uuid'; import * as path from 'path'; import { Page } from '@docmost/db/types/entity.types'; +import { isAttachmentNode } from '../../common/helpers/prosemirror/utils'; export type PageExportTree = Record; @@ -25,43 +26,6 @@ export function getPageTitle(title: string) { return title ? title : 'untitled'; } -export function getProsemirrorContent(content: any) { - return ( - content ?? { - type: 'doc', - content: [{ type: 'paragraph', attrs: { textAlign: 'left' } }], - } - ); -} - -export function getAttachmentIds(prosemirrorJson: any) { - const doc = jsonToNode(prosemirrorJson); - const attachmentIds = []; - - doc?.descendants((node: Node) => { - if (isAttachmentNode(node.type.name)) { - if (node.attrs.attachmentId && isValidUUID(node.attrs.attachmentId)) { - if (!attachmentIds.includes(node.attrs.attachmentId)) { - attachmentIds.push(node.attrs.attachmentId); - } - } - } - }); - - return attachmentIds; -} - -export function isAttachmentNode(nodeType: string) { - const attachmentNodeTypes = [ - 'attachment', - 'image', - 'video', - 'excalidraw', - 'drawio', - ]; - return attachmentNodeTypes.includes(nodeType); -} - export function updateAttachmentUrlsToLocalPaths(prosemirrorJson: any) { const doc = jsonToNode(prosemirrorJson); if (!doc) return null; diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 3ceeb789..95df255d 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -4,7 +4,12 @@ import { FastifyAdapter, NestFastifyApplication, } from '@nestjs/platform-fastify'; -import { Logger, NotFoundException, ValidationPipe } from '@nestjs/common'; +import { + Logger, + NotFoundException, + RequestMethod, + ValidationPipe, +} from '@nestjs/common'; import { TransformHttpResponseInterceptor } from './common/interceptors/http-response.interceptor'; import { WsRedisIoAdapter } from './ws/adapter/ws-redis.adapter'; import { InternalLogFilter } from './common/logger/internal-log-filter'; @@ -26,7 +31,9 @@ async function bootstrap() { }, ); - app.setGlobalPrefix('api', { exclude: ['robots.txt'] }); + app.setGlobalPrefix('api', { + exclude: ['robots.txt', 'share/:shareId/p/:pageSlug'], + }); const reflector = app.get(Reflector); const redisIoAdapter = new WsRedisIoAdapter(app); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8772d574..2fd73d8b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -419,43 +419,43 @@ importers: version: 8.1.1 '@nestjs/bullmq': specifier: ^11.0.2 - version: 11.0.2(@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)(bullmq@5.41.3) + version: 11.0.2(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)(bullmq@5.41.3) '@nestjs/common': - specifier: ^11.0.10 - version: 11.0.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + specifier: ^11.0.20 + version: 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/config': - specifier: ^4.0.0 - version: 4.0.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))(rxjs@7.8.1) + specifier: ^4.0.2 + version: 4.0.2(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1) '@nestjs/core': - specifier: ^11.0.10 - version: 11.0.10(@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/websockets@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1) + specifier: ^11.0.20 + version: 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/event-emitter': specifier: ^3.0.0 - version: 3.0.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) + version: 3.0.0(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20) '@nestjs/jwt': specifier: ^11.0.0 - version: 11.0.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)) + version: 11.0.0(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)) '@nestjs/mapped-types': specifier: ^2.1.0 - version: 2.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))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + version: 2.1.0(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@nestjs/passport': specifier: ^11.0.5 - version: 11.0.5(@nestjs/common@11.0.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0) + version: 11.0.5(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0) '@nestjs/platform-fastify': - specifier: ^11.0.10 - version: 11.0.10(@fastify/static@8.1.1)(@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) + specifier: ^11.0.20 + version: 11.0.20(@fastify/static@8.1.1)(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20) '@nestjs/platform-socket.io': - specifier: ^11.0.10 - version: 11.0.10(@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/websockets@11.0.10)(rxjs@7.8.1) + specifier: ^11.0.20 + version: 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(rxjs@7.8.1) '@nestjs/schedule': specifier: ^5.0.1 - version: 5.0.1(@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) + version: 5.0.1(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20) '@nestjs/terminus': specifier: ^11.0.0 - version: 11.0.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)(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 11.0.0(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/websockets': - specifier: ^11.0.10 - version: 11.0.10(@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)(@nestjs/platform-socket.io@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1) + specifier: ^11.0.20 + version: 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)(@nestjs/platform-socket.io@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@node-saml/passport-saml': specifier: ^5.0.1 version: 5.0.1 @@ -509,7 +509,7 @@ importers: version: 3.3.11 nestjs-kysely: 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.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)(kysely@0.27.5)(reflect-metadata@0.2.2) nodemailer: specifier: ^6.10.0 version: 6.10.0 @@ -564,7 +564,7 @@ importers: version: 11.0.1(chokidar@4.0.3)(typescript@5.7.3) '@nestjs/testing': specifier: ^11.0.10 - version: 11.0.10(@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) + version: 11.0.10(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20) '@types/bcrypt': specifier: ^5.0.2 version: 5.0.2 @@ -2083,8 +2083,8 @@ packages: '@fastify/cookie@11.0.2': resolution: {integrity: sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==} - '@fastify/cors@10.0.2': - resolution: {integrity: sha512-DGdxOG36sS/tZv1NFiCJGi7wGuXOSPL2CmNX5PbOVKx0C6LuIALRMrqLByHTCcX1Rbl8NJ9IWlJex32bzydvlw==} + '@fastify/cors@11.0.1': + resolution: {integrity: sha512-dmZaE7M1f4SM8ZZuk5RhSsDJ+ezTgI7v3HHRj8Ow9CneczsPLZV6+2j2uwdaSLn8zhTv6QV0F4ZRcqdalGx1pQ==} '@fastify/deepmerge@2.0.2': resolution: {integrity: sha512-3wuLdX5iiiYeZWP6bQrjqhrcvBIf0NHbQH1Ur1WbHvoiuTYUEItgygea3zs8aHpiitn0lOB8gX20u1qO+FDm7Q==} @@ -2566,8 +2566,8 @@ packages: '@swc/core': optional: true - '@nestjs/common@11.0.10': - resolution: {integrity: sha512-pzGXp14KF2Q4CDZGQgPK4l8zEg7i6cNkb+10yc8ZA5K41cLe3ZbWW1YxtY2e/glHauOJwTLSVjH4tiRVtOTizg==} + '@nestjs/common@11.0.20': + resolution: {integrity: sha512-/GH8NDCczjn6+6RNEtSNAts/nq/wQE8L1qZ9TRjqjNqEsZNE1vpFuRIhmcO2isQZ0xY5rySnpaRdrOAul3gQ3A==} peerDependencies: class-transformer: '*' class-validator: '*' @@ -2579,14 +2579,14 @@ packages: class-validator: optional: true - '@nestjs/config@4.0.0': - resolution: {integrity: sha512-hyhUMtVwlT+tavtPNyekl8iP0QTU1U6awKrgdOSxhMhp3TQMltx7hz2yqGTcARp+19zWPfgJudyxthuD3lPp/Q==} + '@nestjs/config@4.0.2': + resolution: {integrity: sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==} peerDependencies: '@nestjs/common': ^10.0.0 || ^11.0.0 rxjs: ^7.1.0 - '@nestjs/core@11.0.10': - resolution: {integrity: sha512-f0qB8ztNWZeAD4E4fUdHConmNYCa/A78U7WJu5mX9OLYfOAs3ESYCDfsH9MRUvkA4Ft4Y1uMmyJo5L4fg4+beg==} + '@nestjs/core@11.0.20': + resolution: {integrity: sha512-yUkEzBGiRNSEThVl6vMCXgoA9sDGWoRbJsTLdYdCC7lg7PE1iXBnna1FiBfQjT995pm0fjyM1e3WsXmyWeJXbw==} engines: {node: '>= 20'} peerDependencies: '@nestjs/common': ^11.0.0 @@ -2633,11 +2633,11 @@ packages: '@nestjs/common': ^10.0.0 || ^11.0.0 passport: ^0.5.0 || ^0.6.0 || ^0.7.0 - '@nestjs/platform-fastify@11.0.10': - resolution: {integrity: sha512-aOvuFsSUsfGziy6OmJwVDNx6aXougCMeUpEAlphuCLehSwfZQxhpy4SpThxTAtHU7RdmgGO7VfUGH+uUY8vHdQ==} + '@nestjs/platform-fastify@11.0.20': + resolution: {integrity: sha512-MZnjO77N/XesVzXhn8qnSEcnjXVIHxkh5zTz8SEIr6K2yWgGJZbTlNm7ul6l7QBeaCeNZtZJlvY/F+4Dbx8yCQ==} peerDependencies: '@fastify/static': ^8.0.0 - '@fastify/view': ^10.0.0 + '@fastify/view': ^10.0.0 || ^11.0.0 '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 peerDependenciesMeta: @@ -2646,8 +2646,8 @@ packages: '@fastify/view': optional: true - '@nestjs/platform-socket.io@11.0.10': - resolution: {integrity: sha512-39lAjq0+kZRiMuscDcugoG+onPDciM4jhuf8ZDjVcuSwtib1OGwrFtErSzp/KJsmHPSStgapbNev7eFi32uWQA==} + '@nestjs/platform-socket.io@11.0.20': + resolution: {integrity: sha512-fUyDjLt0wJ4WK+rXrd5/oSWw5xWpfDOknpP7YNgaFfvYW726KuS5gWysV7JPD2mgH85S6i+qiO3qZvHIs5DvxQ==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/websockets': ^11.0.0 @@ -2725,8 +2725,8 @@ packages: '@nestjs/platform-express': optional: true - '@nestjs/websockets@11.0.10': - resolution: {integrity: sha512-GPIEfqJyAkTHrHGK9w2OU8LJaZAZKW8WpWcTplThLxMelRq7mBkYOaGvc6dpr7fE1wWzWkwY0ZjQEnwnVmmxSg==} + '@nestjs/websockets@11.0.20': + resolution: {integrity: sha512-qcybahXdrPJFMILhAwJML9D/bExBEBFsfwFiePCeI4f//tiP0rXiLspLVOHClSeUPBaCNrx+Ae/HVe9UP+wtOg==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -3747,6 +3747,13 @@ packages: '@tiptap/core': ^2.7.0 '@tiptap/pm': ^2.7.0 + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@tsconfig/node10@1.0.9': resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} @@ -5134,6 +5141,15 @@ packages: supports-color: optional: true + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} @@ -5564,8 +5580,8 @@ packages: fastify-plugin@5.0.1: resolution: {integrity: sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==} - fastify@5.2.1: - resolution: {integrity: sha512-rslrNBF67eg8/Gyn7P2URV8/6pz8kSAscFL4EThZJ8JBMaXacVdVE4hmUcnPNKERl5o/xTiBSLfdowBRhVF1WA==} + fastify@5.3.0: + resolution: {integrity: sha512-vDpCJa4KRkHrdDMpDNtyPaIDi/ptCwoJ0M8RiefuIMvyXTgG63xYGe9DYYiCpydjh0ETIaLoSyKBNKkh7ew1eA==} fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -5581,6 +5597,9 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -5592,6 +5611,10 @@ packages: file-saver@2.0.5: resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} + file-type@20.4.1: + resolution: {integrity: sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==} + engines: {node: '>=18'} + filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} @@ -6560,6 +6583,10 @@ packages: linkifyjs@4.2.0: resolution: {integrity: sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==} + load-esm@1.0.2: + resolution: {integrity: sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw==} + engines: {node: '>=13.2.0'} + loader-runner@4.3.0: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} engines: {node: '>=6.11.5'} @@ -6813,9 +6840,6 @@ packages: mlly@1.7.3: resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==} - mnemonist@0.39.8: - resolution: {integrity: sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==} - ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -7002,9 +7026,6 @@ packages: resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} - obliterator@2.0.4: - resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} - obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} @@ -7165,6 +7186,10 @@ packages: peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + peek-readable@7.0.0: + resolution: {integrity: sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ==} + engines: {node: '>=18'} + pg-cloudflare@1.1.1: resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} @@ -7388,6 +7413,9 @@ packages: process-warning@4.0.0: resolution: {integrity: sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -7874,6 +7902,9 @@ packages: secure-json-parse@3.0.2: resolution: {integrity: sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==} + secure-json-parse@4.0.0: + resolution: {integrity: sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==} + selderee@0.11.0: resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} @@ -8091,6 +8122,10 @@ packages: strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + strtok3@10.2.2: + resolution: {integrity: sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg==} + engines: {node: '>=18'} + styled-jsx@5.1.1: resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -8234,6 +8269,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + token-types@6.0.0: + resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==} + engines: {node: '>=14.16'} + tough-cookie@5.1.0: resolution: {integrity: sha512-rvZUv+7MoBYTiDmFPBrhL7Ujx9Sk+q9wwm22x8c8T5IJaR+Wsyc7TNxbVxo84kZoRJZZMazowFLqpankBEQrGg==} engines: {node: '>=16'} @@ -8432,6 +8471,10 @@ packages: resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} engines: {node: '>=8'} + uint8array-extras@1.4.0: + resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} + engines: {node: '>=18'} + unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} @@ -10906,8 +10949,8 @@ snapshots: '@fastify/ajv-compiler@4.0.2': dependencies: - ajv: 8.12.0 - ajv-formats: 3.0.1(ajv@8.12.0) + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) fast-uri: 3.0.6 '@fastify/busboy@3.1.1': {} @@ -10917,10 +10960,10 @@ snapshots: cookie: 1.0.2 fastify-plugin: 5.0.1 - '@fastify/cors@10.0.2': + '@fastify/cors@11.0.1': dependencies: fastify-plugin: 5.0.1 - mnemonist: 0.39.8 + toad-cache: 3.7.0 '@fastify/deepmerge@2.0.2': {} @@ -11545,17 +11588,17 @@ snapshots: '@emnapi/runtime': 1.2.0 '@tybys/wasm-util': 0.9.0 - '@nestjs/bull-shared@11.0.2(@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)': + '@nestjs/bull-shared@11.0.2(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)': dependencies: - '@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(@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/websockets@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) tslib: 2.8.1 - '@nestjs/bullmq@11.0.2(@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)(bullmq@5.41.3)': + '@nestjs/bullmq@11.0.2(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)(bullmq@5.41.3)': dependencies: - '@nestjs/bull-shared': 11.0.2(@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) - '@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(@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/websockets@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/bull-shared': 11.0.2(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20) + '@nestjs/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) bullmq: 5.41.3 tslib: 2.8.1 @@ -11588,9 +11631,11 @@ snapshots: - uglify-js - webpack-cli - '@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/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: + file-type: 20.4.1 iterare: 1.2.1 + load-esm: 1.0.2 reflect-metadata: 0.2.2 rxjs: 7.8.1 tslib: 2.8.1 @@ -11598,18 +11643,20 @@ snapshots: optionalDependencies: class-transformer: 0.5.1 class-validator: 0.14.1 + transitivePeerDependencies: + - supports-color - '@nestjs/config@4.0.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))(rxjs@7.8.1)': + '@nestjs/config@4.0.2(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1)': dependencies: - '@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/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) dotenv: 16.4.7 dotenv-expand: 12.0.1 lodash: 4.17.21 rxjs: 7.8.1 - '@nestjs/core@11.0.10(@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/websockets@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/core@11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: - '@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/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nuxt/opencollective': 0.4.1 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -11619,51 +11666,52 @@ snapshots: tslib: 2.8.1 uid: 2.0.2 optionalDependencies: - '@nestjs/websockets': 11.0.10(@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)(@nestjs/platform-socket.io@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/websockets': 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)(@nestjs/platform-socket.io@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/event-emitter@3.0.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)': + '@nestjs/event-emitter@3.0.0(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)': dependencies: - '@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(@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/websockets@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) eventemitter2: 6.4.9 - '@nestjs/jwt@11.0.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/jwt@11.0.0(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))': dependencies: - '@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/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@types/jsonwebtoken': 9.0.7 jsonwebtoken: 9.0.2 - '@nestjs/mapped-types@2.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))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)': + '@nestjs/mapped-types@2.1.0(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)': dependencies: - '@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/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) reflect-metadata: 0.2.2 optionalDependencies: class-transformer: 0.5.1 class-validator: 0.14.1 - '@nestjs/passport@11.0.5(@nestjs/common@11.0.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0)': + '@nestjs/passport@11.0.5(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0)': dependencies: - '@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/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) passport: 0.7.0 - '@nestjs/platform-fastify@11.0.10(@fastify/static@8.1.1)(@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)': + '@nestjs/platform-fastify@11.0.20(@fastify/static@8.1.1)(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)': dependencies: - '@fastify/cors': 10.0.2 + '@fastify/cors': 11.0.1 '@fastify/formbody': 8.0.2 '@fastify/middie': 9.0.3 - '@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(@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/websockets@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1) - fastify: 5.2.1 + '@nestjs/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) + fast-querystring: 1.1.2 + fastify: 5.3.0 light-my-request: 6.6.0 path-to-regexp: 8.2.0 tslib: 2.8.1 optionalDependencies: '@fastify/static': 8.1.1 - '@nestjs/platform-socket.io@11.0.10(@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/websockets@11.0.10)(rxjs@7.8.1)': + '@nestjs/platform-socket.io@11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(rxjs@7.8.1)': dependencies: - '@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/websockets': 11.0.10(@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)(@nestjs/platform-socket.io@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/websockets': 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)(@nestjs/platform-socket.io@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) rxjs: 7.8.1 socket.io: 4.8.1 tslib: 2.8.1 @@ -11672,10 +11720,10 @@ snapshots: - supports-color - utf-8-validate - '@nestjs/schedule@5.0.1(@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)': + '@nestjs/schedule@5.0.1(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)': dependencies: - '@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(@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/websockets@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) cron: 3.5.0 '@nestjs/schematics@11.0.1(chokidar@4.0.3)(typescript@5.7.3)': @@ -11689,32 +11737,32 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/terminus@11.0.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)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/terminus@11.0.0(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: - '@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(@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/websockets@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) boxen: 5.1.2 check-disk-space: 3.4.0 reflect-metadata: 0.2.2 rxjs: 7.8.1 - '@nestjs/testing@11.0.10(@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)': + '@nestjs/testing@11.0.10(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)': dependencies: - '@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(@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/websockets@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) tslib: 2.8.1 - '@nestjs/websockets@11.0.10(@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)(@nestjs/platform-socket.io@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/websockets@11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)(@nestjs/platform-socket.io@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: - '@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(@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/websockets@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) iterare: 1.2.1 object-hash: 3.0.0 reflect-metadata: 0.2.2 rxjs: 7.8.1 tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-socket.io': 11.0.10(@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/websockets@11.0.10)(rxjs@7.8.1) + '@nestjs/platform-socket.io': 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(rxjs@7.8.1) '@next/env@14.2.10': {} @@ -11798,7 +11846,7 @@ snapshots: nx: 20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)) semver: 7.6.3 tmp: 0.2.1 - tslib: 2.8.0 + tslib: 2.8.1 yargs-parser: 21.1.1 '@nx/js@20.4.5(@babel/traverse@7.27.0)(@swc/core@1.5.25(@swc/helpers@0.5.5))(@types/node@22.13.4)(nx@20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)))(typescript@5.7.3)': @@ -11880,7 +11928,7 @@ snapshots: chalk: 4.1.2 enquirer: 2.3.6 nx: 20.4.5(@swc/core@1.5.25(@swc/helpers@0.5.5)) - tslib: 2.8.0 + tslib: 2.8.1 yargs-parser: 21.1.1 transitivePeerDependencies: - '@swc-node/register' @@ -12797,6 +12845,16 @@ snapshots: '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) '@tiptap/pm': 2.10.3 + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.0 + fflate: 0.8.2 + token-types: 6.0.0 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + '@tsconfig/node10@1.0.9': {} '@tsconfig/node12@1.0.11': {} @@ -13521,10 +13579,6 @@ snapshots: optionalDependencies: ajv: 8.12.0 - ajv-formats@3.0.1(ajv@8.12.0): - optionalDependencies: - ajv: 8.12.0 - ajv-formats@3.0.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -14459,6 +14513,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.0: + dependencies: + ms: 2.1.3 + decimal.js@10.4.3: {} dedent@1.5.1(babel-plugin-macros@3.1.0): @@ -14574,7 +14632,7 @@ snapshots: dotenv-expand@11.0.6: dependencies: - dotenv: 16.4.5 + dotenv: 16.4.7 dotenv-expand@12.0.1: dependencies: @@ -15034,8 +15092,8 @@ snapshots: fast-json-stringify@6.0.1: dependencies: '@fastify/merge-json-schemas': 0.2.1 - ajv: 8.12.0 - ajv-formats: 3.0.1(ajv@8.12.0) + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) fast-uri: 3.0.6 json-schema-ref-resolver: 2.0.1 rfdc: 1.3.1 @@ -15058,7 +15116,7 @@ snapshots: fastify-plugin@5.0.1: {} - fastify@5.2.1: + fastify@5.3.0: dependencies: '@fastify/ajv-compiler': 4.0.2 '@fastify/error': 4.0.0 @@ -15070,10 +15128,10 @@ snapshots: find-my-way: 9.2.0 light-my-request: 6.6.0 pino: 9.1.0 - process-warning: 4.0.0 + process-warning: 5.0.0 rfdc: 1.3.1 - secure-json-parse: 3.0.2 - semver: 7.6.3 + secure-json-parse: 4.0.0 + semver: 7.7.1 toad-cache: 3.7.0 fastq@1.17.1: @@ -15088,6 +15146,8 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fflate@0.8.2: {} + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -15098,6 +15158,15 @@ snapshots: file-saver@2.0.5: {} + file-type@20.4.1: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.2.2 + token-types: 6.0.0 + uint8array-extras: 1.4.0 + transitivePeerDependencies: + - supports-color + filelist@1.0.4: dependencies: minimatch: 5.1.6 @@ -16293,6 +16362,8 @@ snapshots: linkifyjs@4.2.0: {} + load-esm@1.0.2: {} + loader-runner@4.3.0: {} local-pkg@0.5.1: @@ -16529,10 +16600,6 @@ snapshots: pkg-types: 1.2.1 ufo: 1.5.4 - mnemonist@0.39.8: - dependencies: - obliterator: 2.0.4 - ms@2.1.2: {} ms@2.1.3: {} @@ -16571,10 +16638,10 @@ snapshots: neo-async@2.6.2: {} - nestjs-kysely@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): + nestjs-kysely@1.1.0(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.20)(kysely@0.27.5)(reflect-metadata@0.2.2): dependencies: - '@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(@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/websockets@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.20)(reflect-metadata@0.2.2)(rxjs@7.8.1) kysely: 0.27.5 reflect-metadata: 0.2.2 @@ -16748,8 +16815,6 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 - obliterator@2.0.4: {} - obuf@1.1.2: {} oidc-token-hash@5.0.3: {} @@ -16919,6 +16984,8 @@ snapshots: peberminta@0.9.0: {} + peek-readable@7.0.0: {} + pg-cloudflare@1.1.1: optional: true @@ -17128,6 +17195,8 @@ snapshots: process-warning@4.0.0: {} + process-warning@5.0.0: {} + process@0.11.10: {} prompts@2.4.2: @@ -17703,6 +17772,8 @@ snapshots: secure-json-parse@3.0.2: {} + secure-json-parse@4.0.0: {} + selderee@0.11.0: dependencies: parseley: 0.12.1 @@ -17958,6 +18029,11 @@ snapshots: strnum@1.0.5: {} + strtok3@10.2.2: + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 7.0.0 + styled-jsx@5.1.1(@babel/core@7.24.5)(babel-plugin-macros@3.1.0)(react@18.3.1): dependencies: client-only: 0.0.1 @@ -18099,6 +18175,11 @@ snapshots: toidentifier@1.0.1: {} + token-types@6.0.0: + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + tough-cookie@5.1.0: dependencies: tldts: 6.1.72 @@ -18304,6 +18385,8 @@ snapshots: dependencies: '@lukeed/csprng': 1.1.0 + uint8array-extras@1.4.0: {} + unbox-primitive@1.0.2: dependencies: call-bind: 1.0.7 From 3430f715ec2c3aec9e81a032edcb5b0d20666662 Mon Sep 17 00:00:00 2001 From: Diego Ochoa <88157175+daoch@users.noreply.github.com> Date: Tue, 22 Apr 2025 14:47:57 -0500 Subject: [PATCH 28/63] feat: remember and restore previous route when exiting settings (#1046) Improves user experience by allowing users to return to the previous page after visiting the Settings section. Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com> --- apps/client/src/App.tsx | 4 +++- .../settings/atoms/settings-origin-atom.ts | 10 ++++++++++ .../src/components/settings/settings-sidebar.tsx | 12 +++++++++--- apps/client/src/hooks/use-settings-navigation.ts | 14 ++++++++++++++ apps/client/src/hooks/use-track-origin.ts | 16 ++++++++++++++++ 5 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 apps/client/src/components/settings/atoms/settings-origin-atom.ts create mode 100644 apps/client/src/hooks/use-settings-navigation.ts create mode 100644 apps/client/src/hooks/use-track-origin.ts diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index 4399062f..6fab378c 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -30,10 +30,12 @@ import SharedPage from "@/pages/share/shared-page.tsx"; import Shares from "@/pages/settings/shares/shares.tsx"; import ShareLayout from "@/features/share/components/share-layout.tsx"; import ShareRedirect from '@/pages/share/share-redirect.tsx'; +import { useTrackOrigin } from "@/hooks/use-track-origin"; export default function App() { const { t } = useTranslation(); useRedirectToCloudSelect(); + useTrackOrigin(); return ( <> @@ -59,7 +61,7 @@ export default function App() { } /> } /> - + } /> } /> diff --git a/apps/client/src/components/settings/atoms/settings-origin-atom.ts b/apps/client/src/components/settings/atoms/settings-origin-atom.ts new file mode 100644 index 00000000..36ea889b --- /dev/null +++ b/apps/client/src/components/settings/atoms/settings-origin-atom.ts @@ -0,0 +1,10 @@ +import { atom, WritableAtom } from "jotai"; + +export const settingsOriginAtom: WritableAtom = atom( + null, + (get, set, newValue) => { + if (get(settingsOriginAtom) !== newValue) { + set(settingsOriginAtom, newValue); + } + } +); diff --git a/apps/client/src/components/settings/settings-sidebar.tsx b/apps/client/src/components/settings/settings-sidebar.tsx index 483c0026..79ee511d 100644 --- a/apps/client/src/components/settings/settings-sidebar.tsx +++ b/apps/client/src/components/settings/settings-sidebar.tsx @@ -13,7 +13,7 @@ import { IconKey, IconWorld, } from "@tabler/icons-react"; -import { Link, useLocation, useNavigate } from "react-router-dom"; +import { Link, useLocation } from "react-router-dom"; import classes from "./settings.module.css"; import { useTranslation } from "react-i18next"; import { isCloud } from "@/lib/config.ts"; @@ -32,6 +32,7 @@ import { import AppVersion from "@/components/settings/app-version.tsx"; import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts"; +import { useSettingsNavigation } from "@/hooks/use-settings-navigation"; interface DataItem { label: string; @@ -105,7 +106,7 @@ export default function SettingsSidebar() { const { t } = useTranslation(); const location = useLocation(); const [active, setActive] = useState(location.pathname); - const navigate = useNavigate(); + const { goBack } = useSettingsNavigation(); const { isAdmin } = useUserRole(); const [workspace] = useAtom(workspaceAtom); const [mobileSidebarOpened] = useAtom(mobileSidebarAtom); @@ -210,7 +211,12 @@ export default function SettingsSidebar() {
navigate(-1)} + onClick={() => { + goBack(); + if (mobileSidebarOpened) { + toggleMobileSidebar(); + } + }} variant="transparent" c="gray" aria-label="Back" diff --git a/apps/client/src/hooks/use-settings-navigation.ts b/apps/client/src/hooks/use-settings-navigation.ts new file mode 100644 index 00000000..389445c5 --- /dev/null +++ b/apps/client/src/hooks/use-settings-navigation.ts @@ -0,0 +1,14 @@ +import { settingsOriginAtom } from "@/components/settings/atoms/settings-origin-atom"; +import { useAtomValue } from "jotai"; +import { useNavigate } from "react-router-dom"; + +export function useSettingsNavigation() { + const navigate = useNavigate(); + const origin = useAtomValue(settingsOriginAtom); + + const goBack = () => { + navigate(origin ?? "/home", { replace: true }); + }; + + return { goBack }; +} \ No newline at end of file diff --git a/apps/client/src/hooks/use-track-origin.ts b/apps/client/src/hooks/use-track-origin.ts new file mode 100644 index 00000000..089dffbe --- /dev/null +++ b/apps/client/src/hooks/use-track-origin.ts @@ -0,0 +1,16 @@ +import { settingsOriginAtom } from "@/components/settings/atoms/settings-origin-atom"; +import { useAtomValue, useSetAtom } from "jotai"; +import { useEffect } from "react"; +import { useLocation } from "react-router-dom"; + +export function useTrackOrigin() { + const location = useLocation(); + const setOrigin = useSetAtom(settingsOriginAtom); + + useEffect(() => { + const isInSettings = location.pathname.startsWith("/settings"); + if (!isInSettings) { + setOrigin(location.pathname); + } + }, [location.pathname, setOrigin]); +} \ No newline at end of file From 00d92a369057766bf9cd95be52efec8379a094c2 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Tue, 22 Apr 2025 20:57:07 +0100 Subject: [PATCH 29/63] New Crowdin updates (#1008) * New translations translation.json (Russian) * New translations translation.json (Russian) * New translations translation.json (Chinese Simplified) * New translations translation.json (Spanish) * New translations translation.json (French) * New translations translation.json (Spanish) * New translations translation.json (German) * New translations translation.json (Italian) * New translations translation.json (Japanese) * New translations translation.json (Korean) * New translations translation.json (Dutch) * New translations translation.json (Russian) * New translations translation.json (Chinese Simplified) * New translations translation.json (English) * New translations translation.json (Portuguese, Brazilian) --- .../public/locales/de-DE/translation.json | 23 +++++++++- .../public/locales/es-ES/translation.json | 25 ++++++++++- .../public/locales/fr-FR/translation.json | 23 +++++++++- .../public/locales/it-IT/translation.json | 23 +++++++++- .../public/locales/ja-JP/translation.json | 23 +++++++++- .../public/locales/ko-KR/translation.json | 23 +++++++++- .../public/locales/nl-NL/translation.json | 23 +++++++++- .../public/locales/pt-BR/translation.json | 23 +++++++++- .../public/locales/ru-RU/translation.json | 45 ++++++++++++++----- .../public/locales/zh-CN/translation.json | 25 ++++++++++- 10 files changed, 233 insertions(+), 23 deletions(-) diff --git a/apps/client/public/locales/de-DE/translation.json b/apps/client/public/locales/de-DE/translation.json index 294bfa79..3ba481d1 100644 --- a/apps/client/public/locales/de-DE/translation.json +++ b/apps/client/public/locales/de-DE/translation.json @@ -362,5 +362,26 @@ "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." + "Add headings (H1, H2, H3) to generate a table of contents.": "Fügen Sie Überschriften (H1, H2, H3) hinzu, um ein Inhaltsverzeichnis zu erstellen.", + "Share": "Teilen", + "Public sharing": "Öffentliches Teilen", + "Shared by": "Geteilt von", + "Shared at": "Geteilt am", + "Inherits public sharing from": "Erbt das öffentliche Teilen von", + "Share to web": "Im Web teilen", + "Shared to web": "Im Web geteilt", + "Anyone with the link can view this page": "Jeder mit dem Link kann diese Seite ansehen", + "Make this page publicly accessible": "Diese Seite öffentlich zugänglich machen", + "Include sub-pages": "Unterseiten einbeziehen", + "Make sub-pages public too": "Unterseiten auch öffentlich machen", + "Allow search engines to index page": "Suchmaschinen erlauben, die Seite zu indexieren", + "Open page": "Seite öffnen", + "Page": "Seite", + "Delete public share link": "Öffentlichen Freigabelink löschen", + "Delete share": "Freigabe löschen", + "Are you sure you want to delete this shared link?": "Möchten Sie diesen Freigabelink wirklich löschen?", + "Publicly shared pages from spaces you are a member of will appear here": "Öffentlich geteilte Seiten aus Bereichen, in denen Sie Mitglied sind, erscheinen hier", + "Share deleted successfully": "Freigabe erfolgreich gelöscht", + "Share not found": "Freigabe nicht gefunden", + "Failed to share page": "Fehler beim Teilen der Seite" } diff --git a/apps/client/public/locales/es-ES/translation.json b/apps/client/public/locales/es-ES/translation.json index f17d13d9..eed09b2c 100644 --- a/apps/client/public/locales/es-ES/translation.json +++ b/apps/client/public/locales/es-ES/translation.json @@ -94,7 +94,7 @@ "Invited members will be granted access to spaces the groups can access": "Los miembros invitados recibirán acceso a los espacios a los que los grupos pueden acceder", "Join the workspace": "Unirse al espacio de trabajo", "Language": "Idioma", - "Light": "Ligero", + "Light": "Claro", "Link copied": "Enlace copiado", "Login": "Iniciar sesión", "Logout": "Cerrar sesión", @@ -362,5 +362,26 @@ "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." + "Add headings (H1, H2, H3) to generate a table of contents.": "Añadir encabezados (H1, H2, H3) para generar un índice de contenidos.", + "Share": "Compartir", + "Public sharing": "Compartición pública", + "Shared by": "Compartido por", + "Shared at": "Compartido en", + "Inherits public sharing from": "Hereda la compartición pública de", + "Share to web": "Compartir en la web", + "Shared to web": "Compartido en la web", + "Anyone with the link can view this page": "Cualquiera con el enlace puede ver esta página", + "Make this page publicly accessible": "Hacer esta página accesible públicamente", + "Include sub-pages": "Incluir subpáginas", + "Make sub-pages public too": "Hacer públicas también las subpáginas", + "Allow search engines to index page": "Permitir a los motores de búsqueda indexar la página", + "Open page": "Abrir página", + "Page": "Página", + "Delete public share link": "Eliminar enlace de compartición pública", + "Delete share": "Eliminar compartición", + "Are you sure you want to delete this shared link?": "¿Está seguro de que desea eliminar este enlace compartido?", + "Publicly shared pages from spaces you are a member of will appear here": "Las páginas compartidas públicamente de los espacios a los que pertenece aparecerán aquí", + "Share deleted successfully": "Compartición eliminada con éxito", + "Share not found": "Compartición no encontrada", + "Failed to share page": "Error al compartir la página" } diff --git a/apps/client/public/locales/fr-FR/translation.json b/apps/client/public/locales/fr-FR/translation.json index 408ff420..4a8b32f7 100644 --- a/apps/client/public/locales/fr-FR/translation.json +++ b/apps/client/public/locales/fr-FR/translation.json @@ -362,5 +362,26 @@ "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." + "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.", + "Share": "Partager", + "Public sharing": "Partage public", + "Shared by": "Partagé par", + "Shared at": "Partagé à", + "Inherits public sharing from": "Hérite du partage public de", + "Share to web": "Partager sur le web", + "Shared to web": "Partagé sur le web", + "Anyone with the link can view this page": "Toute personne avec le lien peut voir cette page", + "Make this page publicly accessible": "Rendre cette page accessible au public", + "Include sub-pages": "Inclure les sous-pages", + "Make sub-pages public too": "Rendre également les sous-pages publiques", + "Allow search engines to index page": "Autoriser les moteurs de recherche à indexer la page", + "Open page": "Ouvrir la page", + "Page": "Page", + "Delete public share link": "Supprimer le lien de partage public", + "Delete share": "Supprimer le partage", + "Are you sure you want to delete this shared link?": "Êtes-vous sûr de vouloir supprimer ce lien partagé ?", + "Publicly shared pages from spaces you are a member of will appear here": "Les pages partagées publiquement des espaces dont vous êtes membre apparaîtront ici", + "Share deleted successfully": "Partage supprimé avec succès", + "Share not found": "Partage non trouvé", + "Failed to share page": "Échec du partage de la page" } diff --git a/apps/client/public/locales/it-IT/translation.json b/apps/client/public/locales/it-IT/translation.json index b92377c7..28eb6771 100644 --- a/apps/client/public/locales/it-IT/translation.json +++ b/apps/client/public/locales/it-IT/translation.json @@ -362,5 +362,26 @@ "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." + "Add headings (H1, H2, H3) to generate a table of contents.": "Aggiungi intestazioni (H1, H2, H3) per generare un sommario.", + "Share": "Condividi", + "Public sharing": "Condivisione pubblica", + "Shared by": "Condiviso da", + "Shared at": "Condiviso il", + "Inherits public sharing from": "Eredita la condivisione pubblica da", + "Share to web": "Condividi su web", + "Shared to web": "Condiviso su web", + "Anyone with the link can view this page": "Chiunque abbia il link può visualizzare questa pagina", + "Make this page publicly accessible": "Rendi questa pagina accessibile pubblicamente", + "Include sub-pages": "Includi sotto-pagine", + "Make sub-pages public too": "Rendi pubbliche anche le sotto-pagine", + "Allow search engines to index page": "Permetti ai motori di ricerca di indicizzare la pagina", + "Open page": "Apri pagina", + "Page": "Pagina", + "Delete public share link": "Elimina il link di condivisione pubblica", + "Delete share": "Elimina condivisione", + "Are you sure you want to delete this shared link?": "Sei sicuro di voler eliminare questo link condiviso?", + "Publicly shared pages from spaces you are a member of will appear here": "Le pagine condivise pubblicamente dagli spazi di cui sei membro appariranno qui", + "Share deleted successfully": "Condivisione eliminata con successo", + "Share not found": "Condivisione non trovata", + "Failed to share page": "Condivisione della pagina fallita" } diff --git a/apps/client/public/locales/ja-JP/translation.json b/apps/client/public/locales/ja-JP/translation.json index cc6141c4..cba12e63 100644 --- a/apps/client/public/locales/ja-JP/translation.json +++ b/apps/client/public/locales/ja-JP/translation.json @@ -362,5 +362,26 @@ "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)を追加して目次を生成します。" + "Add headings (H1, H2, H3) to generate a table of contents.": "見出し(H1、H2、H3)を追加して目次を生成します。", + "Share": "共有", + "Public sharing": "公開共有", + "Shared by": "共有者", + "Shared at": "共有日時", + "Inherits public sharing from": "から公開共有を継承する", + "Share to web": "ウェブで共有", + "Shared to web": "ウェブに共有済み", + "Anyone with the link can view this page": "リンクを持っている人はこのページを閲覧できます", + "Make this page publicly accessible": "このページを公開します", + "Include sub-pages": "サブページを含む", + "Make sub-pages public too": "サブページも公開する", + "Allow search engines to index page": "検索エンジンにページのインデックス作成を許可する", + "Open page": "ページを開く", + "Page": "ページ", + "Delete public share link": "公開リンクを削除", + "Delete share": "共有を削除", + "Are you sure you want to delete this shared link?": "この共有リンクを削除してもよろしいですか?", + "Publicly shared pages from spaces you are a member of will appear here": "メンバーであるスペースからの公開ページがここに表示されます", + "Share deleted successfully": "共有が正常に削除されました", + "Share not found": "共有が見つかりません", + "Failed to share page": "ページの共有に失敗しました" } diff --git a/apps/client/public/locales/ko-KR/translation.json b/apps/client/public/locales/ko-KR/translation.json index 1e87c62e..fd8db95b 100644 --- a/apps/client/public/locales/ko-KR/translation.json +++ b/apps/client/public/locales/ko-KR/translation.json @@ -362,5 +362,26 @@ "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)을 추가하세요." + "Add headings (H1, H2, H3) to generate a table of contents.": "목차를 생성하려면 제목 (H1, H2, H3)을 추가하세요.", + "Share": "공유", + "Public sharing": "공개 공유", + "Shared by": "공유자", + "Shared at": "공유 시간", + "Inherits public sharing from": "로부터 공개 공유를 상속함", + "Share to web": "웹에 공유", + "Shared to web": "웹에 공유됨", + "Anyone with the link can view this page": "링크가 있는 사람은 이 페이지를 볼 수 있습니다", + "Make this page publicly accessible": "이 페이지를 공개적으로 접근 가능하게 만들기", + "Include sub-pages": "하위 페이지 포함", + "Make sub-pages public too": "하위 페이지도 공개로 설정", + "Allow search engines to index page": "검색 엔진이 페이지를 색인할 수 있도록 허용", + "Open page": "페이지 열기", + "Page": "페이지", + "Delete public share link": "공유 링크 삭제", + "Delete share": "공유 삭제", + "Are you sure you want to delete this shared link?": "이 공유 링크를 삭제하시겠습니까?", + "Publicly shared pages from spaces you are a member of will appear here": "회원인 공간의 공개 공유된 페이지가 여기에 표시됩니다", + "Share deleted successfully": "공유가 성공적으로 삭제되었습니다", + "Share not found": "공유를 찾을 수 없습니다", + "Failed to share page": "페이지 공유에 실패했습니다" } diff --git a/apps/client/public/locales/nl-NL/translation.json b/apps/client/public/locales/nl-NL/translation.json index dba2ee9a..1879078e 100644 --- a/apps/client/public/locales/nl-NL/translation.json +++ b/apps/client/public/locales/nl-NL/translation.json @@ -362,5 +362,26 @@ "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." + "Add headings (H1, H2, H3) to generate a table of contents.": "Voeg koppen (H1, H2, H3) toe om een inhoudsopgave te genereren.", + "Share": "Delen", + "Public sharing": "Openbaar delen", + "Shared by": "Gedeeld door", + "Shared at": "Gedeeld op", + "Inherits public sharing from": "Erft openbaar delen van", + "Share to web": "Delen naar web", + "Shared to web": "Gedeeld naar web", + "Anyone with the link can view this page": "Iedereen met de link kan deze pagina bekijken", + "Make this page publicly accessible": "Maak deze pagina openbaar toegankelijk", + "Include sub-pages": "Inclusief subpagina's", + "Make sub-pages public too": "Maak subpagina's ook openbaar", + "Allow search engines to index page": "Sta zoekmachines toe om pagina te indexeren", + "Open page": "Pagina openen", + "Page": "Pagina", + "Delete public share link": "Verwijder openbare deel-link", + "Delete share": "Verwijder deel", + "Are you sure you want to delete this shared link?": "Weet u zeker dat u deze gedeelde link wilt verwijderen?", + "Publicly shared pages from spaces you are a member of will appear here": "Openbaar gedeelde pagina's van ruimtes waarvan u lid bent, verschijnen hier", + "Share deleted successfully": "Delen succesvol verwijderd", + "Share not found": "Delen niet gevonden", + "Failed to share page": "Pagina delen mislukt" } diff --git a/apps/client/public/locales/pt-BR/translation.json b/apps/client/public/locales/pt-BR/translation.json index 526d298f..0986a2b7 100644 --- a/apps/client/public/locales/pt-BR/translation.json +++ b/apps/client/public/locales/pt-BR/translation.json @@ -362,5 +362,26 @@ "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." + "Add headings (H1, H2, H3) to generate a table of contents.": "Adicionar títulos (H1, H2, H3) para gerar uma tabela de conteúdo.", + "Share": "Compartilhar", + "Public sharing": "Compartilhamento público", + "Shared by": "Compartilhado por", + "Shared at": "Compartilhado em", + "Inherits public sharing from": "Herdado do compartilhamento público de", + "Share to web": "Compartilhar na web", + "Shared to web": "Compartilhado na web", + "Anyone with the link can view this page": "Qualquer um com o link pode ver esta página", + "Make this page publicly accessible": "Tornar esta página publicamente acessível", + "Include sub-pages": "Incluir sub-páginas", + "Make sub-pages public too": "Tornar as sub-páginas públicas também", + "Allow search engines to index page": "Permitir que mecanismos de busca indexem a página", + "Open page": "Abrir página", + "Page": "Página", + "Delete public share link": "Excluir o link público compartilhado", + "Delete share": "Excluir compartilhamento", + "Are you sure you want to delete this shared link?": "Tem certeza de que deseja excluir este link compartilhado?", + "Publicly shared pages from spaces you are a member of will appear here": "Páginas compartilhadas publicamente de espaços que você é membro aparecerão aqui", + "Share deleted successfully": "Compartilhamento excluído com sucesso", + "Share not found": "Compartilhamento não encontrado", + "Failed to share page": "Falha ao compartilhar página" } diff --git a/apps/client/public/locales/ru-RU/translation.json b/apps/client/public/locales/ru-RU/translation.json index dce96d80..27105bfc 100644 --- a/apps/client/public/locales/ru-RU/translation.json +++ b/apps/client/public/locales/ru-RU/translation.json @@ -13,11 +13,11 @@ "Are you sure you want to remove this user from the group? The user will lose access to resources this group has access to.": "Вы уверены, что хотите удалить этого пользователя из группы? Пользователь потеряет доступ к материалам, к которым у этой группы есть доступ.", "Are you sure you want to remove this user from the space? The user will lose all access to this space.": "Вы уверены, что хотите удалить этого пользователя из пространства? Пользователь потеряет весь доступ к этому пространству.", "Are you sure you want to restore this version? Any changes not versioned will be lost.": "Вы уверены, что хотите восстановить эту версию? Все не зафиксированные изменения будут потеряны.", - "Can become members of groups and spaces in workspace": "Могут становиться участниками групп и пространств в рабочем пространстве", + "Can become members of groups and spaces in workspace": "Могут становиться участниками групп и пространств в рабочей области", "Can create and edit pages in space.": "Может создавать и редактировать страницы в пространстве.", "Can edit": "Может изменять", - "Can manage workspace": "Может управлять рабочим пространством", - "Can manage workspace but cannot delete it": "Может управлять рабочим пространством, но не может его удалить", + "Can manage workspace": "Может управлять рабочей областью", + "Can manage workspace but cannot delete it": "Может управлять рабочей областью, но не может ее удалить", "Can view": "Может просматривать", "Can view pages in space but not edit.": "Может просматривать страницы в пространстве, но не может их редактировать.", "Cancel": "Отменить", @@ -34,7 +34,7 @@ "Create group": "Создать группу", "Create page": "Создать страницу", "Create space": "Создать пространство", - "Create workspace": "Создать рабочее пространство", + "Create workspace": "Создать рабочую область", "Current password": "Текущий пароль", "Dark": "Темная", "Date": "Дата", @@ -92,7 +92,7 @@ "Invite new members": "Пригласить новых участников", "Invited members who are yet to accept their invitation will appear here.": "Приглашённые участники, которые ещё не приняли приглашение, появятся здесь.", "Invited members will be granted access to spaces the groups can access": "Приглашённые участники получат доступ к пространствам, доступ к которым есть у группы", - "Join the workspace": "Присоединиться к рабочему пространству", + "Join the workspace": "Присоединиться к рабочей области", "Language": "Язык", "Light": "Светлая", "Link copied": "Ссылка скопирована", @@ -150,7 +150,7 @@ "Send invitation": "Отправить приглашение", "Invitation sent": "Приглашение отправлено", "Settings": "Настройки", - "Setup workspace": "Настроить рабочее пространство", + "Setup workspace": "Настроить рабочую область", "Sign In": "Вход", "Sign Up": "Регистрация", "Slug": "Slug", @@ -177,9 +177,9 @@ "Untitled": "Без названия", "Updated successfully": "Обновлено успешно", "User": "Пользователь", - "Workspace": "Рабочее пространство", - "Workspace Name": "Имя рабочего пространства", - "Workspace settings": "Настройки рабочего пространства", + "Workspace": "Рабочая область", + "Workspace Name": "Имя рабочей области", + "Workspace settings": "Настройки рабочей области", "You can change your password here.": "Вы можете изменить свой пароль здесь.", "Your Email": "Ваш адрес электронной почты", "Your import is complete.": "Ваш импорт завершен.", @@ -217,9 +217,9 @@ "Revoke invitation": "Отозвать приглашение", "Revoke": "Отозвать", "Don't": "Нет", - "Are you sure you want to revoke this invitation? The user will not be able to join the workspace.": "Вы уверены, что хотите отозвать это приглашение? Пользователь не сможет присоединиться к рабочему пространству.", + "Are you sure you want to revoke this invitation? The user will not be able to join the workspace.": "Вы уверены, что хотите отозвать это приглашение? Пользователь не сможет присоединиться к рабочей области.", "Resend invitation": "Отправить приглашение повторно", - "Anyone with this link can join this workspace.": "Любой, у кого есть эта ссылка, может присоединиться к этому рабочему пространству.", + "Anyone with this link can join this workspace.": "Любой, у кого есть данная ссылка, может присоединиться к этой рабочей области.", "Invite link": "Ссылка для приглашения", "Copy": "Копировать", "Copied": "Скопировано", @@ -362,5 +362,26 @@ "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), чтобы создать оглавление." + "Add headings (H1, H2, H3) to generate a table of contents.": "Добавьте заголовки (H1, H2, H3), чтобы создать оглавление.", + "Share": "Поделиться", + "Public sharing": "Общий доступ", + "Shared by": "Поделился", + "Shared at": "Поделился в", + "Inherits public sharing from": "Наследует общий доступ от", + "Share to web": "Поделиться в интернете", + "Shared to web": "Размещено в интернете", + "Anyone with the link can view this page": "Любой, у кого есть ссылка, может просмотреть эту страницу", + "Make this page publicly accessible": "Сделать эту страницу общедоступной", + "Include sub-pages": "Включить подстраницы", + "Make sub-pages public too": "Сделать подстраницы также общедоступными", + "Allow search engines to index page": "Разрешить поисковым системам индексировать страницу", + "Open page": "Открыть страницу", + "Page": "Страница", + "Delete public share link": "Удалить ссылку на общий доступ", + "Delete share": "Удалить общий доступ", + "Are you sure you want to delete this shared link?": "Вы уверены, что хотите удалить эту ссылку общего доступа?", + "Publicly shared pages from spaces you are a member of will appear here": "Общие страницы из пространств, участником которых вы являетесь, появятся здесь", + "Share deleted successfully": "Общий доступ успешно удален", + "Share not found": "Общий доступ не найден", + "Failed to share page": "Не удалось поделиться страницей" } diff --git a/apps/client/public/locales/zh-CN/translation.json b/apps/client/public/locales/zh-CN/translation.json index 9763c602..94c3007c 100644 --- a/apps/client/public/locales/zh-CN/translation.json +++ b/apps/client/public/locales/zh-CN/translation.json @@ -298,7 +298,7 @@ "Heading 2": "2 级标题", "Heading 3": "3 级标题", "To-do List": "代办列表", - "Bullet List": "无需列表", + "Bullet List": "无序列表", "Numbered List": "有序列表", "Blockquote": "引用块", "Just start typing with plain text.": "只需开始键入纯文本", @@ -362,5 +362,26 @@ "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)以生成目录。" + "Add headings (H1, H2, H3) to generate a table of contents.": "添加标题(H1,H2,H3)以生成目录。", + "Share": "分享", + "Public sharing": "公开分享", + "Shared by": "分享者", + "Shared at": "分享时间", + "Inherits public sharing from": "继承自的公开分享", + "Share to web": "分享到网页", + "Shared to web": "已分享到网页", + "Anyone with the link can view this page": "任何有链接的人都可以查看此页面", + "Make this page publicly accessible": "使此页面可公开访问", + "Include sub-pages": "包括子页面", + "Make sub-pages public too": "将子页面也设为公开", + "Allow search engines to index page": "允许搜索引擎索引页面", + "Open page": "打开页面", + "Page": "页面", + "Delete public share link": "删除公开分享链接", + "Delete share": "删除分享", + "Are you sure you want to delete this shared link?": "您确定要删除此分享链接吗?", + "Publicly shared pages from spaces you are a member of will appear here": "您所在空间的公开共享页面会显示在此处", + "Share deleted successfully": "分享已成功删除", + "Share not found": "未找到分享", + "Failed to share page": "页面分享失败" } From acffeacdbcb408b7d152b3619f93c404c1907fd5 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:47:34 +0100 Subject: [PATCH 30/63] fix TOC --- apps/client/src/features/share/components/share-shell.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/client/src/features/share/components/share-shell.tsx b/apps/client/src/features/share/components/share-shell.tsx index 82863b34..799958d2 100644 --- a/apps/client/src/features/share/components/share-shell.tsx +++ b/apps/client/src/features/share/components/share-shell.tsx @@ -55,19 +55,15 @@ export default function ShareShell({ const readOnlyEditor = useAtomValue(readOnlyEditorAtom); const [navbarOutside, setNavbarOutside] = useState(null); - const [asideOutside, setAsideOutside] = useState(null); useClickOutside( () => { if (mobileOpened) { toggleMobile(); } - if (mobileTocOpened) { - toggleTocMobile(); - } }, null, - [navbarOutside, asideOutside], + [navbarOutside], ); return ( @@ -181,7 +177,6 @@ export default function ShareShell({ p="md" withBorder={mobileTocOpened} className={classes.aside} - ref={setAsideOutside} >
From 0ec3ff2965880db3d1775b2015f0c684e76a6adc Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:48:12 +0100 Subject: [PATCH 31/63] Add empty placeholder text --- .../components/table-of-contents/table-of-contents.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/client/src/features/editor/components/table-of-contents/table-of-contents.tsx b/apps/client/src/features/editor/components/table-of-contents/table-of-contents.tsx index b309d67d..123889f3 100644 --- a/apps/client/src/features/editor/components/table-of-contents/table-of-contents.tsx +++ b/apps/client/src/features/editor/components/table-of-contents/table-of-contents.tsx @@ -143,6 +143,12 @@ export const TableOfContents: FC = (props) => { {t("Add headings (H1, H2, H3) to generate a table of contents.")} )} + + {props.isShare && ( + + {t("No table of contents.")} + + )} ); } From de5f90309c5dd3e0615bab3a3ec910422967609e Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:49:45 +0100 Subject: [PATCH 32/63] v0.20.0 --- apps/client/package.json | 2 +- apps/server/package.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index ffd94cae..e56b7e68 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "0.10.2", + "version": "0.20.0", "scripts": { "dev": "vite", "build": "tsc && vite build", diff --git a/apps/server/package.json b/apps/server/package.json index efd8d8ca..935647ed 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.10.2", + "version": "0.20.0", "description": "", "author": "", "private": true, diff --git a/package.json b/package.json index d84046e5..0cafcc3f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "docmost", "homepage": "https://docmost.com", - "version": "0.10.2", + "version": "0.20.0", "private": true, "scripts": { "build": "nx run-many -t build", From c26a851d52394e5c01717bf8674ae953ed7080e8 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:32:35 +0100 Subject: [PATCH 33/63] feat: enhance public sharing (#1057) * fix tree nodes sort * remove comment mark in shares * remove clickoutside hook for now * feat: search in shared pages * fix user-select * use Link * render page icons --- .../components/search-control.module.css | 44 ++++++++++ .../search/components/search-control.tsx | 56 ++++++++++++ apps/client/src/features/search/constants.ts | 7 ++ .../features/search/queries/search-query.ts | 11 +++ .../src/features/search/search-spotlight.tsx | 21 ++--- .../search/services/search-service.ts | 7 ++ .../search/share-search-spotlight.tsx | 87 +++++++++++++++++++ .../src/features/search/types/search.types.ts | 1 + .../features/share/components/share-shell.tsx | 46 +++++----- .../features/share/components/shared-tree.tsx | 16 ++++ apps/client/src/features/share/utils.ts | 20 +++-- .../components/sidebar/space-sidebar.tsx | 12 +-- .../src/common/helpers/prosemirror/utils.ts | 13 +++ apps/server/src/core/search/dto/search.dto.ts | 17 ++++ .../src/core/search/search.controller.ts | 42 +++++++-- apps/server/src/core/search/search.service.ts | 71 +++++++++++++-- apps/server/src/core/share/share.service.ts | 10 +-- 17 files changed, 420 insertions(+), 61 deletions(-) create mode 100644 apps/client/src/features/search/components/search-control.module.css create mode 100644 apps/client/src/features/search/components/search-control.tsx create mode 100644 apps/client/src/features/search/constants.ts create mode 100644 apps/client/src/features/search/share-search-spotlight.tsx diff --git a/apps/client/src/features/search/components/search-control.module.css b/apps/client/src/features/search/components/search-control.module.css new file mode 100644 index 00000000..5e5a9c26 --- /dev/null +++ b/apps/client/src/features/search/components/search-control.module.css @@ -0,0 +1,44 @@ +.root { + height: 34px; + padding-left: var(--mantine-spacing-sm); + padding-right: 4px; + border-radius: var(--mantine-radius-md); + color: var(--mantine-color-placeholder); + border: 1px solid; + + @mixin light { + border-color: var(--mantine-color-gray-3); + background-color: var(--mantine-color-white); + } + + @mixin dark { + border-color: var(--mantine-color-dark-4); + background-color: var(--mantine-color-dark-6); + } + + @mixin rtl { + padding-left: 4px; + padding-right: var(--mantine-spacing-sm); + } +} + +.shortcut { + font-size: 11px; + line-height: 1; + padding: 4px 7px; + border-radius: var(--mantine-radius-sm); + border: 1px solid; + font-weight: bold; + + @mixin light { + color: var(--mantine-color-gray-7); + border-color: var(--mantine-color-gray-2); + background-color: var(--mantine-color-gray-0); + } + + @mixin dark { + color: var(--mantine-color-dark-0); + border-color: var(--mantine-color-dark-7); + background-color: var(--mantine-color-dark-7); + } +} \ No newline at end of file diff --git a/apps/client/src/features/search/components/search-control.tsx b/apps/client/src/features/search/components/search-control.tsx new file mode 100644 index 00000000..3ae74da2 --- /dev/null +++ b/apps/client/src/features/search/components/search-control.tsx @@ -0,0 +1,56 @@ +import { IconSearch } from "@tabler/icons-react"; +import cx from "clsx"; +import { + ActionIcon, + BoxProps, + ElementProps, + Group, + rem, + Text, + Tooltip, + UnstyledButton, +} from "@mantine/core"; +import classes from "./search-control.module.css"; +import React from "react"; +import { useTranslation } from "react-i18next"; + +interface SearchControlProps extends BoxProps, ElementProps<"button"> {} + +export function SearchControl({ className, ...others }: SearchControlProps) { + const { t } = useTranslation(); + + return ( + + + + + {t("Search")} + + + Ctrl + K + + + + ); +} + +interface SearchMobileControlProps { + onSearch: () => void; +} + +export function SearchMobileControl({ onSearch }: SearchMobileControlProps) { + const { t } = useTranslation(); + + return ( + + + + + + ); +} diff --git a/apps/client/src/features/search/constants.ts b/apps/client/src/features/search/constants.ts new file mode 100644 index 00000000..a4c6c2f7 --- /dev/null +++ b/apps/client/src/features/search/constants.ts @@ -0,0 +1,7 @@ +import { createSpotlight } from '@mantine/spotlight'; + +export const [searchSpotlightStore, searchSpotlight] = createSpotlight(); + +export const [shareSearchSpotlightStore, shareSearchSpotlight] = + createSpotlight(); + diff --git a/apps/client/src/features/search/queries/search-query.ts b/apps/client/src/features/search/queries/search-query.ts index 2505e7a2..6b8c0296 100644 --- a/apps/client/src/features/search/queries/search-query.ts +++ b/apps/client/src/features/search/queries/search-query.ts @@ -1,6 +1,7 @@ import { useQuery, UseQueryResult } from "@tanstack/react-query"; import { searchPage, + searchShare, searchSuggestions, } from "@/features/search/services/search-service"; import { @@ -30,3 +31,13 @@ export function useSearchSuggestionsQuery( enabled: !!params.query, }); } + +export function useShareSearchQuery( + params: IPageSearchParams, +): UseQueryResult { + return useQuery({ + queryKey: ["share-search", params], + queryFn: () => searchShare(params), + enabled: !!params.query, + }); +} diff --git a/apps/client/src/features/search/search-spotlight.tsx b/apps/client/src/features/search/search-spotlight.tsx index 52e24557..581524fc 100644 --- a/apps/client/src/features/search/search-spotlight.tsx +++ b/apps/client/src/features/search/search-spotlight.tsx @@ -2,36 +2,36 @@ import { Group, Center, Text } from "@mantine/core"; import { Spotlight } from "@mantine/spotlight"; import { IconSearch } from "@tabler/icons-react"; import React, { useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { Link } from "react-router-dom"; import { useDebouncedValue } from "@mantine/hooks"; import { usePageSearchQuery } from "@/features/search/queries/search-query"; import { buildPageUrl } from "@/features/page/page.utils.ts"; import { getPageIcon } from "@/lib"; import { useTranslation } from "react-i18next"; +import { searchSpotlightStore } from "./constants"; interface SearchSpotlightProps { spaceId?: string; } export function SearchSpotlight({ spaceId }: SearchSpotlightProps) { const { t } = useTranslation(); - const navigate = useNavigate(); const [query, setQuery] = useState(""); const [debouncedSearchQuery] = useDebouncedValue(query, 300); - const { - data: searchResults, - isLoading, - error, - } = usePageSearchQuery({ query: debouncedSearchQuery, spaceId }); + const { data: searchResults } = usePageSearchQuery({ + query: debouncedSearchQuery, + spaceId, + }); const pages = ( searchResults && searchResults.length > 0 ? searchResults : [] ).map((page) => ( - navigate(buildPageUrl(page.space.slug, page.slugId, page.title)) - } + component={Link} + //@ts-ignore + to={buildPageUrl(page.space.slug, page.slugId, page.title)} + style={{ userSelect: "none" }} >
{getPageIcon(page?.icon)}
@@ -54,6 +54,7 @@ export function SearchSpotlight({ spaceId }: SearchSpotlightProps) { return ( <> ("/search/suggest", params); return req.data; } + +export async function searchShare( + params: IPageSearchParams, +): Promise { + const req = await api.post("/search/share-search", params); + return req.data; +} diff --git a/apps/client/src/features/search/share-search-spotlight.tsx b/apps/client/src/features/search/share-search-spotlight.tsx new file mode 100644 index 00000000..bfbced6e --- /dev/null +++ b/apps/client/src/features/search/share-search-spotlight.tsx @@ -0,0 +1,87 @@ +import { Group, Center, Text } from "@mantine/core"; +import { Spotlight } from "@mantine/spotlight"; +import { IconSearch } from "@tabler/icons-react"; +import React, { useState } from "react"; +import { Link } from "react-router-dom"; +import { useDebouncedValue } from "@mantine/hooks"; +import { useShareSearchQuery } from "@/features/search/queries/search-query"; +import { buildSharedPageUrl } from "@/features/page/page.utils.ts"; +import { getPageIcon } from "@/lib"; +import { useTranslation } from "react-i18next"; +import { shareSearchSpotlightStore } from "@/features/search/constants.ts"; + +interface ShareSearchSpotlightProps { + shareId?: string; +} +export function ShareSearchSpotlight({ shareId }: ShareSearchSpotlightProps) { + const { t } = useTranslation(); + const [query, setQuery] = useState(""); + const [debouncedSearchQuery] = useDebouncedValue(query, 300); + + const { data: searchResults } = useShareSearchQuery({ + query: debouncedSearchQuery, + shareId, + }); + + const pages = ( + searchResults && searchResults.length > 0 ? searchResults : [] + ).map((page) => ( + + +
{getPageIcon(page?.icon)}
+ +
+ {page.title} + + {page?.highlight && ( + + )} +
+
+
+ )); + + return ( + <> + + } + /> + + {query.length === 0 && pages.length === 0 && ( + {t("Start typing to search...")} + )} + + {query.length > 0 && pages.length === 0 && ( + {t("No results found...")} + )} + + {pages.length > 0 && pages} + + + + ); +} diff --git a/apps/client/src/features/search/types/search.types.ts b/apps/client/src/features/search/types/search.types.ts index 5a346f6b..1338e121 100644 --- a/apps/client/src/features/search/types/search.types.ts +++ b/apps/client/src/features/search/types/search.types.ts @@ -35,4 +35,5 @@ export interface ISuggestionResult { export interface IPageSearchParams { query: string; spaceId?: string; + shareId?: string; } diff --git a/apps/client/src/features/share/components/share-shell.tsx b/apps/client/src/features/share/components/share-shell.tsx index 799958d2..7fa0c941 100644 --- a/apps/client/src/features/share/components/share-shell.tsx +++ b/apps/client/src/features/share/components/share-shell.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import { ActionIcon, Affix, @@ -30,7 +30,12 @@ import { import { IconList } from "@tabler/icons-react"; import { useToggleToc } from "@/features/share/hooks/use-toggle-toc.ts"; import classes from "./share.module.css"; -import { useClickOutside } from "@mantine/hooks"; +import { + SearchControl, + SearchMobileControl, +} from "@/features/search/components/search-control.tsx"; +import { ShareSearchSpotlight } from "@/features/search/share-search-spotlight"; +import { shareSearchSpotlight } from "@/features/search/constants"; const MemoizedSharedTree = React.memo(SharedTree); @@ -54,21 +59,9 @@ export default function ShareShell({ const { data } = useGetSharedPageTreeQuery(shareId); const readOnlyEditor = useAtomValue(readOnlyEditorAtom); - const [navbarOutside, setNavbarOutside] = useState(null); - - useClickOutside( - () => { - if (mobileOpened) { - toggleMobile(); - } - }, - null, - [navbarOutside], - ); - return ( 1 && { navbar: { width: 300, @@ -91,7 +84,7 @@ export default function ShareShell({ > - + {data?.pageTree?.length > 1 && ( <> @@ -116,8 +109,21 @@ export default function ShareShell({ )} + + {shareId && ( + + + + )} + <> + {shareId && ( + + + + )} + {data?.pageTree?.length > 1 && ( - + )} @@ -186,6 +188,8 @@ export default function ShareShell({
+ + ); } diff --git a/apps/client/src/features/share/components/shared-tree.tsx b/apps/client/src/features/share/components/shared-tree.tsx index 5e85ab57..486127b9 100644 --- a/apps/client/src/features/share/components/shared-tree.tsx +++ b/apps/client/src/features/share/components/shared-tree.tsx @@ -15,6 +15,7 @@ import clsx from "clsx"; import { IconChevronDown, IconChevronRight, + IconFileDescription, IconPointFilled, } from "@tabler/icons-react"; import { ActionIcon, Box } from "@mantine/core"; @@ -23,6 +24,7 @@ import { OpenMap } from "react-arborist/dist/main/state/open-slice"; import classes from "@/features/page/tree/styles/tree.module.css"; import styles from "./share.module.css"; import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; +import EmojiPicker from "@/components/ui/emoji-picker.tsx"; interface SharedTree { sharedPageTree: ISharedPageTree; @@ -141,6 +143,20 @@ function Node({ node, style, tree }: NodeRendererProps) { }} > +
+ {}} + icon={ + node.data.icon ? ( + node.data.icon + ) : ( + + ) + } + readOnly={true} + removeEmojiAction={() => {}} + /> +
{node.data.name || t("untitled")} diff --git a/apps/client/src/features/share/utils.ts b/apps/client/src/features/share/utils.ts index 74ec349f..cc73eb57 100644 --- a/apps/client/src/features/share/utils.ts +++ b/apps/client/src/features/share/utils.ts @@ -11,11 +11,13 @@ export type SharedPageTreeNode = { parentPageId: string; hasChildren: boolean; children: SharedPageTreeNode[]; - label: string, - value: string, + label: string; + value: string; }; -export function buildSharedPageTree(pages: Partial): SharedPageTreeNode[] { +export function buildSharedPageTree( + pages: Partial, +): SharedPageTreeNode[] { const pageMap: Record = {}; // Initialize each page as a tree node and store it in a map. @@ -30,7 +32,7 @@ export function buildSharedPageTree(pages: Partial): SharedPageTreeNode hasChildren: false, spaceId: page.spaceId, parentPageId: page.parentPageId, - label: page.title || 'untitled', + label: page.title || "untitled", value: page.id, children: [], }; @@ -55,6 +57,12 @@ export function buildSharedPageTree(pages: Partial): SharedPageTreeNode } }); - // Return the sorted tree. - return sortPositionKeys(tree); + function sortTree(nodes: SharedPageTreeNode[]): SharedPageTreeNode[] { + return sortPositionKeys(nodes).map((node: SharedPageTreeNode) => ({ + ...node, + children: sortTree(node.children), + })); + } + + return sortTree(tree); } diff --git a/apps/client/src/features/space/components/sidebar/space-sidebar.tsx b/apps/client/src/features/space/components/sidebar/space-sidebar.tsx index 528e8051..5dbd420a 100644 --- a/apps/client/src/features/space/components/sidebar/space-sidebar.tsx +++ b/apps/client/src/features/space/components/sidebar/space-sidebar.tsx @@ -6,7 +6,6 @@ import { Tooltip, UnstyledButton, } from "@mantine/core"; -import { spotlight } from "@mantine/spotlight"; import { IconArrowDown, IconDots, @@ -16,9 +15,8 @@ import { IconSearch, IconSettings, } from "@tabler/icons-react"; - import classes from "./space-sidebar.module.css"; -import React, { useMemo } from "react"; +import React from "react"; import { useAtom } from "jotai"; import { SearchSpotlight } from "@/features/search/search-spotlight.tsx"; import { treeApiAtom } from "@/features/page/tree/atoms/tree-api-atom.ts"; @@ -40,6 +38,7 @@ import { SwitchSpace } from "./switch-space"; import ExportModal from "@/components/common/export-modal"; import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts"; +import { searchSpotlight } from "@/features/search/constants"; export function SpaceSidebar() { const { t } = useTranslation(); @@ -51,7 +50,7 @@ export function SpaceSidebar() { const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom); const { spaceSlug } = useParams(); - const { data: space, isLoading, isError } = useGetSpaceBySlugQuery(spaceSlug); + const { data: space } = useGetSpaceBySlugQuery(spaceSlug); const spaceRules = space?.membership?.permissions; const spaceAbility = useSpaceAbility(spaceRules); @@ -100,7 +99,10 @@ export function SpaceSidebar() {
- +
{ if (query.length < 1) { return; } const searchQuery = tsquery(query.trim() + '*'); - const queryResults = await this.db + let queryResults = this.db .selectFrom('pages') .select([ 'id', @@ -43,18 +49,71 @@ export class SearchService { 'highlight', ), ]) - .select((eb) => this.pageRepo.withSpace(eb)) - .where('spaceId', '=', searchParams.spaceId) .where('tsv', '@@', sql`to_tsquery(${searchQuery})`) .$if(Boolean(searchParams.creatorId), (qb) => qb.where('creatorId', '=', searchParams.creatorId), ) .orderBy('rank', 'desc') .limit(searchParams.limit | 20) - .offset(searchParams.offset || 0) - .execute(); + .offset(searchParams.offset || 0); - const searchResults = queryResults.map((result) => { + if (!searchParams.shareId) { + queryResults = queryResults.select((eb) => this.pageRepo.withSpace(eb)); + } + + if (searchParams.spaceId) { + // search by spaceId + queryResults = queryResults.where('spaceId', '=', searchParams.spaceId); + } else if (opts.userId && !searchParams.spaceId) { + // only search spaces the user is a member of + const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds( + opts.userId, + ); + if (userSpaceIds.length > 0) { + queryResults = queryResults + .where('spaceId', 'in', userSpaceIds) + .where('workspaceId', '=', opts.workspaceId); + } else { + return []; + } + } else if (searchParams.shareId && !searchParams.spaceId && !opts.userId) { + // search in shares + const shareId = searchParams.shareId; + const share = await this.shareRepo.findById(shareId); + if (!share || share.workspaceId !== opts.workspaceId) { + return []; + } + + const pageIdsToSearch = []; + if (share.includeSubPages) { + const pageList = await this.pageRepo.getPageAndDescendants( + share.pageId, + { + includeContent: false, + }, + ); + + pageIdsToSearch.push(...pageList.map((page) => page.id)); + } else { + pageIdsToSearch.push(share.pageId); + } + + if (pageIdsToSearch.length > 0) { + queryResults = queryResults + .where('id', 'in', pageIdsToSearch) + .where('workspaceId', '=', opts.workspaceId); + } else { + return []; + } + } else { + return []; + } + + //@ts-ignore + queryResults = await queryResults.execute(); + + //@ts-ignore + const searchResults = queryResults.map((result: SearchResponseDto) => { if (result.highlight) { result.highlight = result.highlight .replace(/\r\n|\r|\n/g, ' ') diff --git a/apps/server/src/core/share/share.service.ts b/apps/server/src/core/share/share.service.ts index a9140c0b..d71b6acd 100644 --- a/apps/server/src/core/share/share.service.ts +++ b/apps/server/src/core/share/share.service.ts @@ -15,6 +15,7 @@ import { getAttachmentIds, getProsemirrorContent, isAttachmentNode, + removeMarkTypeFromDoc, } from '../../common/helpers/prosemirror/utils'; import { Node } from '@tiptap/pm/model'; import { ShareRepo } from '@docmost/db/repos/share/share.repo'; @@ -223,11 +224,7 @@ export class ShareService { .end() .as('found'), ]) - .where( - isValidUUID(childPageId) ? 'id' : 'slugId', - '=', - childPageId, - ) + .where(isValidUUID(childPageId) ? 'id' : 'slugId', '=', childPageId) .unionAll((exp) => exp .selectFrom('pages as p') @@ -292,6 +289,7 @@ export class ShareService { updateAttachmentAttr(node, 'url', token); }); - return doc.toJSON(); + const removeCommentMarks = removeMarkTypeFromDoc(doc, 'comment'); + return removeCommentMarks.toJSON(); } } From c528f7e8584a144669e0525e83cd9df208442128 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:34:28 +0100 Subject: [PATCH 34/63] v0.20.1 --- apps/client/package.json | 2 +- apps/server/package.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index e56b7e68..57e0026a 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "0.20.0", + "version": "0.20.1", "scripts": { "dev": "vite", "build": "tsc && vite build", diff --git a/apps/server/package.json b/apps/server/package.json index 935647ed..a1a6f081 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.20.0", + "version": "0.20.1", "description": "", "author": "", "private": true, diff --git a/package.json b/package.json index 0cafcc3f..8e9fcead 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "docmost", "homepage": "https://docmost.com", - "version": "0.20.0", + "version": "0.20.1", "private": true, "scripts": { "build": "nx run-many -t build", From 08f223899a415b5d95b53a0c1d003401778f0dc2 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Wed, 23 Apr 2025 16:07:58 +0100 Subject: [PATCH 35/63] cloud trial refactor --- apps/client/src/ee/billing/components/billing-trial.tsx | 5 +++-- apps/client/src/ee/hooks/use-trial-end-action.tsx | 4 ++-- apps/client/src/lib/config.ts | 4 ++++ apps/client/vite.config.ts | 2 ++ apps/server/src/ee | 2 +- apps/server/src/integrations/static/static.module.ts | 3 +++ 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/client/src/ee/billing/components/billing-trial.tsx b/apps/client/src/ee/billing/components/billing-trial.tsx index 82dcbb03..38acc17c 100644 --- a/apps/client/src/ee/billing/components/billing-trial.tsx +++ b/apps/client/src/ee/billing/components/billing-trial.tsx @@ -1,6 +1,7 @@ import { Alert } from "@mantine/core"; import { useBillingQuery } from "@/ee/billing/queries/billing-query.ts"; import useTrial from "@/ee/hooks/use-trial.tsx"; +import { getBillingTrialDays } from '@/lib/config.ts'; export default function BillingTrial() { const { data: billing, isLoading } = useBillingQuery(); @@ -15,14 +16,14 @@ export default function BillingTrial() { {trialDaysLeft > 0 && !billing && ( You have {trialDaysLeft} {trialDaysLeft === 1 ? "day" : "days"} left - in your 7-day trial. Please subscribe to a plan before your trial + in your {getBillingTrialDays()}-day free trial. Please subscribe to a paid plan before your trial ends. )} {trialDaysLeft === 0 && ( - Your 7-day trial has come to an end. Please subscribe to a plan to + Your {getBillingTrialDays()}-day free trial has come to an end. Please subscribe to a paid plan to continue using this service. )} diff --git a/apps/client/src/ee/hooks/use-trial-end-action.tsx b/apps/client/src/ee/hooks/use-trial-end-action.tsx index 931fd6a8..09248c6a 100644 --- a/apps/client/src/ee/hooks/use-trial-end-action.tsx +++ b/apps/client/src/ee/hooks/use-trial-end-action.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { useLocation, useNavigate } from "react-router-dom"; -import { isCloud } from "@/lib/config.ts"; +import { getBillingTrialDays, isCloud } from "@/lib/config.ts"; import APP_ROUTE from "@/lib/app-route.ts"; import useUserRole from "@/hooks/use-user-role.tsx"; import { notifications } from "@mantine/notifications"; @@ -18,7 +18,7 @@ export const useTrialEndAction = () => { notifications.show({ position: "top-right", color: "red", - title: "Your 7-day trial has ended", + title: `Your ${getBillingTrialDays()}-day trial has ended`, message: "Please upgrade to a paid plan or contact your workspace admin.", autoClose: false, diff --git a/apps/client/src/lib/config.ts b/apps/client/src/lib/config.ts index 9e1efc30..2f621b91 100644 --- a/apps/client/src/lib/config.ts +++ b/apps/client/src/lib/config.ts @@ -74,6 +74,10 @@ export function getDrawioUrl() { return getConfigValue("DRAWIO_URL", "https://embed.diagrams.net"); } +export function getBillingTrialDays() { + return getConfigValue("BILLING_TRIAL_DAYS"); +} + function getConfigValue(key: string, defaultValue: string = undefined): string { const rawValue = import.meta.env.DEV ? process?.env?.[key] diff --git a/apps/client/vite.config.ts b/apps/client/vite.config.ts index 2e557fe5..a6efc4bc 100644 --- a/apps/client/vite.config.ts +++ b/apps/client/vite.config.ts @@ -12,6 +12,7 @@ export default defineConfig(({ mode }) => { CLOUD, SUBDOMAIN_HOST, COLLAB_URL, + BILLING_TRIAL_DAYS, } = loadEnv(mode, envPath, ""); return { @@ -23,6 +24,7 @@ export default defineConfig(({ mode }) => { CLOUD, SUBDOMAIN_HOST, COLLAB_URL, + BILLING_TRIAL_DAYS, }, APP_VERSION: JSON.stringify(process.env.npm_package_version), }, diff --git a/apps/server/src/ee b/apps/server/src/ee index d3095f2d..4e7319ab 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit d3095f2d8bd2870da7f3b534c83c84e8fb3099bc +Subproject commit 4e7319ab01f0ea535bb4dd6b98ea996fcd1c3ffc diff --git a/apps/server/src/integrations/static/static.module.ts b/apps/server/src/integrations/static/static.module.ts index e5152ef8..15db42b0 100644 --- a/apps/server/src/integrations/static/static.module.ts +++ b/apps/server/src/integrations/static/static.module.ts @@ -42,6 +42,9 @@ export class StaticModule implements OnModuleInit { ? this.environmentService.getSubdomainHost() : undefined, COLLAB_URL: this.environmentService.getCollabUrl(), + BILLING_TRIAL_DAYS: this.environmentService.isCloud() + ? this.environmentService.getBillingTrialDays() + : undefined, }; const windowScriptContent = ``; From 33c314d4e8679f4ced56754208ed51ca49dc3b79 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:56:54 +0100 Subject: [PATCH 36/63] remove clickoutside hook --- .../src/components/layouts/global/global-app-shell.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/apps/client/src/components/layouts/global/global-app-shell.tsx b/apps/client/src/components/layouts/global/global-app-shell.tsx index c8aff4cc..58ff5609 100644 --- a/apps/client/src/components/layouts/global/global-app-shell.tsx +++ b/apps/client/src/components/layouts/global/global-app-shell.tsx @@ -14,7 +14,6 @@ import { AppHeader } from "@/components/layouts/global/app-header.tsx"; import Aside from "@/components/layouts/global/aside.tsx"; import classes from "./app-shell.module.css"; import { useTrialEndAction } from "@/ee/hooks/use-trial-end-action.tsx"; -import { useClickOutside, useMergedRef } from "@mantine/hooks"; import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts"; export default function GlobalAppShell({ @@ -30,13 +29,6 @@ export default function GlobalAppShell({ const [sidebarWidth, setSidebarWidth] = useAtom(sidebarWidthAtom); const [isResizing, setIsResizing] = useState(false); const sidebarRef = useRef(null); - const navbarOutsideRef = useClickOutside(() => { - if (mobileOpened) { - toggleMobile(); - } - }); - - const mergedRef = useMergedRef(sidebarRef, navbarOutsideRef); const startResizing = React.useCallback((mouseDownEvent) => { mouseDownEvent.preventDefault(); @@ -112,7 +104,7 @@ export default function GlobalAppShell({
{isSpaceRoute && } From 31e5c0c66046a02012b719b4333213a8a9deeb84 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:57:14 +0100 Subject: [PATCH 37/63] v0.20.2 --- apps/client/package.json | 2 +- apps/server/package.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index 57e0026a..cd731696 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "0.20.1", + "version": "0.20.2", "scripts": { "dev": "vite", "build": "tsc && vite build", diff --git a/apps/server/package.json b/apps/server/package.json index a1a6f081..10637756 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.20.1", + "version": "0.20.2", "description": "", "author": "", "private": true, diff --git a/package.json b/package.json index 8e9fcead..8547d75f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "docmost", "homepage": "https://docmost.com", - "version": "0.20.1", + "version": "0.20.2", "private": true, "scripts": { "build": "nx run-many -t build", From 7993532111109861d573df648b02e85401f2277b Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Thu, 24 Apr 2025 23:18:54 +0100 Subject: [PATCH 38/63] fix page export (#1081) --- apps/server/src/database/repos/page/page.repo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/database/repos/page/page.repo.ts b/apps/server/src/database/repos/page/page.repo.ts index 8f06c4d4..52a69707 100644 --- a/apps/server/src/database/repos/page/page.repo.ts +++ b/apps/server/src/database/repos/page/page.repo.ts @@ -244,7 +244,7 @@ export class PageRepo { 'p.spaceId', 'p.workspaceId', ]) - .$if(opts?.includeContent, (qb) => qb.select('content')) + .$if(opts?.includeContent, (qb) => qb.select('p.content')) .innerJoin('page_hierarchy as ph', 'p.parentPageId', 'ph.id'), ), ) From 0289c5cb09af53927bacbaf15931543eed682f84 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Thu, 24 Apr 2025 23:19:39 +0100 Subject: [PATCH 39/63] Reduce markdown checkbox space --- apps/server/src/integrations/export/turndown-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/integrations/export/turndown-utils.ts b/apps/server/src/integrations/export/turndown-utils.ts index ce55c659..44e606f3 100644 --- a/apps/server/src/integrations/export/turndown-utils.ts +++ b/apps/server/src/integrations/export/turndown-utils.ts @@ -68,7 +68,7 @@ function taskList(turndownService: TurndownService) { ) as HTMLInputElement; const isChecked = checkbox.checked; - return `- ${isChecked ? '[x]' : '[ ]'} ${content.trim()} \n`; + return `- ${isChecked ? '[x]' : '[ ]'} ${content.trim()} \n`; }, }); } From 9bbd62e0f044e1f5cd85827462823bb563af2148 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Thu, 24 Apr 2025 23:22:53 +0100 Subject: [PATCH 40/63] v0.20.3 --- apps/client/package.json | 2 +- apps/server/package.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index cd731696..cc1c2d0b 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "0.20.2", + "version": "0.20.3", "scripts": { "dev": "vite", "build": "tsc && vite build", diff --git a/apps/server/package.json b/apps/server/package.json index 10637756..2f3a99bd 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.20.2", + "version": "0.20.3", "description": "", "author": "", "private": true, diff --git a/package.json b/package.json index 8547d75f..4ef488db 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "docmost", "homepage": "https://docmost.com", - "version": "0.20.2", + "version": "0.20.3", "private": true, "scripts": { "build": "nx run-many -t build", From e8847bd9cdca23bda532542f4d268c832725821b Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Tue, 29 Apr 2025 23:29:00 +0100 Subject: [PATCH 41/63] fix: handle unhandled exceptions (#1116) * Handle unhandled exceptions * cleanup --- .../src/collaboration/collaboration.module.ts | 11 ++++++++++- .../extensions/persistence.extension.ts | 2 +- apps/server/src/main.ts | 15 +++++++++------ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/apps/server/src/collaboration/collaboration.module.ts b/apps/server/src/collaboration/collaboration.module.ts index bd5e1e6f..30cb0ccf 100644 --- a/apps/server/src/collaboration/collaboration.module.ts +++ b/apps/server/src/collaboration/collaboration.module.ts @@ -1,4 +1,4 @@ -import { Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; +import { Logger, Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; import { AuthenticationExtension } from './extensions/authentication.extension'; import { PersistenceExtension } from './extensions/persistence.extension'; import { CollaborationGateway } from './collaboration.gateway'; @@ -22,6 +22,7 @@ import { LoggerExtension } from './extensions/logger.extension'; imports: [TokenModule], }) export class CollaborationModule implements OnModuleInit, OnModuleDestroy { + private readonly logger = new Logger(CollaborationModule.name); private collabWsAdapter: CollabWsAdapter; private path = '/collab'; @@ -38,7 +39,15 @@ export class CollaborationModule implements OnModuleInit, OnModuleDestroy { wss.on('connection', (client: WebSocket, request: IncomingMessage) => { this.collaborationGateway.handleConnection(client, request); + + client.on('error', (error) => { + this.logger.error('WebSocket client error:', error); + }); }); + + wss.on('error', (error) => + this.logger.log('WebSocket server error:', error), + ); } async onModuleDestroy(): Promise { diff --git a/apps/server/src/collaboration/extensions/persistence.extension.ts b/apps/server/src/collaboration/extensions/persistence.extension.ts index a1f9f519..3c206e1a 100644 --- a/apps/server/src/collaboration/extensions/persistence.extension.ts +++ b/apps/server/src/collaboration/extensions/persistence.extension.ts @@ -130,7 +130,7 @@ export class PersistenceExtension implements Extension { ); this.contributors.delete(documentName); } catch (err) { - this.logger.log('Contributors error:' + err?.['message']); + this.logger.debug('Contributors error:' + err?.['message']); } await this.pageRepo.updatePage( diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 95df255d..a371b9b9 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -4,12 +4,7 @@ import { FastifyAdapter, NestFastifyApplication, } from '@nestjs/platform-fastify'; -import { - Logger, - NotFoundException, - RequestMethod, - ValidationPipe, -} from '@nestjs/common'; +import { Logger, NotFoundException, ValidationPipe } from '@nestjs/common'; import { TransformHttpResponseInterceptor } from './common/interceptors/http-response.interceptor'; import { WsRedisIoAdapter } from './ws/adapter/ws-redis.adapter'; import { InternalLogFilter } from './common/logger/internal-log-filter'; @@ -92,6 +87,14 @@ async function bootstrap() { const logger = new Logger('NestApplication'); + process.on('unhandledRejection', (reason, promise) => { + logger.error(`UnhandledRejection: ${promise}, reason: ${reason}`); + }); + + process.on('uncaughtException', (error) => { + logger.error('UncaughtException:', error); + }); + const port = process.env.PORT || 3000; await app.listen(port, '0.0.0.0', () => { logger.log( From 8327251ab680452579bfe528370e6003c193b641 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 29 Apr 2025 23:30:12 +0100 Subject: [PATCH 42/63] fix typo --- apps/client/src/ee/security/components/enforce-sso.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/ee/security/components/enforce-sso.tsx b/apps/client/src/ee/security/components/enforce-sso.tsx index 4cbbc7ac..31a9f6fa 100644 --- a/apps/client/src/ee/security/components/enforce-sso.tsx +++ b/apps/client/src/ee/security/components/enforce-sso.tsx @@ -15,7 +15,7 @@ export default function EnforceSso() { {t("Enforce SSO")} {t( - "Once enforced, members will not able able to login with email and password.", + "Once enforced, members will not able to login with email and password.", )}
From 0402f7efb590d1efbecb5be4dfe20de264186e75 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:33:01 +0100 Subject: [PATCH 43/63] sync --- apps/server/src/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/ee b/apps/server/src/ee index 4e7319ab..96404fc1 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit 4e7319ab01f0ea535bb4dd6b98ea996fcd1c3ffc +Subproject commit 96404fc121844a707bd0fb1e280df6e2d52e8973 From de7982fe301dfc162ad7ad128ab506882b73ab82 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:43:16 +0100 Subject: [PATCH 44/63] feat: copy page to different space (#1118) * Add copy page to space endpoint * copy storage function * copy function * feat: copy attachments too * Copy page - WIP * fix type * sync * cleanup --- .../public/locales/en-US/translation.json | 5 +- .../page/components/copy-page-modal.tsx | 105 +++++++++++ .../components/header/page-header-menu.tsx | 8 +- .../page/components/move-page-modal.tsx | 8 +- .../features/page/services/page-service.ts | 6 + .../page/tree/components/space-tree.tsx | 24 +++ .../src/features/page/types/page.types.ts | 7 +- .../src/common/helpers/prosemirror/utils.ts | 25 ++- .../server/src/core/page/dto/copy-page.dto.ts | 24 +++ .../server/src/core/page/dto/move-page.dto.ts | 10 +- apps/server/src/core/page/page.controller.ts | 31 ++++ apps/server/src/core/page/page.module.ts | 2 + .../src/core/page/services/page.service.ts | 166 +++++++++++++++++- .../storage/drivers/local.driver.ts | 10 ++ .../integrations/storage/drivers/s3.driver.ts | 17 ++ .../interfaces/storage-driver.interface.ts | 2 + .../integrations/storage/storage.service.ts | 5 + 17 files changed, 441 insertions(+), 14 deletions(-) create mode 100644 apps/client/src/features/page/components/copy-page-modal.tsx create mode 100644 apps/server/src/core/page/dto/copy-page.dto.ts diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 0746ed15..127730d5 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -383,5 +383,8 @@ "Publicly shared pages from spaces you are a member of will appear here": "Publicly shared pages from spaces you are a member of will appear here", "Share deleted successfully": "Share deleted successfully", "Share not found": "Share not found", - "Failed to share page": "Failed to share page" + "Failed to share page": "Failed to share page", + "Copy page": "Copy page", + "Copy page to a different space.": "Copy page to a different space.", + "Page copied successfully": "Page copied successfully" } diff --git a/apps/client/src/features/page/components/copy-page-modal.tsx b/apps/client/src/features/page/components/copy-page-modal.tsx new file mode 100644 index 00000000..e639fbac --- /dev/null +++ b/apps/client/src/features/page/components/copy-page-modal.tsx @@ -0,0 +1,105 @@ +import { Modal, Button, Group, Text } from "@mantine/core"; +import { copyPageToSpace } 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 CopyPageModalProps { + pageId: string; + currentSpaceSlug: string; + open: boolean; + onClose: () => void; +} + +export default function CopyPageModal({ + pageId, + currentSpaceSlug, + open, + onClose, +}: CopyPageModalProps) { + const { t } = useTranslation(); + const [targetSpace, setTargetSpace] = useState(null); + const navigate = useNavigate(); + + const handleCopy = async () => { + if (!targetSpace) return; + + try { + const copiedPage = await copyPageToSpace({ + pageId, + spaceId: targetSpace.id, + }); + queryClient.removeQueries({ + predicate: (item) => + ["pages", "sidebar-pages", "root-sidebar-pages"].includes( + item.queryKey[0] as string, + ), + }); + + const pageUrl = buildPageUrl( + copiedPage.space.slug, + copiedPage.slugId, + copiedPage.title, + ); + navigate(pageUrl); + notifications.show({ + message: t("Page copied successfully"), + }); + onClose(); + setTargetSpace(null); + } catch (err) { + notifications.show({ + message: err.response?.data.message || "An error occurred", + color: "red", + }); + console.log(err); + } + }; + + const handleChange = (space: ISpace) => { + setTargetSpace(space); + }; + + return ( + e.stopPropagation()} + > + + + + {t("Copy page")} + + + + + {t("Copy page to a different space.")} + + + + + + + + + + + ); +} diff --git a/apps/client/src/features/page/components/header/page-header-menu.tsx b/apps/client/src/features/page/components/header/page-header-menu.tsx index 9267ad9e..09305491 100644 --- a/apps/client/src/features/page/components/header/page-header-menu.tsx +++ b/apps/client/src/features/page/components/header/page-header-menu.tsx @@ -12,7 +12,7 @@ import { IconTrash, IconWifiOff, } from "@tabler/icons-react"; -import React, { useEffect } from "react"; +import React from "react"; import useToggleAside from "@/hooks/use-toggle-aside.tsx"; import { useAtom } from "jotai"; import { historyAtoms } from "@/features/page-history/atoms/history-atoms.ts"; @@ -35,7 +35,7 @@ import { 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"; -import ShareModal from '@/features/share/components/share-modal.tsx'; +import ShareModal from "@/features/share/components/share-modal.tsx"; interface PageHeaderMenuProps { readOnly?: boolean; @@ -59,7 +59,7 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) { )} - + { const pageUrl = diff --git a/apps/client/src/features/page/components/move-page-modal.tsx b/apps/client/src/features/page/components/move-page-modal.tsx index 4788951b..74880084 100644 --- a/apps/client/src/features/page/components/move-page-modal.tsx +++ b/apps/client/src/features/page/components/move-page-modal.tsx @@ -46,6 +46,7 @@ export default function MovePageModal({ message: t("Page moved successfully"), }); onClose(); + setTargetSpace(null); } catch (err) { notifications.show({ message: err.response?.data.message || "An error occurred", @@ -53,7 +54,6 @@ export default function MovePageModal({ }); console.log(err); } - setTargetSpace(null); }; const handleChange = (space: ISpace) => { @@ -69,7 +69,7 @@ export default function MovePageModal({ yOffset="10vh" xOffset={0} mah={400} - onClick={e => e.stopPropagation()} + onClick={(e) => e.stopPropagation()} > @@ -78,7 +78,9 @@ export default function MovePageModal({ - {t("Move page to a different space.")} + + {t("Move page to a different space.")} + { await api.post("/pages/move-to-space", data); } +export async function copyPageToSpace(data: ICopyPageToSpace): Promise { + const req = await api.post("/pages/copy-to-space", data); + return req.data; +} + export async function getSidebarPages( params: SidebarPagesParams, ): Promise> { diff --git a/apps/client/src/features/page/tree/components/space-tree.tsx b/apps/client/src/features/page/tree/components/space-tree.tsx index 5a00f258..1df62678 100644 --- a/apps/client/src/features/page/tree/components/space-tree.tsx +++ b/apps/client/src/features/page/tree/components/space-tree.tsx @@ -15,6 +15,7 @@ import { IconArrowRight, IconChevronDown, IconChevronRight, + IconCopy, IconDotsVertical, IconFileDescription, IconFileExport, @@ -60,6 +61,7 @@ import ExportModal from "@/components/common/export-modal"; import MovePageModal from "../../components/move-page-modal.tsx"; import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts"; +import CopyPageModal from "../../components/copy-page-modal.tsx"; interface SpaceTreeProps { spaceId: string; @@ -448,6 +450,10 @@ function NodeMenu({ node, treeApi }: NodeMenuProps) { movePageModalOpened, { open: openMovePageModal, close: closeMoveSpaceModal }, ] = useDisclosure(false); + const [ + copyPageModalOpened, + { open: openCopyPageModal, close: closeCopySpaceModal }, + ] = useDisclosure(false); const handleCopyLink = () => { const pageUrl = @@ -511,6 +517,17 @@ function NodeMenu({ node, treeApi }: NodeMenuProps) { {t("Move")} + } + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + openCopyPageModal(); + }} + > + {t("Copy")} + + + + + ability.cannot(SpaceCaslAction.Edit, SpaceCaslSubject.Page), + ) + ) { + throw new ForbiddenException(); + } + + return this.pageService.copyPageToSpace(copiedPage, dto.spaceId, user); + } + @HttpCode(HttpStatus.OK) @Post('move') async movePage(@Body() dto: MovePageDto, @AuthUser() user: User) { diff --git a/apps/server/src/core/page/page.module.ts b/apps/server/src/core/page/page.module.ts index 93f8f719..fd336537 100644 --- a/apps/server/src/core/page/page.module.ts +++ b/apps/server/src/core/page/page.module.ts @@ -2,10 +2,12 @@ import { Module } from '@nestjs/common'; import { PageService } from './services/page.service'; import { PageController } from './page.controller'; import { PageHistoryService } from './services/page-history.service'; +import { StorageModule } from '../../integrations/storage/storage.module'; @Module({ controllers: [PageController], providers: [PageService, PageHistoryService], exports: [PageService, PageHistoryService], + imports: [StorageModule] }) export class PageModule {} diff --git a/apps/server/src/core/page/services/page.service.ts b/apps/server/src/core/page/services/page.service.ts index 5e4553c6..41f3055e 100644 --- a/apps/server/src/core/page/services/page.service.ts +++ b/apps/server/src/core/page/services/page.service.ts @@ -1,12 +1,13 @@ import { BadRequestException, Injectable, + Logger, NotFoundException, } from '@nestjs/common'; import { CreatePageDto } from '../dto/create-page.dto'; import { UpdatePageDto } from '../dto/update-page.dto'; import { PageRepo } from '@docmost/db/repos/page/page.repo'; -import { Page } from '@docmost/db/types/entity.types'; +import { InsertablePage, Page, User } from '@docmost/db/types/entity.types'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; import { executeWithPagination, @@ -21,13 +22,28 @@ import { DB } from '@docmost/db/types/db'; import { generateSlugId } from '../../../common/helpers'; import { executeTx } from '@docmost/db/utils'; import { AttachmentRepo } from '@docmost/db/repos/attachment/attachment.repo'; +import { v7 as uuid7 } from 'uuid'; +import { + createYdocFromJson, + getAttachmentIds, + getProsemirrorContent, + isAttachmentNode, + removeMarkTypeFromDoc, +} from '../../../common/helpers/prosemirror/utils'; +import { jsonToNode, jsonToText } from 'src/collaboration/collaboration.util'; +import { CopyPageMapEntry, ICopyPageAttachment } from '../dto/copy-page.dto'; +import { Node as PMNode } from '@tiptap/pm/model'; +import { StorageService } from '../../../integrations/storage/storage.service'; @Injectable() export class PageService { + private readonly logger = new Logger(PageService.name); + constructor( private pageRepo: PageRepo, private attachmentRepo: AttachmentRepo, @InjectKysely() private readonly db: KyselyDB, + private readonly storageService: StorageService, ) {} async findById( @@ -242,6 +258,154 @@ export class PageService { }); } + async copyPageToSpace(rootPage: Page, spaceId: string, authUser: User) { + //TODO: + // i. maintain internal links within copied pages + + const nextPosition = await this.nextPagePosition(spaceId); + + const pages = await this.pageRepo.getPageAndDescendants(rootPage.id, { + includeContent: true, + }); + + const pageMap = new Map(); + pages.forEach((page) => { + pageMap.set(page.id, { + newPageId: uuid7(), + newSlugId: generateSlugId(), + oldSlugId: page.slugId, + }); + }); + + const attachmentMap = new Map(); + + const insertablePages: InsertablePage[] = await Promise.all( + pages.map(async (page) => { + const pageContent = getProsemirrorContent(page.content); + const pageFromMap = pageMap.get(page.id); + + const doc = jsonToNode(pageContent); + const prosemirrorDoc = removeMarkTypeFromDoc(doc, 'comment'); + + const attachmentIds = getAttachmentIds(prosemirrorDoc.toJSON()); + + if (attachmentIds.length > 0) { + attachmentIds.forEach((attachmentId: string) => { + const newPageId = pageFromMap.newPageId; + const newAttachmentId = uuid7(); + attachmentMap.set(attachmentId, { + newPageId: newPageId, + oldPageId: page.id, + oldAttachmentId: attachmentId, + newAttachmentId: newAttachmentId, + }); + + prosemirrorDoc.descendants((node: PMNode) => { + if (isAttachmentNode(node.type.name)) { + if (node.attrs.attachmentId === attachmentId) { + //@ts-ignore + node.attrs.attachmentId = newAttachmentId; + + if (node.attrs.src) { + //@ts-ignore + node.attrs.src = node.attrs.src.replace( + attachmentId, + newAttachmentId, + ); + } + if (node.attrs.src) { + //@ts-ignore + node.attrs.src = node.attrs.src.replace( + attachmentId, + newAttachmentId, + ); + } + } + } + }); + }); + } + + const prosemirrorJson = prosemirrorDoc.toJSON(); + + return { + id: pageFromMap.newPageId, + slugId: pageFromMap.newSlugId, + title: page.title, + icon: page.icon, + content: prosemirrorJson, + textContent: jsonToText(prosemirrorJson), + ydoc: createYdocFromJson(prosemirrorJson), + position: page.id === rootPage.id ? nextPosition : page.position, + spaceId: spaceId, + workspaceId: page.workspaceId, + creatorId: authUser.id, + lastUpdatedById: authUser.id, + parentPageId: page.parentPageId + ? pageMap.get(page.parentPageId)?.newPageId + : null, + }; + }), + ); + + await this.db.insertInto('pages').values(insertablePages).execute(); + + //TODO: best to handle this in a queue + const attachmentsIds = Array.from(attachmentMap.keys()); + if (attachmentsIds.length > 0) { + const attachments = await this.db + .selectFrom('attachments') + .selectAll() + .where('id', 'in', attachmentsIds) + .where('workspaceId', '=', rootPage.workspaceId) + .execute(); + + for (const attachment of attachments) { + try { + const pageAttachment = attachmentMap.get(attachment.id); + + // make sure the copied attachment belongs to the page it was copied from + if (attachment.pageId !== pageAttachment.oldPageId) { + continue; + } + + const newAttachmentId = pageAttachment.newAttachmentId; + + const newPageId = pageAttachment.newPageId; + + const newPathFile = attachment.filePath.replace( + attachment.id, + newAttachmentId, + ); + await this.storageService.copy(attachment.filePath, newPathFile); + await this.db + .insertInto('attachments') + .values({ + id: newAttachmentId, + type: attachment.type, + filePath: newPathFile, + fileName: attachment.fileName, + fileSize: attachment.fileSize, + mimeType: attachment.mimeType, + fileExt: attachment.fileExt, + creatorId: attachment.creatorId, + workspaceId: attachment.workspaceId, + pageId: newPageId, + spaceId: spaceId, + }) + .execute(); + } catch (err) { + this.logger.log(err); + } + } + } + + const newPageId = pageMap.get(rootPage.id).newPageId; + return await this.pageRepo.findById(newPageId, { + includeSpace: true, + }); + } + async movePage(dto: MovePageDto, movedPage: Page) { // validate position value by attempting to generate a key try { diff --git a/apps/server/src/integrations/storage/drivers/local.driver.ts b/apps/server/src/integrations/storage/drivers/local.driver.ts index f2553733..e3da7700 100644 --- a/apps/server/src/integrations/storage/drivers/local.driver.ts +++ b/apps/server/src/integrations/storage/drivers/local.driver.ts @@ -25,6 +25,16 @@ export class LocalDriver implements StorageDriver { } } + async copy(fromFilePath: string, toFilePath: string): Promise { + try { + if (await this.exists(fromFilePath)) { + await fs.copy(fromFilePath, toFilePath); + } + } catch (err) { + throw new Error(`Failed to copy file: ${(err as Error).message}`); + } + } + async read(filePath: string): Promise { try { return await fs.readFile(this._fullPath(filePath)); diff --git a/apps/server/src/integrations/storage/drivers/s3.driver.ts b/apps/server/src/integrations/storage/drivers/s3.driver.ts index 78f7548c..41feb365 100644 --- a/apps/server/src/integrations/storage/drivers/s3.driver.ts +++ b/apps/server/src/integrations/storage/drivers/s3.driver.ts @@ -1,5 +1,6 @@ import { S3StorageConfig, StorageDriver, StorageOption } from '../interfaces'; import { + CopyObjectCommand, DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, @@ -39,6 +40,22 @@ export class S3Driver implements StorageDriver { } } + async copy(fromFilePath: string, toFilePath: string): Promise { + try { + if (await this.exists(fromFilePath)) { + await this.s3Client.send( + new CopyObjectCommand({ + Bucket: this.config.bucket, + CopySource: `${this.config.bucket}/${fromFilePath}`, + Key: toFilePath, + }), + ); + } + } catch (err) { + throw new Error(`Failed to copy file: ${(err as Error).message}`); + } + } + async read(filePath: string): Promise { try { const command = new GetObjectCommand({ diff --git a/apps/server/src/integrations/storage/interfaces/storage-driver.interface.ts b/apps/server/src/integrations/storage/interfaces/storage-driver.interface.ts index 419587f4..6f18ff45 100644 --- a/apps/server/src/integrations/storage/interfaces/storage-driver.interface.ts +++ b/apps/server/src/integrations/storage/interfaces/storage-driver.interface.ts @@ -1,6 +1,8 @@ export interface StorageDriver { upload(filePath: string, file: Buffer): Promise; + copy(fromFilePath: string, toFilePath: string): Promise; + read(filePath: string): Promise; exists(filePath: string): Promise; diff --git a/apps/server/src/integrations/storage/storage.service.ts b/apps/server/src/integrations/storage/storage.service.ts index acbd2188..fe4c1cb5 100644 --- a/apps/server/src/integrations/storage/storage.service.ts +++ b/apps/server/src/integrations/storage/storage.service.ts @@ -14,6 +14,11 @@ export class StorageService { this.logger.debug(`File uploaded successfully. Path: ${filePath}`); } + async copy(fromFilePath: string, toFilePath: string) { + await this.storageDriver.copy(fromFilePath, toFilePath); + this.logger.debug(`File copied successfully. Path: ${toFilePath}`); + } + async read(filePath: string): Promise { return this.storageDriver.read(filePath); } From 7adbf85030c5f7397ebc795f631231fa418bc21d Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:44:58 +0100 Subject: [PATCH 45/63] v0.20.4 --- apps/client/package.json | 2 +- apps/server/package.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index cc1c2d0b..586f50a1 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "0.20.3", + "version": "0.20.4", "scripts": { "dev": "vite", "build": "tsc && vite build", diff --git a/apps/server/package.json b/apps/server/package.json index 2f3a99bd..5082d129 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.20.3", + "version": "0.20.4", "description": "", "author": "", "private": true, diff --git a/package.json b/package.json index 4ef488db..3731015c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "docmost", "homepage": "https://docmost.com", - "version": "0.20.3", + "version": "0.20.4", "private": true, "scripts": { "build": "nx run-many -t build", From bc3cb2d63f942f221bb91b4bde31fd1c93cd79df Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Wed, 7 May 2025 15:10:58 +0100 Subject: [PATCH 46/63] fix: increase random subdomain suffix --- apps/server/src/core/workspace/services/workspace.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/src/core/workspace/services/workspace.service.ts b/apps/server/src/core/workspace/services/workspace.service.ts index eb42f9e9..d6359893 100644 --- a/apps/server/src/core/workspace/services/workspace.service.ts +++ b/apps/server/src/core/workspace/services/workspace.service.ts @@ -387,14 +387,14 @@ export class WorkspaceService { .replace(/[^a-z0-9]/g, '') .substring(0, 20); // Ensure we leave room for a random suffix. - const maxSuffixLength = 3; + const maxSuffixLength = 6; if (subdomain.length < 4) { subdomain = `${subdomain}-${generateRandomSuffix(maxSuffixLength)}`; } if (DISALLOWED_HOSTNAMES.includes(subdomain)) { - subdomain = `myworkspace-${generateRandomSuffix(maxSuffixLength)}`; + subdomain = `workspace-${generateRandomSuffix(maxSuffixLength)}`; } let uniqueHostname = subdomain; From 55d1a2c9326171c091063bf567f3954f3021cb72 Mon Sep 17 00:00:00 2001 From: edo0 <16632292+edo0@users.noreply.github.com> Date: Fri, 9 May 2025 11:11:02 +0100 Subject: [PATCH 47/63] Fix typo in enforce-sso.tsx (#1145) --- apps/client/src/ee/security/components/enforce-sso.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/ee/security/components/enforce-sso.tsx b/apps/client/src/ee/security/components/enforce-sso.tsx index 31a9f6fa..183046e3 100644 --- a/apps/client/src/ee/security/components/enforce-sso.tsx +++ b/apps/client/src/ee/security/components/enforce-sso.tsx @@ -15,7 +15,7 @@ export default function EnforceSso() { {t("Enforce SSO")} {t( - "Once enforced, members will not able to login with email and password.", + "Once enforced, members will not be able to login with email and password.", )}
From c6bca6a602783791428429f4bad312e48133d771 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Fri, 9 May 2025 16:44:33 +0100 Subject: [PATCH 48/63] fix deprecated kysely usage --- apps/server/src/database/repos/user-token/user-token.repo.ts | 2 +- apps/server/src/database/repos/workspace/workspace.repo.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/src/database/repos/user-token/user-token.repo.ts b/apps/server/src/database/repos/user-token/user-token.repo.ts index 58971dc4..0137cb0a 100644 --- a/apps/server/src/database/repos/user-token/user-token.repo.ts +++ b/apps/server/src/database/repos/user-token/user-token.repo.ts @@ -70,7 +70,7 @@ export class UserTokenRepo { .where('userId', '=', userId) .where('workspaceId', '=', workspaceId) .where('type', '=', tokenType) - .orderBy('expiresAt desc') + .orderBy('expiresAt', 'desc') .execute(); } diff --git a/apps/server/src/database/repos/workspace/workspace.repo.ts b/apps/server/src/database/repos/workspace/workspace.repo.ts index a38d02a7..8b9765f4 100644 --- a/apps/server/src/database/repos/workspace/workspace.repo.ts +++ b/apps/server/src/database/repos/workspace/workspace.repo.ts @@ -70,7 +70,7 @@ export class WorkspaceRepo { return await this.db .selectFrom('workspaces') .selectAll() - .orderBy('createdAt asc') + .orderBy('createdAt', 'asc') .limit(1) .executeTakeFirst(); } From 3a75251e75fe6dd52cb7604219e5ed8a829c2c95 Mon Sep 17 00:00:00 2001 From: fuscodev Date: Fri, 16 May 2025 17:00:47 +0200 Subject: [PATCH 49/63] fix alignment in shared page (#1123) --- apps/server/src/collaboration/collaboration.util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/collaboration/collaboration.util.ts b/apps/server/src/collaboration/collaboration.util.ts index f91d5722..db2771b2 100644 --- a/apps/server/src/collaboration/collaboration.util.ts +++ b/apps/server/src/collaboration/collaboration.util.ts @@ -46,7 +46,7 @@ export const tiptapExtensions = [ codeBlock: false, }), Comment, - TextAlign, + TextAlign.configure({ types: ["heading", "paragraph"] }), TaskList, TaskItem, Underline, From 00f4588c21316e6de541e61687b4e79a0e7202c5 Mon Sep 17 00:00:00 2001 From: fuscodev Date: Fri, 16 May 2025 17:11:29 +0200 Subject: [PATCH 50/63] fix title update (#1154) --- .../src/features/editor/title-editor.tsx | 10 +++-- .../src/features/page/queries/page-query.ts | 44 +++++++++++-------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/apps/client/src/features/editor/title-editor.tsx b/apps/client/src/features/editor/title-editor.tsx index 9cb17aa0..e5ac7da6 100644 --- a/apps/client/src/features/editor/title-editor.tsx +++ b/apps/client/src/features/editor/title-editor.tsx @@ -10,7 +10,7 @@ import { pageEditorAtom, titleEditorAtom, } from "@/features/editor/atoms/editor-atoms"; -import { useUpdatePageMutation } from "@/features/page/queries/page-query"; +import { updatePageData, useUpdateTitlePageMutation } from "@/features/page/queries/page-query"; import { useDebouncedCallback } from "@mantine/hooks"; import { useAtom } from "jotai"; import { useQueryEmit } from "@/features/websocket/use-query-emit.ts"; @@ -38,7 +38,7 @@ export function TitleEditor({ editable, }: TitleEditorProps) { const { t } = useTranslation(); - const { mutateAsync: updatePageMutationAsync } = useUpdatePageMutation(); + const { mutateAsync: updateTitlePageMutationAsync } = useUpdateTitlePageMutation(); const pageEditor = useAtomValue(pageEditorAtom); const [, setTitleEditor] = useAtom(titleEditorAtom); const emit = useQueryEmit(); @@ -94,7 +94,7 @@ export function TitleEditor({ return; } - updatePageMutationAsync({ + updateTitlePageMutationAsync({ pageId: pageId, title: titleEditor.getText(), }).then((page) => { @@ -106,6 +106,10 @@ export function TitleEditor({ payload: { title: page.title, slugId: page.slugId }, }; + if (page.title !== titleEditor.getText()) return; + + updatePageData(page); + localEmitter.emit("message", event); emit(event); }); diff --git a/apps/client/src/features/page/queries/page-query.ts b/apps/client/src/features/page/queries/page-query.ts index 1e41b519..6842cc9e 100644 --- a/apps/client/src/features/page/queries/page-query.ts +++ b/apps/client/src/features/page/queries/page-query.ts @@ -63,28 +63,36 @@ export function useCreatePageMutation() { }); } -export function useUpdatePageMutation() { - const queryClient = useQueryClient(); +export function updatePageData(data: IPage) { + const pageBySlug = queryClient.getQueryData([ + "pages", + data.slugId, + ]); + const pageById = queryClient.getQueryData(["pages", data.id]); + if (pageBySlug) { + queryClient.setQueryData(["pages", data.slugId], { + ...pageBySlug, + ...data, + }); + } + + if (pageById) { + queryClient.setQueryData(["pages", data.id], { ...pageById, ...data }); + } +} + +export function useUpdateTitlePageMutation() { + return useMutation>({ + mutationFn: (data) => updatePage(data), + }); +} + +export function useUpdatePageMutation() { return useMutation>({ mutationFn: (data) => updatePage(data), onSuccess: (data) => { - const pageBySlug = queryClient.getQueryData([ - "pages", - data.slugId, - ]); - const pageById = queryClient.getQueryData(["pages", data.id]); - - if (pageBySlug) { - queryClient.setQueryData(["pages", data.slugId], { - ...pageBySlug, - ...data, - }); - } - - if (pageById) { - queryClient.setQueryData(["pages", data.id], { ...pageById, ...data }); - } + updatePage(data); }, }); } From 1413033568b31f11adb38cd99e30487aa42ddb11 Mon Sep 17 00:00:00 2001 From: fuscodev Date: Fri, 16 May 2025 17:18:23 +0200 Subject: [PATCH 51/63] feat: realtime comments (#1144) * init * fix: close bubblemenu after comment and wait before scroll * scroll to comment when click * highlight comment animation --- .../comment/components/comment-dialog.tsx | 17 +++++++++- .../comment/components/comment-editor.tsx | 4 +++ .../comment/components/comment-list-item.tsx | 34 +++++++++++++++++-- .../comment/components/comment-list.tsx | 17 +++++++--- .../comment/components/comment.module.css | 1 + .../src/features/editor/page-editor.tsx | 9 +++-- .../src/features/editor/styles/core.css | 13 +++++++ .../src/features/websocket/types/types.ts | 7 +++- .../websocket/use-query-subscription.ts | 6 ++++ 9 files changed, 96 insertions(+), 12 deletions(-) diff --git a/apps/client/src/features/comment/components/comment-dialog.tsx b/apps/client/src/features/comment/components/comment-dialog.tsx index 2e27f65b..080c731f 100644 --- a/apps/client/src/features/comment/components/comment-dialog.tsx +++ b/apps/client/src/features/comment/components/comment-dialog.tsx @@ -15,6 +15,7 @@ import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar- import { useEditor } from "@tiptap/react"; import { CustomAvatar } from "@/components/ui/custom-avatar.tsx"; import { useTranslation } from "react-i18next"; +import { useQueryEmit } from "@/features/websocket/use-query-emit"; interface CommentDialogProps { editor: ReturnType; @@ -35,6 +36,8 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) { const createCommentMutation = useCreateCommentMutation(); const { isPending } = createCommentMutation; + const emit = useQueryEmit(); + const handleDialogClose = () => { setShowCommentPopup(false); editor.chain().focus().unsetCommentDecoration().run(); @@ -63,11 +66,23 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) { .run(); setActiveCommentId(createdComment.id); + //unselect text to close bubble menu + editor.commands.setTextSelection({ from: editor.view.state.selection.from, to: editor.view.state.selection.from }); + setAsideState({ tab: "comments", isAsideOpen: true }); setTimeout(() => { const selector = `div[data-comment-id="${createdComment.id}"]`; const commentElement = document.querySelector(selector); - commentElement?.scrollIntoView(); + commentElement?.scrollIntoView({ behavior: "smooth", block: "center" }); + + editor.view.dispatch( + editor.state.tr.scrollIntoView() + ); + }, 400); + + emit({ + operation: "invalidateComment", + pageId: pageId, }); } finally { setShowCommentPopup(false); diff --git a/apps/client/src/features/comment/components/comment-editor.tsx b/apps/client/src/features/comment/components/comment-editor.tsx index 09116f1a..32339a3d 100644 --- a/apps/client/src/features/comment/components/comment-editor.tsx +++ b/apps/client/src/features/comment/components/comment-editor.tsx @@ -53,6 +53,10 @@ const CommentEditor = forwardRef( autofocus: (autofocus && "end") || false, }); + useEffect(() => { + commentEditor.commands.setContent(defaultContent); + }, [defaultContent]); + useEffect(() => { setTimeout(() => { if (autofocus) { diff --git a/apps/client/src/features/comment/components/comment-list-item.tsx b/apps/client/src/features/comment/components/comment-list-item.tsx index 293c4f9e..9e136a1d 100644 --- a/apps/client/src/features/comment/components/comment-list-item.tsx +++ b/apps/client/src/features/comment/components/comment-list-item.tsx @@ -1,5 +1,5 @@ import { Group, Text, Box } from "@mantine/core"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import classes from "./comment.module.css"; import { useAtom, useAtomValue } from "jotai"; import { timeAgo } from "@/lib/time"; @@ -15,12 +15,14 @@ import { import { IComment } from "@/features/comment/types/comment.types"; import { CustomAvatar } from "@/components/ui/custom-avatar.tsx"; import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts"; +import { useQueryEmit } from "@/features/websocket/use-query-emit"; interface CommentListItemProps { comment: IComment; + pageId: string; } -function CommentListItem({ comment }: CommentListItemProps) { +function CommentListItem({ comment, pageId }: CommentListItemProps) { const { hovered, ref } = useHover(); const [isEditing, setIsEditing] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -29,6 +31,11 @@ function CommentListItem({ comment }: CommentListItemProps) { const updateCommentMutation = useUpdateCommentMutation(); const deleteCommentMutation = useDeleteCommentMutation(comment.pageId); const [currentUser] = useAtom(currentUserAtom); + const emit = useQueryEmit(); + + useEffect(() => { + setContent(comment.content) + }, [comment]); async function handleUpdateComment() { try { @@ -39,6 +46,11 @@ function CommentListItem({ comment }: CommentListItemProps) { }; await updateCommentMutation.mutateAsync(commentToUpdate); setIsEditing(false); + + emit({ + operation: "invalidateComment", + pageId: pageId, + }); } catch (error) { console.error("Failed to update comment:", error); } finally { @@ -50,11 +62,27 @@ function CommentListItem({ comment }: CommentListItemProps) { try { await deleteCommentMutation.mutateAsync(comment.id); editor?.commands.unsetComment(comment.id); + + emit({ + operation: "invalidateComment", + pageId: pageId, + }); } catch (error) { console.error("Failed to delete comment:", error); } } + function handleCommentClick(comment: IComment) { + const el = document.querySelector(`.comment-mark[data-comment-id="${comment.id}"]`); + if (el) { + el.scrollIntoView({ behavior: "smooth", block: "center" }); + el.classList.add("comment-highlight"); + setTimeout(() => { + el.classList.remove("comment-highlight"); + }, 3000); + } + } + function handleEditToggle() { setIsEditing(true); } @@ -99,7 +127,7 @@ function CommentListItem({ comment }: CommentListItemProps) {
{!comment.parentCommentId && comment?.selection && ( - + handleCommentClick(comment)}> {comment?.selection} )} diff --git a/apps/client/src/features/comment/components/comment-list.tsx b/apps/client/src/features/comment/components/comment-list.tsx index 3296d5ea..6f0d877a 100644 --- a/apps/client/src/features/comment/components/comment-list.tsx +++ b/apps/client/src/features/comment/components/comment-list.tsx @@ -14,6 +14,7 @@ import { usePageQuery } from "@/features/page/queries/page-query.ts"; import { IPagination } from "@/lib/types.ts"; import { extractPageSlugId } from "@/lib"; import { useTranslation } from "react-i18next"; +import { useQueryEmit } from "@/features/websocket/use-query-emit"; function CommentList() { const { t } = useTranslation(); @@ -26,6 +27,7 @@ function CommentList() { } = useCommentsQuery({ pageId: page?.id, limit: 100 }); const createCommentMutation = useCreateCommentMutation(); const [isLoading, setIsLoading] = useState(false); + const emit = useQueryEmit(); const handleAddReply = useCallback( async (commentId: string, content: string) => { @@ -38,6 +40,11 @@ function CommentList() { }; await createCommentMutation.mutateAsync(commentData); + + emit({ + operation: "invalidateComment", + pageId: page?.id, + }); } catch (error) { console.error("Failed to post comment:", error); } finally { @@ -59,8 +66,8 @@ function CommentList() { data-comment-id={comment.id} >
- - + +
@@ -99,8 +106,9 @@ function CommentList() { interface ChildCommentsProps { comments: IPagination; parentId: string; + pageId: string; } -const ChildComments = ({ comments, parentId }: ChildCommentsProps) => { +const ChildComments = ({ comments, parentId, pageId }: ChildCommentsProps) => { const getChildComments = useCallback( (parentId: string) => comments.items.filter( @@ -113,10 +121,11 @@ const ChildComments = ({ comments, parentId }: ChildCommentsProps) => {
{getChildComments(parentId).map((childComment) => (
- +
))} diff --git a/apps/client/src/features/comment/components/comment.module.css b/apps/client/src/features/comment/components/comment.module.css index b3d0a261..5679c9b2 100644 --- a/apps/client/src/features/comment/components/comment.module.css +++ b/apps/client/src/features/comment/components/comment.module.css @@ -11,6 +11,7 @@ border-left: 2px solid var(--mantine-color-gray-6); padding: 8px; background: var(--mantine-color-gray-light); + cursor: pointer; } .commentEditor { diff --git a/apps/client/src/features/editor/page-editor.tsx b/apps/client/src/features/editor/page-editor.tsx index 32845c6d..07d9da74 100644 --- a/apps/client/src/features/editor/page-editor.tsx +++ b/apps/client/src/features/editor/page-editor.tsx @@ -219,9 +219,12 @@ export default function PageEditor({ setActiveCommentId(commentId); setAsideState({ tab: "comments", isAsideOpen: true }); - const selector = `div[data-comment-id="${commentId}"]`; - const commentElement = document.querySelector(selector); - commentElement?.scrollIntoView(); + //wait if aside is closed + setTimeout(() => { + const selector = `div[data-comment-id="${commentId}"]`; + const commentElement = document.querySelector(selector); + commentElement?.scrollIntoView({ behavior: "smooth", block: "center" }); + }, 400); }; useEffect(() => { diff --git a/apps/client/src/features/editor/styles/core.css b/apps/client/src/features/editor/styles/core.css index 952e91fb..38acb74b 100644 --- a/apps/client/src/features/editor/styles/core.css +++ b/apps/client/src/features/editor/styles/core.css @@ -144,6 +144,19 @@ border-bottom: 2px solid rgb(166, 158, 12); } + .comment-highlight { + animation: flash-highlight 3s ease-out; + } + + @keyframes flash-highlight { + 0% { + background-color: #ff4d4d; + } + 100% { + background-color: rgba(255, 215, 0, 0.14); + } + } + .resize-cursor { cursor: ew-resize; cursor: col-resize; diff --git a/apps/client/src/features/websocket/types/types.ts b/apps/client/src/features/websocket/types/types.ts index a3e32ac2..48e7d819 100644 --- a/apps/client/src/features/websocket/types/types.ts +++ b/apps/client/src/features/websocket/types/types.ts @@ -7,6 +7,11 @@ export type InvalidateEvent = { id?: string; }; +export type InvalidateCommentsEvent = { + operation: "invalidateComment"; + pageId: string; +}; + export type UpdateEvent = { operation: "updateOne"; spaceId: string; @@ -52,4 +57,4 @@ export type DeleteTreeNodeEvent = { } }; -export type WebSocketEvent = InvalidateEvent | UpdateEvent | DeleteEvent | AddTreeNodeEvent | MoveTreeNodeEvent | DeleteTreeNodeEvent; +export type WebSocketEvent = InvalidateEvent | InvalidateCommentsEvent | UpdateEvent | DeleteEvent | AddTreeNodeEvent | MoveTreeNodeEvent | DeleteTreeNodeEvent; diff --git a/apps/client/src/features/websocket/use-query-subscription.ts b/apps/client/src/features/websocket/use-query-subscription.ts index 576b6777..c9e53aa6 100644 --- a/apps/client/src/features/websocket/use-query-subscription.ts +++ b/apps/client/src/features/websocket/use-query-subscription.ts @@ -3,6 +3,7 @@ import { socketAtom } from "@/features/websocket/atoms/socket-atom.ts"; import { useAtom } from "jotai"; import { useQueryClient } from "@tanstack/react-query"; import { WebSocketEvent } from "@/features/websocket/types"; +import { RQ_KEY } from "../comment/queries/comment-query"; export const useQuerySubscription = () => { const queryClient = useQueryClient(); @@ -21,6 +22,11 @@ export const useQuerySubscription = () => { queryKey: [...data.entity, data.id].filter(Boolean), }); break; + case "invalidateComment": + queryClient.invalidateQueries({ + queryKey: RQ_KEY(data.pageId), + }); + break; case "updateOne": entity = data.entity[0]; if (entity === "pages") { From fb7e4a79569971436b5c2ed068f096f09d6fd5c2 Mon Sep 17 00:00:00 2001 From: fuscodev Date: Fri, 16 May 2025 17:24:31 +0200 Subject: [PATCH 52/63] fix: copy/move select (#1174) --- .../src/features/space/components/sidebar/space-select.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/features/space/components/sidebar/space-select.tsx b/apps/client/src/features/space/components/sidebar/space-select.tsx index db540474..a20c7e2d 100644 --- a/apps/client/src/features/space/components/sidebar/space-select.tsx +++ b/apps/client/src/features/space/components/sidebar/space-select.tsx @@ -81,7 +81,7 @@ export function SpaceSelect({ nothingFoundMessage={t("No space found")} limit={50} checkIconPosition="right" - comboboxProps={{ width, withinPortal: false }} + comboboxProps={{ width, withinPortal: true, position: "bottom" }} dropdownOpened={opened} /> ); From 1c200dbd0f23ac4303aeb3fa5c85e786f5d84e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=81=E5=9C=B0?= Date: Fri, 16 May 2025 23:26:05 +0800 Subject: [PATCH 53/63] fix(table-hover): adjust row height to prevent unexpected scrollbar on hover (#1124) fix: Hover table style height error causing scrollbar to appear #1108 --- apps/client/src/features/editor/styles/table.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/features/editor/styles/table.css b/apps/client/src/features/editor/styles/table.css index 19a1029b..7d02ef03 100644 --- a/apps/client/src/features/editor/styles/table.css +++ b/apps/client/src/features/editor/styles/table.css @@ -47,7 +47,7 @@ .column-resize-handle { background-color: #adf; - bottom: -2px; + bottom: -1px; position: absolute; right: -2px; pointer-events: none; From b0491d5da4772285382436beb084daad6c4b1513 Mon Sep 17 00:00:00 2001 From: fuscodev Date: Fri, 16 May 2025 20:15:11 +0200 Subject: [PATCH 54/63] feat: create new page from mention (#1153) * init * create page in relative parent root --- .../components/mention/mention-list.tsx | 98 +++++++++++++++++-- 1 file changed, 90 insertions(+), 8 deletions(-) diff --git a/apps/client/src/features/editor/components/mention/mention-list.tsx b/apps/client/src/features/editor/components/mention/mention-list.tsx index 4a4ae74a..389c2ce5 100644 --- a/apps/client/src/features/editor/components/mention/mention-list.tsx +++ b/apps/client/src/features/editor/components/mention/mention-list.tsx @@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useImperativeHandle, + useMemo, useRef, useState, } from "react"; @@ -18,7 +19,7 @@ import { import clsx from "clsx"; import classes from "./mention.module.css"; import { CustomAvatar } from "@/components/ui/custom-avatar.tsx"; -import { IconFileDescription } from "@tabler/icons-react"; +import { IconFileDescription, IconPlus } from "@tabler/icons-react"; import { useSpaceQuery } from "@/features/space/queries/space-query.ts"; import { useParams } from "react-router-dom"; import { v7 as uuid7 } from "uuid"; @@ -28,14 +29,28 @@ import { MentionListProps, MentionSuggestionItem, } from "@/features/editor/components/mention/mention.type.ts"; +import { IPage } from "@/features/page/types/page.types"; +import { useCreatePageMutation, usePageQuery } from "@/features/page/queries/page-query"; +import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom"; +import { SimpleTree } from "react-arborist"; +import { SpaceTreeNode } from "@/features/page/tree/types"; +import { useTranslation } from "react-i18next"; +import { useQueryEmit } from "@/features/websocket/use-query-emit"; +import { extractPageSlugId } from "@/lib"; const MentionList = forwardRef((props, ref) => { const [selectedIndex, setSelectedIndex] = useState(1); const viewportRef = useRef(null); - const { spaceSlug } = useParams(); + const { pageSlug, spaceSlug } = useParams(); + const { data: page } = usePageQuery({ pageId: extractPageSlugId(pageSlug) }); const { data: space } = useSpaceQuery(spaceSlug); const [currentUser] = useAtom(currentUserAtom); const [renderItems, setRenderItems] = useState([]); + const { t } = useTranslation(); + const [data, setData] = useAtom(treeDataAtom); + const tree = useMemo(() => new SimpleTree(data), [data]); + const createPageMutation = useCreatePageMutation(); + const emit = useQueryEmit(); const { data: suggestion, isLoading } = useSearchSuggestionsQuery({ query: props.query, @@ -45,12 +60,23 @@ const MentionList = forwardRef((props, ref) => { limit: 10, }); + const createPageItem = (label: string) : MentionSuggestionItem => { + return { + id: null, + label: label, + entityType: "page", + entityId: null, + slugId: null, + icon: null, + } + } + useEffect(() => { if (suggestion && !isLoading) { let items: MentionSuggestionItem[] = []; if (suggestion?.users?.length > 0) { - items.push({ entityType: "header", label: "Users" }); + items.push({ entityType: "header", label: t("Users") }); items = items.concat( suggestion.users.map((user) => ({ @@ -64,7 +90,7 @@ const MentionList = forwardRef((props, ref) => { } if (suggestion?.pages?.length > 0) { - items.push({ entityType: "header", label: "Pages" }); + items.push({ entityType: "header", label: t("Pages") }); items = items.concat( suggestion.pages.map((page) => ({ id: uuid7(), @@ -76,6 +102,7 @@ const MentionList = forwardRef((props, ref) => { })), ); } + items.push(createPageItem(props.query)); setRenderItems(items); // update editor storage @@ -96,7 +123,7 @@ const MentionList = forwardRef((props, ref) => { creatorId: currentUser?.user.id, }); } - if (item.entityType === "page") { + if (item.entityType === "page" && item.id!==null) { props.command({ id: item.id, label: item.label || "Untitled", @@ -106,6 +133,9 @@ const MentionList = forwardRef((props, ref) => { creatorId: currentUser?.user.id, }); } + if (item.entityType === "page" && item.id===null) { + createPage(item.label); + } } }, [renderItems], @@ -167,6 +197,58 @@ const MentionList = forwardRef((props, ref) => { }, })); + const createPage = async (title: string) => { + const payload: { spaceId: string; parentPageId?: string; title: string } = { + spaceId: space.id, + parentPageId: page.id || null, + title: title + }; + + let createdPage: IPage; + try { + createdPage = await createPageMutation.mutateAsync(payload); + const parentId = page.id || null; + const data = { + id: createdPage.id, + slugId: createdPage.slugId, + name: createdPage.title, + position: createdPage.position, + spaceId: createdPage.spaceId, + parentPageId: createdPage.parentPageId, + children: [], + } as any; + + const lastIndex = tree.data.length; + + tree.create({ parentId, index: lastIndex, data }); + setData(tree.data); + + props.command({ + id: uuid7(), + label: createdPage.title || "Untitled", + entityType: "page", + entityId: createdPage.id, + slugId: createdPage.slugId, + creatorId: currentUser?.user.id, + }); + + setTimeout(() => { + emit({ + operation: "addTreeNode", + spaceId: space.id, + payload: { + parentId, + index: lastIndex, + data, + }, + }); + }, 50); + + } catch (err) { + throw new Error("Failed to create page"); + } + } + // if no results and enter what to do? useEffect(() => { @@ -178,7 +260,7 @@ const MentionList = forwardRef((props, ref) => { if (renderItems.length === 0) { return ( - No results + { t("No results") } ); } @@ -248,14 +330,14 @@ const MentionList = forwardRef((props, ref) => { color="gray" size={18} > - + { (item.id) ? : } )}
- {item.label} + { (item.id) ? item.label : t("Create page") + ': ' + item.label }
From e3ba817723faab9bffa87a4d7fea2863f9425d01 Mon Sep 17 00:00:00 2001 From: fuscodev Date: Fri, 16 May 2025 21:01:27 +0200 Subject: [PATCH 55/63] feat: comment editor emoji picker and ctrl+enter action (#1121) * commenteditor-emoji-picker * capture Mac command key * remove tooltip --------- Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com> --- .../comment/components/comment-actions.tsx | 4 +-- .../comment/components/comment-dialog.tsx | 1 + .../comment/components/comment-editor.tsx | 31 +++++++++++++++++++ .../comment/components/comment-list-item.tsx | 1 + .../comment/components/comment-list.tsx | 1 + .../comment/components/comment.module.css | 4 ++- .../emoji-menu/render-emoji-items.ts | 2 +- 7 files changed, 40 insertions(+), 4 deletions(-) diff --git a/apps/client/src/features/comment/components/comment-actions.tsx b/apps/client/src/features/comment/components/comment-actions.tsx index e5c4f1a2..882c6f74 100644 --- a/apps/client/src/features/comment/components/comment-actions.tsx +++ b/apps/client/src/features/comment/components/comment-actions.tsx @@ -1,4 +1,4 @@ -import { Button, Group } from "@mantine/core"; +import { Button, Group, Tooltip } from "@mantine/core"; import { useTranslation } from "react-i18next"; type CommentActionsProps = { @@ -15,7 +15,7 @@ function CommentActions({ isCommentEditor, }: CommentActionsProps) { const { t } = useTranslation(); - + return ( {isCommentEditor && ( diff --git a/apps/client/src/features/comment/components/comment-dialog.tsx b/apps/client/src/features/comment/components/comment-dialog.tsx index 080c731f..7a4b55aa 100644 --- a/apps/client/src/features/comment/components/comment-dialog.tsx +++ b/apps/client/src/features/comment/components/comment-dialog.tsx @@ -124,6 +124,7 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) { { + if ( + [ + "ArrowUp", + "ArrowDown", + "ArrowLeft", + "ArrowRight", + "Enter", + ].includes(event.key) + ) { + const emojiCommand = document.querySelector("#emoji-command"); + if (emojiCommand) { + return true; + } + } + + if ((event.ctrlKey || event.metaKey) && event.key === "Enter") { + event.preventDefault(); + if (onSave) onSave(); + + return true; + } + }, + }, + }, onUpdate({ editor }) { if (onUpdate) onUpdate(editor.getJSON()); }, diff --git a/apps/client/src/features/comment/components/comment-list-item.tsx b/apps/client/src/features/comment/components/comment-list-item.tsx index 9e136a1d..cbf2a4e9 100644 --- a/apps/client/src/features/comment/components/comment-list-item.tsx +++ b/apps/client/src/features/comment/components/comment-list-item.tsx @@ -140,6 +140,7 @@ function CommentListItem({ comment, pageId }: CommentListItemProps) { defaultContent={content} editable={true} onUpdate={(newContent: any) => setContent(newContent)} + onSave={handleUpdateComment} autofocus={true} /> diff --git a/apps/client/src/features/comment/components/comment-list.tsx b/apps/client/src/features/comment/components/comment-list.tsx index 6f0d877a..1aded495 100644 --- a/apps/client/src/features/comment/components/comment-list.tsx +++ b/apps/client/src/features/comment/components/comment-list.tsx @@ -151,6 +151,7 @@ const CommentEditorWithActions = ({ commentId, onSave, isLoading }) => { {focused && } diff --git a/apps/client/src/features/comment/components/comment.module.css b/apps/client/src/features/comment/components/comment.module.css index 5679c9b2..dad0d94a 100644 --- a/apps/client/src/features/comment/components/comment.module.css +++ b/apps/client/src/features/comment/components/comment.module.css @@ -17,17 +17,19 @@ .commentEditor { .focused { + border-radius: var(--mantine-radius-sm); box-shadow: 0 0 0 2px var(--mantine-color-blue-3); } .ProseMirror :global(.ProseMirror){ + border-radius: var(--mantine-radius-sm); max-width: 100%; white-space: pre-wrap; word-break: break-word; max-height: 20vh; padding-left: 6px; padding-right: 6px; - margin-top: 2px; + margin-top: 10px; margin-bottom: 2px; overflow: hidden auto; } diff --git a/apps/client/src/features/editor/components/emoji-menu/render-emoji-items.ts b/apps/client/src/features/editor/components/emoji-menu/render-emoji-items.ts index ba035a8b..82fb24a9 100644 --- a/apps/client/src/features/editor/components/emoji-menu/render-emoji-items.ts +++ b/apps/client/src/features/editor/components/emoji-menu/render-emoji-items.ts @@ -33,7 +33,7 @@ const renderEmojiItems = () => { showOnCreate: true, interactive: true, trigger: "manual", - placement: "bottom-start", + placement: "bottom", }); }, onStart: (props: { From 4a0b4040edb2dec0e265c36aaab1089c93348333 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Sat, 17 May 2025 18:03:01 +0000 Subject: [PATCH 56/63] Add second plan (#1187) --- apps/client/src/ee/billing/types/billing.types.ts | 1 + apps/client/src/ee/hooks/use-plan.tsx | 8 ++++++-- apps/client/src/ee/security/pages/security.tsx | 5 +++-- apps/server/src/ee | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/client/src/ee/billing/types/billing.types.ts b/apps/client/src/ee/billing/types/billing.types.ts index 89d936f0..240e55fd 100644 --- a/apps/client/src/ee/billing/types/billing.types.ts +++ b/apps/client/src/ee/billing/types/billing.types.ts @@ -1,5 +1,6 @@ export enum BillingPlan { STANDARD = "standard", + BUSINESS = "business", } export interface IBilling { diff --git a/apps/client/src/ee/hooks/use-plan.tsx b/apps/client/src/ee/hooks/use-plan.tsx index 52790178..a9296c58 100644 --- a/apps/client/src/ee/hooks/use-plan.tsx +++ b/apps/client/src/ee/hooks/use-plan.tsx @@ -2,14 +2,18 @@ import { useAtom } from "jotai"; import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts"; import { BillingPlan } from "@/ee/billing/types/billing.types.ts"; -export const usePlan = () => { +const usePlan = () => { const [workspace] = useAtom(workspaceAtom); const isStandard = typeof workspace?.plan === "string" && workspace?.plan.toLowerCase() === BillingPlan.STANDARD.toLowerCase(); - return { isStandard }; + const isBusiness = + typeof workspace?.plan === "string" && + workspace?.plan.toLowerCase() === BillingPlan.BUSINESS.toLowerCase(); + + return { isStandard, isBusiness }; }; export default usePlan; diff --git a/apps/client/src/ee/security/pages/security.tsx b/apps/client/src/ee/security/pages/security.tsx index 9a85cff2..de8efc06 100644 --- a/apps/client/src/ee/security/pages/security.tsx +++ b/apps/client/src/ee/security/pages/security.tsx @@ -10,11 +10,13 @@ import EnforceSso from "@/ee/security/components/enforce-sso.tsx"; import AllowedDomains from "@/ee/security/components/allowed-domains.tsx"; import { useTranslation } from "react-i18next"; import useLicense from "@/ee/hooks/use-license.tsx"; +import usePlan from "@/ee/hooks/use-plan.tsx"; export default function Security() { const { t } = useTranslation(); const { isAdmin } = useUserRole(); const { hasLicenseKey } = useLicense(); + const { isBusiness } = usePlan(); if (!isAdmin) { return null; @@ -35,8 +37,7 @@ export default function Security() { Single sign-on (SSO) - {/*TODO: revisit when we add a second plan */} - {!isCloud() && hasLicenseKey ? ( + {(isCloud() && isBusiness) || (!isCloud() && hasLicenseKey) ? ( <> diff --git a/apps/server/src/ee b/apps/server/src/ee index 96404fc1..12f576ce 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit 96404fc121844a707bd0fb1e280df6e2d52e8973 +Subproject commit 12f576ce721747903a9fadef3d58e0aa4e7ea3da From 7491224d0ffd0b931137a3b4bc7228ffbdd6ef7a Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Sat, 17 May 2025 18:17:34 +0000 Subject: [PATCH 57/63] hide shared page branding in EE (#1193) * hide shared page branding in EE * Hide branding in business plan --- .../features/share/components/share-branding.tsx | 16 ++++++++++++++++ .../features/share/components/share-shell.tsx | 12 ++---------- .../src/features/share/types/share.types.ts | 2 ++ apps/client/src/pages/share/shared-page.tsx | 5 ++++- apps/server/src/core/share/share.controller.ts | 16 ++++++++++++++-- apps/server/src/ee | 2 +- 6 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 apps/client/src/features/share/components/share-branding.tsx diff --git a/apps/client/src/features/share/components/share-branding.tsx b/apps/client/src/features/share/components/share-branding.tsx new file mode 100644 index 00000000..4b3dfb3e --- /dev/null +++ b/apps/client/src/features/share/components/share-branding.tsx @@ -0,0 +1,16 @@ +import { Affix, Button } from "@mantine/core"; + +export default function ShareBranding() { + return ( + + + + ); +} diff --git a/apps/client/src/features/share/components/share-shell.tsx b/apps/client/src/features/share/components/share-shell.tsx index 7fa0c941..8550f59f 100644 --- a/apps/client/src/features/share/components/share-shell.tsx +++ b/apps/client/src/features/share/components/share-shell.tsx @@ -36,6 +36,7 @@ import { } from "@/features/search/components/search-control.tsx"; import { ShareSearchSpotlight } from "@/features/search/share-search-spotlight"; import { shareSearchSpotlight } from "@/features/search/constants"; +import ShareBranding from '@/features/share/components/share-branding.tsx'; const MemoizedSharedTree = React.memo(SharedTree); @@ -163,16 +164,7 @@ export default function ShareShell({ {children} - - - + {data && shareId && !data.hasLicenseKey && } ; + hasLicenseKey: boolean; } diff --git a/apps/client/src/pages/share/shared-page.tsx b/apps/client/src/pages/share/shared-page.tsx index a574a614..50e5837d 100644 --- a/apps/client/src/pages/share/shared-page.tsx +++ b/apps/client/src/pages/share/shared-page.tsx @@ -7,8 +7,9 @@ import React, { useEffect } from "react"; import ReadonlyPageEditor from "@/features/editor/readonly-page-editor.tsx"; import { extractPageSlugId } from "@/lib"; import { Error404 } from "@/components/ui/error-404.tsx"; +import ShareBranding from "@/features/share/components/share-branding.tsx"; -export default function SingleSharedPage() { +export default function SharedPage() { const { t } = useTranslation(); const { pageSlug } = useParams(); const { shareId } = useParams(); @@ -53,6 +54,8 @@ export default function SingleSharedPage() { content={data.page.content} /> + + {data && !shareId && !data.hasLicenseKey && }
); } diff --git a/apps/server/src/core/share/share.controller.ts b/apps/server/src/core/share/share.controller.ts index 5e8debe0..b9a9fcbf 100644 --- a/apps/server/src/core/share/share.controller.ts +++ b/apps/server/src/core/share/share.controller.ts @@ -30,6 +30,7 @@ import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; import { Public } from '../../common/decorators/public.decorator'; import { ShareRepo } from '@docmost/db/repos/share/share.repo'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; +import { EnvironmentService } from '../../integrations/environment/environment.service'; @UseGuards(JwtAuthGuard) @Controller('shares') @@ -39,6 +40,7 @@ export class ShareController { private readonly spaceAbility: SpaceAbilityFactory, private readonly shareRepo: ShareRepo, private readonly pageRepo: PageRepo, + private readonly environmentService: EnvironmentService, ) {} @HttpCode(HttpStatus.OK) @@ -61,7 +63,12 @@ export class ShareController { throw new BadRequestException(); } - return this.shareService.getSharedPage(dto, workspace.id); + return { + ...(await this.shareService.getSharedPage(dto, workspace.id)), + hasLicenseKey: + Boolean(workspace.licenseKey) || + (this.environmentService.isCloud() && workspace.plan === 'business'), + }; } @Public() @@ -166,6 +173,11 @@ export class ShareController { @Body() dto: ShareIdDto, @AuthWorkspace() workspace: Workspace, ) { - return this.shareService.getShareTree(dto.shareId, workspace.id); + return { + ...(await this.shareService.getShareTree(dto.shareId, workspace.id)), + hasLicenseKey: + Boolean(workspace.licenseKey) || + (this.environmentService.isCloud() && workspace.plan === 'business'), + }; } } diff --git a/apps/server/src/ee b/apps/server/src/ee index 12f576ce..77d86e61 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit 12f576ce721747903a9fadef3d58e0aa4e7ea3da +Subproject commit 77d86e6119a22981f9fc7658006229dfeb96122b From 343b2976c2a106c76a770533b450616dff7d4576 Mon Sep 17 00:00:00 2001 From: sanua356 <51795446+sanua356@users.noreply.github.com> Date: Mon, 19 May 2025 22:05:31 +0300 Subject: [PATCH 58/63] #1186/chore: add support language abap syntax highlight (#1188) --- apps/client/package.json | 1 + apps/client/src/features/editor/extensions/extensions.ts | 3 ++- pnpm-lock.yaml | 8 ++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/client/package.json b/apps/client/package.json index 586f50a1..3a658caf 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -29,6 +29,7 @@ "clsx": "^2.1.1", "emoji-mart": "^5.6.0", "file-saver": "^2.0.5", + "highlightjs-sap-abap": "^0.3.0", "i18next": "^23.14.0", "i18next-http-backend": "^2.6.1", "jotai": "^2.12.1", diff --git a/apps/client/src/features/editor/extensions/extensions.ts b/apps/client/src/features/editor/extensions/extensions.ts index c131ad70..eb0e62f2 100644 --- a/apps/client/src/features/editor/extensions/extensions.ts +++ b/apps/client/src/features/editor/extensions/extensions.ts @@ -58,6 +58,7 @@ import ExcalidrawView from "@/features/editor/components/excalidraw/excalidraw-v import EmbedView from "@/features/editor/components/embed/embed-view.tsx"; import plaintext from "highlight.js/lib/languages/plaintext"; import powershell from "highlight.js/lib/languages/powershell"; +import abap from "highlightjs-sap-abap"; import elixir from "highlight.js/lib/languages/elixir"; import erlang from "highlight.js/lib/languages/erlang"; import dockerfile from "highlight.js/lib/languages/dockerfile"; @@ -76,7 +77,7 @@ import { CharacterCount } from "@tiptap/extension-character-count"; const lowlight = createLowlight(common); lowlight.register("mermaid", plaintext); lowlight.register("powershell", powershell); -lowlight.register("powershell", powershell); +lowlight.register("abap", abap); lowlight.register("erlang", erlang); lowlight.register("elixir", elixir); lowlight.register("dockerfile", dockerfile); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fd73d8b..a57f3fe1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -257,6 +257,9 @@ importers: file-saver: specifier: ^2.0.5 version: 2.0.5 + highlightjs-sap-abap: + specifier: ^0.3.0 + version: 0.3.0 i18next: specifier: ^23.14.0 version: 23.14.0 @@ -5872,6 +5875,9 @@ packages: resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==} engines: {node: '>=12.0.0'} + highlightjs-sap-abap@0.3.0: + resolution: {integrity: sha512-nSiUvEOCycjtFA3pHaTowrbAAk5+lciBHyoVkDsd6FTRBtW9sT2dt42o2jAKbXjZVUidtacdk+j0Y2xnd233Mw==} + hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -15436,6 +15442,8 @@ snapshots: highlight.js@11.10.0: {} + highlightjs-sap-abap@0.3.0: {} + hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 From 858ff9da068ace1aee359d157aac887f3c66c928 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 20 May 2025 09:27:30 -0700 Subject: [PATCH 59/63] sync --- apps/server/src/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/ee b/apps/server/src/ee index 77d86e61..b312008b 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit 77d86e6119a22981f9fc7658006229dfeb96122b +Subproject commit b312008b4b7ed3d5862436b279d91aeddb6048d7 From ce1503af852aa0e0da95d4db790b12cc09926fde Mon Sep 17 00:00:00 2001 From: fuscodev Date: Sun, 8 Jun 2025 04:27:09 +0200 Subject: [PATCH 60/63] fix: sidebar list when changing workspace (#1150) * init * navigate in overview if current page is in deleted node * fix: implement pagination in sidebar-pages queries * fix: appendNodeChildren() Preserve deeper children if they exist and remove node if deleted --- .../src/features/editor/title-editor.tsx | 2 +- .../src/features/page/queries/page-query.ts | 193 +++++++++++++++++- .../features/page/services/page-service.ts | 27 +++ .../page/tree/atoms/tree-data-atom.ts | 15 ++ .../page/tree/components/space-tree.tsx | 61 +++--- .../page/tree/hooks/use-tree-mutation.ts | 24 ++- .../src/features/page/tree/utils/utils.ts | 35 +++- .../src/features/websocket/types/types.ts | 5 +- .../websocket/use-query-subscription.ts | 23 ++- 9 files changed, 326 insertions(+), 59 deletions(-) diff --git a/apps/client/src/features/editor/title-editor.tsx b/apps/client/src/features/editor/title-editor.tsx index e5ac7da6..869cb1eb 100644 --- a/apps/client/src/features/editor/title-editor.tsx +++ b/apps/client/src/features/editor/title-editor.tsx @@ -103,7 +103,7 @@ export function TitleEditor({ spaceId: page.spaceId, entity: ["pages"], id: page.id, - payload: { title: page.title, slugId: page.slugId }, + payload: { title: page.title, slugId: page.slugId, parentPageId: page.parentPageId, icon: page.icon }, }; if (page.title !== titleEditor.getText()) return; diff --git a/apps/client/src/features/page/queries/page-query.ts b/apps/client/src/features/page/queries/page-query.ts index 6842cc9e..6a460c68 100644 --- a/apps/client/src/features/page/queries/page-query.ts +++ b/apps/client/src/features/page/queries/page-query.ts @@ -1,5 +1,8 @@ import { + InfiniteData, + QueryKey, useInfiniteQuery, + UseInfiniteQueryResult, useMutation, useQuery, useQueryClient, @@ -14,6 +17,7 @@ import { movePage, getPageBreadcrumbs, getRecentChanges, + getAllSidebarPages, } from "@/features/page/services/page-service"; import { IMovePage, @@ -56,7 +60,9 @@ export function useCreatePageMutation() { const { t } = useTranslation(); return useMutation>({ mutationFn: (data) => createPage(data), - onSuccess: (data) => {}, + onSuccess: (data) => { + invalidateOnCreatePage(data); + }, onError: (error) => { notifications.show({ message: t("Failed to create page"), color: "red" }); }, @@ -80,6 +86,8 @@ export function updatePageData(data: IPage) { if (pageById) { queryClient.setQueryData(["pages", data.id], { ...pageById, ...data }); } + + invalidateOnUpdatePage(data.spaceId, data.parentPageId, data.id, data.title, data.icon); } export function useUpdateTitlePageMutation() { @@ -93,6 +101,8 @@ export function useUpdatePageMutation() { mutationFn: (data) => updatePage(data), onSuccess: (data) => { updatePage(data); + + invalidateOnUpdatePage(data.spaceId, data.parentPageId, data.id, data.title, data.icon); }, }); } @@ -101,8 +111,9 @@ export function useDeletePageMutation() { const { t } = useTranslation(); return useMutation({ mutationFn: (pageId: string) => deletePage(pageId), - onSuccess: () => { + onSuccess: (data, pageId) => { notifications.show({ message: t("Page deleted successfully") }); + invalidateOnDeletePage(pageId); }, onError: (error) => { notifications.show({ message: t("Failed to delete page"), color: "red" }); @@ -113,15 +124,21 @@ export function useDeletePageMutation() { export function useMovePageMutation() { return useMutation({ mutationFn: (data) => movePage(data), + onSuccess: () => { + invalidateOnMovePage(); + }, }); } -export function useGetSidebarPagesQuery( - data: SidebarPagesParams, -): UseQueryResult, Error> { - return useQuery({ +export function useGetSidebarPagesQuery(data: SidebarPagesParams|null): UseInfiniteQueryResult, unknown>> { + return useInfiniteQuery({ queryKey: ["sidebar-pages", data], - queryFn: () => getSidebarPages(data), + queryFn: ({ pageParam }) => getSidebarPages({ ...data, page: pageParam }), + initialPageParam: 1, + getPreviousPageParam: (firstPage) => + firstPage.meta.hasPrevPage ? firstPage.meta.page - 1 : undefined, + getNextPageParam: (lastPage) => + lastPage.meta.hasNextPage ? lastPage.meta.page + 1 : undefined, }); } @@ -149,14 +166,16 @@ export function usePageBreadcrumbsQuery( }); } -export async function fetchAncestorChildren(params: SidebarPagesParams) { +export async function fetchAllAncestorChildren(params: SidebarPagesParams) { // not using a hook here, so we can call it inside a useEffect hook const response = await queryClient.fetchQuery({ queryKey: ["sidebar-pages", params], - queryFn: () => getSidebarPages(params), + queryFn: () => getAllSidebarPages(params), staleTime: 30 * 60 * 1000, }); - return buildTree(response.items); + + const allItems = response.pages.flatMap((page) => page.items); + return buildTree(allItems); } export function useRecentChangesQuery( @@ -168,3 +187,157 @@ export function useRecentChangesQuery( refetchOnMount: true, }); } + +export function invalidateOnCreatePage(data: Partial) { + const newPage: Partial = { + creatorId: data.creatorId, + hasChildren: data.hasChildren, + icon: data.icon, + id: data.id, + parentPageId: data.parentPageId, + position: data.position, + slugId: data.slugId, + spaceId: data.spaceId, + title: data.title, + }; + + let queryKey: QueryKey = null; + if (data.parentPageId===null) { + queryKey = ['root-sidebar-pages', data.spaceId]; + }else{ + queryKey = ['sidebar-pages', {pageId: data.parentPageId, spaceId: data.spaceId}] + } + + //update all sidebar pages + queryClient.setQueryData>>>(queryKey, (old) => { + if (!old) return old; + return { + ...old, + pages: old.pages.map((page,index) => { + if (index === old.pages.length - 1) { + return { + ...page, + items: [...page.items, newPage], + }; + } + return page; + }), + }; + }); + + //update sidebar haschildren + if (data.parentPageId!==null){ + //update sub sidebar pages haschildern + const subSideBarMatches = queryClient.getQueriesData({ + queryKey: ['sidebar-pages'], + exact: false, + }); + + subSideBarMatches.forEach(([key, d]) => { + queryClient.setQueryData>>(key, (old) => { + if (!old) return old; + return { + ...old, + pages: old.pages.map((page) => ({ + ...page, + items: page.items.map((sidebarPage: IPage) => + sidebarPage.id === data.parentPageId ? { ...sidebarPage, hasChildren: true } : sidebarPage + ) + })), + }; + }); + }); + + //update root sidebar pages haschildern + const rootSideBarMatches = queryClient.getQueriesData({ + queryKey: ['root-sidebar-pages', data.spaceId], + exact: false, + }); + + rootSideBarMatches.forEach(([key, d]) => { + queryClient.setQueryData>>(key, (old) => { + if (!old) return old; + return { + ...old, + pages: old.pages.map((page) => ({ + ...page, + items: page.items.map((sidebarPage: IPage) => + sidebarPage.id === data.parentPageId ? { ...sidebarPage, hasChildren: true } : sidebarPage + ) + })), + }; + }); + }); + } + + //update recent changes + queryClient.invalidateQueries({ + queryKey: ["recent-changes", data.spaceId], + }); +} + +export function invalidateOnUpdatePage(spaceId: string, parentPageId: string, id: string, title: string, icon: string) { + let queryKey: QueryKey = null; + if(parentPageId===null){ + queryKey = ['root-sidebar-pages', spaceId]; + }else{ + queryKey = ['sidebar-pages', {pageId: parentPageId, spaceId: spaceId}] + } + //update all sidebar pages + queryClient.setQueryData>>(queryKey, (old) => { + if (!old) return old; + return { + ...old, + pages: old.pages.map((page) => ({ + ...page, + items: page.items.map((sidebarPage: IPage) => + sidebarPage.id === id ? { ...sidebarPage, title: title, icon: icon } : sidebarPage + ) + })), + }; + }); + + //update recent changes + queryClient.invalidateQueries({ + queryKey: ["recent-changes", spaceId], + }); +} + +export function invalidateOnMovePage() { + //for move invalidate all sidebars for now (how to do???) + //invalidate all root sidebar pages + queryClient.invalidateQueries({ + queryKey: ["root-sidebar-pages"], + }); + //invalidate all sub sidebar pages + queryClient.invalidateQueries({ + queryKey: ['sidebar-pages'], + }); + // --- +} + +export function invalidateOnDeletePage(pageId: string) { + //update all sidebar pages + const allSideBarMatches = queryClient.getQueriesData({ + predicate: (query) => + query.queryKey[0] === 'root-sidebar-pages' || query.queryKey[0] === 'sidebar-pages', + }); + + allSideBarMatches.forEach(([key, d]) => { + queryClient.setQueryData>>(key, (old) => { + if (!old) return old; + return { + ...old, + pages: old.pages.map((page) => ({ + ...page, + items: page.items.filter((sidebarPage: IPage) => sidebarPage.id !== pageId), + })), + }; + }); + }); + + //update recent changes + queryClient.invalidateQueries({ + queryKey: ["recent-changes"], + }); +} \ No newline at end of file diff --git a/apps/client/src/features/page/services/page-service.ts b/apps/client/src/features/page/services/page-service.ts index 5e69a34a..e7e15608 100644 --- a/apps/client/src/features/page/services/page-service.ts +++ b/apps/client/src/features/page/services/page-service.ts @@ -10,6 +10,7 @@ import { } from "@/features/page/types/page.types"; import { IAttachment, IPagination } from "@/lib/types.ts"; import { saveAs } from "file-saver"; +import { InfiniteData } from "@tanstack/react-query"; export async function createPage(data: Partial): Promise { const req = await api.post("/pages/create", data); @@ -52,6 +53,32 @@ export async function getSidebarPages( return req.data; } +export async function getAllSidebarPages( + params: SidebarPagesParams, +): Promise, unknown>> { + let page = 1; + let hasNextPage = false; + const pages: IPagination[] = []; + const pageParams: number[] = []; + + do { + const req = await api.post("/pages/sidebar-pages", { ...params, page: page }); + + const data: IPagination = req.data; + pages.push(data); + pageParams.push(page); + + hasNextPage = data.meta.hasNextPage; + + page += 1; + } while (hasNextPage); + + return { + pageParams, + pages, + }; +} + export async function getPageBreadcrumbs( pageId: string, ): Promise> { diff --git a/apps/client/src/features/page/tree/atoms/tree-data-atom.ts b/apps/client/src/features/page/tree/atoms/tree-data-atom.ts index e3910cb8..7d0ec503 100644 --- a/apps/client/src/features/page/tree/atoms/tree-data-atom.ts +++ b/apps/client/src/features/page/tree/atoms/tree-data-atom.ts @@ -1,4 +1,19 @@ import { atom } from "jotai"; import { SpaceTreeNode } from "@/features/page/tree/types"; +import { appendNodeChildren } from "../utils"; export const treeDataAtom = atom([]); + +// Atom +export const appendNodeChildrenAtom = atom( + null, + ( + get, + set, + { parentId, children }: { parentId: string; children: SpaceTreeNode[] } + ) => { + const currentTree = get(treeDataAtom); + const updatedTree = appendNodeChildren(currentTree, parentId, children); + set(treeDataAtom, updatedTree); + } +); diff --git a/apps/client/src/features/page/tree/components/space-tree.tsx b/apps/client/src/features/page/tree/components/space-tree.tsx index 1df62678..da5b1832 100644 --- a/apps/client/src/features/page/tree/components/space-tree.tsx +++ b/apps/client/src/features/page/tree/components/space-tree.tsx @@ -2,7 +2,7 @@ import { NodeApi, NodeRendererProps, Tree, TreeApi } from "react-arborist"; import { atom, useAtom } from "jotai"; import { treeApiAtom } from "@/features/page/tree/atoms/tree-api-atom.ts"; import { - fetchAncestorChildren, + fetchAllAncestorChildren, useGetRootSidebarPagesQuery, usePageQuery, useUpdatePageMutation, @@ -24,7 +24,7 @@ import { IconPointFilled, IconTrash, } from "@tabler/icons-react"; -import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts"; +import { appendNodeChildrenAtom, treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts"; import clsx from "clsx"; import EmojiPicker from "@/components/ui/emoji-picker.tsx"; import { useTreeMutation } from "@/features/page/tree/hooks/use-tree-mutation.ts"; @@ -140,7 +140,7 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) { if (ancestor.id === currentPage.id) { return; } - const children = await fetchAncestorChildren({ + const children = await fetchAllAncestorChildren({ pageId: ancestor.id, spaceId: ancestor.spaceId, }); @@ -237,6 +237,7 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps) { const { t } = useTranslation(); const updatePageMutation = useUpdatePageMutation(); const [treeData, setTreeData] = useAtom(treeDataAtom); + const [, appendChildren] = useAtom(appendNodeChildrenAtom); const emit = useQueryEmit(); const { spaceSlug } = useParams(); const timerRef = useRef(null); @@ -262,9 +263,10 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps) { async function handleLoadChildren(node: NodeApi) { if (!node.data.hasChildren) return; - if (node.data.children && node.data.children.length > 0) { - return; - } + // in conflict with use-query-subscription.ts => case "addTreeNode","moveTreeNode" etc with websocket + // if (node.data.children && node.data.children.length > 0) { + // return; + // } try { const params: SidebarPagesParams = { @@ -272,21 +274,12 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps) { spaceId: node.data.spaceId, }; - const newChildren = await queryClient.fetchQuery({ - queryKey: ["sidebar-pages", params], - queryFn: () => getSidebarPages(params), - staleTime: 10 * 60 * 1000, + const childrenTree = await fetchAllAncestorChildren(params); + + appendChildren({ + parentId: node.data.id, + children: childrenTree, }); - - const childrenTree = buildTree(newChildren.items); - - const updatedTreeData = appendNodeChildren( - treeData, - node.data.id, - childrenTree, - ); - - setTreeData(updatedTreeData); } catch (error) { console.error("Failed to fetch children:", error); } @@ -304,17 +297,17 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps) { const handleEmojiSelect = (emoji: { native: string }) => { handleUpdateNodeIcon(node.id, emoji.native); - updatePageMutation.mutateAsync({ pageId: node.id, icon: emoji.native }); - - setTimeout(() => { - emit({ - operation: "updateOne", - spaceId: node.data.spaceId, - entity: ["pages"], - id: node.id, - payload: { icon: emoji.native }, - }); - }, 50); + updatePageMutation.mutateAsync({ pageId: node.id, icon: emoji.native }).then((data) => { + setTimeout(() => { + emit({ + operation: "updateOne", + spaceId: node.data.spaceId, + entity: ["pages"], + id: node.id, + payload: { icon: emoji.native, parentPageId: data.parentPageId}, + }); + }, 50); + }); }; const handleRemoveEmoji = () => { @@ -576,6 +569,12 @@ interface PageArrowProps { } function PageArrow({ node, onExpandTree }: PageArrowProps) { + useEffect(() => { + if(node.isOpen){ + onExpandTree(); + } + }, []); + return ( (spaceId: string) { return data; }; - const onMove: MoveHandler = (args: { + const onMove: MoveHandler = async (args: { dragIds: string[]; dragNodes: NodeApi[]; parentId: string | null; @@ -176,7 +176,7 @@ export function useTreeMutation(spaceId: string) { }; try { - movePageMutation.mutateAsync(payload); + await movePageMutation.mutateAsync(payload); setTimeout(() => { emit({ @@ -206,6 +206,23 @@ export function useTreeMutation(spaceId: string) { } }; + const isPageInNode = ( + node: { data: SpaceTreeNode; children?: any[] }, + pageSlug: string + ): boolean => { + if (node.data.slugId === pageSlug) { + return true; + } + for (const item of node.children) { + if (item.data.slugId === pageSlug) { + return true; + } else { + return isPageInNode(item, pageSlug); + } + } + return false; + }; + const onDelete: DeleteHandler = async (args: { ids: string[] }) => { try { await deletePageMutation.mutateAsync(args.ids[0]); @@ -218,8 +235,7 @@ export function useTreeMutation(spaceId: string) { tree.drop({ id: args.ids[0] }); setData(tree.data); - // navigate only if the current url is same as the deleted page - if (pageSlug && node.data.slugId === pageSlug.split("-")[1]) { + if (pageSlug && isPageInNode(node, pageSlug.split("-")[1])) { navigate(getSpaceUrl(spaceSlug)); } diff --git a/apps/client/src/features/page/tree/utils/utils.ts b/apps/client/src/features/page/tree/utils/utils.ts index 7ae84e38..910799c8 100644 --- a/apps/client/src/features/page/tree/utils/utils.ts +++ b/apps/client/src/features/page/tree/utils/utils.ts @@ -164,16 +164,35 @@ export function appendNodeChildren( nodeId: string, children: SpaceTreeNode[], ) { - return treeItems.map((nodeItem) => { - if (nodeItem.id === nodeId) { - return { ...nodeItem, children }; - } - if (nodeItem.children) { + // Preserve deeper children if they exist and remove node if deleted + return treeItems.map((node) => { + if (node.id === nodeId) { + const newIds = new Set(children.map(c => c.id)); + + const existingMap = new Map( + (node.children ?? []).filter(c => newIds.has(c.id)).map(c => [c.id, c]) + ); + + const merged = children.map((newChild) => { + const existing = existingMap.get(newChild.id); + return existing && existing.children + ? { ...newChild, children: existing.children } + : newChild; + }); + return { - ...nodeItem, - children: appendNodeChildren(nodeItem.children, nodeId, children), + ...node, + children: merged, }; } - return nodeItem; + + if (node.children) { + return { + ...node, + children: appendNodeChildren(node.children, nodeId, children), + }; + } + + return node; }); } diff --git a/apps/client/src/features/websocket/types/types.ts b/apps/client/src/features/websocket/types/types.ts index 48e7d819..bda76b4e 100644 --- a/apps/client/src/features/websocket/types/types.ts +++ b/apps/client/src/features/websocket/types/types.ts @@ -1,4 +1,5 @@ import { SpaceTreeNode } from "@/features/page/tree/types.ts"; +import { IPage } from "@/features/page/types/page.types"; export type InvalidateEvent = { operation: "invalidate"; @@ -17,7 +18,7 @@ export type UpdateEvent = { spaceId: string; entity: Array; id: string; - payload: Partial; + payload: Partial; }; export type DeleteEvent = { @@ -25,7 +26,7 @@ export type DeleteEvent = { spaceId: string; entity: Array; id: string; - payload?: Partial; + payload?: Partial; }; export type AddTreeNodeEvent = { diff --git a/apps/client/src/features/websocket/use-query-subscription.ts b/apps/client/src/features/websocket/use-query-subscription.ts index c9e53aa6..8e3ad569 100644 --- a/apps/client/src/features/websocket/use-query-subscription.ts +++ b/apps/client/src/features/websocket/use-query-subscription.ts @@ -1,8 +1,11 @@ import React from "react"; import { socketAtom } from "@/features/websocket/atoms/socket-atom.ts"; import { useAtom } from "jotai"; -import { useQueryClient } from "@tanstack/react-query"; +import { InfiniteData, useQueryClient } from "@tanstack/react-query"; import { WebSocketEvent } from "@/features/websocket/types"; +import { IPage } from "../page/types/page.types"; +import { IPagination } from "@/lib/types"; +import { invalidateOnCreatePage, invalidateOnDeletePage, invalidateOnMovePage, invalidateOnUpdatePage } from "../page/queries/page-query"; import { RQ_KEY } from "../comment/queries/comment-query"; export const useQuerySubscription = () => { @@ -27,6 +30,16 @@ export const useQuerySubscription = () => { queryKey: RQ_KEY(data.pageId), }); break; + case "addTreeNode": + invalidateOnCreatePage(data.payload.data); + break; + case "moveTreeNode": + invalidateOnMovePage(); + break; + case "deleteTreeNode": + const pageId = data.payload.node.id; + invalidateOnDeletePage(pageId); + break; case "updateOne": entity = data.entity[0]; if (entity === "pages") { @@ -43,7 +56,11 @@ export const useQuerySubscription = () => { ...data.payload, }); } - + + if (entity === "pages") { + invalidateOnUpdatePage(data.spaceId, data.payload.parentPageId, data.id, data.payload.title, data.payload.icon); + } + /* queryClient.setQueriesData( { queryKey: [data.entity, data.id] }, @@ -55,7 +72,7 @@ export const useQuerySubscription = () => { : update(oldData as Record); }, ); - */ + */ break; } }); From 6d024fc3de140e0d6748abec543dd6670f4b1882 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Mon, 9 Jun 2025 04:29:27 +0100 Subject: [PATCH 61/63] feat: bulk page imports (#1219) * refactor imports - WIP * Add readstream * WIP * fix attachmentId render * fix attachmentId render * turndown video tag * feat: add stream upload support and improve file handling - Add stream upload functionality to storage drivers\n- Improve ZIP file extraction with better encoding handling\n- Fix attachment ID rendering issues\n- Add AWS S3 upload stream support\n- Update dependencies for better compatibility * WIP * notion formatter * move embed parser to editor-ext package * import embeds * utility files * cleanup * Switch from happy-dom to cheerio * Refine code * WIP * bug fixes and UI * sync * WIP * sync * keep import modal mounted * Show modal during upload * WIP * WIP --- .../src/components/icons/confluence-icon.tsx | 20 + .../editor/components/embed/embed-view.tsx | 7 +- .../file-task/services/file-task-service.ts | 14 + .../file-task/types/file-task.types.ts | 17 + .../page/components/page-import-modal.tsx | 224 +++++++++++- .../features/page/services/page-service.ts | 22 +- .../page/tree/components/space-tree.tsx | 54 +-- .../src/features/page/tree/utils/utils.ts | 25 +- .../src/features/websocket/types/types.ts | 21 +- .../websocket/use-query-subscription.ts | 38 +- apps/client/src/lib/config.ts | 5 + apps/client/vite.config.ts | 2 + apps/server/package.json | 7 +- .../extensions/persistence.extension.ts | 2 +- apps/server/src/common/helpers/utils.ts | 6 + .../migrations/20250521T154949-file_tasks.ts | 39 ++ apps/server/src/database/types/db.d.ts | 19 + .../server/src/database/types/entity.types.ts | 6 + apps/server/src/ee | 2 +- .../environment/environment.service.ts | 4 + .../src/integrations/export/turndown-utils.ts | 23 +- .../integrations/import/dto/file-task-dto.ts | 18 + .../import/file-task.controller.ts | 79 ++++ .../integrations/import/import.controller.ts | 77 +++- .../src/integrations/import/import.module.ts | 19 +- .../import/processors/file-task.processor.ts | 76 ++++ .../import/services/file-task.service.ts | 346 ++++++++++++++++++ .../services/import-attachment.service.ts | 303 +++++++++++++++ .../import/{ => services}/import.service.ts | 104 +++++- .../integrations/import/utils/file.utils.ts | 187 ++++++++++ .../import/utils/import-formatter.ts | 254 +++++++++++++ .../integrations/import/utils/import.utils.ts | 66 ++++ .../queue/constants/queue.constants.ts | 4 + .../src/integrations/queue/queue.module.ts | 8 + .../storage/drivers/local.driver.ts | 23 +- .../integrations/storage/drivers/s3.driver.ts | 36 ++ .../interfaces/storage-driver.interface.ts | 6 + .../integrations/storage/storage.service.ts | 10 + apps/server/src/main.ts | 2 +- packages/editor-ext/src/index.ts | 3 +- .../editor-ext/src/lib/embed-provider.ts | 97 ++--- packages/editor-ext/src/lib/image/image.ts | 2 +- packages/editor-ext/src/lib/trailing-node.ts | 9 +- packages/editor-ext/src/lib/video/video.ts | 10 +- pnpm-lock.yaml | 215 +++++++++-- 45 files changed, 2362 insertions(+), 149 deletions(-) create mode 100644 apps/client/src/components/icons/confluence-icon.tsx create mode 100644 apps/client/src/features/file-task/services/file-task-service.ts create mode 100644 apps/client/src/features/file-task/types/file-task.types.ts create mode 100644 apps/server/src/database/migrations/20250521T154949-file_tasks.ts create mode 100644 apps/server/src/integrations/import/dto/file-task-dto.ts create mode 100644 apps/server/src/integrations/import/file-task.controller.ts create mode 100644 apps/server/src/integrations/import/processors/file-task.processor.ts create mode 100644 apps/server/src/integrations/import/services/file-task.service.ts create mode 100644 apps/server/src/integrations/import/services/import-attachment.service.ts rename apps/server/src/integrations/import/{ => services}/import.service.ts (61%) create mode 100644 apps/server/src/integrations/import/utils/file.utils.ts create mode 100644 apps/server/src/integrations/import/utils/import-formatter.ts create mode 100644 apps/server/src/integrations/import/utils/import.utils.ts rename apps/client/src/features/editor/components/embed/providers.ts => packages/editor-ext/src/lib/embed-provider.ts (58%) diff --git a/apps/client/src/components/icons/confluence-icon.tsx b/apps/client/src/components/icons/confluence-icon.tsx new file mode 100644 index 00000000..499f18da --- /dev/null +++ b/apps/client/src/components/icons/confluence-icon.tsx @@ -0,0 +1,20 @@ +import { rem } from "@mantine/core"; + +interface Props { + size?: number | string; +} + +export function ConfluenceIcon({ size }: Props) { + return ( + + + + ); +} diff --git a/apps/client/src/features/editor/components/embed/embed-view.tsx b/apps/client/src/features/editor/components/embed/embed-view.tsx index 02ae6edf..77743a07 100644 --- a/apps/client/src/features/editor/components/embed/embed-view.tsx +++ b/apps/client/src/features/editor/components/embed/embed-view.tsx @@ -15,13 +15,10 @@ import { import { IconEdit } from "@tabler/icons-react"; import { z } from "zod"; import { useForm, zodResolver } from "@mantine/form"; -import { - getEmbedProviderById, - getEmbedUrlAndProvider, -} from "@/features/editor/components/embed/providers.ts"; import { notifications } from "@mantine/notifications"; import { useTranslation } from "react-i18next"; import i18n from "i18next"; +import { getEmbedProviderById, getEmbedUrlAndProvider } from '@docmost/editor-ext'; const schema = z.object({ url: z @@ -101,7 +98,7 @@ export default function EmbedView(props: NodeViewProps) { {t("Embed {{provider}}", { - provider: getEmbedProviderById(provider).name, + provider: getEmbedProviderById(provider)?.name, })}
diff --git a/apps/client/src/features/file-task/services/file-task-service.ts b/apps/client/src/features/file-task/services/file-task-service.ts new file mode 100644 index 00000000..ffccbaae --- /dev/null +++ b/apps/client/src/features/file-task/services/file-task-service.ts @@ -0,0 +1,14 @@ +import api from "@/lib/api-client"; +import { IFileTask } from "@/features/file-task/types/file-task.types.ts"; + +export async function getFileTaskById(fileTaskId: string): Promise { + const req = await api.post("/file-tasks/info", { + fileTaskId: fileTaskId, + }); + return req.data; +} + +export async function getFileTasks(): Promise { + const req = await api.post("/file-tasks"); + return req.data; +} diff --git a/apps/client/src/features/file-task/types/file-task.types.ts b/apps/client/src/features/file-task/types/file-task.types.ts new file mode 100644 index 00000000..917e1757 --- /dev/null +++ b/apps/client/src/features/file-task/types/file-task.types.ts @@ -0,0 +1,17 @@ +export interface IFileTask { + id: string; + type: "import" | "export"; + source: string; + status: string; + fileName: string; + filePath: string; + fileSize: number; + fileExt: string; + errorMessage: string | null; + creatorId: string; + spaceId: string; + workspaceId: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; +} \ No newline at end of file diff --git a/apps/client/src/features/page/components/page-import-modal.tsx b/apps/client/src/features/page/components/page-import-modal.tsx index f07fd8a9..90c08bb6 100644 --- a/apps/client/src/features/page/components/page-import-modal.tsx +++ b/apps/client/src/features/page/components/page-import-modal.tsx @@ -1,18 +1,38 @@ -import { Modal, Button, SimpleGrid, FileButton } from "@mantine/core"; import { + Modal, + Button, + SimpleGrid, + FileButton, + Group, + Text, + Tooltip, +} from "@mantine/core"; +import { + IconBrandNotion, IconCheck, IconFileCode, + IconFileTypeZip, IconMarkdown, IconX, } from "@tabler/icons-react"; -import { importPage } from "@/features/page/services/page-service.ts"; +import { + importPage, + importZip, +} from "@/features/page/services/page-service.ts"; import { notifications } from "@mantine/notifications"; import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts"; import { useAtom } from "jotai"; import { buildTree } from "@/features/page/tree/utils"; import { IPage } from "@/features/page/types/page.types.ts"; -import React from "react"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; +import { ConfluenceIcon } from "@/components/icons/confluence-icon.tsx"; +import { getFileImportSizeLimit, isCloud } from "@/lib/config.ts"; +import { formatBytes } from "@/lib"; +import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts"; +import { getFileTaskById } from "@/features/file-task/services/file-task-service.ts"; +import { queryClient } from "@/main.tsx"; +import { useQueryEmit } from "@/features/websocket/use-query-emit.ts"; interface PageImportModalProps { spaceId: string; @@ -36,6 +56,7 @@ export default function PageImportModal({ yOffset="10vh" xOffset={0} mah={400} + keepMounted={true} > @@ -59,6 +80,133 @@ interface ImportFormatSelection { function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) { const { t } = useTranslation(); const [treeData, setTreeData] = useAtom(treeDataAtom); + const [workspace] = useAtom(workspaceAtom); + const [fileTaskId, setFileTaskId] = useState(null); + const emit = useQueryEmit(); + + const canUseConfluence = isCloud() || workspace?.hasLicenseKey; + + const handleZipUpload = async (selectedFile: File, source: string) => { + if (!selectedFile) { + return; + } + + try { + onClose(); + + notifications.show({ + id: "import", + title: t("Uploading import file"), + message: t("Please don't close this tab."), + loading: true, + withCloseButton: false, + autoClose: false, + }); + + const importTask = await importZip(selectedFile, spaceId, source); + notifications.update({ + id: "import", + title: t("Importing pages"), + message: t( + "Page import is in progress. You can check back later if this takes longer.", + ), + loading: true, + withCloseButton: true, + autoClose: false, + }); + + setFileTaskId(importTask.id); + } catch (err) { + console.log("Failed to upload import file", err); + notifications.update({ + id: "import", + color: "red", + title: t("Failed to upload import file"), + message: err?.response.data.message, + icon: , + loading: false, + withCloseButton: true, + autoClose: false, + }); + } + }; + + useEffect(() => { + if (!fileTaskId) return; + + const intervalId = setInterval(async () => { + try { + const fileTask = await getFileTaskById(fileTaskId); + const status = fileTask.status; + + if (status === "success") { + notifications.update({ + id: "import", + color: "teal", + title: t("Import complete"), + message: t("Your pages were successfully imported."), + icon: , + loading: false, + withCloseButton: true, + autoClose: false, + }); + clearInterval(intervalId); + setFileTaskId(null); + + await queryClient.refetchQueries({ + queryKey: ["root-sidebar-pages", fileTask.spaceId], + }); + + setTimeout(() => { + emit({ + operation: "refetchRootTreeNodeEvent", + spaceId: spaceId, + }); + }, 50); + } + + if (status === "failed") { + notifications.update({ + id: "import", + color: "red", + title: t("Page import failed"), + message: t( + "Something went wrong while importing pages: {{reason}}.", + { + reason: fileTask.errorMessage, + }, + ), + icon: , + loading: false, + withCloseButton: true, + autoClose: false, + }); + clearInterval(intervalId); + setFileTaskId(null); + console.error(fileTask.errorMessage); + } + } catch (err) { + notifications.update({ + id: "import", + color: "red", + title: t("Import failed"), + message: t( + "Something went wrong while importing pages: {{reason}}.", + { + reason: err.response?.data.message, + }, + ), + icon: , + loading: false, + withCloseButton: true, + autoClose: false, + }); + clearInterval(intervalId); + setFileTaskId(null); + console.error("Failed to fetch import status", err); + } + }, 3000); + }, [fileTaskId]); const handleFileUpload = async (selectedFiles: File[]) => { if (!selectedFiles) { @@ -120,6 +268,7 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) { } }; + // @ts-ignore return ( <> @@ -148,7 +297,76 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) { )} + + handleZipUpload(file, "notion")} + accept="application/zip" + > + {(props) => ( + + )} + + handleZipUpload(file, "confluence")} + accept="application/zip" + > + {(props) => ( + + + + )} + + + +
+ + Import zip file + + + {t( + `Upload zip file containing Markdown and HTML files. Max: {{sizeLimit}}`, + { + sizeLimit: formatBytes(getFileImportSizeLimit()), + }, + )} + + handleZipUpload(file, "generic")} + accept="application/zip" + > + {(props) => ( + + + + )} + +
+
); } diff --git a/apps/client/src/features/page/services/page-service.ts b/apps/client/src/features/page/services/page-service.ts index e7e15608..a8e3d256 100644 --- a/apps/client/src/features/page/services/page-service.ts +++ b/apps/client/src/features/page/services/page-service.ts @@ -7,10 +7,11 @@ import { IPage, IPageInput, SidebarPagesParams, -} from "@/features/page/types/page.types"; +} from '@/features/page/types/page.types'; import { IAttachment, IPagination } from "@/lib/types.ts"; import { saveAs } from "file-saver"; import { InfiniteData } from "@tanstack/react-query"; +import { IFileTask } from '@/features/file-task/types/file-task.types.ts'; export async function createPage(data: Partial): Promise { const req = await api.post("/pages/create", data); @@ -119,6 +120,25 @@ export async function importPage(file: File, spaceId: string) { return req.data; } +export async function importZip( + file: File, + spaceId: string, + source?: string, +): Promise { + const formData = new FormData(); + formData.append("spaceId", spaceId); + formData.append("source", source); + formData.append("file", file); + + const req = await api.post("/pages/import-zip", formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + + return req.data; +} + export async function uploadFile( file: File, pageId: string, diff --git a/apps/client/src/features/page/tree/components/space-tree.tsx b/apps/client/src/features/page/tree/components/space-tree.tsx index da5b1832..db818518 100644 --- a/apps/client/src/features/page/tree/components/space-tree.tsx +++ b/apps/client/src/features/page/tree/components/space-tree.tsx @@ -24,7 +24,10 @@ import { IconPointFilled, IconTrash, } from "@tabler/icons-react"; -import { appendNodeChildrenAtom, treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts"; +import { + appendNodeChildrenAtom, + treeDataAtom, +} from "@/features/page/tree/atoms/tree-data-atom.ts"; import clsx from "clsx"; import EmojiPicker from "@/components/ui/emoji-picker.tsx"; import { useTreeMutation } from "@/features/page/tree/hooks/use-tree-mutation.ts"; @@ -32,6 +35,7 @@ import { appendNodeChildren, buildTree, buildTreeWithChildren, + mergeRootTrees, updateTreeNodeIcon, } from "@/features/page/tree/utils/utils.ts"; import { SpaceTreeNode } from "@/features/page/tree/types.ts"; @@ -104,17 +108,17 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) { const allItems = pagesData.pages.flatMap((page) => page.items); const treeData = buildTree(allItems); - if (data.length < 1 || data?.[0].spaceId !== spaceId) { - //Thoughts - // don't reset if there is data in state - // we only expect to call this once on initial load - // even if we decide to refetch, it should only update - // and append root pages instead of resetting the entire tree - // which looses async loaded children too - setData(treeData); - setIsDataLoaded(true); - setOpenTreeNodes({}); - } + setData((prev) => { + // fresh space; full reset + if (prev.length === 0 || prev[0]?.spaceId !== spaceId) { + setIsDataLoaded(true); + setOpenTreeNodes({}); + return treeData; + } + + // same space; append only missing roots + return mergeRootTrees(prev, treeData); + }); } }, [pagesData, hasNextPage]); @@ -297,17 +301,19 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps) { const handleEmojiSelect = (emoji: { native: string }) => { handleUpdateNodeIcon(node.id, emoji.native); - updatePageMutation.mutateAsync({ pageId: node.id, icon: emoji.native }).then((data) => { - setTimeout(() => { - emit({ - operation: "updateOne", - spaceId: node.data.spaceId, - entity: ["pages"], - id: node.id, - payload: { icon: emoji.native, parentPageId: data.parentPageId}, - }); - }, 50); - }); + updatePageMutation + .mutateAsync({ pageId: node.id, icon: emoji.native }) + .then((data) => { + setTimeout(() => { + emit({ + operation: "updateOne", + spaceId: node.data.spaceId, + entity: ["pages"], + id: node.id, + payload: { icon: emoji.native, parentPageId: data.parentPageId }, + }); + }, 50); + }); }; const handleRemoveEmoji = () => { @@ -570,7 +576,7 @@ interface PageArrowProps { function PageArrow({ node, onExpandTree }: PageArrowProps) { useEffect(() => { - if(node.isOpen){ + if (node.isOpen) { onExpandTree(); } }, []); diff --git a/apps/client/src/features/page/tree/utils/utils.ts b/apps/client/src/features/page/tree/utils/utils.ts index 910799c8..8ec1b884 100644 --- a/apps/client/src/features/page/tree/utils/utils.ts +++ b/apps/client/src/features/page/tree/utils/utils.ts @@ -121,7 +121,6 @@ export const deleteTreeNode = ( .filter((node) => node !== null); }; - export function buildTreeWithChildren(items: SpaceTreeNode[]): SpaceTreeNode[] { const nodeMap = {}; let result: SpaceTreeNode[] = []; @@ -167,10 +166,12 @@ export function appendNodeChildren( // Preserve deeper children if they exist and remove node if deleted return treeItems.map((node) => { if (node.id === nodeId) { - const newIds = new Set(children.map(c => c.id)); + const newIds = new Set(children.map((c) => c.id)); const existingMap = new Map( - (node.children ?? []).filter(c => newIds.has(c.id)).map(c => [c.id, c]) + (node.children ?? []) + .filter((c) => newIds.has(c.id)) + .map((c) => [c.id, c]), ); const merged = children.map((newChild) => { @@ -196,3 +197,21 @@ export function appendNodeChildren( return node; }); } + +/** + * Merge root nodes; keep existing ones intact, append new ones, + */ +export function mergeRootTrees( + prevRoots: SpaceTreeNode[], + incomingRoots: SpaceTreeNode[], +): SpaceTreeNode[] { + const seen = new Set(prevRoots.map((r) => r.id)); + + // add new roots that were not present before + const merged = [...prevRoots]; + incomingRoots.forEach((node) => { + if (!seen.has(node.id)) merged.push(node); + }); + + return sortPositionKeys(merged); +} diff --git a/apps/client/src/features/websocket/types/types.ts b/apps/client/src/features/websocket/types/types.ts index bda76b4e..25b57df9 100644 --- a/apps/client/src/features/websocket/types/types.ts +++ b/apps/client/src/features/websocket/types/types.ts @@ -47,15 +47,28 @@ export type MoveTreeNodeEvent = { parentId: string; index: number; position: string; - } + }; }; export type DeleteTreeNodeEvent = { operation: "deleteTreeNode"; spaceId: string; payload: { - node: SpaceTreeNode - } + node: SpaceTreeNode; + }; }; -export type WebSocketEvent = InvalidateEvent | InvalidateCommentsEvent | UpdateEvent | DeleteEvent | AddTreeNodeEvent | MoveTreeNodeEvent | DeleteTreeNodeEvent; +export type RefetchRootTreeNodeEvent = { + operation: "refetchRootTreeNodeEvent"; + spaceId: string; +}; + +export type WebSocketEvent = + | InvalidateEvent + | InvalidateCommentsEvent + | UpdateEvent + | DeleteEvent + | AddTreeNodeEvent + | MoveTreeNodeEvent + | DeleteTreeNodeEvent + | RefetchRootTreeNodeEvent; diff --git a/apps/client/src/features/websocket/use-query-subscription.ts b/apps/client/src/features/websocket/use-query-subscription.ts index 8e3ad569..29a815be 100644 --- a/apps/client/src/features/websocket/use-query-subscription.ts +++ b/apps/client/src/features/websocket/use-query-subscription.ts @@ -5,8 +5,14 @@ import { InfiniteData, useQueryClient } from "@tanstack/react-query"; import { WebSocketEvent } from "@/features/websocket/types"; import { IPage } from "../page/types/page.types"; import { IPagination } from "@/lib/types"; -import { invalidateOnCreatePage, invalidateOnDeletePage, invalidateOnMovePage, invalidateOnUpdatePage } from "../page/queries/page-query"; +import { + invalidateOnCreatePage, + invalidateOnDeletePage, + invalidateOnMovePage, + invalidateOnUpdatePage, +} from "../page/queries/page-query"; import { RQ_KEY } from "../comment/queries/comment-query"; +import { queryClient } from "@/main.tsx"; export const useQuerySubscription = () => { const queryClient = useQueryClient(); @@ -37,8 +43,7 @@ export const useQuerySubscription = () => { invalidateOnMovePage(); break; case "deleteTreeNode": - const pageId = data.payload.node.id; - invalidateOnDeletePage(pageId); + invalidateOnDeletePage(data.payload.node.id); break; case "updateOne": entity = data.entity[0]; @@ -50,17 +55,23 @@ export const useQuerySubscription = () => { } // only update if data was already in cache - if(queryClient.getQueryData([...data.entity, queryKeyId])){ + if (queryClient.getQueryData([...data.entity, queryKeyId])) { queryClient.setQueryData([...data.entity, queryKeyId], { ...queryClient.getQueryData([...data.entity, queryKeyId]), ...data.payload, }); } - + if (entity === "pages") { - invalidateOnUpdatePage(data.spaceId, data.payload.parentPageId, data.id, data.payload.title, data.payload.icon); + invalidateOnUpdatePage( + data.spaceId, + data.payload.parentPageId, + data.id, + data.payload.title, + data.payload.icon, + ); } - + /* queryClient.setQueriesData( { queryKey: [data.entity, data.id] }, @@ -72,8 +83,19 @@ export const useQuerySubscription = () => { : update(oldData as Record); }, ); - */ + */ break; + case "refetchRootTreeNodeEvent": { + const spaceId = data.spaceId; + queryClient.refetchQueries({ + queryKey: ["root-sidebar-pages", spaceId], + }); + + queryClient.invalidateQueries({ + queryKey: ["recent-changes", spaceId], + }); + break; + } } }); }, [queryClient, socket]); diff --git a/apps/client/src/lib/config.ts b/apps/client/src/lib/config.ts index 2f621b91..717bf9ff 100644 --- a/apps/client/src/lib/config.ts +++ b/apps/client/src/lib/config.ts @@ -70,6 +70,11 @@ export function getFileUploadSizeLimit() { return bytes(limit); } +export function getFileImportSizeLimit() { + const limit = getConfigValue("FILE_IMPORT_SIZE_LIMIT", "200mb"); + return bytes(limit); +} + export function getDrawioUrl() { return getConfigValue("DRAWIO_URL", "https://embed.diagrams.net"); } diff --git a/apps/client/vite.config.ts b/apps/client/vite.config.ts index a6efc4bc..cc8a01fd 100644 --- a/apps/client/vite.config.ts +++ b/apps/client/vite.config.ts @@ -8,6 +8,7 @@ export default defineConfig(({ mode }) => { const { APP_URL, FILE_UPLOAD_SIZE_LIMIT, + FILE_IMPORT_SIZE_LIMIT, DRAWIO_URL, CLOUD, SUBDOMAIN_HOST, @@ -20,6 +21,7 @@ export default defineConfig(({ mode }) => { "process.env": { APP_URL, FILE_UPLOAD_SIZE_LIMIT, + FILE_IMPORT_SIZE_LIMIT, DRAWIO_URL, CLOUD, SUBDOMAIN_HOST, diff --git a/apps/server/package.json b/apps/server/package.json index 5082d129..d105f20c 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -31,6 +31,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "3.701.0", + "@aws-sdk/lib-storage": "3.701.0", "@aws-sdk/s3-request-presigner": "3.701.0", "@casl/ability": "^6.7.3", "@fastify/cookie": "^11.0.2", @@ -56,6 +57,7 @@ "bcrypt": "^5.1.1", "bullmq": "^5.41.3", "cache-manager": "^6.4.0", + "cheerio": "^1.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "cookie": "^1.0.2", @@ -80,7 +82,9 @@ "sanitize-filename-ts": "^1.0.2", "socket.io": "^4.8.1", "stripe": "^17.5.0", - "ws": "^8.18.0" + "tmp-promise": "^3.0.3", + "ws": "^8.18.0", + "yauzl": "^3.2.0" }, "devDependencies": { "@eslint/js": "^9.20.0", @@ -99,6 +103,7 @@ "@types/pg": "^8.11.11", "@types/supertest": "^6.0.2", "@types/ws": "^8.5.14", + "@types/yauzl": "^2.10.3", "eslint": "^9.20.1", "eslint-config-prettier": "^10.0.1", "globals": "^15.15.0", diff --git a/apps/server/src/collaboration/extensions/persistence.extension.ts b/apps/server/src/collaboration/extensions/persistence.extension.ts index 3c206e1a..88284fd2 100644 --- a/apps/server/src/collaboration/extensions/persistence.extension.ts +++ b/apps/server/src/collaboration/extensions/persistence.extension.ts @@ -130,7 +130,7 @@ export class PersistenceExtension implements Extension { ); this.contributors.delete(documentName); } catch (err) { - this.logger.debug('Contributors error:' + err?.['message']); + //this.logger.debug('Contributors error:' + err?.['message']); } await this.pageRepo.updatePage( diff --git a/apps/server/src/common/helpers/utils.ts b/apps/server/src/common/helpers/utils.ts index e2a4d5eb..d1748850 100644 --- a/apps/server/src/common/helpers/utils.ts +++ b/apps/server/src/common/helpers/utils.ts @@ -1,5 +1,6 @@ import * as path from 'path'; import * as bcrypt from 'bcrypt'; +import { sanitize } from 'sanitize-filename-ts'; export const envPath = path.resolve(process.cwd(), '..', '..', '.env'); @@ -62,3 +63,8 @@ export function extractDateFromUuid7(uuid7: string) { return new Date(timestamp); } + +export function sanitizeFileName(fileName: string): string { + const sanitizedFilename = sanitize(fileName).replace(/ /g, '_'); + return sanitizedFilename.slice(0, 255); +} diff --git a/apps/server/src/database/migrations/20250521T154949-file_tasks.ts b/apps/server/src/database/migrations/20250521T154949-file_tasks.ts new file mode 100644 index 00000000..523ae86b --- /dev/null +++ b/apps/server/src/database/migrations/20250521T154949-file_tasks.ts @@ -0,0 +1,39 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('file_tasks') + .addColumn('id', 'uuid', (col) => + col.primaryKey().defaultTo(sql`gen_uuid_v7()`), + ) + // type (import, export) + .addColumn('type', 'varchar', (col) => col) + // source (generic, notion, confluence) + .addColumn('source', 'varchar', (col) => col) + // status (pending|processing|success|failed), + .addColumn('status', 'varchar', (col) => col) + .addColumn('file_name', 'varchar', (col) => col.notNull()) + .addColumn('file_path', 'varchar', (col) => col.notNull()) + .addColumn('file_size', 'int8', (col) => col) + .addColumn('file_ext', 'varchar', (col) => col) + .addColumn('error_message', 'varchar', (col) => col) + .addColumn('creator_id', 'uuid', (col) => col.references('users.id')) + .addColumn('space_id', 'uuid', (col) => + col.references('spaces.id').onDelete('cascade'), + ) + .addColumn('workspace_id', 'uuid', (col) => + col.references('workspaces.id').onDelete('cascade').notNull(), + ) + .addColumn('created_at', 'timestamptz', (col) => + col.notNull().defaultTo(sql`now()`), + ) + .addColumn('updated_at', 'timestamptz', (col) => + col.notNull().defaultTo(sql`now()`), + ) + .addColumn('deleted_at', 'timestamptz', (col) => col) + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('file_tasks').execute(); +} diff --git a/apps/server/src/database/types/db.d.ts b/apps/server/src/database/types/db.d.ts index 8c4cbd57..4545ebc4 100644 --- a/apps/server/src/database/types/db.d.ts +++ b/apps/server/src/database/types/db.d.ts @@ -122,6 +122,24 @@ export interface Comments { workspaceId: string; } +export interface FileTasks { + createdAt: Generated; + creatorId: string | null; + deletedAt: Timestamp | null; + errorMessage: string | null; + fileExt: string | null; + fileName: string; + filePath: string; + fileSize: Int8 | null; + id: Generated; + source: string | null; + spaceId: string | null; + status: string | null; + type: string | null; + updatedAt: Generated; + workspaceId: string; +} + export interface Groups { createdAt: Generated; creatorId: string | null; @@ -298,6 +316,7 @@ export interface DB { backlinks: Backlinks; billing: Billing; comments: Comments; + fileTasks: FileTasks; groups: Groups; groupUsers: GroupUsers; pageHistory: PageHistory; diff --git a/apps/server/src/database/types/entity.types.ts b/apps/server/src/database/types/entity.types.ts index 6cb55a11..db2c2823 100644 --- a/apps/server/src/database/types/entity.types.ts +++ b/apps/server/src/database/types/entity.types.ts @@ -17,6 +17,7 @@ import { AuthProviders, AuthAccounts, Shares, + FileTasks, } from './db'; // Workspace @@ -107,3 +108,8 @@ export type UpdatableAuthAccount = Updateable>; export type Share = Selectable; export type InsertableShare = Insertable; export type UpdatableShare = Updateable>; + +// File Task +export type FileTask = Selectable; +export type InsertableFileTask = Insertable; +export type UpdatableFileTask = Updateable>; diff --git a/apps/server/src/ee b/apps/server/src/ee index b312008b..70eb45ea 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit b312008b4b7ed3d5862436b279d91aeddb6048d7 +Subproject commit 70eb45eaec84f61cb94a83a153915ce443ccc437 diff --git a/apps/server/src/integrations/environment/environment.service.ts b/apps/server/src/integrations/environment/environment.service.ts index ac26b4fb..d6336993 100644 --- a/apps/server/src/integrations/environment/environment.service.ts +++ b/apps/server/src/integrations/environment/environment.service.ts @@ -67,6 +67,10 @@ export class EnvironmentService { return this.configService.get('FILE_UPLOAD_SIZE_LIMIT', '50mb'); } + getFileImportSizeLimit(): string { + return this.configService.get('FILE_IMPORT_SIZE_LIMIT', '200mb'); + } + getAwsS3AccessKeyId(): string { return this.configService.get('AWS_S3_ACCESS_KEY_ID'); } diff --git a/apps/server/src/integrations/export/turndown-utils.ts b/apps/server/src/integrations/export/turndown-utils.ts index 44e606f3..54fdef12 100644 --- a/apps/server/src/integrations/export/turndown-utils.ts +++ b/apps/server/src/integrations/export/turndown-utils.ts @@ -1,5 +1,6 @@ import * as TurndownService from '@joplin/turndown'; import * as TurndownPluginGfm from '@joplin/turndown-plugin-gfm'; +import * as path from 'path'; export function turndown(html: string): string { const turndownService = new TurndownService({ @@ -23,6 +24,7 @@ export function turndown(html: string): string { mathInline, mathBlock, iframeEmbed, + video, ]); return turndownService.turndown(html).replaceAll('
', ' '); } @@ -87,8 +89,12 @@ function preserveDetail(turndownService: TurndownService) { } const detailsContent = Array.from(node.childNodes) - .filter(child => child.nodeName !== 'SUMMARY') - .map(child => (child.nodeType === 1 ? turndownService.turndown((child as HTMLElement).outerHTML) : child.textContent)) + .filter((child) => child.nodeName !== 'SUMMARY') + .map((child) => + child.nodeType === 1 + ? turndownService.turndown((child as HTMLElement).outerHTML) + : child.textContent, + ) .join(''); return `\n
\n${detailSummary}\n\n${detailsContent}\n\n
\n`; @@ -135,3 +141,16 @@ function iframeEmbed(turndownService: TurndownService) { }, }); } + +function video(turndownService: TurndownService) { + turndownService.addRule('video', { + filter: function (node: HTMLInputElement) { + return node.tagName === 'VIDEO'; + }, + replacement: function (content: any, node: HTMLInputElement) { + const src = node.getAttribute('src') || ''; + const name = path.basename(src); + return '[' + name + '](' + src + ')'; + }, + }); +} diff --git a/apps/server/src/integrations/import/dto/file-task-dto.ts b/apps/server/src/integrations/import/dto/file-task-dto.ts new file mode 100644 index 00000000..9cdea395 --- /dev/null +++ b/apps/server/src/integrations/import/dto/file-task-dto.ts @@ -0,0 +1,18 @@ +import { IsNotEmpty, IsUUID } from 'class-validator'; + +export class FileTaskIdDto { + @IsNotEmpty() + @IsUUID() + fileTaskId: string; +} + +export type ImportPageNode = { + id: string; + slugId: string; + name: string; + content: string; + position?: string | null; + parentPageId: string | null; + fileExtension: string; + filePath: string; +}; \ No newline at end of file diff --git a/apps/server/src/integrations/import/file-task.controller.ts b/apps/server/src/integrations/import/file-task.controller.ts new file mode 100644 index 00000000..305779b4 --- /dev/null +++ b/apps/server/src/integrations/import/file-task.controller.ts @@ -0,0 +1,79 @@ +import { + Body, + Controller, + ForbiddenException, + HttpCode, + HttpStatus, + NotFoundException, + Post, + UseGuards, +} from '@nestjs/common'; +import SpaceAbilityFactory from '../../core/casl/abilities/space-ability.factory'; +import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; +import { User } from '@docmost/db/types/entity.types'; +import { + SpaceCaslAction, + SpaceCaslSubject, +} from '../../core/casl/interfaces/space-ability.type'; +import { InjectKysely } from 'nestjs-kysely'; +import { KyselyDB } from '@docmost/db/types/kysely.types'; +import { AuthUser } from '../../common/decorators/auth-user.decorator'; +import { FileTaskIdDto } from './dto/file-task-dto'; +import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo'; + +@Controller('file-tasks') +export class FileTaskController { + constructor( + private readonly spaceMemberRepo: SpaceMemberRepo, + private readonly spaceAbility: SpaceAbilityFactory, + @InjectKysely() private readonly db: KyselyDB, + ) {} + + @UseGuards(JwtAuthGuard) + @HttpCode(HttpStatus.OK) + @Post() + async getFileTasks(@AuthUser() user: User) { + const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(user.id); + + if (!userSpaceIds || userSpaceIds.length === 0) { + return []; + } + + const fileTasks = await this.db + .selectFrom('fileTasks') + .selectAll() + .where('spaceId', 'in', userSpaceIds) + .execute(); + + if (!fileTasks) { + throw new NotFoundException('File task not found'); + } + + return fileTasks; + } + + @UseGuards(JwtAuthGuard) + @HttpCode(HttpStatus.OK) + @Post('info') + async getFileTask(@Body() dto: FileTaskIdDto, @AuthUser() user: User) { + const fileTask = await this.db + .selectFrom('fileTasks') + .selectAll() + .where('id', '=', dto.fileTaskId) + .executeTakeFirst(); + + if (!fileTask || !fileTask.spaceId) { + throw new NotFoundException('File task not found'); + } + + const ability = await this.spaceAbility.createForUser( + user, + fileTask.spaceId, + ); + if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) { + throw new ForbiddenException(); + } + + return fileTask; + } +} diff --git a/apps/server/src/integrations/import/import.controller.ts b/apps/server/src/integrations/import/import.controller.ts index 975301af..1adb82eb 100644 --- a/apps/server/src/integrations/import/import.controller.ts +++ b/apps/server/src/integrations/import/import.controller.ts @@ -21,8 +21,9 @@ import { import { FileInterceptor } from '../../common/interceptors/file.interceptor'; import * as bytes from 'bytes'; import * as path from 'path'; -import { ImportService } from './import.service'; +import { ImportService } from './services/import.service'; import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator'; +import { EnvironmentService } from '../environment/environment.service'; @Controller() export class ImportController { @@ -31,6 +32,7 @@ export class ImportController { constructor( private readonly importService: ImportService, private readonly spaceAbility: SpaceAbilityFactory, + private readonly environmentService: EnvironmentService, ) {} @UseInterceptors(FileInterceptor) @@ -44,18 +46,18 @@ export class ImportController { ) { const validFileExtensions = ['.md', '.html']; - const maxFileSize = bytes('100mb'); + const maxFileSize = bytes('10mb'); let file = null; try { file = await req.file({ - limits: { fileSize: maxFileSize, fields: 3, files: 1 }, + limits: { fileSize: maxFileSize, fields: 4, files: 1 }, }); } catch (err: any) { this.logger.error(err.message); if (err?.statusCode === 413) { throw new BadRequestException( - `File too large. Exceeds the 100mb import limit`, + `File too large. Exceeds the 10mb import limit`, ); } } @@ -73,7 +75,7 @@ export class ImportController { const spaceId = file.fields?.spaceId?.value; if (!spaceId) { - throw new BadRequestException('spaceId or format not found'); + throw new BadRequestException('spaceId is required'); } const ability = await this.spaceAbility.createForUser(user, spaceId); @@ -83,4 +85,69 @@ export class ImportController { return this.importService.importPage(file, user.id, spaceId, workspace.id); } + + @UseInterceptors(FileInterceptor) + @UseGuards(JwtAuthGuard) + @HttpCode(HttpStatus.OK) + @Post('pages/import-zip') + async importZip( + @Req() req: any, + @AuthUser() user: User, + @AuthWorkspace() workspace: Workspace, + ) { + const validFileExtensions = ['.zip']; + + const maxFileSize = bytes(this.environmentService.getFileImportSizeLimit()); + + let file = null; + try { + file = await req.file({ + limits: { fileSize: maxFileSize, fields: 3, files: 1 }, + }); + } catch (err: any) { + this.logger.error(err.message); + if (err?.statusCode === 413) { + throw new BadRequestException( + `File too large. Exceeds the ${this.environmentService.getFileImportSizeLimit()} import limit`, + ); + } + } + + if (!file) { + throw new BadRequestException('Failed to upload file'); + } + + if ( + !validFileExtensions.includes(path.extname(file.filename).toLowerCase()) + ) { + throw new BadRequestException('Invalid import file extension.'); + } + + const spaceId = file.fields?.spaceId?.value; + const source = file.fields?.source?.value; + + const validZipSources = ['generic', 'notion', 'confluence']; + if (!validZipSources.includes(source)) { + throw new BadRequestException( + 'Invalid import source. Import source must either be generic, notion or confluence.', + ); + } + + if (!spaceId) { + throw new BadRequestException('spaceId is required'); + } + + const ability = await this.spaceAbility.createForUser(user, spaceId); + if (ability.cannot(SpaceCaslAction.Edit, SpaceCaslSubject.Page)) { + throw new ForbiddenException(); + } + + return this.importService.importZip( + file, + source, + user.id, + spaceId, + workspace.id, + ); + } } diff --git a/apps/server/src/integrations/import/import.module.ts b/apps/server/src/integrations/import/import.module.ts index 60498808..40a49023 100644 --- a/apps/server/src/integrations/import/import.module.ts +++ b/apps/server/src/integrations/import/import.module.ts @@ -1,9 +1,22 @@ import { Module } from '@nestjs/common'; -import { ImportService } from './import.service'; +import { ImportService } from './services/import.service'; import { ImportController } from './import.controller'; +import { StorageModule } from '../storage/storage.module'; +import { FileTaskService } from './services/file-task.service'; +import { FileTaskProcessor } from './processors/file-task.processor'; +import { ImportAttachmentService } from './services/import-attachment.service'; +import { FileTaskController } from './file-task.controller'; +import { PageModule } from '../../core/page/page.module'; @Module({ - providers: [ImportService], - controllers: [ImportController], + providers: [ + ImportService, + FileTaskService, + FileTaskProcessor, + ImportAttachmentService, + ], + exports: [ImportService, ImportAttachmentService], + controllers: [ImportController, FileTaskController], + imports: [StorageModule, PageModule], }) export class ImportModule {} diff --git a/apps/server/src/integrations/import/processors/file-task.processor.ts b/apps/server/src/integrations/import/processors/file-task.processor.ts new file mode 100644 index 00000000..9431ccec --- /dev/null +++ b/apps/server/src/integrations/import/processors/file-task.processor.ts @@ -0,0 +1,76 @@ +import { Logger, OnModuleDestroy } from '@nestjs/common'; +import { OnWorkerEvent, Processor, WorkerHost } from '@nestjs/bullmq'; +import { Job } from 'bullmq'; +import { QueueJob, QueueName } from 'src/integrations/queue/constants'; +import { FileTaskService } from '../services/file-task.service'; +import { FileTaskStatus } from '../utils/file.utils'; +import { StorageService } from '../../storage/storage.service'; + +@Processor(QueueName.FILE_TASK_QUEUE) +export class FileTaskProcessor extends WorkerHost implements OnModuleDestroy { + private readonly logger = new Logger(FileTaskProcessor.name); + + constructor( + private readonly fileTaskService: FileTaskService, + private readonly storageService: StorageService, + ) { + super(); + } + + async process(job: Job): Promise { + try { + switch (job.name) { + case QueueJob.IMPORT_TASK: + await this.fileTaskService.processZIpImport(job.data.fileTaskId); + break; + case QueueJob.EXPORT_TASK: + // TODO: export task + break; + } + } catch (err) { + this.logger.error('File task failed', err); + throw err; + } + } + + @OnWorkerEvent('active') + onActive(job: Job) { + this.logger.debug(`Processing ${job.name} job`); + } + + @OnWorkerEvent('failed') + async onFailed(job: Job) { + this.logger.error( + `Error processing ${job.name} job. Reason: ${job.failedReason}`, + ); + + try { + const fileTaskId = job.data.fileTaskId; + await this.fileTaskService.updateTaskStatus( + fileTaskId, + FileTaskStatus.Failed, + job.failedReason, + ); + + const fileTask = await this.fileTaskService.getFileTask(fileTaskId); + if (fileTask) { + await this.storageService.delete(fileTask.filePath); + } + } catch (err) { + this.logger.error(err); + } + } + + @OnWorkerEvent('completed') + onCompleted(job: Job) { + this.logger.log( + `Completed ${job.name} job for File task ID ${job.data.fileTaskId}`, + ); + } + + async onModuleDestroy(): Promise { + if (this.worker) { + await this.worker.close(); + } + } +} diff --git a/apps/server/src/integrations/import/services/file-task.service.ts b/apps/server/src/integrations/import/services/file-task.service.ts new file mode 100644 index 00000000..f054017d --- /dev/null +++ b/apps/server/src/integrations/import/services/file-task.service.ts @@ -0,0 +1,346 @@ +import { Injectable, Logger } from '@nestjs/common'; +import * as path from 'path'; +import { jsonToText } from '../../../collaboration/collaboration.util'; +import { InjectKysely } from 'nestjs-kysely'; +import { KyselyDB } from '@docmost/db/types/kysely.types'; +import { + extractZip, + FileImportSource, + FileTaskStatus, +} from '../utils/file.utils'; +import { StorageService } from '../../storage/storage.service'; +import * as tmp from 'tmp-promise'; +import { pipeline } from 'node:stream/promises'; +import { createWriteStream } from 'node:fs'; +import { ImportService } from './import.service'; +import { promises as fs } from 'fs'; +import { generateSlugId } from '../../../common/helpers'; +import { v7 } from 'uuid'; +import { generateJitteredKeyBetween } from 'fractional-indexing-jittered'; +import { FileTask, InsertablePage } from '@docmost/db/types/entity.types'; +import { markdownToHtml } from '@docmost/editor-ext'; +import { getProsemirrorContent } from '../../../common/helpers/prosemirror/utils'; +import { formatImportHtml } from '../utils/import-formatter'; +import { + buildAttachmentCandidates, + collectMarkdownAndHtmlFiles, +} from '../utils/import.utils'; +import { executeTx } from '@docmost/db/utils'; +import { BacklinkRepo } from '@docmost/db/repos/backlink/backlink.repo'; +import { ImportAttachmentService } from './import-attachment.service'; +import { ModuleRef } from '@nestjs/core'; +import { PageService } from '../../../core/page/services/page.service'; +import { ImportPageNode } from '../dto/file-task-dto'; + +@Injectable() +export class FileTaskService { + private readonly logger = new Logger(FileTaskService.name); + + constructor( + private readonly storageService: StorageService, + private readonly importService: ImportService, + private readonly pageService: PageService, + private readonly backlinkRepo: BacklinkRepo, + @InjectKysely() private readonly db: KyselyDB, + private readonly importAttachmentService: ImportAttachmentService, + private moduleRef: ModuleRef, + ) {} + + async processZIpImport(fileTaskId: string): Promise { + const fileTask = await this.db + .selectFrom('fileTasks') + .selectAll() + .where('id', '=', fileTaskId) + .executeTakeFirst(); + + if (!fileTask) { + this.logger.log(`Import file task with ID ${fileTaskId} not found`); + return; + } + + if (fileTask.status === FileTaskStatus.Failed) { + return; + } + + if (fileTask.status === FileTaskStatus.Success) { + this.logger.log('Imported task already processed.'); + return; + } + + const { path: tmpZipPath, cleanup: cleanupTmpFile } = await tmp.file({ + prefix: 'docmost-import', + postfix: '.zip', + discardDescriptor: true, + }); + + const { path: tmpExtractDir, cleanup: cleanupTmpDir } = await tmp.dir({ + prefix: 'docmost-extract-', + unsafeCleanup: true, + }); + + try { + const fileStream = await this.storageService.readStream( + fileTask.filePath, + ); + await pipeline(fileStream, createWriteStream(tmpZipPath)); + await extractZip(tmpZipPath, tmpExtractDir); + } catch (err) { + await cleanupTmpFile(); + await cleanupTmpDir(); + + throw err; + } + + try { + if ( + fileTask.source === FileImportSource.Generic || + fileTask.source === FileImportSource.Notion + ) { + await this.processGenericImport({ + extractDir: tmpExtractDir, + fileTask, + }); + } + + if (fileTask.source === FileImportSource.Confluence) { + let ConfluenceModule: any; + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + ConfluenceModule = require('./../../../ee/confluence-import/confluence-import.service'); + } catch (err) { + this.logger.error( + 'Confluence import requested but EE module not bundled in this build', + ); + return; + } + const confluenceImportService = this.moduleRef.get( + ConfluenceModule.ConfluenceImportService, + { strict: false }, + ); + + await confluenceImportService.processConfluenceImport({ + extractDir: tmpExtractDir, + fileTask, + }); + } + try { + await this.updateTaskStatus(fileTaskId, FileTaskStatus.Success, null); + await cleanupTmpFile(); + await cleanupTmpDir(); + // delete stored file on success + await this.storageService.delete(fileTask.filePath); + } catch (err) { + this.logger.error( + `Failed to delete import file from storage. Task ID: ${fileTaskId}`, + err, + ); + } + } catch (err) { + await cleanupTmpFile(); + await cleanupTmpDir(); + + throw err; + } + } + + async processGenericImport(opts: { + extractDir: string; + fileTask: FileTask; + }): Promise { + const { extractDir, fileTask } = opts; + const allFiles = await collectMarkdownAndHtmlFiles(extractDir); + const attachmentCandidates = await buildAttachmentCandidates(extractDir); + + const pagesMap = new Map(); + + for (const absPath of allFiles) { + const relPath = path + .relative(extractDir, absPath) + .split(path.sep) + .join('/'); // normalize to forward-slashes + const ext = path.extname(relPath).toLowerCase(); + let content = await fs.readFile(absPath, 'utf-8'); + + if (ext.toLowerCase() === '.md') { + content = await markdownToHtml(content); + } + + pagesMap.set(relPath, { + id: v7(), + slugId: generateSlugId(), + name: path.basename(relPath, ext), + content, + parentPageId: null, + fileExtension: ext, + filePath: relPath, + }); + } + + // parent/child linking + pagesMap.forEach((page, filePath) => { + const segments = filePath.split('/'); + segments.pop(); + let parentPage = null; + while (segments.length) { + const tryMd = segments.join('/') + '.md'; + const tryHtml = segments.join('/') + '.html'; + if (pagesMap.has(tryMd)) { + parentPage = pagesMap.get(tryMd)!; + break; + } + if (pagesMap.has(tryHtml)) { + parentPage = pagesMap.get(tryHtml)!; + break; + } + segments.pop(); + } + if (parentPage) page.parentPageId = parentPage.id; + }); + + // generate position keys + const siblingsMap = new Map(); + + pagesMap.forEach((page) => { + const group = siblingsMap.get(page.parentPageId) ?? []; + group.push(page); + siblingsMap.set(page.parentPageId, group); + }); + + // get root pages + const rootSibs = siblingsMap.get(null); + + if (rootSibs?.length) { + rootSibs.sort((a, b) => a.name.localeCompare(b.name)); + + // get first position key from the server + const nextPosition = await this.pageService.nextPagePosition( + fileTask.spaceId, + ); + + let prevPos: string | null = null; + rootSibs.forEach((page, idx) => { + if (idx === 0) { + page.position = nextPosition; + } else { + page.position = generateJitteredKeyBetween(prevPos, null); + } + prevPos = page.position; + }); + } + + // non-root buckets (children & deeper levels) + siblingsMap.forEach((sibs, parentId) => { + if (parentId === null) return; // root already done + + sibs.sort((a, b) => a.name.localeCompare(b.name)); + + let prevPos: string | null = null; + for (const page of sibs) { + page.position = generateJitteredKeyBetween(prevPos, null); + prevPos = page.position; + } + }); + + // internal page links + const filePathToPageMetaMap = new Map< + string, + { id: string; title: string; slugId: string } + >(); + pagesMap.forEach((page) => { + filePathToPageMetaMap.set(page.filePath, { + id: page.id, + title: page.name, + slugId: page.slugId, + }); + }); + + const pageResults = await Promise.all( + Array.from(pagesMap.values()).map(async (page) => { + const htmlContent = + await this.importAttachmentService.processAttachments({ + html: page.content, + pageRelativePath: page.filePath, + extractDir, + pageId: page.id, + fileTask, + attachmentCandidates, + }); + + const { html, backlinks } = await formatImportHtml({ + html: htmlContent, + currentFilePath: page.filePath, + filePathToPageMetaMap: filePathToPageMetaMap, + creatorId: fileTask.creatorId, + sourcePageId: page.id, + workspaceId: fileTask.workspaceId, + }); + + const pmState = getProsemirrorContent( + await this.importService.processHTML(html), + ); + + const { title, prosemirrorJson } = + this.importService.extractTitleAndRemoveHeading(pmState); + + const insertablePage: InsertablePage = { + id: page.id, + slugId: page.slugId, + title: title || page.name, + content: prosemirrorJson, + textContent: jsonToText(prosemirrorJson), + ydoc: await this.importService.createYdoc(prosemirrorJson), + position: page.position!, + spaceId: fileTask.spaceId, + workspaceId: fileTask.workspaceId, + creatorId: fileTask.creatorId, + lastUpdatedById: fileTask.creatorId, + parentPageId: page.parentPageId, + }; + + return { insertablePage, backlinks }; + }), + ); + + const insertablePages = pageResults.map((r) => r.insertablePage); + const insertableBacklinks = pageResults.flatMap((r) => r.backlinks); + + if (insertablePages.length < 1) return; + const validPageIds = new Set(insertablePages.map((row) => row.id)); + const filteredBacklinks = insertableBacklinks.filter( + ({ sourcePageId, targetPageId }) => + validPageIds.has(sourcePageId) && validPageIds.has(targetPageId), + ); + + await executeTx(this.db, async (trx) => { + await trx.insertInto('pages').values(insertablePages).execute(); + + if (filteredBacklinks.length > 0) { + await this.backlinkRepo.insertBacklink(filteredBacklinks, trx); + } + }); + } + + async getFileTask(fileTaskId: string) { + return this.db + .selectFrom('fileTasks') + .selectAll() + .where('id', '=', fileTaskId) + .executeTakeFirst(); + } + + async updateTaskStatus( + fileTaskId: string, + status: FileTaskStatus, + errorMessage?: string, + ) { + try { + await this.db + .updateTable('fileTasks') + .set({ status: status, errorMessage, updatedAt: new Date() }) + .where('id', '=', fileTaskId) + .execute(); + } catch (err) { + this.logger.error(err); + } + } +} diff --git a/apps/server/src/integrations/import/services/import-attachment.service.ts b/apps/server/src/integrations/import/services/import-attachment.service.ts new file mode 100644 index 00000000..cd9039e2 --- /dev/null +++ b/apps/server/src/integrations/import/services/import-attachment.service.ts @@ -0,0 +1,303 @@ +import { Injectable, Logger } from '@nestjs/common'; +import * as path from 'path'; +import { InjectKysely } from 'nestjs-kysely'; +import { KyselyDB } from '@docmost/db/types/kysely.types'; +import { cleanUrlString } from '../utils/file.utils'; +import { StorageService } from '../../storage/storage.service'; +import { createReadStream } from 'node:fs'; +import { promises as fs } from 'fs'; +import { getMimeType, sanitizeFileName } from '../../../common/helpers'; +import { v7 } from 'uuid'; +import { FileTask } from '@docmost/db/types/entity.types'; +import { getAttachmentFolderPath } from '../../../core/attachment/attachment.utils'; +import { AttachmentType } from '../../../core/attachment/attachment.constants'; +import { unwrapFromParagraph } from '../utils/import-formatter'; +import { resolveRelativeAttachmentPath } from '../utils/import.utils'; +import { load } from 'cheerio'; + +@Injectable() +export class ImportAttachmentService { + private readonly logger = new Logger(ImportAttachmentService.name); + + constructor( + private readonly storageService: StorageService, + @InjectKysely() private readonly db: KyselyDB, + ) {} + + async processAttachments(opts: { + html: string; + pageRelativePath: string; + extractDir: string; + pageId: string; + fileTask: FileTask; + attachmentCandidates: Map; + }): Promise { + const { + html, + pageRelativePath, + extractDir, + pageId, + fileTask, + attachmentCandidates, + } = opts; + + const attachmentTasks: Promise[] = []; + + /** + * Cache keyed by the *relative* path that appears in the HTML. + * Ensures we upload (and DB-insert) each attachment at most once, + * even if it’s referenced multiple times on the page. + */ + const processed = new Map< + string, + { + attachmentId: string; + storageFilePath: string; + apiFilePath: string; + fileNameWithExt: string; + abs: string; + } + >(); + + const uploadOnce = (relPath: string) => { + const abs = attachmentCandidates.get(relPath)!; + const attachmentId = v7(); + const ext = path.extname(abs); + + const fileNameWithExt = + sanitizeFileName(path.basename(abs, ext)) + ext.toLowerCase(); + + const storageFilePath = `${getAttachmentFolderPath( + AttachmentType.File, + fileTask.workspaceId, + )}/${attachmentId}/${fileNameWithExt}`; + + const apiFilePath = `/api/files/${attachmentId}/${fileNameWithExt}`; + + attachmentTasks.push( + (async () => { + const fileStream = createReadStream(abs); + await this.storageService.uploadStream(storageFilePath, fileStream); + const stat = await fs.stat(abs); + + await this.db + .insertInto('attachments') + .values({ + id: attachmentId, + filePath: storageFilePath, + fileName: fileNameWithExt, + fileSize: stat.size, + mimeType: getMimeType(fileNameWithExt), + type: 'file', + fileExt: ext, + creatorId: fileTask.creatorId, + workspaceId: fileTask.workspaceId, + pageId, + spaceId: fileTask.spaceId, + }) + .execute(); + })(), + ); + + return { + attachmentId, + storageFilePath, + apiFilePath, + fileNameWithExt, + abs, + }; + }; + + /** + * – Returns cached data if we’ve already processed this path. + * – Otherwise calls `uploadOnce`, stores the result, and returns it. + */ + const processFile = (relPath: string) => { + const cached = processed.get(relPath); + if (cached) return cached; + + const fresh = uploadOnce(relPath); + processed.set(relPath, fresh); + return fresh; + }; + + const pageDir = path.dirname(pageRelativePath); + const $ = load(html); + + // image + for (const imgEl of $('img').toArray()) { + const $img = $(imgEl); + const src = cleanUrlString($img.attr('src') ?? '')!; + if (!src || src.startsWith('http')) continue; + + const relPath = resolveRelativeAttachmentPath( + src, + pageDir, + attachmentCandidates, + ); + if (!relPath) continue; + + const { attachmentId, apiFilePath, abs } = processFile(relPath); + const stat = await fs.stat(abs); + + const width = $img.attr('width') ?? '100%'; + const align = $img.attr('data-align') ?? 'center'; + + $img + .attr('src', apiFilePath) + .attr('data-attachment-id', attachmentId) + .attr('data-size', stat.size.toString()) + .attr('width', width) + .attr('data-align', align); + + unwrapFromParagraph($, $img); + } + + // video + for (const vidEl of $('video').toArray()) { + const $vid = $(vidEl); + const src = cleanUrlString($vid.attr('src') ?? '')!; + if (!src || src.startsWith('http')) continue; + + const relPath = resolveRelativeAttachmentPath( + src, + pageDir, + attachmentCandidates, + ); + if (!relPath) continue; + + const { attachmentId, apiFilePath, abs } = processFile(relPath); + const stat = await fs.stat(abs); + + const width = $vid.attr('width') ?? '100%'; + const align = $vid.attr('data-align') ?? 'center'; + + $vid + .attr('src', apiFilePath) + .attr('data-attachment-id', attachmentId) + .attr('data-size', stat.size.toString()) + .attr('width', width) + .attr('data-align', align); + + unwrapFromParagraph($, $vid); + } + + //
+ for (const el of $('div[data-type="attachment"]').toArray()) { + const $oldDiv = $(el); + const rawUrl = cleanUrlString($oldDiv.attr('data-attachment-url') ?? '')!; + if (!rawUrl || rawUrl.startsWith('http')) continue; + + const relPath = resolveRelativeAttachmentPath( + rawUrl, + pageDir, + attachmentCandidates, + ); + if (!relPath) continue; + + const { attachmentId, apiFilePath, abs } = processFile(relPath); + const stat = await fs.stat(abs); + const fileName = path.basename(abs); + const mime = getMimeType(abs); + + const $newDiv = $('
') + .attr('data-type', 'attachment') + .attr('data-attachment-url', apiFilePath) + .attr('data-attachment-name', fileName) + .attr('data-attachment-mime', mime) + .attr('data-attachment-size', stat.size.toString()) + .attr('data-attachment-id', attachmentId); + + $oldDiv.replaceWith($newDiv); + unwrapFromParagraph($, $newDiv); + } + + // rewrite other attachments via + for (const aEl of $('a').toArray()) { + const $a = $(aEl); + const href = cleanUrlString($a.attr('href') ?? '')!; + if (!href || href.startsWith('http')) continue; + + const relPath = resolveRelativeAttachmentPath( + href, + pageDir, + attachmentCandidates, + ); + if (!relPath) continue; + + const { attachmentId, apiFilePath, abs } = processFile(relPath); + const stat = await fs.stat(abs); + const ext = path.extname(relPath).toLowerCase(); + + if (ext === '.mp4') { + const $video = $('
') + .attr('data-type', 'attachment') + .attr('data-attachment-url', apiFilePath) + .attr('data-attachment-name', attachmentName) + .attr('data-attachment-mime', getMimeType(abs)) + .attr('data-attachment-size', stat.size.toString()) + .attr('data-attachment-id', attachmentId); + + $a.replaceWith($div); + unwrapFromParagraph($, $div); + } + } + + // excalidraw and drawio + for (const type of ['excalidraw', 'drawio'] as const) { + for (const el of $(`div[data-type="${type}"]`).toArray()) { + const $oldDiv = $(el); + const rawSrc = cleanUrlString($oldDiv.attr('data-src') ?? '')!; + if (!rawSrc || rawSrc.startsWith('http')) continue; + + const relPath = resolveRelativeAttachmentPath( + rawSrc, + pageDir, + attachmentCandidates, + ); + if (!relPath) continue; + + const { attachmentId, apiFilePath, abs } = processFile(relPath); + const stat = await fs.stat(abs); + const fileName = path.basename(abs); + + const width = $oldDiv.attr('data-width') || '100%'; + const align = $oldDiv.attr('data-align') || 'center'; + + const $newDiv = $('
') + .attr('data-type', type) + .attr('data-src', apiFilePath) + .attr('data-title', fileName) + .attr('data-width', width) + .attr('data-size', stat.size.toString()) + .attr('data-align', align) + .attr('data-attachment-id', attachmentId); + + $oldDiv.replaceWith($newDiv); + unwrapFromParagraph($, $newDiv); + } + } + + // wait for all uploads & DB inserts + try { + await Promise.all(attachmentTasks); + } catch (err) { + this.logger.log('Import attachment upload error', err); + } + + return $.root().html() || ''; + } +} diff --git a/apps/server/src/integrations/import/import.service.ts b/apps/server/src/integrations/import/services/import.service.ts similarity index 61% rename from apps/server/src/integrations/import/import.service.ts rename to apps/server/src/integrations/import/services/import.service.ts index f77df0dc..a3da4918 100644 --- a/apps/server/src/integrations/import/import.service.ts +++ b/apps/server/src/integrations/import/services/import.service.ts @@ -4,16 +4,27 @@ import { MultipartFile } from '@fastify/multipart'; import { sanitize } from 'sanitize-filename-ts'; import * as path from 'path'; import { - htmlToJson, jsonToText, + htmlToJson, + jsonToText, tiptapExtensions, -} from '../../collaboration/collaboration.util'; +} from '../../../collaboration/collaboration.util'; import { InjectKysely } from 'nestjs-kysely'; import { KyselyDB } from '@docmost/db/types/kysely.types'; -import { generateSlugId } from '../../common/helpers'; +import { generateSlugId, sanitizeFileName } from '../../../common/helpers'; import { generateJitteredKeyBetween } from 'fractional-indexing-jittered'; import { TiptapTransformer } from '@hocuspocus/transformer'; import * as Y from 'yjs'; -import { markdownToHtml } from "@docmost/editor-ext"; +import { markdownToHtml } from '@docmost/editor-ext'; +import { + FileTaskStatus, + FileTaskType, + getFileTaskFolderPath, +} from '../utils/file.utils'; +import { v7 as uuid7 } from 'uuid'; +import { StorageService } from '../../storage/storage.service'; +import { InjectQueue } from '@nestjs/bullmq'; +import { Queue } from 'bullmq'; +import { QueueJob, QueueName } from '../../queue/constants'; @Injectable() export class ImportService { @@ -21,7 +32,10 @@ export class ImportService { constructor( private readonly pageRepo: PageRepo, + private readonly storageService: StorageService, @InjectKysely() private readonly db: KyselyDB, + @InjectQueue(QueueName.FILE_TASK_QUEUE) + private readonly fileTaskQueue: Queue, ) {} async importPage( @@ -113,7 +127,7 @@ export class ImportService { async createYdoc(prosemirrorJson: any): Promise { if (prosemirrorJson) { - this.logger.debug(`Converting prosemirror json state to ydoc`); + // this.logger.debug(`Converting prosemirror json state to ydoc`); const ydoc = TiptapTransformer.toYdoc( prosemirrorJson, @@ -129,20 +143,34 @@ export class ImportService { } extractTitleAndRemoveHeading(prosemirrorState: any) { - let title = null; + let title: string | null = null; + + const content = prosemirrorState.content ?? []; if ( - prosemirrorState?.content?.length > 0 && - prosemirrorState.content[0].type === 'heading' && - prosemirrorState.content[0].attrs?.level === 1 + content.length > 0 && + content[0].type === 'heading' && + content[0].attrs?.level === 1 ) { - title = prosemirrorState.content[0].content[0].text; - - // remove h1 header node from state - prosemirrorState.content.shift(); + title = content[0].content?.[0]?.text ?? null; + content.shift(); } - return { title, prosemirrorJson: prosemirrorState }; + // ensure at least one paragraph + if (content.length === 0) { + content.push({ + type: 'paragraph', + content: [], + }); + } + + return { + title, + prosemirrorJson: { + ...prosemirrorState, + content, + }, + }; } async getNewPagePosition(spaceId: string): Promise { @@ -161,4 +189,52 @@ export class ImportService { return generateJitteredKeyBetween(null, null); } } + + async importZip( + filePromise: Promise, + source: string, + userId: string, + spaceId: string, + workspaceId: string, + ) { + const file = await filePromise; + const fileBuffer = await file.toBuffer(); + const fileExtension = path.extname(file.filename).toLowerCase(); + const fileName = sanitizeFileName( + path.basename(file.filename, fileExtension), + ); + const fileSize = fileBuffer.length; + + const fileNameWithExt = fileName + fileExtension; + + const fileTaskId = uuid7(); + const filePath = `${getFileTaskFolderPath(FileTaskType.Import, workspaceId)}/${fileTaskId}/${fileNameWithExt}`; + + // upload file + await this.storageService.upload(filePath, fileBuffer); + + const fileTask = await this.db + .insertInto('fileTasks') + .values({ + id: fileTaskId, + type: FileTaskType.Import, + source: source, + status: FileTaskStatus.Processing, + fileName: fileNameWithExt, + filePath: filePath, + fileSize: fileSize, + fileExt: 'zip', + creatorId: userId, + spaceId: spaceId, + workspaceId: workspaceId, + }) + .returningAll() + .executeTakeFirst(); + + await this.fileTaskQueue.add(QueueJob.IMPORT_TASK, { + fileTaskId: fileTaskId, + }); + + return fileTask; + } } diff --git a/apps/server/src/integrations/import/utils/file.utils.ts b/apps/server/src/integrations/import/utils/file.utils.ts new file mode 100644 index 00000000..b3d39cda --- /dev/null +++ b/apps/server/src/integrations/import/utils/file.utils.ts @@ -0,0 +1,187 @@ +import * as yauzl from 'yauzl'; +import * as path from 'path'; +import * as fs from 'node:fs'; + +export enum FileTaskType { + Import = 'import', + Export = 'export', +} + +export enum FileImportSource { + Generic = 'generic', + Notion = 'notion', + Confluence = 'confluence', +} + +export enum FileTaskStatus { + Processing = 'processing', + Success = 'success', + Failed = 'failed', +} + +export function getFileTaskFolderPath( + type: FileTaskType, + workspaceId: string, +): string { + switch (type) { + case FileTaskType.Import: + return `${workspaceId}/imports`; + case FileTaskType.Export: + return `${workspaceId}/exports`; + } +} + +/** + * Extracts a ZIP archive. + */ +export async function extractZip( + source: string, + target: string, +): Promise { + return extractZipInternal(source, target, true); +} + +/** + * Internal helper to extract a ZIP, with optional single-nested-ZIP handling. + * @param source Path to the ZIP file + * @param target Directory to extract into + * @param allowNested Whether to check and unwrap one level of nested ZIP + */ +function extractZipInternal( + source: string, + target: string, + allowNested: boolean, +): Promise { + return new Promise((resolve, reject) => { + yauzl.open( + source, + { lazyEntries: true, decodeStrings: false, autoClose: true }, + (err, zipfile) => { + if (err) return reject(err); + + // Handle one level of nested ZIP if allowed + if (allowNested && zipfile.entryCount === 1) { + zipfile.readEntry(); + zipfile.once('entry', (entry) => { + const name = entry.fileName.toString('utf8').replace(/^\/+/, ''); + const isZip = + !/\/$/.test(entry.fileName) && + name.toLowerCase().endsWith('.zip'); + if (isZip) { + // temporary name to avoid overwriting file + const nestedPath = source.endsWith('.zip') + ? source.slice(0, -4) + '.inner.zip' + : source + '.inner.zip'; + + zipfile.openReadStream(entry, (openErr, rs) => { + if (openErr) return reject(openErr); + const ws = fs.createWriteStream(nestedPath); + rs.on('error', reject); + ws.on('error', reject); + ws.on('finish', () => { + zipfile.close(); + extractZipInternal(nestedPath, target, false) + .then(() => { + fs.unlinkSync(nestedPath); + resolve(); + }) + .catch(reject); + }); + rs.pipe(ws); + }); + } else { + zipfile.close(); + extractZipInternal(source, target, false).then(resolve, reject); + } + }); + zipfile.once('error', reject); + return; + } + + // Normal extraction + zipfile.readEntry(); + zipfile.on('entry', (entry) => { + const name = entry.fileName.toString('utf8'); + const safe = name.replace(/^\/+/, ''); + if (safe.startsWith('__MACOSX/')) { + zipfile.readEntry(); + return; + } + + const fullPath = path.join(target, safe); + + // Handle directories + if (/\/$/.test(name)) { + try { + fs.mkdirSync(fullPath, { recursive: true }); + } catch (mkdirErr: any) { + if (mkdirErr.code === 'ENAMETOOLONG') { + console.warn(`Skipping directory (path too long): ${fullPath}`); + zipfile.readEntry(); + return; + } + return reject(mkdirErr); + } + zipfile.readEntry(); + return; + } + + // Handle files + try { + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); + } catch (mkdirErr: any) { + if (mkdirErr.code === 'ENAMETOOLONG') { + console.warn( + `Skipping file directory creation (path too long): ${fullPath}`, + ); + zipfile.readEntry(); + return; + } + return reject(mkdirErr); + } + + zipfile.openReadStream(entry, (openErr, rs) => { + if (openErr) return reject(openErr); + + let ws: fs.WriteStream; + try { + ws = fs.createWriteStream(fullPath); + } catch (openWsErr: any) { + if (openWsErr.code === 'ENAMETOOLONG') { + console.warn( + `Skipping file write (path too long): ${fullPath}`, + ); + zipfile.readEntry(); + return; + } + return reject(openWsErr); + } + + rs.on('error', (err) => reject(err)); + ws.on('error', (err) => { + if ((err as any).code === 'ENAMETOOLONG') { + console.warn( + `Skipping file write on stream (path too long): ${fullPath}`, + ); + zipfile.readEntry(); + } else { + reject(err); + } + }); + ws.on('finish', () => zipfile.readEntry()); + rs.pipe(ws); + }); + }); + + zipfile.on('end', () => resolve()); + zipfile.on('error', (err) => reject(err)); + }, + ); + }); +} + +export function cleanUrlString(url: string): string { + if (!url) return null; + const [mainUrl] = url.split('?', 1); + return mainUrl; +} diff --git a/apps/server/src/integrations/import/utils/import-formatter.ts b/apps/server/src/integrations/import/utils/import-formatter.ts new file mode 100644 index 00000000..92291d39 --- /dev/null +++ b/apps/server/src/integrations/import/utils/import-formatter.ts @@ -0,0 +1,254 @@ +import { getEmbedUrlAndProvider } from '@docmost/editor-ext'; +import * as path from 'path'; +import { v7 } from 'uuid'; +import { InsertableBacklink } from '@docmost/db/types/entity.types'; +import { Cheerio, CheerioAPI, load } from 'cheerio'; + +export async function formatImportHtml(opts: { + html: string; + currentFilePath: string; + filePathToPageMetaMap: Map< + string, + { id: string; title: string; slugId: string } + >; + creatorId: string; + sourcePageId: string; + workspaceId: string; + pageDir?: string; + attachmentCandidates?: string[]; +}): Promise<{ html: string; backlinks: InsertableBacklink[] }> { + const { + html, + currentFilePath, + filePathToPageMetaMap, + creatorId, + sourcePageId, + workspaceId, + } = opts; + const $: CheerioAPI = load(html); + const $root: Cheerio = $.root(); + + notionFormatter($, $root); + defaultHtmlFormatter($, $root); + + const backlinks = await rewriteInternalLinksToMentionHtml( + $, + $root, + currentFilePath, + filePathToPageMetaMap, + creatorId, + sourcePageId, + workspaceId, + ); + + return { + html: $root.html() || '', + backlinks, + }; +} + +export function defaultHtmlFormatter($: CheerioAPI, $root: Cheerio) { + $root.find('a[href]').each((_, el) => { + const $el = $(el); + const url = $el.attr('href')!; + const { provider } = getEmbedUrlAndProvider(url); + if (provider === 'iframe') return; + + const embed = `
`; + $el.replaceWith(embed); + }); + + $root.find('iframe[src]').each((_, el) => { + const $el = $(el); + const url = $el.attr('src')!; + const { provider } = getEmbedUrlAndProvider(url); + + const embed = `
`; + $el.replaceWith(embed); + }); +} + +export function notionFormatter($: CheerioAPI, $root: Cheerio) { + // remove empty description paragraphs + $root.find('p.page-description').each((_, el) => { + if (!$(el).text().trim()) $(el).remove(); + }); + + // block math → mathBlock + $root.find('figure.equation').each((_: any, fig: any) => { + const $fig = $(fig); + const tex = $fig + .find('annotation[encoding="application/x-tex"]') + .text() + .trim(); + const $math = $('
') + .attr('data-type', 'mathBlock') + .attr('data-katex', 'true') + .text(tex); + $fig.replaceWith($math); + }); + + // inline math → mathInline + $root.find('span.notion-text-equation-token').each((_, tok) => { + const $tok = $(tok); + const $prev = $tok.prev('style'); + if ($prev.length) $prev.remove(); + const tex = $tok + .find('annotation[encoding="application/x-tex"]') + .text() + .trim(); + const $inline = $('') + .attr('data-type', 'mathInline') + .attr('data-katex', 'true') + .text(tex); + $tok.replaceWith($inline); + }); + + // callouts + $root + .find('figure.callout') + .get() + .reverse() + .forEach((fig) => { + const $fig = $(fig); + const $content = $fig.find('div').eq(1); + if (!$content.length) return; + const $wrapper = $('
') + .attr('data-type', 'callout') + .attr('data-callout-type', 'info'); + // @ts-ignore + $content.children().each((_, child) => $wrapper.append(child)); + $fig.replaceWith($wrapper); + }); + + // to-do lists + $root.find('ul.to-do-list').each((_, list) => { + const $old = $(list); + const $new = $('