mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 22:53:08 +08:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b3d50558db | |||
| 605bf760be | |||
| c5c9237b00 | |||
| 511c822a90 | |||
| 7af31fe228 | |||
| dd460713ce | |||
| b0f3bec4d1 | |||
| 8ea92668b2 | |||
| dcf9228344 | |||
| dbd1308c72 | |||
| 03ae58253a | |||
| b99d803b81 | |||
| b9b5ddb848 | |||
| dd8c42e1f3 | |||
| 13c29545a2 | |||
| 0c5c83a17a | |||
| 5bda5623f2 | |||
| 051bc80ab7 | |||
| 78d363febb | |||
| 8681d9a8c4 | |||
| b9543b01bd | |||
| 5510434221 | |||
| f671e7a3b9 | |||
| 974bcea690 | |||
| 601ed88931 | |||
| cfbaedcd63 | |||
| 5fc04aa7df | |||
| c357f169e1 | |||
| 1cbd2854bb | |||
| 3af1482a31 | |||
| d31d1f7bbd | |||
| cc0146d0cd | |||
| 83ce9cf240 | |||
| e7e85e9fdd | |||
| 2d710612b1 | |||
| a0814ef49a | |||
| bf17289ab2 | |||
| c2cd412ac7 | |||
| 71dfcf6bce | |||
| 23e8ab032e | |||
| 0e1d4e5eee | |||
| 6f83f32d5c | |||
| 63ea2f7663 | |||
| 66a3dad632 | |||
| 2adc6a60d2 |
+10
-10
@@ -42,15 +42,15 @@
|
|||||||
"mermaid": "^11.12.2",
|
"mermaid": "^11.12.2",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"posthog-js": "^1.255.1",
|
"posthog-js": "^1.255.1",
|
||||||
"react": "^18.3.1",
|
"react": "^19.2.3",
|
||||||
"react-arborist": "3.4.0",
|
"react-arborist": "3.4.0",
|
||||||
"react-clear-modal": "^2.0.17",
|
"react-clear-modal": "^2.0.17",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^19.2.3",
|
||||||
"react-drawio": "^1.0.7",
|
"react-drawio": "^1.0.7",
|
||||||
"react-error-boundary": "^4.1.2",
|
"react-error-boundary": "^6.1.0",
|
||||||
"react-helmet-async": "^2.0.5",
|
"react-helmet-async": "^2.0.5",
|
||||||
"react-i18next": "^15.0.1",
|
"react-i18next": "^15.0.1",
|
||||||
"react-router-dom": "^7.12.0",
|
"react-router-dom": "^7.13.0",
|
||||||
"semver": "^7.7.3",
|
"semver": "^7.7.3",
|
||||||
"socket.io-client": "^4.8.3",
|
"socket.io-client": "^4.8.3",
|
||||||
"tiptap-extension-global-drag-handle": "^0.1.18",
|
"tiptap-extension-global-drag-handle": "^0.1.18",
|
||||||
@@ -63,13 +63,13 @@
|
|||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/katex": "^0.16.7",
|
"@types/katex": "^0.16.7",
|
||||||
"@types/node": "22.19.1",
|
"@types/node": "22.19.1",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^19.2.9",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.1.2",
|
||||||
"eslint": "^9.15.0",
|
"eslint": "^9.15.0",
|
||||||
"eslint-plugin-react": "^7.37.2",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^5.1.0",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.16",
|
"eslint-plugin-react-refresh": "^0.4.26",
|
||||||
"globals": "^15.13.0",
|
"globals": "^15.13.0",
|
||||||
"optics-ts": "^2.4.1",
|
"optics-ts": "^2.4.1",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Wählen Sie Ihre bevorzugte Benutzersprache.",
|
"Choose your preferred interface language.": "Wählen Sie Ihre bevorzugte Benutzersprache.",
|
||||||
"Choose your preferred page width.": "Wählen Sie Ihre bevorzugte Seitenbreite.",
|
"Choose your preferred page width.": "Wählen Sie Ihre bevorzugte Seitenbreite.",
|
||||||
"Confirm": "Bestätigen",
|
"Confirm": "Bestätigen",
|
||||||
"Copy as Markdown": "Als Markdown kopieren",
|
|
||||||
"Copy link": "Link kopieren",
|
"Copy link": "Link kopieren",
|
||||||
"Create": "Erstellen",
|
"Create": "Erstellen",
|
||||||
"Create group": "Gruppe erstellen",
|
"Create group": "Gruppe erstellen",
|
||||||
@@ -235,9 +234,7 @@
|
|||||||
"Anyone with this link can join this workspace.": "Jeder mit diesem Link kann dem Arbeitsbereich beitreten.",
|
"Anyone with this link can join this workspace.": "Jeder mit diesem Link kann dem Arbeitsbereich beitreten.",
|
||||||
"Invite link": "Einladungslink",
|
"Invite link": "Einladungslink",
|
||||||
"Copy": "Kopieren",
|
"Copy": "Kopieren",
|
||||||
"Copy to space": "In Raum kopieren",
|
|
||||||
"Copied": "Kopiert",
|
"Copied": "Kopiert",
|
||||||
"Duplicate": "Duplizieren",
|
|
||||||
"Select a user": "Benutzer auswählen",
|
"Select a user": "Benutzer auswählen",
|
||||||
"Select a group": "Gruppe auswählen",
|
"Select a group": "Gruppe auswählen",
|
||||||
"Export all pages and attachments in this space.": "Alle Seiten und Anhänge in diesem Bereich exportieren.",
|
"Export all pages and attachments in this space.": "Alle Seiten und Anhänge in diesem Bereich exportieren.",
|
||||||
@@ -254,7 +251,6 @@
|
|||||||
"Export failed:": "Export fehlgeschlagen:",
|
"Export failed:": "Export fehlgeschlagen:",
|
||||||
"export error": "Exportfehler",
|
"export error": "Exportfehler",
|
||||||
"Export page": "Seite exportieren",
|
"Export page": "Seite exportieren",
|
||||||
"Export successful": "Export erfolgreich",
|
|
||||||
"Export space": "Bereich exportieren",
|
"Export space": "Bereich exportieren",
|
||||||
"Export {{type}}": "Exportiere {{type}}",
|
"Export {{type}}": "Exportiere {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "Datei überschreitet das Anhängelimit von {{limit}}",
|
"File exceeds the {{limit}} attachment limit": "Datei überschreitet das Anhängelimit von {{limit}}",
|
||||||
@@ -330,8 +326,6 @@
|
|||||||
"Upload any image from your device.": "Laden Sie ein beliebiges Bild von Ihrem Gerät hoch.",
|
"Upload any image from your device.": "Laden Sie ein beliebiges Bild von Ihrem Gerät hoch.",
|
||||||
"Upload any video from your device.": "Laden Sie ein beliebiges Video von Ihrem Gerät hoch.",
|
"Upload any video from your device.": "Laden Sie ein beliebiges Video von Ihrem Gerät hoch.",
|
||||||
"Upload any file from your device.": "Laden Sie eine beliebige Datei von Ihrem Gerät hoch.",
|
"Upload any file from your device.": "Laden Sie eine beliebige Datei von Ihrem Gerät hoch.",
|
||||||
"Uploading {{name}}": "Lade {{name}} hoch",
|
|
||||||
"Uploading file": "Datei wird hochgeladen",
|
|
||||||
"Table": "Tabelle",
|
"Table": "Tabelle",
|
||||||
"Insert a table.": "Tabelle einfügen.",
|
"Insert a table.": "Tabelle einfügen.",
|
||||||
"Insert collapsible block.": "Einklappbaren Block einfügen.",
|
"Insert collapsible block.": "Einklappbaren Block einfügen.",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Choose your preferred interface language.",
|
"Choose your preferred interface language.": "Choose your preferred interface language.",
|
||||||
"Choose your preferred page width.": "Choose your preferred page width.",
|
"Choose your preferred page width.": "Choose your preferred page width.",
|
||||||
"Confirm": "Confirm",
|
"Confirm": "Confirm",
|
||||||
"Copy as Markdown": "Copy as Markdown",
|
|
||||||
"Copy link": "Copy link",
|
"Copy link": "Copy link",
|
||||||
"Create": "Create",
|
"Create": "Create",
|
||||||
"Create group": "Create group",
|
"Create group": "Create group",
|
||||||
@@ -254,7 +253,6 @@
|
|||||||
"Export failed:": "Export failed:",
|
"Export failed:": "Export failed:",
|
||||||
"export error": "export error",
|
"export error": "export error",
|
||||||
"Export page": "Export page",
|
"Export page": "Export page",
|
||||||
"Export successful": "Export successful",
|
|
||||||
"Export space": "Export space",
|
"Export space": "Export space",
|
||||||
"Export {{type}}": "Export {{type}}",
|
"Export {{type}}": "Export {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "File exceeds the {{limit}} attachment limit",
|
"File exceeds the {{limit}} attachment limit": "File exceeds the {{limit}} attachment limit",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Elige tu idioma de interfaz preferido.",
|
"Choose your preferred interface language.": "Elige tu idioma de interfaz preferido.",
|
||||||
"Choose your preferred page width.": "Elige el ancho de página que prefieras.",
|
"Choose your preferred page width.": "Elige el ancho de página que prefieras.",
|
||||||
"Confirm": "Confirmar",
|
"Confirm": "Confirmar",
|
||||||
"Copy as Markdown": "Copiar como Markdown",
|
|
||||||
"Copy link": "Copiar enlace",
|
"Copy link": "Copiar enlace",
|
||||||
"Create": "Crear",
|
"Create": "Crear",
|
||||||
"Create group": "Crear grupo",
|
"Create group": "Crear grupo",
|
||||||
@@ -254,7 +253,6 @@
|
|||||||
"Export failed:": "Exportación fallida:",
|
"Export failed:": "Exportación fallida:",
|
||||||
"export error": "error de exportación",
|
"export error": "error de exportación",
|
||||||
"Export page": "Exportar página",
|
"Export page": "Exportar página",
|
||||||
"Export successful": "Exportación exitosa",
|
|
||||||
"Export space": "Exportar espacio",
|
"Export space": "Exportar espacio",
|
||||||
"Export {{type}}": "Exportar {{type}}",
|
"Export {{type}}": "Exportar {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "El archivo supera el límite de {{limit}} adjuntos",
|
"File exceeds the {{limit}} attachment limit": "El archivo supera el límite de {{limit}} adjuntos",
|
||||||
@@ -330,8 +328,6 @@
|
|||||||
"Upload any image from your device.": "Sube cualquier imagen desde tu dispositivo.",
|
"Upload any image from your device.": "Sube cualquier imagen desde tu dispositivo.",
|
||||||
"Upload any video from your device.": "Sube cualquier video desde tu dispositivo.",
|
"Upload any video from your device.": "Sube cualquier video desde tu dispositivo.",
|
||||||
"Upload any file from your device.": "Sube cualquier archivo desde tu dispositivo.",
|
"Upload any file from your device.": "Sube cualquier archivo desde tu dispositivo.",
|
||||||
"Uploading {{name}}": "Subiendo {{name}}",
|
|
||||||
"Uploading file": "Subiendo archivo",
|
|
||||||
"Table": "Tabla",
|
"Table": "Tabla",
|
||||||
"Insert a table.": "Insertar una tabla.",
|
"Insert a table.": "Insertar una tabla.",
|
||||||
"Insert collapsible block.": "Insertar bloque desplegable.",
|
"Insert collapsible block.": "Insertar bloque desplegable.",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Choisissez votre langue d'interface préférée.",
|
"Choose your preferred interface language.": "Choisissez votre langue d'interface préférée.",
|
||||||
"Choose your preferred page width.": "Choisissez votre largeur de page préférée.",
|
"Choose your preferred page width.": "Choisissez votre largeur de page préférée.",
|
||||||
"Confirm": "Confirmer",
|
"Confirm": "Confirmer",
|
||||||
"Copy as Markdown": "Copier comme Markdown",
|
|
||||||
"Copy link": "Copier le lien",
|
"Copy link": "Copier le lien",
|
||||||
"Create": "Créer",
|
"Create": "Créer",
|
||||||
"Create group": "Créer groupe",
|
"Create group": "Créer groupe",
|
||||||
@@ -254,7 +253,6 @@
|
|||||||
"Export failed:": "Échec de l'exportation :",
|
"Export failed:": "Échec de l'exportation :",
|
||||||
"export error": "exporter l'erreur",
|
"export error": "exporter l'erreur",
|
||||||
"Export page": "Exporter la page",
|
"Export page": "Exporter la page",
|
||||||
"Export successful": "Exportation réussie",
|
|
||||||
"Export space": "Exporter l'espace",
|
"Export space": "Exporter l'espace",
|
||||||
"Export {{type}}": "Exporter {{type}}",
|
"Export {{type}}": "Exporter {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "Le fichier dépasse la limite de {{limit}} pièces jointes",
|
"File exceeds the {{limit}} attachment limit": "Le fichier dépasse la limite de {{limit}} pièces jointes",
|
||||||
@@ -330,8 +328,6 @@
|
|||||||
"Upload any image from your device.": "Téléchargez n'importe quelle image depuis votre appareil.",
|
"Upload any image from your device.": "Téléchargez n'importe quelle image depuis votre appareil.",
|
||||||
"Upload any video from your device.": "Téléchargez n'importe quelle vidéo depuis votre appareil.",
|
"Upload any video from your device.": "Téléchargez n'importe quelle vidéo depuis votre appareil.",
|
||||||
"Upload any file from your device.": "Téléchargez n'importe quel fichier depuis votre appareil.",
|
"Upload any file from your device.": "Téléchargez n'importe quel fichier depuis votre appareil.",
|
||||||
"Uploading {{name}}": "Téléchargement de {{name}}",
|
|
||||||
"Uploading file": "Téléchargement du fichier",
|
|
||||||
"Table": "Tableau",
|
"Table": "Tableau",
|
||||||
"Insert a table.": "Insérez un tableau.",
|
"Insert a table.": "Insérez un tableau.",
|
||||||
"Insert collapsible block.": "Insérer un bloc repliable.",
|
"Insert collapsible block.": "Insérer un bloc repliable.",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Scegli la lingua da utilizzare per l'interfaccia.",
|
"Choose your preferred interface language.": "Scegli la lingua da utilizzare per l'interfaccia.",
|
||||||
"Choose your preferred page width.": "Scegli la larghezza della pagina che preferisci.",
|
"Choose your preferred page width.": "Scegli la larghezza della pagina che preferisci.",
|
||||||
"Confirm": "Conferma",
|
"Confirm": "Conferma",
|
||||||
"Copy as Markdown": "Copia come Markdown",
|
|
||||||
"Copy link": "Copia link",
|
"Copy link": "Copia link",
|
||||||
"Create": "Crea",
|
"Create": "Crea",
|
||||||
"Create group": "Crea gruppo",
|
"Create group": "Crea gruppo",
|
||||||
@@ -254,7 +253,6 @@
|
|||||||
"Export failed:": "Esportazione fallita:",
|
"Export failed:": "Esportazione fallita:",
|
||||||
"export error": "errore di esportazione",
|
"export error": "errore di esportazione",
|
||||||
"Export page": "Esporta pagina",
|
"Export page": "Esporta pagina",
|
||||||
"Export successful": "Esportazione riuscita",
|
|
||||||
"Export space": "Esporta spazio",
|
"Export space": "Esporta spazio",
|
||||||
"Export {{type}}": "Esporta {{type}}",
|
"Export {{type}}": "Esporta {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "Il file supera il limite per gli allegati di {{limit}}",
|
"File exceeds the {{limit}} attachment limit": "Il file supera il limite per gli allegati di {{limit}}",
|
||||||
@@ -330,8 +328,6 @@
|
|||||||
"Upload any image from your device.": "Carica un'immagine dal tuo dispositivo.",
|
"Upload any image from your device.": "Carica un'immagine dal tuo dispositivo.",
|
||||||
"Upload any video from your device.": "Carica qualsiasi video dal tuo dispositivo.",
|
"Upload any video from your device.": "Carica qualsiasi video dal tuo dispositivo.",
|
||||||
"Upload any file from your device.": "Carica qualsiasi file dal tuo dispositivo.",
|
"Upload any file from your device.": "Carica qualsiasi file dal tuo dispositivo.",
|
||||||
"Uploading {{name}}": "Caricamento di {{name}}",
|
|
||||||
"Uploading file": "Caricamento file",
|
|
||||||
"Table": "Tabella",
|
"Table": "Tabella",
|
||||||
"Insert a table.": "Inserisci una tabella.",
|
"Insert a table.": "Inserisci una tabella.",
|
||||||
"Insert collapsible block.": "Inserisci blocco comprimibile.",
|
"Insert collapsible block.": "Inserisci blocco comprimibile.",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "お好みの言語を選択してください",
|
"Choose your preferred interface language.": "お好みの言語を選択してください",
|
||||||
"Choose your preferred page width.": "お好みのページ幅を選択してください",
|
"Choose your preferred page width.": "お好みのページ幅を選択してください",
|
||||||
"Confirm": "確認",
|
"Confirm": "確認",
|
||||||
"Copy as Markdown": "Markdownとしてコピー",
|
|
||||||
"Copy link": "リンクをコピー",
|
"Copy link": "リンクをコピー",
|
||||||
"Create": "新規作成",
|
"Create": "新規作成",
|
||||||
"Create group": "グループを作成",
|
"Create group": "グループを作成",
|
||||||
@@ -254,7 +253,6 @@
|
|||||||
"Export failed:": "エクスポートに失敗しました:",
|
"Export failed:": "エクスポートに失敗しました:",
|
||||||
"export error": "エクスポートエラー",
|
"export error": "エクスポートエラー",
|
||||||
"Export page": "エクスポートページ",
|
"Export page": "エクスポートページ",
|
||||||
"Export successful": "エクスポート成功",
|
|
||||||
"Export space": "エクスポートスペース",
|
"Export space": "エクスポートスペース",
|
||||||
"Export {{type}}": "{{type}}をエクスポート",
|
"Export {{type}}": "{{type}}をエクスポート",
|
||||||
"File exceeds the {{limit}} attachment limit": "ファイルが{{limit}}の添付制限を超えています",
|
"File exceeds the {{limit}} attachment limit": "ファイルが{{limit}}の添付制限を超えています",
|
||||||
@@ -330,8 +328,6 @@
|
|||||||
"Upload any image from your device.": "デバイスから画像をアップロードします",
|
"Upload any image from your device.": "デバイスから画像をアップロードします",
|
||||||
"Upload any video from your device.": "デバイスから動画をアップロードします",
|
"Upload any video from your device.": "デバイスから動画をアップロードします",
|
||||||
"Upload any file from your device.": "デバイスからファイルをアップロードします",
|
"Upload any file from your device.": "デバイスからファイルをアップロードします",
|
||||||
"Uploading {{name}}": "{{name}} をアップロード中",
|
|
||||||
"Uploading file": "ファイルをアップロード中",
|
|
||||||
"Table": "テーブル",
|
"Table": "テーブル",
|
||||||
"Insert a table.": "テーブルを挿入します",
|
"Insert a table.": "テーブルを挿入します",
|
||||||
"Insert collapsible block.": "折りたたみブロックを挿入します",
|
"Insert collapsible block.": "折りたたみブロックを挿入します",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "선호하는 인터페이스 언어를 선택하세요.",
|
"Choose your preferred interface language.": "선호하는 인터페이스 언어를 선택하세요.",
|
||||||
"Choose your preferred page width.": "선호하는 페이지 너비를 선택하세요.",
|
"Choose your preferred page width.": "선호하는 페이지 너비를 선택하세요.",
|
||||||
"Confirm": "확인",
|
"Confirm": "확인",
|
||||||
"Copy as Markdown": "Markdown으로 복사",
|
|
||||||
"Copy link": "링크 복사",
|
"Copy link": "링크 복사",
|
||||||
"Create": "생성",
|
"Create": "생성",
|
||||||
"Create group": "팀 생성",
|
"Create group": "팀 생성",
|
||||||
@@ -254,7 +253,6 @@
|
|||||||
"Export failed:": "내보내기 실패:",
|
"Export failed:": "내보내기 실패:",
|
||||||
"export error": "내보내기 오류",
|
"export error": "내보내기 오류",
|
||||||
"Export page": "페이지 내보내기",
|
"Export page": "페이지 내보내기",
|
||||||
"Export successful": "내보내기 성공",
|
|
||||||
"Export space": "Space 내보내기",
|
"Export space": "Space 내보내기",
|
||||||
"Export {{type}}": "{{type}} 내보내기",
|
"Export {{type}}": "{{type}} 내보내기",
|
||||||
"File exceeds the {{limit}} attachment limit": "첨부 파일 크기 제한 {{limit}}을 초과했습니다",
|
"File exceeds the {{limit}} attachment limit": "첨부 파일 크기 제한 {{limit}}을 초과했습니다",
|
||||||
@@ -330,8 +328,6 @@
|
|||||||
"Upload any image from your device.": "기기에서 이미지를 업로드하세요.",
|
"Upload any image from your device.": "기기에서 이미지를 업로드하세요.",
|
||||||
"Upload any video from your device.": "기기에서 비디오를 업로드하세요.",
|
"Upload any video from your device.": "기기에서 비디오를 업로드하세요.",
|
||||||
"Upload any file from your device.": "기기에서 파일을 업로드하세요.",
|
"Upload any file from your device.": "기기에서 파일을 업로드하세요.",
|
||||||
"Uploading {{name}}": "{{name}} 업로드 중",
|
|
||||||
"Uploading file": "파일 업로드 중",
|
|
||||||
"Table": "테이블",
|
"Table": "테이블",
|
||||||
"Insert a table.": "테이블 삽입.",
|
"Insert a table.": "테이블 삽입.",
|
||||||
"Insert collapsible block.": "접을 수 있는 블록 삽입.",
|
"Insert collapsible block.": "접을 수 있는 블록 삽입.",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Kies uw gewenste interfacetaal.",
|
"Choose your preferred interface language.": "Kies uw gewenste interfacetaal.",
|
||||||
"Choose your preferred page width.": "Kies uw gewenste paginabreedte.",
|
"Choose your preferred page width.": "Kies uw gewenste paginabreedte.",
|
||||||
"Confirm": "Bevestig",
|
"Confirm": "Bevestig",
|
||||||
"Copy as Markdown": "Kopiëren als Markdown",
|
|
||||||
"Copy link": "Link kopiëren",
|
"Copy link": "Link kopiëren",
|
||||||
"Create": "Aanmaken",
|
"Create": "Aanmaken",
|
||||||
"Create group": "Groep aanmaken",
|
"Create group": "Groep aanmaken",
|
||||||
@@ -254,7 +253,6 @@
|
|||||||
"Export failed:": "Exporteren mislukt:",
|
"Export failed:": "Exporteren mislukt:",
|
||||||
"export error": "Exporteer fout",
|
"export error": "Exporteer fout",
|
||||||
"Export page": "Exporteer pagina",
|
"Export page": "Exporteer pagina",
|
||||||
"Export successful": "Export succesvol",
|
|
||||||
"Export space": "Exporteer ruimte",
|
"Export space": "Exporteer ruimte",
|
||||||
"Export {{type}}": "Exporteer {{type}}",
|
"Export {{type}}": "Exporteer {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "Bestand overschrijdt de bijlagelimiet van {{limit}}",
|
"File exceeds the {{limit}} attachment limit": "Bestand overschrijdt de bijlagelimiet van {{limit}}",
|
||||||
@@ -330,8 +328,6 @@
|
|||||||
"Upload any image from your device.": "Upload een afbeelding vanaf uw apparaat.",
|
"Upload any image from your device.": "Upload een afbeelding vanaf uw apparaat.",
|
||||||
"Upload any video from your device.": "Upload een video vanaf uw apparaat.",
|
"Upload any video from your device.": "Upload een video vanaf uw apparaat.",
|
||||||
"Upload any file from your device.": "Upload een bestand vanaf uw apparaat.",
|
"Upload any file from your device.": "Upload een bestand vanaf uw apparaat.",
|
||||||
"Uploading {{name}}": "Uploaden {{name}}",
|
|
||||||
"Uploading file": "Bestand uploaden",
|
|
||||||
"Table": "Tabel",
|
"Table": "Tabel",
|
||||||
"Insert a table.": "Voeg een tabel in.",
|
"Insert a table.": "Voeg een tabel in.",
|
||||||
"Insert collapsible block.": "Inklapbaar blok invoegen.",
|
"Insert collapsible block.": "Inklapbaar blok invoegen.",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Escolha o idioma da interface.",
|
"Choose your preferred interface language.": "Escolha o idioma da interface.",
|
||||||
"Choose your preferred page width.": "Escolha a largura preferida da página.",
|
"Choose your preferred page width.": "Escolha a largura preferida da página.",
|
||||||
"Confirm": "Confirmar",
|
"Confirm": "Confirmar",
|
||||||
"Copy as Markdown": "Copiar como Markdown",
|
|
||||||
"Copy link": "Copiar link",
|
"Copy link": "Copiar link",
|
||||||
"Create": "Criar",
|
"Create": "Criar",
|
||||||
"Create group": "Criar grupo",
|
"Create group": "Criar grupo",
|
||||||
@@ -254,7 +253,6 @@
|
|||||||
"Export failed:": "Falha ao exportar:",
|
"Export failed:": "Falha ao exportar:",
|
||||||
"export error": "erro de exportação",
|
"export error": "erro de exportação",
|
||||||
"Export page": "Exportar página",
|
"Export page": "Exportar página",
|
||||||
"Export successful": "Exportação bem-sucedida",
|
|
||||||
"Export space": "Exportar espaço",
|
"Export space": "Exportar espaço",
|
||||||
"Export {{type}}": "Exportar para {{type}}",
|
"Export {{type}}": "Exportar para {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "O arquivo excede o limite de anexos {{limit}}",
|
"File exceeds the {{limit}} attachment limit": "O arquivo excede o limite de anexos {{limit}}",
|
||||||
@@ -330,8 +328,6 @@
|
|||||||
"Upload any image from your device.": "Envie qualquer imagem do seu dispositivo.",
|
"Upload any image from your device.": "Envie qualquer imagem do seu dispositivo.",
|
||||||
"Upload any video from your device.": "Envie qualquer vídeo do seu dispositivo.",
|
"Upload any video from your device.": "Envie qualquer vídeo do seu dispositivo.",
|
||||||
"Upload any file from your device.": "Envie qualquer arquivo do seu dispositivo.",
|
"Upload any file from your device.": "Envie qualquer arquivo do seu dispositivo.",
|
||||||
"Uploading {{name}}": "Enviando {{name}}",
|
|
||||||
"Uploading file": "Enviando arquivo",
|
|
||||||
"Table": "Tabela",
|
"Table": "Tabela",
|
||||||
"Insert a table.": "Insira uma tabela.",
|
"Insert a table.": "Insira uma tabela.",
|
||||||
"Insert collapsible block.": "Insira um bloco colapsável.",
|
"Insert collapsible block.": "Insira um bloco colapsável.",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Выберите предпочитаемый язык интерфейса.",
|
"Choose your preferred interface language.": "Выберите предпочитаемый язык интерфейса.",
|
||||||
"Choose your preferred page width.": "Выберите предпочитаемую ширину страницы.",
|
"Choose your preferred page width.": "Выберите предпочитаемую ширину страницы.",
|
||||||
"Confirm": "Подтвердить",
|
"Confirm": "Подтвердить",
|
||||||
"Copy as Markdown": "Копировать как Markdown",
|
|
||||||
"Copy link": "Копировать ссылку",
|
"Copy link": "Копировать ссылку",
|
||||||
"Create": "Создать",
|
"Create": "Создать",
|
||||||
"Create group": "Создать группу",
|
"Create group": "Создать группу",
|
||||||
@@ -254,7 +253,6 @@
|
|||||||
"Export failed:": "Экспортирование не удалось:",
|
"Export failed:": "Экспортирование не удалось:",
|
||||||
"export error": "ошибка экспорта",
|
"export error": "ошибка экспорта",
|
||||||
"Export page": "Экспорт страницы",
|
"Export page": "Экспорт страницы",
|
||||||
"Export successful": "Экспорт выполнен успешно",
|
|
||||||
"Export space": "Экспорт пространства",
|
"Export space": "Экспорт пространства",
|
||||||
"Export {{type}}": "Экспорт {{type}}",
|
"Export {{type}}": "Экспорт {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "Файл превышает лимит вложений {{limit}}",
|
"File exceeds the {{limit}} attachment limit": "Файл превышает лимит вложений {{limit}}",
|
||||||
@@ -330,8 +328,6 @@
|
|||||||
"Upload any image from your device.": "Загрузить любое изображение с вашего устройства.",
|
"Upload any image from your device.": "Загрузить любое изображение с вашего устройства.",
|
||||||
"Upload any video from your device.": "Загрузить любое видео с вашего устройства.",
|
"Upload any video from your device.": "Загрузить любое видео с вашего устройства.",
|
||||||
"Upload any file from your device.": "Загрузить любой файл с вашего устройства.",
|
"Upload any file from your device.": "Загрузить любой файл с вашего устройства.",
|
||||||
"Uploading {{name}}": "Загрузка {{name}}",
|
|
||||||
"Uploading file": "Загрузка файла",
|
|
||||||
"Table": "Таблица",
|
"Table": "Таблица",
|
||||||
"Insert a table.": "Вставить таблицу.",
|
"Insert a table.": "Вставить таблицу.",
|
||||||
"Insert collapsible block.": "Вставить сворачиваемый блок.",
|
"Insert collapsible block.": "Вставить сворачиваемый блок.",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "Оберіть бажану мову інтерфейсу.",
|
"Choose your preferred interface language.": "Оберіть бажану мову інтерфейсу.",
|
||||||
"Choose your preferred page width.": "Оберіть бажану ширину сторінки.",
|
"Choose your preferred page width.": "Оберіть бажану ширину сторінки.",
|
||||||
"Confirm": "Підтвердити",
|
"Confirm": "Підтвердити",
|
||||||
"Copy as Markdown": "Скопіювати як Markdown",
|
|
||||||
"Copy link": "Копіювати посилання",
|
"Copy link": "Копіювати посилання",
|
||||||
"Create": "Створити",
|
"Create": "Створити",
|
||||||
"Create group": "Створити групу",
|
"Create group": "Створити групу",
|
||||||
@@ -254,7 +253,6 @@
|
|||||||
"Export failed:": "Експортування не вдалося:",
|
"Export failed:": "Експортування не вдалося:",
|
||||||
"export error": "помилка експорту",
|
"export error": "помилка експорту",
|
||||||
"Export page": "Експорт сторінки",
|
"Export page": "Експорт сторінки",
|
||||||
"Export successful": "Експорт виконано успішно",
|
|
||||||
"Export space": "Експорт простору",
|
"Export space": "Експорт простору",
|
||||||
"Export {{type}}": "Експорт {{type}}",
|
"Export {{type}}": "Експорт {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "Файл перевищує ліміт вкладень {{limit}}",
|
"File exceeds the {{limit}} attachment limit": "Файл перевищує ліміт вкладень {{limit}}",
|
||||||
@@ -330,8 +328,6 @@
|
|||||||
"Upload any image from your device.": "Завантажити будь-яке зображення з вашого пристрою.",
|
"Upload any image from your device.": "Завантажити будь-яке зображення з вашого пристрою.",
|
||||||
"Upload any video from your device.": "Завантажити будь-яке відео з вашого пристрою.",
|
"Upload any video from your device.": "Завантажити будь-яке відео з вашого пристрою.",
|
||||||
"Upload any file from your device.": "Завантажити будь-який файл з вашого пристрою.",
|
"Upload any file from your device.": "Завантажити будь-який файл з вашого пристрою.",
|
||||||
"Uploading {{name}}": "Завантаження {{name}}",
|
|
||||||
"Uploading file": "Завантаження файлу",
|
|
||||||
"Table": "Таблиця",
|
"Table": "Таблиця",
|
||||||
"Insert a table.": "Вставити таблицю.",
|
"Insert a table.": "Вставити таблицю.",
|
||||||
"Insert collapsible block.": "Вставити блок, що згортається.",
|
"Insert collapsible block.": "Вставити блок, що згортається.",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"Choose your preferred interface language.": "选择您喜欢的界面语言。",
|
"Choose your preferred interface language.": "选择您喜欢的界面语言。",
|
||||||
"Choose your preferred page width.": "选择您喜欢的页面宽度。",
|
"Choose your preferred page width.": "选择您喜欢的页面宽度。",
|
||||||
"Confirm": "确认",
|
"Confirm": "确认",
|
||||||
"Copy as Markdown": "复制为Markdown",
|
|
||||||
"Copy link": "复制链接",
|
"Copy link": "复制链接",
|
||||||
"Create": "创建",
|
"Create": "创建",
|
||||||
"Create group": "创建群组",
|
"Create group": "创建群组",
|
||||||
@@ -254,7 +253,6 @@
|
|||||||
"Export failed:": "导出失败:",
|
"Export failed:": "导出失败:",
|
||||||
"export error": "导出出错",
|
"export error": "导出出错",
|
||||||
"Export page": "导出页面",
|
"Export page": "导出页面",
|
||||||
"Export successful": "导出成功",
|
|
||||||
"Export space": "导出空间",
|
"Export space": "导出空间",
|
||||||
"Export {{type}}": "导出为 {{type}}",
|
"Export {{type}}": "导出为 {{type}}",
|
||||||
"File exceeds the {{limit}} attachment limit": "文件超出了 {{limit}} 类型附件限制",
|
"File exceeds the {{limit}} attachment limit": "文件超出了 {{limit}} 类型附件限制",
|
||||||
@@ -330,8 +328,6 @@
|
|||||||
"Upload any image from your device.": "从设备上传任何图像",
|
"Upload any image from your device.": "从设备上传任何图像",
|
||||||
"Upload any video from your device.": "从设备上传任何视频",
|
"Upload any video from your device.": "从设备上传任何视频",
|
||||||
"Upload any file from your device.": "从设备上传任何文件",
|
"Upload any file from your device.": "从设备上传任何文件",
|
||||||
"Uploading {{name}}": "正在上传{{name}}",
|
|
||||||
"Uploading file": "正在上传文件",
|
|
||||||
"Table": "表格",
|
"Table": "表格",
|
||||||
"Insert a table.": "插入一个表格",
|
"Insert a table.": "插入一个表格",
|
||||||
"Insert collapsible block.": "插入一个折叠块",
|
"Insert collapsible block.": "插入一个折叠块",
|
||||||
|
|||||||
@@ -30,11 +30,9 @@ export default function ExportModal({
|
|||||||
const [format, setFormat] = useState<ExportFormat>(ExportFormat.Markdown);
|
const [format, setFormat] = useState<ExportFormat>(ExportFormat.Markdown);
|
||||||
const [includeChildren, setIncludeChildren] = useState<boolean>(false);
|
const [includeChildren, setIncludeChildren] = useState<boolean>(false);
|
||||||
const [includeAttachments, setIncludeAttachments] = useState<boolean>(false);
|
const [includeAttachments, setIncludeAttachments] = useState<boolean>(false);
|
||||||
const [isExporting, setIsExporting] = useState<boolean>(false);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleExport = async () => {
|
const handleExport = async () => {
|
||||||
setIsExporting(true);
|
|
||||||
try {
|
try {
|
||||||
if (type === "page") {
|
if (type === "page") {
|
||||||
await exportPage({
|
await exportPage({
|
||||||
@@ -47,9 +45,6 @@ export default function ExportModal({
|
|||||||
if (type === "space") {
|
if (type === "space") {
|
||||||
await exportSpace({ spaceId: id, format, includeAttachments });
|
await exportSpace({ spaceId: id, format, includeAttachments });
|
||||||
}
|
}
|
||||||
notifications.show({
|
|
||||||
message: t("Export successful"),
|
|
||||||
});
|
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
@@ -57,8 +52,6 @@ export default function ExportModal({
|
|||||||
color: "red",
|
color: "red",
|
||||||
});
|
});
|
||||||
console.error("export error", err);
|
console.error("export error", err);
|
||||||
} finally {
|
|
||||||
setIsExporting(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,7 +136,7 @@ export default function ExportModal({
|
|||||||
<Button onClick={onClose} variant="default">
|
<Button onClick={onClose} variant="default">
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleExport} loading={isExporting}>{t("Export")}</Button>
|
<Button onClick={handleExport}>{t("Export")}</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
|||||||
|
|
||||||
const enterHandler = () => {
|
const enterHandler = () => {
|
||||||
if (!renderItems.length) return;
|
if (!renderItems.length) return;
|
||||||
if (renderItems[selectedIndex]?.entityType !== "header") {
|
if (renderItems[selectedIndex].entityType !== "header") {
|
||||||
selectItem(selectedIndex);
|
selectItem(selectedIndex);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ const mentionRenderItems = () => {
|
|||||||
{
|
{
|
||||||
placement: "bottom-start",
|
placement: "bottom-start",
|
||||||
middleware: [offset(0), flip(), shift()],
|
middleware: [offset(0), flip(), shift()],
|
||||||
},
|
}
|
||||||
).then(({ x, y }) => {
|
).then(({ x, y }) => {
|
||||||
Object.assign(element.style, {
|
Object.assign(element.style, {
|
||||||
left: `${x}px`,
|
left: `${x}px`,
|
||||||
@@ -86,7 +86,7 @@ const mentionRenderItems = () => {
|
|||||||
zIndex: "9999",
|
zIndex: "9999",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onUpdate: (props: {
|
onUpdate: (props: {
|
||||||
@@ -115,30 +115,23 @@ const mentionRenderItems = () => {
|
|||||||
|
|
||||||
// destroy component if space is greater 3 without a match
|
// destroy component if space is greater 3 without a match
|
||||||
if (
|
if (
|
||||||
whitespaceCount > 4 &&
|
whitespaceCount > 3 &&
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
props.editor.storage.mentionItems.length === 1
|
props.editor.storage.mentionItems.length === 0
|
||||||
) {
|
) {
|
||||||
destroy();
|
destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// fallback exit
|
|
||||||
if (whitespaceCount > 7) {
|
|
||||||
destroy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onKeyDown: (props: { event: KeyboardEvent }) => {
|
onKeyDown: (props: { event: KeyboardEvent }) => {
|
||||||
if (props.event.key === "Escape") {
|
if (props.event.key)
|
||||||
destroy();
|
if (
|
||||||
return true;
|
props.event.key === "Escape" ||
|
||||||
}
|
(props.event.key === "Enter" && !component)
|
||||||
|
) {
|
||||||
if (props.event.key === "Enter" && !component) {
|
destroy();
|
||||||
destroy();
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return (component?.ref as any)?.onKeyDown(props);
|
return (component?.ref as any)?.onKeyDown(props);
|
||||||
},
|
},
|
||||||
onExit: () => {
|
onExit: () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
||||||
import { posToDOMRect, findParentNode } from "@tiptap/react";
|
import { posToDOMRect, findParentNode } from "@tiptap/react";
|
||||||
import { Node as PMNode } from "@tiptap/pm/model";
|
import { Node as PMNode } from "@tiptap/pm/model";
|
||||||
import React, { useCallback } from "react";
|
import React, { JSX, useCallback } from "react";
|
||||||
import { ActionIcon, Tooltip } from "@mantine/core";
|
import { ActionIcon, Tooltip } from "@mantine/core";
|
||||||
import { IconTrash } from "@tabler/icons-react";
|
import { IconTrash } from "@tabler/icons-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, { JSX, useCallback } from "react";
|
||||||
import {
|
import {
|
||||||
EditorMenuProps,
|
EditorMenuProps,
|
||||||
ShouldShowProps,
|
ShouldShowProps,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { posToDOMRect, findParentNode } from "@tiptap/react";
|
import { posToDOMRect, findParentNode } from "@tiptap/react";
|
||||||
import { Node as PMNode } from "@tiptap/pm/model";
|
import { Node as PMNode } from "@tiptap/pm/model";
|
||||||
import React, { useCallback } from "react";
|
import React, { JSX, useCallback } from "react";
|
||||||
import {
|
import {
|
||||||
EditorMenuProps,
|
EditorMenuProps,
|
||||||
ShouldShowProps,
|
ShouldShowProps,
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export const mainExtensions = [
|
|||||||
Escape: () => {
|
Escape: () => {
|
||||||
const event = new CustomEvent("closeFindDialogFromEditor", {});
|
const event = new CustomEvent("closeFindDialogFromEditor", {});
|
||||||
document.dispatchEvent(event);
|
document.dispatchEvent(event);
|
||||||
return false;
|
return true;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import "@/features/editor/styles/index.css";
|
import '@/features/editor/styles/index.css';
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from 'react';
|
||||||
import { EditorContent, useEditor } from "@tiptap/react";
|
import { EditorContent, useEditor } from '@tiptap/react';
|
||||||
import { mainExtensions } from "@/features/editor/extensions/extensions";
|
import { mainExtensions } from '@/features/editor/extensions/extensions';
|
||||||
import { Title } from "@mantine/core";
|
import { Title } from '@mantine/core';
|
||||||
import classes from "./history.module.css";
|
|
||||||
|
|
||||||
export interface HistoryEditorProps {
|
export interface HistoryEditorProps {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -27,9 +26,7 @@ export function HistoryEditor({ title, content }: HistoryEditorProps) {
|
|||||||
<div>
|
<div>
|
||||||
<Title order={1}>{title}</Title>
|
<Title order={1}>{title}</Title>
|
||||||
|
|
||||||
{editor && (
|
{editor && <EditorContent editor={editor} />}
|
||||||
<EditorContent editor={editor} className={classes.historyEditor} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,49 +1,37 @@
|
|||||||
.history {
|
.history {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: var(--mantine-spacing-md);
|
padding: var(--mantine-spacing-md);
|
||||||
color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0));
|
color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0));
|
||||||
|
|
||||||
@mixin hover {
|
@mixin hover {
|
||||||
background-color: light-dark(
|
background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-8));
|
||||||
var(--mantine-color-gray-2),
|
}
|
||||||
var(--mantine-color-dark-8)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.historyEditor {
|
|
||||||
:global(.ProseMirror) {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.active {
|
.active {
|
||||||
background-color: light-dark(
|
background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-8));
|
||||||
var(--mantine-color-gray-2),
|
|
||||||
var(--mantine-color-dark-8)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
max-height: rem(700px);
|
max-height: rem(700px);
|
||||||
width: rem(250px);
|
width: rem(250px);
|
||||||
padding: var(--mantine-spacing-sm);
|
padding: var(--mantine-spacing-sm);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-right: rem(1px) solid
|
border-right: rem(1px) solid
|
||||||
light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarFlex {
|
.sidebarFlex {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarMain {
|
.sidebarMain {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarRightSection {
|
.sidebarRightSection {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: rem(16px) rem(40px);
|
padding: rem(16px) rem(40px);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
IconHistory,
|
IconHistory,
|
||||||
IconLink,
|
IconLink,
|
||||||
IconList,
|
IconList,
|
||||||
IconMarkdown,
|
|
||||||
IconMessage,
|
IconMessage,
|
||||||
IconPrinter,
|
IconPrinter,
|
||||||
IconTrash,
|
IconTrash,
|
||||||
@@ -29,7 +28,6 @@ import { useDeletePageModal } from "@/features/page/hooks/use-delete-page-modal.
|
|||||||
import { PageWidthToggle } from "@/features/user/components/page-width-pref.tsx";
|
import { PageWidthToggle } from "@/features/user/components/page-width-pref.tsx";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import ExportModal from "@/components/common/export-modal";
|
import ExportModal from "@/components/common/export-modal";
|
||||||
import { htmlToMarkdown } from "@docmost/editor-ext";
|
|
||||||
import {
|
import {
|
||||||
pageEditorAtom,
|
pageEditorAtom,
|
||||||
yjsConnectionStatusAtom,
|
yjsConnectionStatusAtom,
|
||||||
@@ -62,7 +60,6 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
|
|||||||
const event = new CustomEvent("closeFindDialogFromEditor", {});
|
const event = new CustomEvent("closeFindDialogFromEditor", {});
|
||||||
document.dispatchEvent(event);
|
document.dispatchEvent(event);
|
||||||
},
|
},
|
||||||
{ preventDefault: false },
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
@@ -131,15 +128,6 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
|
|||||||
notifications.show({ message: t("Link copied") });
|
notifications.show({ message: t("Link copied") });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyAsMarkdown = () => {
|
|
||||||
if (!pageEditor) return;
|
|
||||||
const html = pageEditor.getHTML();
|
|
||||||
const markdown = htmlToMarkdown(html);
|
|
||||||
const title = page?.title ? `# ${page.title}\n\n` : "";
|
|
||||||
clipboard.copy(`${title}${markdown}`);
|
|
||||||
notifications.show({ message: t("Copied") });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePrint = () => {
|
const handlePrint = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.print();
|
window.print();
|
||||||
@@ -177,13 +165,6 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
|
|||||||
>
|
>
|
||||||
{t("Copy link")}
|
{t("Copy link")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
<Menu.Item
|
|
||||||
leftSection={<IconMarkdown size={16} />}
|
|
||||||
onClick={handleCopyAsMarkdown}
|
|
||||||
>
|
|
||||||
{t("Copy as Markdown")}
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
|
|
||||||
<Menu.Item leftSection={<IconArrowsHorizontal size={16} />}>
|
<Menu.Item leftSection={<IconArrowsHorizontal size={16} />}>
|
||||||
@@ -299,9 +280,7 @@ function ConnectionWarning() {
|
|||||||
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isDisconnected = ["disconnected", "connecting"].includes(
|
const isDisconnected = ["disconnected", "connecting"].includes(yjsConnectionStatus);
|
||||||
yjsConnectionStatus,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isDisconnected) {
|
if (isDisconnected) {
|
||||||
if (!timeoutRef.current) {
|
if (!timeoutRef.current) {
|
||||||
|
|||||||
@@ -172,10 +172,6 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) {
|
|||||||
queryKey: ["root-sidebar-pages", fileTask.spaceId],
|
queryKey: ["root-sidebar-pages", fileTask.spaceId],
|
||||||
});
|
});
|
||||||
|
|
||||||
await queryClient.invalidateQueries({
|
|
||||||
queryKey: ["recent-changes", fileTask.spaceId],
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
emit({
|
emit({
|
||||||
operation: "refetchRootTreeNodeEvent",
|
operation: "refetchRootTreeNodeEvent",
|
||||||
|
|||||||
@@ -163,6 +163,9 @@ export function useDeletePageMutation() {
|
|||||||
export function useMovePageMutation() {
|
export function useMovePageMutation() {
|
||||||
return useMutation<void, Error, IMovePage>({
|
return useMutation<void, Error, IMovePage>({
|
||||||
mutationFn: (data) => movePage(data),
|
mutationFn: (data) => movePage(data),
|
||||||
|
onSuccess: () => {
|
||||||
|
invalidateOnMovePage();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,127 +458,17 @@ export function invalidateOnUpdatePage(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateCacheOnMovePage(
|
export function invalidateOnMovePage() {
|
||||||
spaceId: string,
|
//for move invalidate all sidebars for now (how to do???)
|
||||||
pageId: string,
|
//invalidate all root sidebar pages
|
||||||
oldParentId: string | null,
|
queryClient.invalidateQueries({
|
||||||
newParentId: string | null,
|
queryKey: ["root-sidebar-pages"],
|
||||||
pageData: Partial<IPage>,
|
});
|
||||||
) {
|
//invalidate all sub sidebar pages
|
||||||
// Remove page from old parent's cache
|
queryClient.invalidateQueries({
|
||||||
const oldQueryKey =
|
queryKey: ["sidebar-pages"],
|
||||||
oldParentId === null
|
});
|
||||||
? ["root-sidebar-pages", spaceId]
|
// ---
|
||||||
: ["sidebar-pages", { pageId: oldParentId, spaceId }];
|
|
||||||
|
|
||||||
queryClient.setQueryData<InfiniteData<IPagination<IPage>>>(
|
|
||||||
oldQueryKey,
|
|
||||||
(old) => {
|
|
||||||
if (!old) return old;
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
pages: old.pages.map((page) => ({
|
|
||||||
...page,
|
|
||||||
items: page.items.filter((item) => item.id !== pageId),
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update old parent's hasChildren flag if it has no more children
|
|
||||||
if (oldParentId !== null) {
|
|
||||||
const oldParentCache = queryClient.getQueryData<
|
|
||||||
InfiniteData<IPagination<IPage>>
|
|
||||||
>(["sidebar-pages", { pageId: oldParentId, spaceId }]);
|
|
||||||
|
|
||||||
const remainingChildren =
|
|
||||||
oldParentCache?.pages.flatMap((p) => p.items).length ?? 0;
|
|
||||||
|
|
||||||
if (remainingChildren === 0) {
|
|
||||||
// Update hasChildren in all caches where old parent appears
|
|
||||||
const allSideBarMatches = queryClient.getQueriesData({
|
|
||||||
predicate: (query) =>
|
|
||||||
query.queryKey[0] === "root-sidebar-pages" ||
|
|
||||||
query.queryKey[0] === "sidebar-pages",
|
|
||||||
});
|
|
||||||
|
|
||||||
allSideBarMatches.forEach(([key]) => {
|
|
||||||
queryClient.setQueryData<InfiniteData<IPagination<IPage>>>(
|
|
||||||
key,
|
|
||||||
(old) => {
|
|
||||||
if (!old) return old;
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
pages: old.pages.map((page) => ({
|
|
||||||
...page,
|
|
||||||
items: page.items.map((item) =>
|
|
||||||
item.id === oldParentId
|
|
||||||
? { ...item, hasChildren: false }
|
|
||||||
: item,
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add page to new parent's cache
|
|
||||||
const newQueryKey =
|
|
||||||
newParentId === null
|
|
||||||
? ["root-sidebar-pages", spaceId]
|
|
||||||
: ["sidebar-pages", { pageId: newParentId, spaceId }];
|
|
||||||
|
|
||||||
queryClient.setQueryData<InfiniteData<IPagination<Partial<IPage>>>>(
|
|
||||||
newQueryKey,
|
|
||||||
(old) => {
|
|
||||||
if (!old) return old;
|
|
||||||
|
|
||||||
// Check if page already exists in new location
|
|
||||||
const exists = old.pages.some((page) =>
|
|
||||||
page.items.some((item) => item.id === pageId),
|
|
||||||
);
|
|
||||||
if (exists) return old;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
pages: old.pages.map((page, index) => {
|
|
||||||
if (index === old.pages.length - 1) {
|
|
||||||
return {
|
|
||||||
...page,
|
|
||||||
items: [...page.items, pageData],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return page;
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update new parent's hasChildren flag
|
|
||||||
if (newParentId !== null) {
|
|
||||||
const allSideBarMatches = queryClient.getQueriesData({
|
|
||||||
predicate: (query) =>
|
|
||||||
query.queryKey[0] === "root-sidebar-pages" ||
|
|
||||||
query.queryKey[0] === "sidebar-pages",
|
|
||||||
});
|
|
||||||
|
|
||||||
allSideBarMatches.forEach(([key]) => {
|
|
||||||
queryClient.setQueryData<InfiniteData<IPagination<IPage>>>(key, (old) => {
|
|
||||||
if (!old) return old;
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
pages: old.pages.map((page) => ({
|
|
||||||
...page,
|
|
||||||
items: page.items.map((item) =>
|
|
||||||
item.id === newParentId ? { ...item, hasChildren: true } : item,
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invalidateOnDeletePage(pageId: string) {
|
export function invalidateOnDeletePage(pageId: string) {
|
||||||
|
|||||||
@@ -118,14 +118,7 @@ export async function exportPage(data: IExportPageParams): Promise<void> {
|
|||||||
.split("filename=")[1]
|
.split("filename=")[1]
|
||||||
.replace(/"/g, "");
|
.replace(/"/g, "");
|
||||||
|
|
||||||
let decodedFileName = fileName;
|
saveAs(req.data, decodeURIComponent(fileName));
|
||||||
try {
|
|
||||||
decodedFileName = decodeURIComponent(fileName);
|
|
||||||
} catch (err) {
|
|
||||||
// fallback to raw filename
|
|
||||||
}
|
|
||||||
|
|
||||||
saveAs(req.data, decodedFileName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function importPage(file: File, spaceId: string) {
|
export async function importPage(file: File, spaceId: string) {
|
||||||
|
|||||||
@@ -94,9 +94,9 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) {
|
|||||||
spaceId,
|
spaceId,
|
||||||
});
|
});
|
||||||
const [, setTreeApi] = useAtom<TreeApi<SpaceTreeNode>>(treeApiAtom);
|
const [, setTreeApi] = useAtom<TreeApi<SpaceTreeNode>>(treeApiAtom);
|
||||||
const treeApiRef = useRef<TreeApi<SpaceTreeNode>>();
|
const treeApiRef = useRef<TreeApi<SpaceTreeNode>>(null);
|
||||||
const [openTreeNodes, setOpenTreeNodes] = useAtom<OpenMap>(openTreeNodesAtom);
|
const [openTreeNodes, setOpenTreeNodes] = useAtom<OpenMap>(openTreeNodesAtom);
|
||||||
const rootElement = useRef<HTMLDivElement>();
|
const rootElement = useRef<HTMLDivElement>(null);
|
||||||
const [isRootReady, setIsRootReady] = useState(false);
|
const [isRootReady, setIsRootReady] = useState(false);
|
||||||
const { ref: sizeRef, width, height } = useElementSize();
|
const { ref: sizeRef, width, height } = useElementSize();
|
||||||
const mergedRef = useMergedRef((element) => {
|
const mergedRef = useMergedRef((element) => {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import {
|
|||||||
useRemovePageMutation,
|
useRemovePageMutation,
|
||||||
useMovePageMutation,
|
useMovePageMutation,
|
||||||
useUpdatePageMutation,
|
useUpdatePageMutation,
|
||||||
updateCacheOnMovePage,
|
|
||||||
} from "@/features/page/queries/page-query.ts";
|
} from "@/features/page/queries/page-query.ts";
|
||||||
import { generateJitteredKeyBetween } from "fractional-indexing-jittered";
|
import { generateJitteredKeyBetween } from "fractional-indexing-jittered";
|
||||||
import { SpaceTreeNode } from "@/features/page/tree/types.ts";
|
import { SpaceTreeNode } from "@/features/page/tree/types.ts";
|
||||||
@@ -176,25 +175,9 @@ export function useTreeMutation<T>(spaceId: string) {
|
|||||||
parentPageId: args.parentId,
|
parentPageId: args.parentId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const draggedNode = args.dragNodes[0];
|
|
||||||
const nodeData = draggedNode.data as SpaceTreeNode;
|
|
||||||
const oldParentId = nodeData.parentPageId ?? null;
|
|
||||||
const pageData = {
|
|
||||||
id: nodeData.id,
|
|
||||||
slugId: nodeData.slugId,
|
|
||||||
title: nodeData.name,
|
|
||||||
icon: nodeData.icon,
|
|
||||||
position: newPosition,
|
|
||||||
spaceId: nodeData.spaceId,
|
|
||||||
parentPageId: args.parentId,
|
|
||||||
hasChildren: nodeData.hasChildren,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await movePageMutation.mutateAsync(payload);
|
await movePageMutation.mutateAsync(payload);
|
||||||
|
|
||||||
updateCacheOnMovePage(spaceId, draggedNodeId, oldParentId, args.parentId, pageData);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
emit({
|
emit({
|
||||||
operation: "moveTreeNode",
|
operation: "moveTreeNode",
|
||||||
@@ -202,10 +185,8 @@ export function useTreeMutation<T>(spaceId: string) {
|
|||||||
payload: {
|
payload: {
|
||||||
id: draggedNodeId,
|
id: draggedNodeId,
|
||||||
parentId: args.parentId,
|
parentId: args.parentId,
|
||||||
oldParentId,
|
|
||||||
index: args.index,
|
index: args.index,
|
||||||
position: newPosition,
|
position: newPosition,
|
||||||
pageData,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default function SharedTree({ sharedPageTree }: SharedTree) {
|
|||||||
const [tree, setTree] = useState<
|
const [tree, setTree] = useState<
|
||||||
TreeApi<SharedPageTreeNode> | null | undefined
|
TreeApi<SharedPageTreeNode> | null | undefined
|
||||||
>(null);
|
>(null);
|
||||||
const rootElement = useRef<HTMLDivElement>();
|
const rootElement = useRef<HTMLDivElement>(null);
|
||||||
const { ref: sizeRef, width, height } = useElementSize();
|
const { ref: sizeRef, width, height } = useElementSize();
|
||||||
const mergedRef = useMergedRef(rootElement, sizeRef);
|
const mergedRef = useMergedRef(rootElement, sizeRef);
|
||||||
const { pageSlug } = useParams();
|
const { pageSlug } = useParams();
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { ISpace } from "../types/space.types";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import APP_ROUTE from "@/lib/app-route";
|
import APP_ROUTE from "@/lib/app-route";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
interface DeleteSpaceModalProps {
|
interface DeleteSpaceModalProps {
|
||||||
space: ISpace;
|
space: ISpace;
|
||||||
@@ -15,7 +14,6 @@ interface DeleteSpaceModalProps {
|
|||||||
export default function DeleteSpaceModal({ space }: DeleteSpaceModalProps) {
|
export default function DeleteSpaceModal({ space }: DeleteSpaceModalProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
|
||||||
const deleteSpaceMutation = useDeleteSpaceMutation();
|
const deleteSpaceMutation = useDeleteSpaceMutation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -37,15 +35,12 @@ export default function DeleteSpaceModal({ space }: DeleteSpaceModalProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsDeleting(true);
|
|
||||||
try {
|
try {
|
||||||
// pass slug too so we can clear the local cache
|
// pass slug too so we can clear the local cache
|
||||||
await deleteSpaceMutation.mutateAsync({ id: space.id, slug: space.slug });
|
await deleteSpaceMutation.mutateAsync({ id: space.id, slug: space.slug });
|
||||||
navigate(APP_ROUTE.HOME);
|
navigate(APP_ROUTE.HOME);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to delete space", error);
|
console.error("Failed to delete space", error);
|
||||||
} finally {
|
|
||||||
setIsDeleting(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,7 +79,7 @@ export default function DeleteSpaceModal({ space }: DeleteSpaceModalProps) {
|
|||||||
<Button onClick={close} variant="default">
|
<Button onClick={close} variant="default">
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleDelete} color="red" loading={isDeleting}>
|
<Button onClick={handleDelete} color="red">
|
||||||
{t("Confirm")}
|
{t("Confirm")}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { SpaceSelect } from "./space-select";
|
import { SpaceSelect } from "./space-select";
|
||||||
import { getSpaceUrl } from "@/lib/config";
|
import { getSpaceUrl } from "@/lib/config";
|
||||||
import { Button, Popover, Text } from "@mantine/core";
|
import { Button, Popover, Text } from "@mantine/core";
|
||||||
import { IconChevronDown, IconChevronUp } from "@tabler/icons-react";
|
import { IconChevronDown } from "@tabler/icons-react";
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
|
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
|
||||||
@@ -21,7 +21,7 @@ export function SwitchSpace({
|
|||||||
spaceIcon,
|
spaceIcon,
|
||||||
}: SwitchSpaceProps) {
|
}: SwitchSpaceProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [opened, { close, toggle }] = useDisclosure(false);
|
const [opened, { close, open, toggle }] = useDisclosure(false);
|
||||||
|
|
||||||
const handleSelect = (value: string) => {
|
const handleSelect = (value: string) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
@@ -44,9 +44,9 @@ export function SwitchSpace({
|
|||||||
variant="subtle"
|
variant="subtle"
|
||||||
fullWidth
|
fullWidth
|
||||||
justify="space-between"
|
justify="space-between"
|
||||||
rightSection={opened ? <IconChevronUp size={18} /> : <IconChevronDown size={18} />}
|
rightSection={<IconChevronDown size={18} />}
|
||||||
color="gray"
|
color="gray"
|
||||||
onClick={toggle}
|
onClick={open}
|
||||||
>
|
>
|
||||||
<CustomAvatar
|
<CustomAvatar
|
||||||
name={spaceName}
|
name={spaceName}
|
||||||
|
|||||||
@@ -69,12 +69,5 @@ export async function exportSpace(data: IExportSpaceParams): Promise<void> {
|
|||||||
.split("filename=")[1]
|
.split("filename=")[1]
|
||||||
.replace(/"/g, "");
|
.replace(/"/g, "");
|
||||||
|
|
||||||
let decodedFileName = fileName;
|
saveAs(req.data, decodeURIComponent(fileName));
|
||||||
try {
|
|
||||||
decodedFileName = decodeURIComponent(fileName);
|
|
||||||
} catch (err) {
|
|
||||||
// fallback to raw filename
|
|
||||||
}
|
|
||||||
|
|
||||||
saveAs(req.data, decodedFileName);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,10 +45,8 @@ export type MoveTreeNodeEvent = {
|
|||||||
payload: {
|
payload: {
|
||||||
id: string;
|
id: string;
|
||||||
parentId: string;
|
parentId: string;
|
||||||
oldParentId: string | null;
|
|
||||||
index: number;
|
index: number;
|
||||||
position: string;
|
position: string;
|
||||||
pageData: Partial<IPage>;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { IPagination } from "@/lib/types";
|
|||||||
import {
|
import {
|
||||||
invalidateOnCreatePage,
|
invalidateOnCreatePage,
|
||||||
invalidateOnDeletePage,
|
invalidateOnDeletePage,
|
||||||
updateCacheOnMovePage,
|
invalidateOnMovePage,
|
||||||
invalidateOnUpdatePage,
|
invalidateOnUpdatePage,
|
||||||
} from "../page/queries/page-query";
|
} from "../page/queries/page-query";
|
||||||
import { RQ_KEY } from "../comment/queries/comment-query";
|
import { RQ_KEY } from "../comment/queries/comment-query";
|
||||||
@@ -41,13 +41,7 @@ export const useQuerySubscription = () => {
|
|||||||
invalidateOnCreatePage(data.payload.data);
|
invalidateOnCreatePage(data.payload.data);
|
||||||
break;
|
break;
|
||||||
case "moveTreeNode":
|
case "moveTreeNode":
|
||||||
updateCacheOnMovePage(
|
invalidateOnMovePage();
|
||||||
data.spaceId,
|
|
||||||
data.payload.id,
|
|
||||||
data.payload.oldParentId,
|
|
||||||
data.payload.parentId,
|
|
||||||
data.payload.pageData,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case "deleteTreeNode":
|
case "deleteTreeNode":
|
||||||
invalidateOnDeletePage(data.payload.node.id);
|
invalidateOnDeletePage(data.payload.node.id);
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ import { generateHTML, generateJSON } from '../common/helpers/prosemirror/html';
|
|||||||
// see: https://github.com/ueberdosis/tiptap/issues/5352
|
// see: https://github.com/ueberdosis/tiptap/issues/5352
|
||||||
// see:https://github.com/ueberdosis/tiptap/issues/4089
|
// see:https://github.com/ueberdosis/tiptap/issues/4089
|
||||||
//import { generateJSON } from '@tiptap/html';
|
//import { generateJSON } from '@tiptap/html';
|
||||||
import { Node, Schema } from '@tiptap/pm/model';
|
import { Node } from '@tiptap/pm/model';
|
||||||
import { Logger } from '@nestjs/common';
|
|
||||||
|
|
||||||
export const tiptapExtensions = [
|
export const tiptapExtensions = [
|
||||||
StarterKit.configure({
|
StarterKit.configure({
|
||||||
@@ -111,53 +110,9 @@ export function jsonToText(tiptapJson: JSONContent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function jsonToNode(tiptapJson: JSONContent) {
|
export function jsonToNode(tiptapJson: JSONContent) {
|
||||||
const schema = getSchema(tiptapExtensions);
|
return Node.fromJSON(getSchema(tiptapExtensions), tiptapJson);
|
||||||
try {
|
|
||||||
return Node.fromJSON(schema, tiptapJson);
|
|
||||||
} catch (error) {
|
|
||||||
if (
|
|
||||||
error instanceof RangeError &&
|
|
||||||
error.message.includes('Unknown node type')
|
|
||||||
) {
|
|
||||||
Logger.warn('Stripping unknown node types from document:', error.message);
|
|
||||||
const cleanedJson = stripUnknownNodes(tiptapJson, schema);
|
|
||||||
return Node.fromJSON(schema, cleanedJson);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPageId(documentName: string) {
|
export function getPageId(documentName: string) {
|
||||||
return documentName.split('.')[1];
|
return documentName.split('.')[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripUnknownNodes(
|
|
||||||
json: JSONContent,
|
|
||||||
schema: Schema,
|
|
||||||
): JSONContent | null {
|
|
||||||
if (!json || typeof json !== 'object') return json;
|
|
||||||
|
|
||||||
// Recursively clean children first, flattening any unwrapped content
|
|
||||||
if (json.content && Array.isArray(json.content)) {
|
|
||||||
const newContent: JSONContent[] = [];
|
|
||||||
for (const child of json.content) {
|
|
||||||
const cleaned = stripUnknownNodes(child, schema);
|
|
||||||
if (Array.isArray(cleaned)) {
|
|
||||||
newContent.push(...cleaned);
|
|
||||||
} else if (cleaned) {
|
|
||||||
newContent.push(cleaned);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
json.content = newContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this node is unknown AFTER processing children
|
|
||||||
if (json.type && !schema.nodes[json.type]) {
|
|
||||||
// Unwrap: return cleaned children directly instead of wrapping
|
|
||||||
return (
|
|
||||||
json.content && json.content.length > 0 ? json.content : null
|
|
||||||
) as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
export type ExportPageMetadata = {
|
|
||||||
pageId: string;
|
|
||||||
slugId: string;
|
|
||||||
icon: string | null;
|
|
||||||
position: string;
|
|
||||||
parentPath: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ExportMetadata = {
|
|
||||||
exportedAt: string;
|
|
||||||
source: 'docmost';
|
|
||||||
version: string;
|
|
||||||
pages: Record<string, ExportPageMetadata>;
|
|
||||||
};
|
|
||||||
@@ -5,14 +5,13 @@ const CONTEXTS_TO_IGNORE = [
|
|||||||
'InstanceLoader',
|
'InstanceLoader',
|
||||||
'RoutesResolver',
|
'RoutesResolver',
|
||||||
'RouterExplorer',
|
'RouterExplorer',
|
||||||
'LegacyRouteConverter',
|
|
||||||
'WebSocketsController',
|
'WebSocketsController',
|
||||||
];
|
];
|
||||||
|
|
||||||
export function createPinoConfig(): Params {
|
export function createPinoConfig(): Params {
|
||||||
const isProduction = process.env.NODE_ENV?.toLowerCase() === 'production';
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
const isDebugMode = process.env.DEBUG_MODE?.toLowerCase() === 'true';
|
const isDebugMode = process.env.DEBUG_MODE === 'true';
|
||||||
const logHttp = process.env.LOG_HTTP?.toLowerCase() === 'true';
|
const logHttp = process.env.LOG_HTTP === 'true';
|
||||||
|
|
||||||
const level = isProduction && !isDebugMode ? 'info' : 'debug';
|
const level = isProduction && !isDebugMode ? 'info' : 'debug';
|
||||||
|
|
||||||
@@ -33,20 +32,14 @@ export function createPinoConfig(): Params {
|
|||||||
: undefined,
|
: undefined,
|
||||||
formatters: {
|
formatters: {
|
||||||
level: (label) => ({ level: label }),
|
level: (label) => ({ level: label }),
|
||||||
},
|
log: (object: Record<string, unknown>) => {
|
||||||
hooks: {
|
|
||||||
logMethod(inputArgs, method) {
|
|
||||||
if (isProduction && !isDebugMode) {
|
if (isProduction && !isDebugMode) {
|
||||||
for (const arg of inputArgs) {
|
const context = object['context'] as string | undefined;
|
||||||
if (typeof arg === 'object' && arg !== null && 'context' in arg) {
|
if (context && CONTEXTS_TO_IGNORE.includes(context)) {
|
||||||
const context = (arg as Record<string, unknown>)['context'];
|
return { filtered: true };
|
||||||
if (typeof context === 'string' && CONTEXTS_TO_IGNORE.includes(context)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return method.apply(this, inputArgs);
|
return object;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
serializers: {
|
serializers: {
|
||||||
|
|||||||
@@ -181,9 +181,7 @@ export class AttachmentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fileStream = await this.storageService.readStream(
|
const fileStream = await this.storageService.read(attachment.filePath);
|
||||||
attachment.filePath,
|
|
||||||
);
|
|
||||||
res.headers({
|
res.headers({
|
||||||
'Content-Type': attachment.mimeType,
|
'Content-Type': attachment.mimeType,
|
||||||
'Cache-Control': 'private, max-age=3600',
|
'Cache-Control': 'private, max-age=3600',
|
||||||
@@ -243,9 +241,7 @@ export class AttachmentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fileStream = await this.storageService.readStream(
|
const fileStream = await this.storageService.read(attachment.filePath);
|
||||||
attachment.filePath,
|
|
||||||
);
|
|
||||||
res.headers({
|
res.headers({
|
||||||
'Content-Type': attachment.mimeType,
|
'Content-Type': attachment.mimeType,
|
||||||
'Cache-Control': 'public, max-age=3600',
|
'Cache-Control': 'public, max-age=3600',
|
||||||
@@ -371,14 +367,14 @@ export class AttachmentController {
|
|||||||
const filePath = `${getAttachmentFolderPath(attachmentType, workspace.id)}/${fileName}`;
|
const filePath = `${getAttachmentFolderPath(attachmentType, workspace.id)}/${fileName}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fileStream = await this.storageService.readStream(filePath);
|
const fileStream = await this.storageService.read(filePath);
|
||||||
res.headers({
|
res.headers({
|
||||||
'Content-Type': getMimeType(filePath),
|
'Content-Type': getMimeType(filePath),
|
||||||
'Cache-Control': 'private, max-age=86400',
|
'Cache-Control': 'private, max-age=86400',
|
||||||
});
|
});
|
||||||
return res.send(fileStream);
|
return res.send(fileStream);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// this.logger.error(err);
|
// this.logger.error(err);
|
||||||
throw new NotFoundException('File not found');
|
throw new NotFoundException('File not found');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
Submodule apps/server/src/ee updated: f858f127b5...b6844b019c
@@ -55,7 +55,7 @@ export class ExportController {
|
|||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
const zipFileStream = await this.exportService.exportPages(
|
const zipFileBuffer = await this.exportService.exportPages(
|
||||||
dto.pageId,
|
dto.pageId,
|
||||||
dto.format,
|
dto.format,
|
||||||
dto.includeAttachments,
|
dto.includeAttachments,
|
||||||
@@ -70,7 +70,7 @@ export class ExportController {
|
|||||||
'attachment; filename="' + encodeURIComponent(fileName) + '"',
|
'attachment; filename="' + encodeURIComponent(fileName) + '"',
|
||||||
});
|
});
|
||||||
|
|
||||||
res.send(zipFileStream);
|
res.send(zipFileBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@@ -100,6 +100,6 @@ export class ExportController {
|
|||||||
'"',
|
'"',
|
||||||
});
|
});
|
||||||
|
|
||||||
res.send(exportFile.fileStream);
|
res.send(exportFile.fileBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { jsonToHtml, jsonToNode } from '../../collaboration/collaboration.util';
|
import { jsonToHtml, jsonToNode } from '../../collaboration/collaboration.util';
|
||||||
|
import { turndown } from './turndown-utils';
|
||||||
import { ExportFormat } from './dto/export-dto';
|
import { ExportFormat } from './dto/export-dto';
|
||||||
import { Page } from '@docmost/db/types/entity.types';
|
import { Page } from '@docmost/db/types/entity.types';
|
||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
@@ -20,23 +21,16 @@ import {
|
|||||||
replaceInternalLinks,
|
replaceInternalLinks,
|
||||||
updateAttachmentUrlsToLocalPaths,
|
updateAttachmentUrlsToLocalPaths,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import {
|
|
||||||
ExportMetadata,
|
|
||||||
ExportPageMetadata,
|
|
||||||
} from '../../common/helpers/types/export-metadata.types';
|
|
||||||
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
||||||
import { Node } from '@tiptap/pm/model';
|
import { Node } from '@tiptap/pm/model';
|
||||||
import { EditorState } from '@tiptap/pm/state';
|
import { EditorState } from '@tiptap/pm/state';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||||
import slugify = require('@sindresorhus/slugify');
|
import slugify = require('@sindresorhus/slugify');
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
||||||
const packageJson = require('../../../package.json');
|
|
||||||
import { EnvironmentService } from '../environment/environment.service';
|
import { EnvironmentService } from '../environment/environment.service';
|
||||||
import {
|
import {
|
||||||
getAttachmentIds,
|
getAttachmentIds,
|
||||||
getProsemirrorContent,
|
getProsemirrorContent,
|
||||||
} from '../../common/helpers/prosemirror/utils';
|
} from '../../common/helpers/prosemirror/utils';
|
||||||
import { htmlToMarkdown } from '@docmost/editor-ext';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportService {
|
export class ExportService {
|
||||||
@@ -89,7 +83,7 @@ export class ExportService {
|
|||||||
/<colgroup[^>]*>[\s\S]*?<\/colgroup>/gim,
|
/<colgroup[^>]*>[\s\S]*?<\/colgroup>/gim,
|
||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
return htmlToMarkdown(newPageHtml);
|
return turndown(newPageHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -161,15 +155,12 @@ export class ExportService {
|
|||||||
'pages.id',
|
'pages.id',
|
||||||
'pages.slugId',
|
'pages.slugId',
|
||||||
'pages.title',
|
'pages.title',
|
||||||
'pages.icon',
|
|
||||||
'pages.position',
|
|
||||||
'pages.content',
|
'pages.content',
|
||||||
'pages.parentPageId',
|
'pages.parentPageId',
|
||||||
'pages.spaceId',
|
'pages.spaceId',
|
||||||
'pages.workspaceId',
|
'pages.workspaceId',
|
||||||
])
|
])
|
||||||
.where('spaceId', '=', spaceId)
|
.where('spaceId', '=', spaceId)
|
||||||
.where('deletedAt', 'is', null)
|
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const tree = buildTree(pages as Page[]);
|
const tree = buildTree(pages as Page[]);
|
||||||
@@ -186,7 +177,7 @@ export class ExportService {
|
|||||||
|
|
||||||
const fileName = `${space.name}-space-export.zip`;
|
const fileName = `${space.name}-space-export.zip`;
|
||||||
return {
|
return {
|
||||||
fileStream: zipFile,
|
fileBuffer: zipFile,
|
||||||
fileName,
|
fileName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -198,12 +189,10 @@ export class ExportService {
|
|||||||
includeAttachments: boolean,
|
includeAttachments: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const slugIdToPath: Record<string, string> = {};
|
const slugIdToPath: Record<string, string> = {};
|
||||||
const pageIdToFilePath: Record<string, string> = {};
|
|
||||||
const pagesMetadata: Record<string, ExportPageMetadata> = {};
|
|
||||||
|
|
||||||
computeLocalPath(tree, format, null, '', slugIdToPath);
|
computeLocalPath(tree, format, null, '', slugIdToPath);
|
||||||
|
|
||||||
const stack: { folder: JSZip; parentPageId: string | null }[] = [
|
const stack: { folder: JSZip; parentPageId: string }[] = [
|
||||||
{ folder: zip, parentPageId: null },
|
{ folder: zip, parentPageId: null },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -243,33 +232,12 @@ export class ExportService {
|
|||||||
`${pageTitle}${getExportExtension(format)}`,
|
`${pageTitle}${getExportExtension(format)}`,
|
||||||
pageExportContent,
|
pageExportContent,
|
||||||
);
|
);
|
||||||
|
|
||||||
pageIdToFilePath[page.id] = currentPagePath;
|
|
||||||
|
|
||||||
const parentPath = parentPageId ? pageIdToFilePath[parentPageId] : null;
|
|
||||||
pagesMetadata[currentPagePath] = {
|
|
||||||
pageId: page.id,
|
|
||||||
slugId: page.slugId,
|
|
||||||
icon: page.icon ?? null,
|
|
||||||
position: page.position,
|
|
||||||
parentPath,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (childPages.length > 0) {
|
if (childPages.length > 0) {
|
||||||
const pageFolder = folder.folder(pageTitle);
|
const pageFolder = folder.folder(pageTitle);
|
||||||
stack.push({ folder: pageFolder, parentPageId: page.id });
|
stack.push({ folder: pageFolder, parentPageId: page.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata: ExportMetadata = {
|
|
||||||
exportedAt: new Date().toISOString(),
|
|
||||||
source: 'docmost',
|
|
||||||
version: packageJson.version,
|
|
||||||
pages: pagesMetadata,
|
|
||||||
};
|
|
||||||
|
|
||||||
zip.file('docmost-metadata.json', JSON.stringify(metadata, null, 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async zipAttachments(prosemirrorJson: any, spaceId: string, zip: JSZip) {
|
async zipAttachments(prosemirrorJson: any, spaceId: string, zip: JSZip) {
|
||||||
|
|||||||
+28
-31
@@ -1,22 +1,22 @@
|
|||||||
import * as _TurndownService from '@joplin/turndown';
|
import * as TurndownService from '@joplin/turndown';
|
||||||
import * as TurndownPluginGfm from '@joplin/turndown-plugin-gfm';
|
import * as TurndownPluginGfm from '@joplin/turndown-plugin-gfm';
|
||||||
import { getBasename } from './basename';
|
import * as path from 'path';
|
||||||
|
|
||||||
// CJS/ESM interop: .default exists in Vite, not in NestJS
|
export function turndown(html: string): string {
|
||||||
const TurndownService = (_TurndownService as any).default || _TurndownService;
|
|
||||||
|
|
||||||
export function htmlToMarkdown(html: string): string {
|
|
||||||
const turndownService = new TurndownService({
|
const turndownService = new TurndownService({
|
||||||
headingStyle: 'atx',
|
headingStyle: 'atx',
|
||||||
codeBlockStyle: 'fenced',
|
codeBlockStyle: 'fenced',
|
||||||
hr: '---',
|
hr: '---',
|
||||||
bulletListMarker: '-',
|
bulletListMarker: '-',
|
||||||
});
|
});
|
||||||
|
const tables = TurndownPluginGfm.tables;
|
||||||
|
const strikethrough = TurndownPluginGfm.strikethrough;
|
||||||
|
const highlightedCodeBlock = TurndownPluginGfm.highlightedCodeBlock;
|
||||||
|
|
||||||
turndownService.use([
|
turndownService.use([
|
||||||
TurndownPluginGfm.tables,
|
tables,
|
||||||
TurndownPluginGfm.strikethrough,
|
strikethrough,
|
||||||
TurndownPluginGfm.highlightedCodeBlock,
|
highlightedCodeBlock,
|
||||||
taskList,
|
taskList,
|
||||||
callout,
|
callout,
|
||||||
preserveDetail,
|
preserveDetail,
|
||||||
@@ -29,33 +29,34 @@ export function htmlToMarkdown(html: string): string {
|
|||||||
return turndownService.turndown(html).replaceAll('<br>', ' ');
|
return turndownService.turndown(html).replaceAll('<br>', ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
function listParagraph(turndownService: _TurndownService) {
|
function listParagraph(turndownService: TurndownService) {
|
||||||
turndownService.addRule('paragraph', {
|
turndownService.addRule('paragraph', {
|
||||||
filter: ['p'],
|
filter: ['p'],
|
||||||
replacement: (content: string, node: HTMLInputElement) => {
|
replacement: (content: any, node: HTMLInputElement) => {
|
||||||
if (node.parentElement?.nodeName === 'LI') {
|
if (node.parentElement?.nodeName === 'LI') {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `\n\n${content}\n\n`;
|
return `\n\n${content}\n\n`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function callout(turndownService: _TurndownService) {
|
function callout(turndownService: TurndownService) {
|
||||||
turndownService.addRule('callout', {
|
turndownService.addRule('callout', {
|
||||||
filter: function (node: HTMLInputElement) {
|
filter: function (node: HTMLInputElement) {
|
||||||
return (
|
return (
|
||||||
node.nodeName === 'DIV' && node.getAttribute('data-type') === 'callout'
|
node.nodeName === 'DIV' && node.getAttribute('data-type') === 'callout'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
replacement: function (content: string, node: HTMLInputElement) {
|
replacement: function (content: any, node: HTMLInputElement) {
|
||||||
const calloutType = node.getAttribute('data-callout-type');
|
const calloutType = node.getAttribute('data-callout-type');
|
||||||
return `\n\n:::${calloutType}\n${content.trim()}\n:::\n\n`;
|
return `\n\n:::${calloutType}\n${content.trim()}\n:::\n\n`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function taskList(turndownService: _TurndownService) {
|
function taskList(turndownService: TurndownService) {
|
||||||
turndownService.addRule('taskListItem', {
|
turndownService.addRule('taskListItem', {
|
||||||
filter: function (node: HTMLInputElement) {
|
filter: function (node: HTMLInputElement) {
|
||||||
return (
|
return (
|
||||||
@@ -63,7 +64,7 @@ function taskList(turndownService: _TurndownService) {
|
|||||||
node.parentNode.nodeName === 'UL'
|
node.parentNode.nodeName === 'UL'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
replacement: function (content: string, node: HTMLInputElement) {
|
replacement: function (content: any, node: HTMLInputElement) {
|
||||||
const checkbox = node.querySelector(
|
const checkbox = node.querySelector(
|
||||||
'input[type="checkbox"]',
|
'input[type="checkbox"]',
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
@@ -78,21 +79,17 @@ function taskList(turndownService: _TurndownService) {
|
|||||||
// Create the checkbox prefix
|
// Create the checkbox prefix
|
||||||
const prefix = `- ${isChecked ? '[x]' : '[ ]'} `;
|
const prefix = `- ${isChecked ? '[x]' : '[ ]'} `;
|
||||||
|
|
||||||
return (
|
return prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '');
|
||||||
prefix +
|
|
||||||
content +
|
|
||||||
(node.nextSibling && !/\n$/.test(content) ? '\n' : '')
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function preserveDetail(turndownService: _TurndownService) {
|
function preserveDetail(turndownService: TurndownService) {
|
||||||
turndownService.addRule('preserveDetail', {
|
turndownService.addRule('preserveDetail', {
|
||||||
filter: function (node: HTMLInputElement) {
|
filter: function (node: HTMLInputElement) {
|
||||||
return node.nodeName === 'DETAILS';
|
return node.nodeName === 'DETAILS';
|
||||||
},
|
},
|
||||||
replacement: function (_content: string, node: HTMLInputElement) {
|
replacement: function (content: any, node: HTMLInputElement) {
|
||||||
const summary = node.querySelector(':scope > summary');
|
const summary = node.querySelector(':scope > summary');
|
||||||
let detailSummary = '';
|
let detailSummary = '';
|
||||||
|
|
||||||
@@ -114,7 +111,7 @@ function preserveDetail(turndownService: _TurndownService) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function mathInline(turndownService: _TurndownService) {
|
function mathInline(turndownService: TurndownService) {
|
||||||
turndownService.addRule('mathInline', {
|
turndownService.addRule('mathInline', {
|
||||||
filter: function (node: HTMLInputElement) {
|
filter: function (node: HTMLInputElement) {
|
||||||
return (
|
return (
|
||||||
@@ -122,13 +119,13 @@ function mathInline(turndownService: _TurndownService) {
|
|||||||
node.getAttribute('data-type') === 'mathInline'
|
node.getAttribute('data-type') === 'mathInline'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
replacement: function (content: string) {
|
replacement: function (content: any, node: HTMLInputElement) {
|
||||||
return `$${content}$`;
|
return `$${content}$`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function mathBlock(turndownService: _TurndownService) {
|
function mathBlock(turndownService: TurndownService) {
|
||||||
turndownService.addRule('mathBlock', {
|
turndownService.addRule('mathBlock', {
|
||||||
filter: function (node: HTMLInputElement) {
|
filter: function (node: HTMLInputElement) {
|
||||||
return (
|
return (
|
||||||
@@ -136,32 +133,32 @@ function mathBlock(turndownService: _TurndownService) {
|
|||||||
node.getAttribute('data-type') === 'mathBlock'
|
node.getAttribute('data-type') === 'mathBlock'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
replacement: function (content: string) {
|
replacement: function (content: any, node: HTMLInputElement) {
|
||||||
return `\n$$\n${content}\n$$\n`;
|
return `\n$$\n${content}\n$$\n`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function iframeEmbed(turndownService: _TurndownService) {
|
function iframeEmbed(turndownService: TurndownService) {
|
||||||
turndownService.addRule('iframeEmbed', {
|
turndownService.addRule('iframeEmbed', {
|
||||||
filter: function (node: HTMLInputElement) {
|
filter: function (node: HTMLInputElement) {
|
||||||
return node.nodeName === 'IFRAME';
|
return node.nodeName === 'IFRAME';
|
||||||
},
|
},
|
||||||
replacement: function (_content: string, node: HTMLInputElement) {
|
replacement: function (content: any, node: HTMLInputElement) {
|
||||||
const src = node.getAttribute('src');
|
const src = node.getAttribute('src');
|
||||||
return '[' + src + '](' + src + ')';
|
return '[' + src + '](' + src + ')';
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function video(turndownService: _TurndownService) {
|
function video(turndownService: TurndownService) {
|
||||||
turndownService.addRule('video', {
|
turndownService.addRule('video', {
|
||||||
filter: function (node: HTMLInputElement) {
|
filter: function (node: HTMLInputElement) {
|
||||||
return node.tagName === 'VIDEO';
|
return node.tagName === 'VIDEO';
|
||||||
},
|
},
|
||||||
replacement: function (_content: string, node: HTMLInputElement) {
|
replacement: function (content: any, node: HTMLInputElement) {
|
||||||
const src = node.getAttribute('src') || '';
|
const src = node.getAttribute('src') || '';
|
||||||
const name = getBasename(src) || src;
|
const name = path.basename(src);
|
||||||
return '[' + name + '](' + src + ')';
|
return '[' + name + '](' + src + ')';
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { jsonToNode } from 'src/collaboration/collaboration.util';
|
import { jsonToNode } from 'src/collaboration/collaboration.util';
|
||||||
import { Logger } from '@nestjs/common';
|
|
||||||
import { ExportFormat } from './dto/export-dto';
|
import { ExportFormat } from './dto/export-dto';
|
||||||
import { Node } from '@tiptap/pm/model';
|
import { Node } from '@tiptap/pm/model';
|
||||||
import { validate as isValidUUID } from 'uuid';
|
import { validate as isValidUUID } from 'uuid';
|
||||||
@@ -89,7 +88,7 @@ export function replaceInternalLinks(
|
|||||||
// if link and text are same, use page title
|
// if link and text are same, use page title
|
||||||
if (markLink === node.text) {
|
if (markLink === node.text) {
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
node.text = getInternalLinkPageName(relativePath, currentPagePath);
|
node.text = getInternalLinkPageName(relativePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,19 +99,10 @@ export function replaceInternalLinks(
|
|||||||
return doc.toJSON();
|
return doc.toJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getInternalLinkPageName(path: string, currentFilePath?: string): string {
|
export function getInternalLinkPageName(path: string): string {
|
||||||
const name = path?.split('/').pop().split('.').slice(0, -1).join('.');
|
return decodeURIComponent(
|
||||||
try {
|
path?.split('/').pop().split('.').slice(0, -1).join('.'),
|
||||||
return decodeURIComponent(name);
|
);
|
||||||
} catch (err) {
|
|
||||||
if (currentFilePath) {
|
|
||||||
Logger.warn(
|
|
||||||
`URI malformed in page ${currentFilePath}: ${name}. Falling back to raw name.`,
|
|
||||||
'ExportUtils',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractPageSlugId(input: string): string {
|
export function extractPageSlugId(input: string): string {
|
||||||
|
|||||||
@@ -15,5 +15,4 @@ export type ImportPageNode = {
|
|||||||
parentPageId: string | null;
|
parentPageId: string | null;
|
||||||
fileExtension: string;
|
fileExtension: string;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
icon?: string | null;
|
|
||||||
};
|
};
|
||||||
@@ -24,8 +24,6 @@ import { formatImportHtml } from '../utils/import-formatter';
|
|||||||
import {
|
import {
|
||||||
buildAttachmentCandidates,
|
buildAttachmentCandidates,
|
||||||
collectMarkdownAndHtmlFiles,
|
collectMarkdownAndHtmlFiles,
|
||||||
encodeFilePath,
|
|
||||||
readDocmostMetadata,
|
|
||||||
stripNotionID,
|
stripNotionID,
|
||||||
} from '../utils/import.utils';
|
} from '../utils/import.utils';
|
||||||
import { executeTx } from '@docmost/db/utils';
|
import { executeTx } from '@docmost/db/utils';
|
||||||
@@ -156,7 +154,6 @@ export class FileImportTaskService {
|
|||||||
const { extractDir, fileTask } = opts;
|
const { extractDir, fileTask } = opts;
|
||||||
const allFiles = await collectMarkdownAndHtmlFiles(extractDir);
|
const allFiles = await collectMarkdownAndHtmlFiles(extractDir);
|
||||||
const attachmentCandidates = await buildAttachmentCandidates(extractDir);
|
const attachmentCandidates = await buildAttachmentCandidates(extractDir);
|
||||||
const docmostMetadata = await readDocmostMetadata(extractDir);
|
|
||||||
|
|
||||||
const pagesMap = new Map<string, ImportPageNode>();
|
const pagesMap = new Map<string, ImportPageNode>();
|
||||||
|
|
||||||
@@ -167,9 +164,6 @@ export class FileImportTaskService {
|
|||||||
.join('/'); // normalize to forward-slashes
|
.join('/'); // normalize to forward-slashes
|
||||||
const ext = path.extname(relPath).toLowerCase();
|
const ext = path.extname(relPath).toLowerCase();
|
||||||
|
|
||||||
const encodedPath = encodeFilePath(relPath);
|
|
||||||
const pageMetadata = docmostMetadata?.pages[encodedPath];
|
|
||||||
|
|
||||||
pagesMap.set(relPath, {
|
pagesMap.set(relPath, {
|
||||||
id: v7(),
|
id: v7(),
|
||||||
slugId: generateSlugId(),
|
slugId: generateSlugId(),
|
||||||
@@ -178,7 +172,6 @@ export class FileImportTaskService {
|
|||||||
parentPageId: null,
|
parentPageId: null,
|
||||||
fileExtension: ext,
|
fileExtension: ext,
|
||||||
filePath: relPath,
|
filePath: relPath,
|
||||||
icon: pageMetadata?.icon ?? null,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,8 +224,6 @@ export class FileImportTaskService {
|
|||||||
|
|
||||||
if (!pagesMap.has(mdPath) && !pagesMap.has(htmlPath)) {
|
if (!pagesMap.has(mdPath) && !pagesMap.has(htmlPath)) {
|
||||||
const folderName = path.basename(folderPath);
|
const folderName = path.basename(folderPath);
|
||||||
const encodedMdPath = encodeFilePath(mdPath);
|
|
||||||
const placeholderMetadata = docmostMetadata?.pages[encodedMdPath];
|
|
||||||
pagesMap.set(mdPath, {
|
pagesMap.set(mdPath, {
|
||||||
id: v7(),
|
id: v7(),
|
||||||
slugId: generateSlugId(),
|
slugId: generateSlugId(),
|
||||||
@@ -241,7 +232,6 @@ export class FileImportTaskService {
|
|||||||
parentPageId: null,
|
parentPageId: null,
|
||||||
fileExtension: '.md',
|
fileExtension: '.md',
|
||||||
filePath: mdPath,
|
filePath: mdPath,
|
||||||
icon: placeholderMetadata?.icon ?? null,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -276,39 +266,11 @@ export class FileImportTaskService {
|
|||||||
siblingsMap.set(page.parentPageId, group);
|
siblingsMap.set(page.parentPageId, group);
|
||||||
});
|
});
|
||||||
|
|
||||||
const encodedPathsMap = new Map<string, string>();
|
|
||||||
if (docmostMetadata) {
|
|
||||||
pagesMap.forEach((_, filePath) => {
|
|
||||||
encodedPathsMap.set(filePath, encodeFilePath(filePath));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort siblings by metadata position if available, otherwise alphabetically
|
|
||||||
const sortSiblings = (siblings: ImportPageNode[]) => {
|
|
||||||
if (docmostMetadata) {
|
|
||||||
siblings.sort((a, b) => {
|
|
||||||
const posA =
|
|
||||||
docmostMetadata.pages[encodedPathsMap.get(a.filePath)]?.position;
|
|
||||||
const posB =
|
|
||||||
docmostMetadata.pages[encodedPathsMap.get(b.filePath)]?.position;
|
|
||||||
if (posA && posB) {
|
|
||||||
// Use direct comparison to match PostgreSQL collation 'C' (byte order)
|
|
||||||
if (posA < posB) return -1;
|
|
||||||
if (posA > posB) return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
siblings.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// get root pages
|
// get root pages
|
||||||
const rootSibs = siblingsMap.get(null);
|
const rootSibs = siblingsMap.get(null);
|
||||||
|
|
||||||
if (rootSibs?.length) {
|
if (rootSibs?.length) {
|
||||||
sortSiblings(rootSibs);
|
rootSibs.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
// get first position key from the server
|
// get first position key from the server
|
||||||
const nextPosition = await this.pageService.nextPagePosition(
|
const nextPosition = await this.pageService.nextPagePosition(
|
||||||
@@ -330,7 +292,7 @@ export class FileImportTaskService {
|
|||||||
siblingsMap.forEach((sibs, parentId) => {
|
siblingsMap.forEach((sibs, parentId) => {
|
||||||
if (parentId === null) return; // root already done
|
if (parentId === null) return; // root already done
|
||||||
|
|
||||||
sortSiblings(sibs);
|
sibs.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
let prevPos: string | null = null;
|
let prevPos: string | null = null;
|
||||||
for (const page of sibs) {
|
for (const page of sibs) {
|
||||||
@@ -464,7 +426,7 @@ export class FileImportTaskService {
|
|||||||
id: page.id,
|
id: page.id,
|
||||||
slugId: page.slugId,
|
slugId: page.slugId,
|
||||||
title: title || page.name,
|
title: title || page.name,
|
||||||
icon: page.icon || pageIcon || null,
|
icon: pageIcon || null,
|
||||||
content: prosemirrorJson,
|
content: prosemirrorJson,
|
||||||
textContent: jsonToText(prosemirrorJson),
|
textContent: jsonToText(prosemirrorJson),
|
||||||
ydoc: await this.importService.createYdoc(prosemirrorJson),
|
ydoc: await this.importService.createYdoc(prosemirrorJson),
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { getEmbedUrlAndProvider } from '@docmost/editor-ext';
|
import { getEmbedUrlAndProvider } from '@docmost/editor-ext';
|
||||||
import { Logger } from '@nestjs/common';
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { v7 } from 'uuid';
|
import { v7 } from 'uuid';
|
||||||
import { InsertableBacklink } from '@docmost/db/types/entity.types';
|
import { InsertableBacklink } from '@docmost/db/types/entity.types';
|
||||||
@@ -281,18 +280,8 @@ export async function rewriteInternalLinksToMentionHtml(
|
|||||||
const $a = $(el);
|
const $a = $(el);
|
||||||
const raw = $a.attr('href')!;
|
const raw = $a.attr('href')!;
|
||||||
if (raw.startsWith('http') || raw.startsWith('/api/')) return;
|
if (raw.startsWith('http') || raw.startsWith('/api/')) return;
|
||||||
let decodedRaw = raw;
|
|
||||||
try {
|
|
||||||
decodedRaw = decodeURIComponent(raw);
|
|
||||||
} catch (err) {
|
|
||||||
Logger.warn(
|
|
||||||
`URI malformed in page ${currentFilePath}: ${raw}. Falling back to raw path.`,
|
|
||||||
'ImportFormatter',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolved = normalize(
|
const resolved = normalize(
|
||||||
path.join(path.dirname(currentFilePath), decodedRaw),
|
path.join(path.dirname(currentFilePath), decodeURIComponent(raw)),
|
||||||
);
|
);
|
||||||
const meta = filePathToPageMetaMap.get(resolved);
|
const meta = filePathToPageMetaMap.get(resolved);
|
||||||
if (!meta) return;
|
if (!meta) return;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { Logger } from '@nestjs/common';
|
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { ExportMetadata } from '../../../common/helpers/types/export-metadata.types';
|
|
||||||
|
|
||||||
export async function buildAttachmentCandidates(
|
export async function buildAttachmentCandidates(
|
||||||
extractDir: string,
|
extractDir: string,
|
||||||
@@ -32,19 +30,8 @@ export function resolveRelativeAttachmentPath(
|
|||||||
pageDir: string,
|
pageDir: string,
|
||||||
attachmentCandidates: Map<string, string>,
|
attachmentCandidates: Map<string, string>,
|
||||||
): string | null {
|
): string | null {
|
||||||
let mainRel = raw.replace(/^\.?\/+/, '');
|
const mainRel = decodeURIComponent(raw.replace(/^\.?\/+/, ''));
|
||||||
try {
|
const fallback = path.normalize(path.join(pageDir, mainRel));
|
||||||
mainRel = decodeURIComponent(mainRel);
|
|
||||||
} catch (err) {
|
|
||||||
Logger.warn(
|
|
||||||
`URI malformed for attachment path: ${mainRel}. Falling back to raw path.`,
|
|
||||||
'ImportUtils',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const fallback = path
|
|
||||||
.normalize(path.join(pageDir, mainRel))
|
|
||||||
.split(path.sep)
|
|
||||||
.join('/');
|
|
||||||
|
|
||||||
if (attachmentCandidates.has(mainRel)) {
|
if (attachmentCandidates.has(mainRel)) {
|
||||||
return mainRel;
|
return mainRel;
|
||||||
@@ -83,26 +70,3 @@ export function stripNotionID(fileName: string): string {
|
|||||||
const notionIdPattern = /[ -]?[a-z0-9]{32}$/i;
|
const notionIdPattern = /[ -]?[a-z0-9]{32}$/i;
|
||||||
return fileName.replace(notionIdPattern, '').trim();
|
return fileName.replace(notionIdPattern, '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodeFilePath(filePath: string): string {
|
|
||||||
return filePath
|
|
||||||
.split('/')
|
|
||||||
.map((segment) => encodeURIComponent(segment))
|
|
||||||
.join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function readDocmostMetadata(
|
|
||||||
extractDir: string,
|
|
||||||
): Promise<ExportMetadata | null> {
|
|
||||||
const metadataPath = path.join(extractDir, 'docmost-metadata.json');
|
|
||||||
try {
|
|
||||||
const content = await fs.readFile(metadataPath, 'utf-8');
|
|
||||||
const metadata = JSON.parse(content) as ExportMetadata;
|
|
||||||
if (metadata.source === 'docmost' && metadata.pages) {
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+27
-28
@@ -30,33 +30,33 @@
|
|||||||
"@joplin/turndown": "^4.0.74",
|
"@joplin/turndown": "^4.0.74",
|
||||||
"@joplin/turndown-plugin-gfm": "^1.0.56",
|
"@joplin/turndown-plugin-gfm": "^1.0.56",
|
||||||
"@sindresorhus/slugify": "1.1.0",
|
"@sindresorhus/slugify": "1.1.0",
|
||||||
"@tiptap/core": "3.17.1",
|
"@tiptap/core": "3.17.0",
|
||||||
"@tiptap/extension-code-block": "3.17.1",
|
"@tiptap/extension-code-block": "3.17.0",
|
||||||
"@tiptap/extension-collaboration": "3.17.1",
|
"@tiptap/extension-collaboration": "3.17.0",
|
||||||
"@tiptap/extension-collaboration-caret": "3.17.1",
|
"@tiptap/extension-collaboration-caret": "3.17.0",
|
||||||
"@tiptap/extension-color": "3.17.1",
|
"@tiptap/extension-color": "3.17.0",
|
||||||
"@tiptap/extension-document": "3.17.1",
|
"@tiptap/extension-document": "3.17.0",
|
||||||
"@tiptap/extension-heading": "3.17.1",
|
"@tiptap/extension-heading": "3.17.0",
|
||||||
"@tiptap/extension-highlight": "3.17.1",
|
"@tiptap/extension-highlight": "3.17.0",
|
||||||
"@tiptap/extension-history": "3.17.1",
|
"@tiptap/extension-history": "3.17.0",
|
||||||
"@tiptap/extension-image": "3.17.1",
|
"@tiptap/extension-image": "3.17.0",
|
||||||
"@tiptap/extension-link": "3.17.1",
|
"@tiptap/extension-link": "3.17.0",
|
||||||
"@tiptap/extension-list": "3.17.1",
|
"@tiptap/extension-list": "3.17.0",
|
||||||
"@tiptap/extension-placeholder": "3.17.1",
|
"@tiptap/extension-placeholder": "3.17.0",
|
||||||
"@tiptap/extension-subscript": "3.17.1",
|
"@tiptap/extension-subscript": "3.17.0",
|
||||||
"@tiptap/extension-superscript": "3.17.1",
|
"@tiptap/extension-superscript": "3.17.0",
|
||||||
"@tiptap/extension-table": "3.17.1",
|
"@tiptap/extension-table": "3.17.0",
|
||||||
"@tiptap/extension-text": "3.17.1",
|
"@tiptap/extension-text": "3.17.0",
|
||||||
"@tiptap/extension-text-align": "3.17.1",
|
"@tiptap/extension-text-align": "3.17.0",
|
||||||
"@tiptap/extension-text-style": "3.17.1",
|
"@tiptap/extension-text-style": "3.17.0",
|
||||||
"@tiptap/extension-typography": "3.17.1",
|
"@tiptap/extension-typography": "3.17.0",
|
||||||
"@tiptap/extension-unique-id": "^3.17.1",
|
"@tiptap/extension-unique-id": "^3.17.0",
|
||||||
"@tiptap/extension-youtube": "3.17.1",
|
"@tiptap/extension-youtube": "3.17.0",
|
||||||
"@tiptap/html": "3.17.1",
|
"@tiptap/html": "3.17.0",
|
||||||
"@tiptap/pm": "3.17.1",
|
"@tiptap/pm": "3.17.0",
|
||||||
"@tiptap/react": "3.17.1",
|
"@tiptap/react": "3.17.0",
|
||||||
"@tiptap/starter-kit": "3.17.1",
|
"@tiptap/starter-kit": "3.17.0",
|
||||||
"@tiptap/suggestion": "3.17.1",
|
"@tiptap/suggestion": "3.17.0",
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"bytes": "^3.1.2",
|
"bytes": "^3.1.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
@@ -79,7 +79,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nx/js": "20.4.5",
|
"@nx/js": "20.4.5",
|
||||||
"@types/bytes": "^3.1.5",
|
"@types/bytes": "^3.1.5",
|
||||||
"@types/turndown": "^5.0.6",
|
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
"nx": "20.4.5",
|
"nx": "20.4.5",
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "all"
|
|
||||||
}
|
|
||||||
@@ -99,7 +99,7 @@ export const embedProviders: IEmbedProvider[] = [
|
|||||||
id: "gsheets",
|
id: "gsheets",
|
||||||
name: "Google Sheets",
|
name: "Google Sheets",
|
||||||
regex:
|
regex:
|
||||||
/^((?:https?:)?\/\/)?((?:www|m)\.)?(docs\.google\.com)\/spreadsheets\/d\/([a-zA-Z0-9_-]+)\/.*$/,
|
/^((?:https?:)?\/\/)?((?:www|m)\.)?(docs\.google\.com)\/spreadsheets\/d\/e\/([a-zA-Z0-9_-]+)\/.*$/,
|
||||||
getEmbedUrl: (match, url: string) => {
|
getEmbedUrl: (match, url: string) => {
|
||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
export * from "./utils/marked.utils";
|
export * from "./utils/marked.utils";
|
||||||
export * from "./utils/turndown.utils";
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Flexible `basename` implementation for node and the browser
|
|
||||||
* @see https://stackoverflow.com/a/59907288/2228771
|
|
||||||
*/
|
|
||||||
export function getBasename(path: string) {
|
|
||||||
// make sure the basename is not empty, if string ends with separator
|
|
||||||
let end = path.length - 1;
|
|
||||||
while (path[end] === '/' || path[end] === '\\') {
|
|
||||||
--end;
|
|
||||||
}
|
|
||||||
|
|
||||||
// support mixing of Win + Unix path separators
|
|
||||||
const i1 = path.lastIndexOf('/', end);
|
|
||||||
const i2 = path.lastIndexOf('\\', end);
|
|
||||||
|
|
||||||
let start: number;
|
|
||||||
if (i1 === -1) {
|
|
||||||
if (i2 === -1) {
|
|
||||||
// no separator in the whole thing
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
start = i2;
|
|
||||||
} else if (i2 === -1) {
|
|
||||||
start = i1;
|
|
||||||
} else {
|
|
||||||
start = Math.max(i1, i2);
|
|
||||||
}
|
|
||||||
return path.substring(start + 1, end + 1);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
// Map @joplin/turndown types to @types/turndown
|
|
||||||
declare module "@joplin/turndown" {
|
|
||||||
import TurndownService from "turndown";
|
|
||||||
export = TurndownService;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "@joplin/turndown-plugin-gfm" {
|
|
||||||
import TurndownService from "turndown";
|
|
||||||
export const tables: TurndownService.Plugin;
|
|
||||||
export const strikethrough: TurndownService.Plugin;
|
|
||||||
export const highlightedCodeBlock: TurndownService.Plugin;
|
|
||||||
}
|
|
||||||
Generated
+1485
-923
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user