diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 0f470446ff..8122e73cac 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -4122,6 +4122,12 @@ importers: '@tiptap/extension-strike': specifier: ^2.11.7 version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + '@tiptap/extension-subscript': + specifier: ^2.11.7 + version: 2.27.2(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + '@tiptap/extension-superscript': + specifier: ^2.11.7 + version: 2.27.2(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) '@tiptap/extension-table': specifier: ^2.11.7 version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) @@ -28387,12 +28393,21 @@ importers: '@tiptap/extension-list-keymap': specifier: ^2.11.7 version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + '@tiptap/extension-mathematics': + specifier: ^2.11.7 + version: 2.27.2(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(katex@0.16.25) '@tiptap/extension-ordered-list': specifier: ^2.11.7 version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) '@tiptap/extension-placeholder': specifier: ^2.11.7 version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) + '@tiptap/extension-subscript': + specifier: ^2.11.7 + version: 2.27.2(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + '@tiptap/extension-superscript': + specifier: ^2.11.7 + version: 2.27.2(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) '@tiptap/extension-table': specifier: ^2.11.7 version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) @@ -28432,6 +28447,9 @@ importers: fast-equals: specifier: ^5.2.2 version: 5.3.2 + katex: + specifier: ^0.16.0 + version: 0.16.25 lib0: specifier: ^0.2.109 version: 0.2.114 @@ -28475,6 +28493,9 @@ importers: '@types/jest': specifier: ^29.5.5 version: 29.5.14 + '@types/katex': + specifier: ^0.16.0 + version: 0.16.8 '@typescript-eslint/eslint-plugin': specifier: ^6.21.0 version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) @@ -39062,6 +39083,12 @@ importers: '@tiptap/extension-placeholder': specifier: ^2.11.7 version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) + '@tiptap/extension-subscript': + specifier: ^2.11.7 + version: 2.27.2(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) + '@tiptap/extension-superscript': + specifier: ^2.11.7 + version: 2.27.2(@tiptap/core@2.27.1(@tiptap/pm@2.27.1)) '@tiptap/extension-table': specifier: ^2.11.7 version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1) @@ -44385,6 +44412,13 @@ packages: peerDependencies: '@tiptap/core': ^2.7.0 + '@tiptap/extension-mathematics@2.27.2': + resolution: {integrity: sha512-ouEMrA7we5p1v7SbHFSrrVQBAUb1moiUOwo0BqdCJXV79YOxdURn+8vlA2rIItO0E3Ya1UFoA1LW+AE0cYn4RQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + katex: ^0.16.4 + '@tiptap/extension-mention@2.27.1': resolution: {integrity: sha512-8qwPIum0rMK/7BaHEN0+M/VkUn00LqhqyRO8JdC/EBVrUBgxKTQsUhIhSstgVAYzD1w7cCUfTFX4bYu9lcFMEQ==} peerDependencies: @@ -44413,6 +44447,16 @@ packages: peerDependencies: '@tiptap/core': ^2.7.0 + '@tiptap/extension-subscript@2.27.2': + resolution: {integrity: sha512-x2Oz7hrI4KvzzB9pWChFRm6JnKdYAUQDyrlSROngtzXT7VpNQNoD5s8OlICzDeNsaRKzhR8omIz2z17S1VB48g==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-superscript@2.27.2': + resolution: {integrity: sha512-VTGJDuNqdesibSVW94Q71VaGVGr/bwBppdaNLn7k6beOegALfIH7ncArlkD/eihOlJ2qaWiT7FoWNLTb/Fdv1w==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/extension-table-cell@2.27.1': resolution: {integrity: sha512-VowNmz1kub2qfntWkU8jGA6DoCl9xjJBWSypuQIeiN/IRId3BMrJodT26pTNJ3ChDMtYaanWaUvYqckRxgTC2A==} peerDependencies: @@ -44792,6 +44836,9 @@ packages: '@types/jsonwebtoken@9.0.10': resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/katex@0.16.8': + resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} + '@types/keygrip@1.0.6': resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==} @@ -55388,6 +55435,12 @@ snapshots: dependencies: '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extension-mathematics@2.27.2(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(katex@0.16.25)': + dependencies: + '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/pm': 2.27.1 + katex: 0.16.25 + '@tiptap/extension-mention@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(@tiptap/suggestion@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1))': dependencies: '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) @@ -55411,6 +55464,14 @@ snapshots: dependencies: '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extension-subscript@2.27.2(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + dependencies: + '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + + '@tiptap/extension-superscript@2.27.2(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': + dependencies: + '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) + '@tiptap/extension-table-cell@2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))': dependencies: '@tiptap/core': 2.27.1(@tiptap/pm@2.27.1) @@ -55880,6 +55941,8 @@ snapshots: '@types/ms': 2.1.0 '@types/node': 22.19.0 + '@types/katex@0.16.8': {} + '@types/keygrip@1.0.6': {} '@types/keyv@3.1.4': @@ -61383,7 +61446,7 @@ snapshots: node-loader@2.0.0(webpack@5.102.1): dependencies: loader-utils: 2.0.4 - webpack: 5.102.1 + webpack: 5.102.1(esbuild@0.25.12)(webpack-cli@5.1.4) node-localstorage@2.2.1: dependencies: diff --git a/desktop/package.json b/desktop/package.json index 7fe030affb..0e47c3d7af 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@hcengineering/desktop", - "version": "0.7.208", + "version": "0.7.327", "main": "dist/main/electron.js", "template": "@hcengineering/webpack-package", "scripts": { diff --git a/foundations/core/packages/text-markdown/src/serializer.ts b/foundations/core/packages/text-markdown/src/serializer.ts index ed490608ce..dd204f3d79 100644 --- a/foundations/core/packages/text-markdown/src/serializer.ts +++ b/foundations/core/packages/text-markdown/src/serializer.ts @@ -421,6 +421,20 @@ export const storeMarks: Record = { mixable: false, expelEnclosingWhitespace: false, escape: false + }, + subscript: { + open: '', + close: '', + mixable: true, + expelEnclosingWhitespace: false, + escape: true + }, + superscript: { + open: '', + close: '', + mixable: true, + expelEnclosingWhitespace: false, + escape: true } } diff --git a/foundations/core/packages/text/package.json b/foundations/core/packages/text/package.json index f47a09c346..54f22a165a 100644 --- a/foundations/core/packages/text/package.json +++ b/foundations/core/packages/text/package.json @@ -80,7 +80,9 @@ "prosemirror-codemark": "^0.4.2", "fast-equals": "^5.2.2", "@tiptap/extension-text-align": "~2.11.0", - "@tiptap/extension-text-style": "~2.11.0" + "@tiptap/extension-text-style": "~2.11.0", + "@tiptap/extension-subscript": "^2.11.7", + "@tiptap/extension-superscript": "^2.11.7" }, "repository": { "type": "git", diff --git a/foundations/core/packages/text/src/tiptapExtensions.ts b/foundations/core/packages/text/src/tiptapExtensions.ts index ff889ad401..cff38446c1 100644 --- a/foundations/core/packages/text/src/tiptapExtensions.ts +++ b/foundations/core/packages/text/src/tiptapExtensions.ts @@ -35,6 +35,9 @@ export { ListItem, type ListItemOptions } from '@tiptap/extension-list-item' export { OrderedList, type OrderedListOptions } from '@tiptap/extension-ordered-list' export { TextAlign, type TextAlignOptions } from '@tiptap/extension-text-align' export { TaskList, type TaskListOptions } from '@tiptap/extension-task-list' +export { Highlight, type HighlightOptions } from '@tiptap/extension-highlight' +export { Subscript } from '@tiptap/extension-subscript' +export { Superscript } from '@tiptap/extension-superscript' export { Table, type TableOptions } from '@tiptap/extension-table' export { TableCell, type TableCellOptions } from '@tiptap/extension-table-cell' diff --git a/models/text-editor/src/index.ts b/models/text-editor/src/index.ts index 71c595414d..b3eda09d20 100644 --- a/models/text-editor/src/index.ts +++ b/models/text-editor/src/index.ts @@ -246,6 +246,72 @@ export function createModel (builder: Builder): void { index: 20 }) + builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, { + action: { + command: 'toggleHighlight' + }, + icon: textEditor.icon.Highlight, + visibilityTester: textEditor.function.IsEditable, + isActive: { + name: 'highlight' + }, + label: textEditor.string.Highlight, + category: 20, + index: 22 + }) + + builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, { + action: { + command: 'toggleSubscript' + }, + icon: textEditor.icon.Subscript, + visibilityTester: textEditor.function.IsEditable, + isActive: { + name: 'subscript' + }, + label: textEditor.string.Subscript, + category: 20, + index: 28 + }) + + builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, { + action: { + command: 'toggleSuperscript' + }, + icon: textEditor.icon.Superscript, + visibilityTester: textEditor.function.IsEditable, + isActive: { + name: 'superscript' + }, + label: textEditor.string.Superscript, + category: 20, + index: 30 + }) + + builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, { + action: { + command: 'insertMathInline' + }, + icon: textEditor.icon.Mathematics, + visibilityTester: textEditor.function.IsEditable, + isActive: textEditor.function.IsMathInlineActive, + label: textEditor.string.Mathematics, + category: 60, + index: 15 + }) + + builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, { + action: { + command: 'insertMathBlock' + }, + icon: textEditor.icon.MathematicsBlock, + visibilityTester: textEditor.function.IsEditable, + isActive: textEditor.function.IsMathBlockActive, + label: textEditor.string.MathematicsBlock, + category: 60, + index: 20 + }) + builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, { action: textEditor.function.SetTextColor, icon: textEditor.icon.TextStyle, diff --git a/models/text-editor/src/plugin.ts b/models/text-editor/src/plugin.ts index e654b6dda6..a8ba1d7f7a 100644 --- a/models/text-editor/src/plugin.ts +++ b/models/text-editor/src/plugin.ts @@ -60,6 +60,9 @@ export default mergeIds(textEditorId, textEditor, { CopyPreviewLinkAction: '' as Resource, SetBackgroundColor: '' as Resource, - SetTextColor: '' as Resource + SetTextColor: '' as Resource, + + IsMathInlineActive: '' as Resource, + IsMathBlockActive: '' as Resource } }) diff --git a/plugins/text-editor-assets/assets/icons.svg b/plugins/text-editor-assets/assets/icons.svg index 7abfc69e21..5e7feb3eb6 100644 --- a/plugins/text-editor-assets/assets/icons.svg +++ b/plugins/text-editor-assets/assets/icons.svg @@ -246,6 +246,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/plugins/text-editor-assets/lang/cs.json b/plugins/text-editor-assets/lang/cs.json index 232c7e726c..bfe6b9fc61 100644 --- a/plugins/text-editor-assets/lang/cs.json +++ b/plugins/text-editor-assets/lang/cs.json @@ -81,6 +81,11 @@ "UnableToLoadEmbeddedContent": "Náhled odkazu nelze načíst kvůli nastavení oprávnění nebo nepodporovanému obsahu", "CannotConnectToCollaborationService": "Nelze se připojit k službě spolupráce", "SourceURL": "Zdrojová URL", - "SelectedDocuments": "Vybrané dokumenty ({count, plural, =1 {1 položka} other {# položek}})" + "SelectedDocuments": "Vybrané dokumenty ({count, plural, =1 {1 položka} other {# položek}})", + "Highlight": "Highlight", + "Subscript": "Subscript", + "Superscript": "Superscript", + "Mathematics": "Mathematics", + "MathematicsBlock": "Mathematics block" } -} \ No newline at end of file +} diff --git a/plugins/text-editor-assets/lang/de.json b/plugins/text-editor-assets/lang/de.json index 1b8204d1a2..22a76b48df 100644 --- a/plugins/text-editor-assets/lang/de.json +++ b/plugins/text-editor-assets/lang/de.json @@ -80,6 +80,11 @@ "UnableToLoadEmbeddedContent": "Die Linkvorschau konnte aufgrund von Berechtigungseinstellungen oder nicht unterstütztem Inhalt nicht geladen werden", "CannotConnectToCollaborationService": "Kann keine Verbindung zum Kollaborationsdienst herstellen", "SourceURL": "Quell-URL", - "SelectedDocuments": "Ausgewählte Dokumente ({count, plural, =1 {1 Element} other {# Elemente}})" + "SelectedDocuments": "Ausgewählte Dokumente ({count, plural, =1 {1 Element} other {# Elemente}})", + "Highlight": "Highlight", + "Subscript": "Subscript", + "Superscript": "Superscript", + "Mathematics": "MathematiK", + "MathematicsBlock": "Mathematik Block" } -} \ No newline at end of file +} diff --git a/plugins/text-editor-assets/lang/en.json b/plugins/text-editor-assets/lang/en.json index d84d1460ca..1bc5268d6a 100644 --- a/plugins/text-editor-assets/lang/en.json +++ b/plugins/text-editor-assets/lang/en.json @@ -29,6 +29,11 @@ "GIF": "GIF", "Mention": "Mention", "Underlined": "Underlined", + "Highlight": "Highlight", + "Subscript": "Subscript", + "Superscript": "Superscript", + "Mathematics": "Mathematics", + "MathematicsBlock": "Mathematics block", "AlignCenter": "Align center", "AlignLeft": "Align left", "AlignRight": "Align right", diff --git a/plugins/text-editor-assets/lang/es.json b/plugins/text-editor-assets/lang/es.json index 0056c25fa5..a535a97525 100644 --- a/plugins/text-editor-assets/lang/es.json +++ b/plugins/text-editor-assets/lang/es.json @@ -21,7 +21,7 @@ "Update": "Actualizar", "Remove": "Eliminar", "NotePlaceholder": "Añadir una nota...", - "SampleText": "Texto de ejemplo", + "SampleText": "Texto de ejemplo", "Send": "Enviar", "Attach": "Adjuntar", "TextStyle": "Estilo de texto", @@ -71,6 +71,11 @@ "UnableToLoadEmbeddedContent": "No se pudo cargar la vista previa del enlace debido a la configuración de permisos o contenido no compatible", "CannotConnectToCollaborationService": "No se puede conectar con el colaborador", "SourceURL": "URL de origen", - "SelectedDocuments": "Documentos seleccionados ({count, plural, =1 {1 elemento} other {# elementos}})" + "SelectedDocuments": "Documentos seleccionados ({count, plural, =1 {1 elemento} other {# elementos}})", + "Highlight": "Highlight", + "Subscript": "Subscript", + "Superscript": "Superscript", + "Mathematics": "Mathematics", + "MathematicsBlock": "Mathematics block" } -} \ No newline at end of file +} diff --git a/plugins/text-editor-assets/lang/fr.json b/plugins/text-editor-assets/lang/fr.json index 6910691a08..55ad149e20 100644 --- a/plugins/text-editor-assets/lang/fr.json +++ b/plugins/text-editor-assets/lang/fr.json @@ -71,6 +71,11 @@ "UnableToLoadEmbeddedContent": "L’aperçu du lien n’a pas pu être chargé en raison des paramètres d’autorisation ou d’un contenu non pris en charge", "CannotConnectToCollaborationService": "Impossible de se connecter au collaborateur", "SourceURL": "URL source", - "SelectedDocuments": "Documents sélectionnés ({count, plural, =1 {1 élément} other {# éléments}})" + "SelectedDocuments": "Documents sélectionnés ({count, plural, =1 {1 élément} other {# éléments}})", + "Highlight": "Highlight", + "Subscript": "Subscript", + "Superscript": "Superscript", + "Mathematics": "Mathematics", + "MathematicsBlock": "Mathematics block" } -} \ No newline at end of file +} diff --git a/plugins/text-editor-assets/lang/it.json b/plugins/text-editor-assets/lang/it.json index 4c3762628e..80dc05a786 100644 --- a/plugins/text-editor-assets/lang/it.json +++ b/plugins/text-editor-assets/lang/it.json @@ -80,6 +80,11 @@ "UnableToLoadEmbeddedContent": "Impossibile caricare l'anteprima del link a causa delle impostazioni dei permessi o di contenuto non supportato", "CannotConnectToCollaborationService": "Impossibile connettersi al servizio di collaborazione", "SourceURL": "URL di origine", - "SelectedDocuments": "Documenti selezionati ({count, plural, =1 {1 elemento} other {# elementi}})" + "SelectedDocuments": "Documenti selezionati ({count, plural, =1 {1 elemento} other {# elementi}})", + "Highlight": "Highlight", + "Subscript": "Subscript", + "Superscript": "Superscript", + "Mathematics": "Mathematics", + "MathematicsBlock": "Mathematics block" } -} \ No newline at end of file +} diff --git a/plugins/text-editor-assets/lang/ja.json b/plugins/text-editor-assets/lang/ja.json index 7a5dd59e33..d864a854af 100644 --- a/plugins/text-editor-assets/lang/ja.json +++ b/plugins/text-editor-assets/lang/ja.json @@ -81,6 +81,11 @@ "UnableToLoadEmbeddedContent": "リンクのプレビューを読み込めません。権限設定または非対応のコンテンツが原因です", "CannotConnectToCollaborationService": "コラボレーションサービスに接続できません", "SourceURL": "ソースURL", - "SelectedDocuments": "選択されたドキュメント ({count, plural, =1 {1 項目} other {# 項目}})" + "SelectedDocuments": "選択されたドキュメント ({count, plural, =1 {1 項目} other {# 項目}})", + "Highlight": "Highlight", + "Subscript": "Subscript", + "Superscript": "Superscript", + "Mathematics": "Mathematics", + "MathematicsBlock": "Mathematics block" } } diff --git a/plugins/text-editor-assets/lang/pt-br.json b/plugins/text-editor-assets/lang/pt-br.json index 5da1bd110a..1f29f9844c 100644 --- a/plugins/text-editor-assets/lang/pt-br.json +++ b/plugins/text-editor-assets/lang/pt-br.json @@ -71,6 +71,11 @@ "UnableToLoadEmbeddedContent": "Não foi possível carregar a pré-visualização do link devido às permissões ou a conteúdo não suportado", "CannotConnectToCollaborationService": "Não foi possível conectar ao serviço de colaboração", "SourceURL": "URL de origem", - "SelectedDocuments": "Documentos selecionados ({count, plural, =1 {1 item} other {# itens}})" + "SelectedDocuments": "Documentos selecionados ({count, plural, =1 {1 item} other {# itens}})", + "Highlight": "Highlight", + "Subscript": "Subscript", + "Superscript": "Superscript", + "Mathematics": "Mathematics", + "MathematicsBlock": "Mathematics block" } } diff --git a/plugins/text-editor-assets/lang/pt.json b/plugins/text-editor-assets/lang/pt.json index 5da1bd110a..1f29f9844c 100644 --- a/plugins/text-editor-assets/lang/pt.json +++ b/plugins/text-editor-assets/lang/pt.json @@ -71,6 +71,11 @@ "UnableToLoadEmbeddedContent": "Não foi possível carregar a pré-visualização do link devido às permissões ou a conteúdo não suportado", "CannotConnectToCollaborationService": "Não foi possível conectar ao serviço de colaboração", "SourceURL": "URL de origem", - "SelectedDocuments": "Documentos selecionados ({count, plural, =1 {1 item} other {# itens}})" + "SelectedDocuments": "Documentos selecionados ({count, plural, =1 {1 item} other {# itens}})", + "Highlight": "Highlight", + "Subscript": "Subscript", + "Superscript": "Superscript", + "Mathematics": "Mathematics", + "MathematicsBlock": "Mathematics block" } } diff --git a/plugins/text-editor-assets/lang/ru.json b/plugins/text-editor-assets/lang/ru.json index b388c4c810..fd2bc0d695 100644 --- a/plugins/text-editor-assets/lang/ru.json +++ b/plugins/text-editor-assets/lang/ru.json @@ -81,6 +81,11 @@ "UnableToLoadEmbeddedContent": "Не удалось загрузить превью ссылки из-за настроек доступа или неподдерживаемого содержимого", "CannotConnectToCollaborationService": "Не удалось подключиться к сервису совместной работы", "SourceURL": "Исходный URL", - "SelectedDocuments": "Выбранные документы ({count, plural, =1 {1 элемент} other {# элементов}})" + "SelectedDocuments": "Выбранные документы ({count, plural, =1 {1 элемент} other {# элементов}})", + "Highlight": "Highlight", + "Subscript": "Subscript", + "Superscript": "Superscript", + "Mathematics": "Mathematics", + "MathematicsBlock": "Mathematics block" } -} \ No newline at end of file +} diff --git a/plugins/text-editor-assets/lang/tr.json b/plugins/text-editor-assets/lang/tr.json index aa8b8cc281..f250809d03 100644 --- a/plugins/text-editor-assets/lang/tr.json +++ b/plugins/text-editor-assets/lang/tr.json @@ -81,6 +81,11 @@ "UnableToLoadEmbeddedContent": "Bağlantı önizlemesi izin ayarları veya desteklenmeyen içerik nedeniyle yüklenemedi", "CannotConnectToCollaborationService": "İşbirliği servisine bağlanılamıyor", "SourceURL": "Kaynak URL", - "SelectedDocuments": "Seçilen belgeler ({count, plural, =1 {1 öğe} other {# öğe}})" + "SelectedDocuments": "Seçilen belgeler ({count, plural, =1 {1 öğe} other {# öğe}})", + "Highlight": "Highlight", + "Subscript": "Subscript", + "Superscript": "Superscript", + "Mathematics": "Mathematics", + "MathematicsBlock": "Mathematics block" } } diff --git a/plugins/text-editor-assets/lang/zh.json b/plugins/text-editor-assets/lang/zh.json index bb286a005f..9052691ca4 100644 --- a/plugins/text-editor-assets/lang/zh.json +++ b/plugins/text-editor-assets/lang/zh.json @@ -39,7 +39,6 @@ "FullDescription": "完整描述", "NoFullDescription": "没有详细描述", "EnableDiffMode": "差异模式", - "AddColumnBefore": "在前面添加", "AddColumnAfter": "在后面添加", "DeleteColumn": "删除", @@ -48,7 +47,6 @@ "DeleteRow": "删除", "DeleteTable": "删除", "Duplicate": "重复", - "CategoryRow": "行", "CategoryColumn": "列", "Table": "表格", @@ -73,6 +71,11 @@ "UnableToLoadEmbeddedContent": "由于权限设置或不支持的内容,无法加载链接预览", "CannotConnectToCollaborationService": "无法连接到协作者", "SourceURL": "源URL", - "SelectedDocuments": "选定的文档 ({count, plural, =1 {1 项} other {# 项}})" + "SelectedDocuments": "选定的文档 ({count, plural, =1 {1 项} other {# 项}})", + "Highlight": "Highlight", + "Subscript": "Subscript", + "Superscript": "Superscript", + "Mathematics": "Mathematics", + "MathematicsBlock": "Mathematics block" } } diff --git a/plugins/text-editor-assets/src/index.ts b/plugins/text-editor-assets/src/index.ts index 0ab87536ce..2d79aac010 100644 --- a/plugins/text-editor-assets/src/index.ts +++ b/plugins/text-editor-assets/src/index.ts @@ -46,6 +46,11 @@ loadMetadata(textEditor.icon, { MergeCells: `${icons}#union`, SplitCells: `${icons}#divide`, Brush: `${icons}#brush`, + Highlight: `${icons}#highlight`, + Subscript: `${icons}#subscript`, + Superscript: `${icons}#superscript`, + Mathematics: `${icons}#mathematics`, + MathematicsBlock: `${icons}#mathematicsBlock`, TextStyle: `${icons}#textStyle`, LinkPreview: `${icons}#link`, Refresh: `${icons}#refresh`, diff --git a/plugins/text-editor-resources/package.json b/plugins/text-editor-resources/package.json index aa438e696a..653283bf0e 100644 --- a/plugins/text-editor-resources/package.json +++ b/plugins/text-editor-resources/package.json @@ -36,6 +36,7 @@ "ts-jest": "^29.1.1", "@types/jest": "^29.5.5", "@types/diff": "~5.0.2", + "@types/katex": "^0.16.0", "svelte-eslint-parser": "^0.33.1" }, "dependencies": { @@ -73,6 +74,8 @@ "@tiptap/extension-code": "^2.11.7", "@tiptap/extension-code-block": "^2.11.7", "@tiptap/extension-highlight": "^2.11.7", + "@tiptap/extension-subscript": "^2.11.7", + "@tiptap/extension-superscript": "^2.11.7", "@tiptap/extension-typography": "^2.11.7", "@tiptap/extension-link": "^2.11.7", "@tiptap/extension-ordered-list": "^2.11.7", @@ -98,6 +101,8 @@ "tippy.js": "~6.3.7", "@hcengineering/chunter": "workspace:^0.7.0", "@tiptap/extension-text-align": "~2.11.0", + "@tiptap/extension-mathematics": "^2.11.7", + "katex": "^0.16.0", "@hcengineering/workbench": "workspace:^0.7.0", "@hcengineering/drive": "workspace:^0.7.0", "@hcengineering/time": "workspace:^0.7.0", diff --git a/plugins/text-editor-resources/src/components/extension/mathematics.ts b/plugins/text-editor-resources/src/components/extension/mathematics.ts new file mode 100644 index 0000000000..25f00adc22 --- /dev/null +++ b/plugins/text-editor-resources/src/components/extension/mathematics.ts @@ -0,0 +1,301 @@ +// +// Copyright © 2025 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { type Editor } from '@tiptap/core' +import Mathematics from '@tiptap/extension-mathematics' +import katex from 'katex' +import { type Node } from '@tiptap/pm/model' +import { type EditorState, Plugin, PluginKey, type Transaction, TextSelection } from '@tiptap/pm/state' +import { Decoration, DecorationSet } from '@tiptap/pm/view' + +declare module '@tiptap/core' { + interface Commands { + mathematics: { + insertMathInline: () => ReturnType + insertMathBlock: () => ReturnType + } + } +} + +function renderKatex (content: string, element: HTMLElement, options: katex.KatexOptions): void { + try { + katex.render(content, element, options) + } catch { + element.innerHTML = content + } +} + +function findInlineMathBoundaries (state: EditorState): { before: number, after: number } | null { + const { from } = state.selection + const $pos = state.doc.resolve(from) + const parentStart = $pos.start() + const parent = $pos.parent + const text = parent.textBetween(0, parent.content.size, null, '\n') + const posInParent = from - parentStart + const before = text.lastIndexOf('$', posInParent - 1) + if (before === -1) return null + if (before > 0 && text[before - 1] === '$') return null + const after = text.indexOf('$', posInParent) + if (after === -1 || after < posInParent) return null + if (after + 1 < text.length && text[after + 1] === '$') return null + return { before: parentStart + before, after: parentStart + after } +} + +function wrapWithMath ( + state: EditorState, + dispatch: ((tr: Transaction) => void) | undefined, + delimiter: string +): boolean { + const { selection } = state + const { from, to } = selection + const dLen = delimiter.length + + const text = state.doc.textBetween(from, to) + const charsBefore = from >= dLen ? state.doc.textBetween(from - dLen, from) : '' + const charsAfter = to + dLen <= state.doc.content.size ? state.doc.textBetween(to, to + dLen) : '' + + if (charsBefore === delimiter && charsAfter === delimiter) { + if (dispatch !== undefined) dispatch(state.tr.delete(to, to + dLen).delete(from - dLen, from)) + return true + } + + const longerDelimiter = delimiter.repeat(2) + if ( + text.startsWith(delimiter) && + text.endsWith(delimiter) && + !text.startsWith(longerDelimiter) && + !text.endsWith(longerDelimiter) + ) { + if (dispatch !== undefined) dispatch(state.tr.delete(to - dLen, to).delete(from, from + dLen)) + return true + } + + if (dispatch !== undefined) dispatch(state.tr.insertText(delimiter, to).insertText(delimiter, from)) + return true +} + +export async function isMathInlineActive (editor: Editor): Promise { + const { state } = editor + const { selection } = state + const { from, to } = selection + if (!selection.empty) { + const text = state.doc.textBetween(from, to) + if (text.startsWith('$') && !text.startsWith('$$') && text.endsWith('$') && !text.endsWith('$$')) return true + const before = from > 0 ? state.doc.textBetween(from - 1, from) : '' + const after = to < state.doc.content.size ? state.doc.textBetween(to, to + 1) : '' + if (before === '$' && after === '$') { + const charBefore2 = from > 1 ? state.doc.textBetween(from - 2, from - 1) : '' + const charAfter2 = to + 1 < state.doc.content.size ? state.doc.textBetween(to + 1, to + 2) : '' + return charBefore2 !== '$' && charAfter2 !== '$' + } + return false + } + return findInlineMathBoundaries(state) !== null +} + +export async function isMathBlockActive (editor: Editor): Promise { + const { state } = editor + const { selection } = state + const { from, to } = selection + if (!selection.empty) { + const text = state.doc.textBetween(from, to) + if (text.startsWith('$$') && text.endsWith('$$')) return true + const before = from >= 2 ? state.doc.textBetween(from - 2, from) : '' + const after = to + 2 <= state.doc.content.size ? state.doc.textBetween(to, to + 2) : '' + return before === '$$' && after === '$$' + } + const $pos = state.doc.resolve(from) + const parentStart = $pos.start() + const parent = $pos.parent + const text = parent.textBetween(0, parent.content.size, null, '\n') + const posInParent = from - parentStart + const before = text.lastIndexOf('$$', posInParent - 1) + if (before === -1) return false + const after = text.indexOf('$$', posInParent) + return after !== -1 && after >= posInParent +} + +function createMathPlugin ( + regex: RegExp, + katexOptions: katex.KatexOptions, + editor: Editor, + shouldRender: (state: EditorState, pos: number, node: Node) => boolean +): Plugin { + return new Plugin({ + key: new PluginKey('mathematics-' + regex.source), + state: { + init () { + return { decorations: undefined as DecorationSet | undefined } + }, + apply (tr, prev, _oldState, newState) { + if (!tr.docChanged && !tr.selectionSet && prev.decorations !== undefined) { + return prev + } + + const { selection } = newState + const isEditable = editor.isEditable + const decorations: Decoration[] = [] + + newState.doc.nodesBetween(0, newState.doc.content.size, (node, pos) => { + if (!node.inlineContent) return true + if (!shouldRender(newState, pos, node)) return false + + const text = node.textBetween(0, node.content.size, null, '\n') + regex.lastIndex = 0 + let match: RegExpExecArray | null + + while ((match = regex.exec(text)) !== null) { + const from = pos + 1 + match.index + const to = from + match[0].length + const content = match.slice(1).find(Boolean) + if (content === undefined) continue + + const isCollapsed = selection.from === selection.to + const anchorIsInside = selection.anchor >= from && selection.anchor <= to + const rangeIsInside = selection.from >= from && selection.to <= to + const isEditing = (isCollapsed && anchorIsInside) || rangeIsInside + + decorations.push( + Decoration.inline( + from, + to, + { + class: + isEditing && isEditable + ? 'Tiptap-mathematics-editor' + : 'Tiptap-mathematics-editor Tiptap-mathematics-editor--hidden', + style: + !isEditing || !isEditable + ? 'display: inline-block; height: 0; opacity: 0; overflow: hidden; position: absolute; width: 0;' + : undefined + }, + { content, isEditable, isEditing } + ) + ) + + if (!isEditable || !isEditing) { + decorations.push( + Decoration.widget( + from, + () => { + const element = document.createElement('span') + element.classList.add('Tiptap-mathematics-render') + if (isEditable) element.classList.add('Tiptap-mathematics-render--editable') + renderKatex(content, element, katexOptions) + return element + }, + { content, isEditable, isEditing } + ) + ) + } + } + + return false + }) + + return { decorations: DecorationSet.create(newState.doc, decorations) } + } + }, + props: { + decorations (state) { + return this.getState(state)?.decorations ?? DecorationSet.empty + } + } + }) +} + +function MathPastePlugin (): Plugin { + const LATEX_RE = /\$\$?[^$]+\$\$?/ + return new Plugin({ + props: { + handleDOMEvents: { + paste (view, event) { + const clipboardData = event.clipboardData + if (clipboardData === null) return false + + const text = clipboardData.getData('text/plain') + if (text === '' || !LATEX_RE.test(text)) return false + + event.preventDefault() + view.dispatch(view.state.tr.insertText(text)) + return true + } + } + } + }) +} + +const shouldRender = (_state: EditorState, _pos: number, node: Node): boolean => { + return node.type.name !== 'codeBlock' +} + +export const MathematicsExtension = Mathematics.extend({ + addCommands () { + return { + insertMathInline: + () => + ({ state, dispatch }) => { + const { selection } = state + const { from } = selection + + if (selection.empty) { + const boundaries = findInlineMathBoundaries(state) + if (boundaries !== null) { + if (dispatch !== undefined) { + dispatch( + state.tr + .delete(boundaries.after, boundaries.after + 1) + .delete(boundaries.before, boundaries.before + 1) + ) + } + return true + } + const tr = state.tr.insertText('$ $', from) + if (dispatch !== undefined) dispatch(tr.setSelection(TextSelection.create(tr.doc, from + 2))) + return true + } + + return wrapWithMath(state, dispatch, '$') + }, + + insertMathBlock: + () => + ({ state, dispatch }) => { + const { selection } = state + const { from } = selection + + if (selection.empty) { + const tr = state.tr.insertText('$$ $$', from) + if (dispatch !== undefined) dispatch(tr.setSelection(TextSelection.create(tr.doc, from + 3))) + return true + } + + return wrapWithMath(state, dispatch, '$$') + } + } + }, + addProseMirrorPlugins () { + return [ + createMathPlugin(/\$\$([\s\S]+?)\$\$/g, { displayMode: true, throwOnError: false }, this.editor, shouldRender), + createMathPlugin( + /(? => ({ CopyPreviewLinkAction: copyPreviewLinkAction, SetBackgroundColor: openBackgroundColorOptions, - SetTextColor: openTextColorOptions + SetTextColor: openTextColorOptions, + + IsMathInlineActive: isMathInlineActive, + IsMathBlockActive: isMathBlockActive } }) diff --git a/plugins/text-editor-resources/src/kits/editor-kit.ts b/plugins/text-editor-resources/src/kits/editor-kit.ts index db441759dc..7429987d4f 100644 --- a/plugins/text-editor-resources/src/kits/editor-kit.ts +++ b/plugins/text-editor-resources/src/kits/editor-kit.ts @@ -23,12 +23,16 @@ import { CommonListKitFactory, extensionKit, mergeKitOptions, - TextColorStylingKit + TextColorStylingKit, + Highlight, + Subscript, + Superscript } from '@hcengineering/text' import textEditor, { type ActionContext, type ExtensionCreator, type TextEditorMode } from '@hcengineering/text-editor' import { type AnyExtension, Extension } from '@tiptap/core' import TableHeader from '@tiptap/extension-table-header' import 'prosemirror-codemark/dist/codemark.css' +import 'katex/dist/katex.min.css' import Collaboration from '@tiptap/extension-collaboration' import CollaborationCursor from '@tiptap/extension-collaboration-cursor' @@ -67,6 +71,7 @@ import { ToolbarExtension } from '../components/extension/toolbar/toolbar' import { ListKeymapExtension } from '../components/extension/shortcuts/listKeymap' import { I18nPlaceholderExtension } from '../components/extension/i18nPlaceholder' import { EditorContextExtension } from '../components/extension/editorContext' +import { MathematicsExtension } from '../components/extension/mathematics' export interface EditorKitContext { mode?: 'full' | 'compact' @@ -101,6 +106,10 @@ const StaticEditorKit = extensionKit( file: e(FileExtension, { inline: true }), image: e(ImageExtension), emoji: e(EmojiExtension), + mathematics: e(MathematicsExtension, context.mode === 'full'), + highlight: e(Highlight, { multicolor: false }), + subscript: e(Subscript), + superscript: e(Superscript), drawingBoard: e(DrawingBoardExtension), embed: e( EmbedNode, diff --git a/plugins/text-editor/src/plugin.ts b/plugins/text-editor/src/plugin.ts index b74dba97c2..b5193e38b0 100644 --- a/plugins/text-editor/src/plugin.ts +++ b/plugins/text-editor/src/plugin.ts @@ -43,6 +43,11 @@ export default plugin(textEditorId, { GIF: '' as IntlString, Mention: '' as IntlString, Underlined: '' as IntlString, + Highlight: '' as IntlString, + Subscript: '' as IntlString, + Superscript: '' as IntlString, + Mathematics: '' as IntlString, + MathematicsBlock: '' as IntlString, EditorPlaceholder: '' as IntlString, Edit: '' as IntlString, Bold: '' as IntlString, @@ -150,6 +155,11 @@ export default plugin(textEditorId, { MergeCells: '' as Asset, SplitCells: '' as Asset, Brush: '' as Asset, + Highlight: '' as Asset, + Subscript: '' as Asset, + Superscript: '' as Asset, + Mathematics: '' as Asset, + MathematicsBlock: '' as Asset, TextStyle: '' as Asset, LinkPreview: '' as Asset, Refresh: '' as Asset, diff --git a/services/github/pod-github/package.json b/services/github/pod-github/package.json index 5ef2266136..e33f8d7a53 100644 --- a/services/github/pod-github/package.json +++ b/services/github/pod-github/package.json @@ -92,6 +92,8 @@ "@tiptap/pm": "^2.11.7", "@tiptap/starter-kit": "^2.11.7", "@tiptap/extension-highlight": "^2.11.7", + "@tiptap/extension-subscript": "^2.11.7", + "@tiptap/extension-superscript": "^2.11.7", "@tiptap/extension-placeholder": "^2.11.7", "@tiptap/extension-mention": "^2.11.7", "@tiptap/extension-typography": "^2.11.7",