@sequent-org/moodboard 1.4.32 → 1.4.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -1
- package/src/assets/fonts/inter/inter-cyrillic-400-normal.woff2 +0 -0
- package/src/assets/fonts/inter/inter-cyrillic-500-normal.woff2 +0 -0
- package/src/assets/fonts/inter/inter-latin-400-normal.woff2 +0 -0
- package/src/assets/fonts/inter/inter-latin-500-normal.woff2 +0 -0
- package/src/assets/icons/attachments.svg +3 -1
- package/src/assets/icons/comments.svg +2 -2
- package/src/assets/icons/connector.svg +6 -0
- package/src/assets/icons/emoji.svg +6 -1
- package/src/assets/icons/frame.svg +4 -1
- package/src/assets/icons/image.svg +5 -1
- package/src/assets/icons/laser.svg +1 -0
- package/src/assets/icons/lasso.svg +5 -0
- package/src/assets/icons/mindmap.svg +10 -2
- package/src/assets/icons/note.svg +4 -1
- package/src/assets/icons/pan.svg +5 -2
- package/src/assets/icons/pencil.svg +4 -1
- package/src/assets/icons/reactions.svg +5 -0
- package/src/assets/icons/redo.svg +3 -2
- package/src/assets/icons/select.svg +2 -8
- package/src/assets/icons/shapes.svg +5 -1
- package/src/assets/icons/text-add.svg +15 -1
- package/src/assets/icons/undo.svg +3 -2
- package/src/assets/reactions/1f44d.svg +20 -0
- package/src/assets/reactions/1f44e.svg +20 -0
- package/src/assets/reactions/2705.svg +20 -0
- package/src/assets/reactions/274c.svg +19 -0
- package/src/assets/reactions/2753.svg +20 -0
- package/src/assets/reactions/2764.svg +22 -0
- package/src/assets/reactions/2b50.svg +19 -0
- package/src/assets/reactions/plus-one.svg +25 -0
- package/src/core/PixiEngine.js +23 -0
- package/src/core/bootstrap/CoreInitializer.js +43 -0
- package/src/core/commands/GroupDeleteCommand.js +13 -1
- package/src/core/commands/UpdateShapeStyleCommand.js +121 -0
- package/src/core/commands/UpdateTextStyleCommand.js +17 -6
- package/src/core/commands/index.js +3 -0
- package/src/core/events/Events.js +22 -0
- package/src/core/flows/LayerAndViewportFlow.js +1 -0
- package/src/core/flows/ObjectLifecycleFlow.js +155 -7
- package/src/core/index.js +28 -1
- package/src/grid/CrossGridZoomPhases.js +3 -3
- package/src/initNoBundler.js +1 -1
- package/src/moodboard/DataManager.js +28 -0
- package/src/moodboard/MoodBoard.js +27 -0
- package/src/moodboard/bootstrap/MoodBoardInitializer.js +69 -1
- package/src/moodboard/bootstrap/MoodBoardUiFactory.js +22 -4
- package/src/moodboard/integration/MoodBoardEventBindings.js +5 -1
- package/src/moodboard/integration/MoodBoardLoadApi.js +10 -1
- package/src/moodboard/lifecycle/MoodBoardDestroyer.js +9 -0
- package/src/objects/ConnectorObject.js +2 -2
- package/src/objects/FrameObject.js +119 -59
- package/src/objects/ShapeObject.js +49 -74
- package/src/objects/shape/ShapeDrawer.js +210 -0
- package/src/services/ConnectorBindingResolver.js +112 -0
- package/src/services/ConnectorRouter.js +210 -0
- package/src/services/comments/CommentService.js +344 -0
- package/src/tools/object-tools/CommentTool.js +85 -0
- package/src/tools/object-tools/DrawingTool.js +110 -10
- package/src/tools/object-tools/LaserPointerTool.js +121 -0
- package/src/tools/object-tools/SelectTool.js +25 -1
- package/src/tools/object-tools/TextTool.js +6 -1
- package/src/tools/object-tools/connector/ConnectorDragController.js +50 -3
- package/src/tools/object-tools/connector/connectorGesture.js +33 -19
- package/src/tools/object-tools/placement/PlacementInputRouter.js +22 -1
- package/src/tools/object-tools/selection/BoxSelectController.js +24 -2
- package/src/tools/object-tools/selection/FrameTitleInlineEditorController.js +139 -0
- package/src/tools/object-tools/selection/InlineEditorController.js +12 -0
- package/src/tools/object-tools/selection/InlineEditorDomFactory.js +36 -0
- package/src/tools/object-tools/selection/LassoSelectController.js +125 -0
- package/src/tools/object-tools/selection/MindmapInlineEditorController.js +1 -0
- package/src/tools/object-tools/selection/SelectInputRouter.js +64 -5
- package/src/tools/object-tools/selection/SelectToolLifecycleController.js +11 -1
- package/src/tools/object-tools/selection/SelectToolSetup.js +13 -1
- package/src/tools/object-tools/selection/TextEditorInteractionController.js +46 -12
- package/src/tools/object-tools/selection/TextEditorSyncService.js +1 -0
- package/src/tools/object-tools/selection/TextInlineEditorController.js +65 -6
- package/src/ui/CommentPopover.js +6 -0
- package/src/ui/CommentsBar.js +91 -0
- package/src/ui/ConnectorPropertiesPanel.js +150 -0
- package/src/ui/ContextMenu.js +25 -0
- package/src/ui/DrawingPropertiesPanel.js +362 -0
- package/src/ui/FilePropertiesPanel.js +5 -0
- package/src/ui/FramePropertiesPanel.js +5 -0
- package/src/ui/HtmlTextLayer.js +246 -66
- package/src/ui/NotePropertiesPanel.js +6 -0
- package/src/ui/ShapePropertiesPanel.js +307 -0
- package/src/ui/TextPropertiesPanel.js +100 -1
- package/src/ui/Toolbar.js +25 -2
- package/src/ui/Topbar.js +2 -2
- package/src/ui/animation/HoverLiftController.js +6 -7
- package/src/ui/chat/ChatComposer.js +58 -7
- package/src/ui/chat/ChatWindow.js +60 -143
- package/src/ui/comments/CommentListPanel.js +213 -0
- package/src/ui/comments/CommentPinLayer.js +448 -0
- package/src/ui/comments/CommentThreadPopover.js +539 -0
- package/src/ui/comments/commentFormat.js +32 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelBindings.js +223 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelEventBridge.js +114 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelMapper.js +144 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelRenderer.js +447 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelState.js +61 -0
- package/src/ui/connectors/ConnectionAnchorsLayer.js +1 -0
- package/src/ui/connectors/ConnectorHandlesLayer.js +321 -0
- package/src/ui/connectors/ConnectorLabelLayer.js +334 -0
- package/src/ui/connectors/ConnectorLayer.js +264 -57
- package/src/ui/handles/HandlesDomRenderer.js +5 -13
- package/src/ui/handles/HandlesEventBridge.js +1 -0
- package/src/ui/handles/SingleSelectionHandlesController.js +4 -0
- package/src/ui/mindmap/MindmapCollapseLayer.js +1 -0
- package/src/ui/mindmap/MindmapConnectionLayer.js +1 -0
- package/src/ui/mindmap/MindmapHtmlTextLayer.js +6 -0
- package/src/ui/shape-properties/ShapePropertiesPanelDom.js +533 -0
- package/src/ui/shape-properties/ShapePropertiesPanelSync.js +132 -0
- package/src/ui/styles/chat.css +709 -19
- package/src/ui/styles/index.css +1 -0
- package/src/ui/styles/panels.css +112 -2
- package/src/ui/styles/shape-properties-panel.css +250 -0
- package/src/ui/styles/toolbar.css +7 -2
- package/src/ui/styles/topbar.css +1 -1
- package/src/ui/styles/workspace.css +257 -6
- package/src/ui/text-properties/TextFormatControls.js +88 -0
- package/src/ui/text-properties/TextListRenderer.js +137 -0
- package/src/ui/text-properties/TextPropertiesPanelBindings.js +27 -0
- package/src/ui/text-properties/TextPropertiesPanelEventBridge.js +3 -1
- package/src/ui/text-properties/TextPropertiesPanelMapper.js +56 -0
- package/src/ui/text-properties/TextPropertiesPanelRenderer.js +24 -0
- package/src/ui/text-properties/TextPropertiesPanelState.js +8 -0
- package/src/ui/toolbar/ReactionsPopupController.js +88 -0
- package/src/ui/toolbar/ToolbarActionRouter.js +71 -5
- package/src/ui/toolbar/ToolbarPopupsController.js +120 -118
- package/src/ui/toolbar/ToolbarRenderer.js +9 -1
- package/src/ui/toolbar/ToolbarStateController.js +4 -1
- package/src/utils/iconLoader.js +17 -16
- package/src/utils/markdown.js +14 -0
- package/src/utils/richText.js +125 -0
|
@@ -14,9 +14,12 @@ export class ToolbarStateController {
|
|
|
14
14
|
|
|
15
15
|
const map = {
|
|
16
16
|
select: 'select',
|
|
17
|
+
lasso: 'lasso',
|
|
17
18
|
pan: 'pan',
|
|
18
19
|
draw: 'pencil',
|
|
19
|
-
|
|
20
|
+
laser: 'laser',
|
|
21
|
+
text: 'text-add',
|
|
22
|
+
connector: 'connector'
|
|
20
23
|
};
|
|
21
24
|
|
|
22
25
|
let btnId = map[toolName];
|
package/src/utils/iconLoader.js
CHANGED
|
@@ -22,6 +22,7 @@ export class IconLoader {
|
|
|
22
22
|
import('../assets/icons/image.svg?raw'),
|
|
23
23
|
import('../assets/icons/shapes.svg?raw'),
|
|
24
24
|
import('../assets/icons/pencil.svg?raw'),
|
|
25
|
+
import('../assets/icons/connector.svg?raw'),
|
|
25
26
|
import('../assets/icons/comments.svg?raw'),
|
|
26
27
|
import('../assets/icons/attachments.svg?raw'),
|
|
27
28
|
import('../assets/icons/emoji.svg?raw'),
|
|
@@ -29,14 +30,17 @@ export class IconLoader {
|
|
|
29
30
|
import('../assets/icons/clear.svg?raw'),
|
|
30
31
|
import('../assets/icons/undo.svg?raw'),
|
|
31
32
|
import('../assets/icons/redo.svg?raw'),
|
|
32
|
-
import('../assets/icons/mindmap.svg?raw')
|
|
33
|
+
import('../assets/icons/mindmap.svg?raw'),
|
|
34
|
+
import('../assets/icons/lasso.svg?raw'),
|
|
35
|
+
import('../assets/icons/laser.svg?raw'),
|
|
36
|
+
import('../assets/icons/reactions.svg?raw')
|
|
33
37
|
]);
|
|
34
38
|
|
|
35
39
|
// Сохраняем иконки в кэш
|
|
36
40
|
const iconNames = [
|
|
37
41
|
'select', 'pan', 'text-add', 'note', 'image', 'shapes',
|
|
38
|
-
'pencil', 'comments', 'attachments', 'emoji', 'frame',
|
|
39
|
-
'clear', 'undo', 'redo', 'mindmap'
|
|
42
|
+
'pencil', 'connector', 'comments', 'attachments', 'emoji', 'frame',
|
|
43
|
+
'clear', 'undo', 'redo', 'mindmap', 'lasso', 'laser', 'reactions'
|
|
40
44
|
];
|
|
41
45
|
|
|
42
46
|
iconNames.forEach((name, index) => {
|
|
@@ -74,11 +78,7 @@ export class IconLoader {
|
|
|
74
78
|
<path d="M8 5L8 19M16 5L16 19M5 8L19 8M5 16L19 16"
|
|
75
79
|
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
76
80
|
</svg>`,
|
|
77
|
-
'text-add':
|
|
78
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
|
79
|
-
<path d="M4 6H20M4 12H20M4 18H12M16 18V22M16 18H20"
|
|
80
|
-
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
81
|
-
</svg>`,
|
|
81
|
+
'text-add': `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v16"/><path d="M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2"/><path d="M9 20h6"/></svg>`,
|
|
82
82
|
'note': `<?xml version="1.0" encoding="UTF-8"?>
|
|
83
83
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
|
84
84
|
<rect x="3" y="3" width="18" height="18" rx="2" fill="#fbbf24" stroke="currentColor" stroke-width="2"/>
|
|
@@ -117,9 +117,9 @@ export class IconLoader {
|
|
|
117
117
|
<circle cx="16" cy="10" r="1" fill="currentColor"/>
|
|
118
118
|
<path d="M8 16C8 16 10 18 12 18C14 18 16 16 16 16" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
119
119
|
</svg>`,
|
|
120
|
-
'frame':
|
|
121
|
-
<
|
|
122
|
-
<rect x="
|
|
120
|
+
'frame': `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
121
|
+
<rect x="1.5" y="1.5" width="17" height="17" rx="2.5" stroke="currentColor" stroke-width="2"/>
|
|
122
|
+
<rect x="1.5" y="1.5" width="7.5" height="5.5" rx="2.5" stroke="currentColor" stroke-width="2"/>
|
|
123
123
|
</svg>`,
|
|
124
124
|
'clear': `<?xml version="1.0" encoding="UTF-8"?>
|
|
125
125
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
|
@@ -191,8 +191,8 @@ export class IconLoader {
|
|
|
191
191
|
loadFallbackIcons() {
|
|
192
192
|
const iconNames = [
|
|
193
193
|
'select', 'pan', 'text-add', 'note', 'image', 'shapes',
|
|
194
|
-
'pencil', 'comments', 'attachments', 'emoji', 'frame',
|
|
195
|
-
'clear', 'undo', 'redo', 'mindmap'
|
|
194
|
+
'pencil', 'connector', 'comments', 'attachments', 'emoji', 'frame',
|
|
195
|
+
'clear', 'undo', 'redo', 'mindmap', 'reactions'
|
|
196
196
|
];
|
|
197
197
|
|
|
198
198
|
iconNames.forEach(name => {
|
|
@@ -213,7 +213,7 @@ export class IconLoader {
|
|
|
213
213
|
const fallbacks = {
|
|
214
214
|
'select': '<svg width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="8" fill="none" stroke="currentColor" stroke-width="2"/></svg>',
|
|
215
215
|
'pan': '<svg width="20" height="20" viewBox="0 0 20 20"><rect x="4" y="4" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2"/></svg>',
|
|
216
|
-
'text-add': '<svg width="
|
|
216
|
+
'text-add': '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v16"/><path d="M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2"/><path d="M9 20h6"/></svg>',
|
|
217
217
|
'note': '<svg width="20" height="20" viewBox="0 0 20 20"><rect x="2" y="2" width="16" height="16" rx="2" fill="#fbbf24"/></svg>',
|
|
218
218
|
'image': '<svg width="20" height="20" viewBox="0 0 20 20"><rect x="2" y="2" width="16" height="16" rx="2" fill="none" stroke="currentColor" stroke-width="2"/></svg>',
|
|
219
219
|
'shapes': '<svg width="20" height="20" viewBox="0 0 20 20"><rect x="2" y="2" width="6" height="6"/><circle cx="14" cy="5" r="3"/><polygon points="10,14 13,18 7,18"/></svg>',
|
|
@@ -221,11 +221,12 @@ export class IconLoader {
|
|
|
221
221
|
'comments': '<svg width="20" height="20" viewBox="0 0 20 20"><path d="M2 4C2 2.89543 2.89543 2 4 2H16C17.1046 2 18 2.89543 18 4V12C18 13.1046 17.1046 14 16 14H8L4 18V4Z" fill="currentColor"/></svg>',
|
|
222
222
|
'attachments': '<svg width="20" height="20" viewBox="0 0 20 20"><path d="M8 2C6.89543 2 6 2.89543 6 4V12C6 14.2091 7.79086 16 10 16C12.2091 16 14 14.2091 14 12V6C14 5.44772 13.5523 5 13 5C12.4477 5 12 5.44772 12 6V12C12 13.1046 11.1046 14 10 14C8.89543 14 8 13.1046 8 12V4C8 3.44772 8.44772 3 9 3C9.55228 3 10 3.44772 10 4V12C10 12.5523 9.55228 13 9 13C8.44772 13 8 12.5523 8 12V4Z" fill="currentColor"/></svg>',
|
|
223
223
|
'emoji': '<svg width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="8" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="7" cy="8" r="1"/><circle cx="13" cy="8" r="1"/><path d="M7 13C7 13 8.5 15 10 15C11.5 15 13 13 13 13" stroke="currentColor" stroke-width="1" fill="none"/></svg>',
|
|
224
|
-
'frame': '<svg width="20" height="20" viewBox="0 0 20 20"><rect x="
|
|
224
|
+
'frame': '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none"><rect x="1.5" y="1.5" width="17" height="17" rx="2.5" stroke="currentColor" stroke-width="2"/><rect x="1.5" y="1.5" width="7.5" height="5.5" rx="2.5" stroke="currentColor" stroke-width="2"/></svg>',
|
|
225
225
|
'clear': '<svg width="20" height="20" viewBox="0 0 20 20"><path d="M3 6H17L16 18H4L3 6Z" fill="currentColor"/></svg>',
|
|
226
226
|
'undo': '<svg width="20" height="20" viewBox="0 0 20 20"><path d="M8 4L3 9L8 14" stroke="currentColor" stroke-width="2" fill="none"/></svg>',
|
|
227
227
|
'redo': '<svg width="20" height="20" viewBox="0 0 20 20"><path d="M12 4L17 9L12 14" stroke="currentColor" stroke-width="2" fill="none"/></svg>',
|
|
228
|
-
'mindmap': '<svg width="20" height="20" viewBox="0 0 20 20"><circle cx="4" cy="4" r="2" fill="currentColor"/><circle cx="16" cy="4" r="2" fill="currentColor"/><circle cx="4" cy="16" r="2" fill="currentColor"/><circle cx="16" cy="16" r="2" fill="currentColor"/><rect x="7" y="8" width="6" height="4" rx="2" fill="currentColor"/><path d="M6 5L8 8M14 8L16 5M8 12L6 15M12 12L14 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>'
|
|
228
|
+
'mindmap': '<svg width="20" height="20" viewBox="0 0 20 20"><circle cx="4" cy="4" r="2" fill="currentColor"/><circle cx="16" cy="4" r="2" fill="currentColor"/><circle cx="4" cy="16" r="2" fill="currentColor"/><circle cx="16" cy="16" r="2" fill="currentColor"/><rect x="7" y="8" width="6" height="4" rx="2" fill="currentColor"/><path d="M6 5L8 8M14 8L16 5M8 12L6 15M12 12L14 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
|
|
229
|
+
'reactions': '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"/></svg>'
|
|
229
230
|
};
|
|
230
231
|
|
|
231
232
|
return fallbacks[iconName] || fallbacks['select'];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { marked } from 'marked';
|
|
2
|
+
import DOMPurify from 'dompurify';
|
|
3
|
+
|
|
4
|
+
// gfm: паритет с league/commonmark в Futurello; breaks: переносы строк как в ответах ИИ
|
|
5
|
+
marked.use({ gfm: true, breaks: true });
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Рендерит markdown в безопасный HTML.
|
|
9
|
+
* DOMPurify обязателен: результат вставляется через innerHTML.
|
|
10
|
+
*/
|
|
11
|
+
export function renderMarkdown(src) {
|
|
12
|
+
if (typeof src !== 'string' || !src) return '';
|
|
13
|
+
return DOMPurify.sanitize(marked.parse(src));
|
|
14
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import 'katex/dist/katex.min.css';
|
|
2
|
+
import katex from 'katex';
|
|
3
|
+
import { renderMarkdown } from './markdown.js';
|
|
4
|
+
|
|
5
|
+
// Рендер «богатого» текста = markdown + LaTeX-формулы KaTeX.
|
|
6
|
+
//
|
|
7
|
+
// Почему отдельный слой над renderMarkdown: marked/DOMPurify портят TeX
|
|
8
|
+
// (`_` → <em>, `\` срезается, `*` → курсив). Поэтому формулы извлекаются ДО
|
|
9
|
+
// markdown-парсинга, заменяются плейсхолдерами, markdown прогоняется как
|
|
10
|
+
// обычно, затем формулы восстанавливаются через katex.renderToString.
|
|
11
|
+
//
|
|
12
|
+
// KaTeX-вывод НЕ пропускается через DOMPurify: это доверенный вывод самой
|
|
13
|
+
// библиотеки (см. docs/security.md — injection-safe), а sanitize вырезал бы
|
|
14
|
+
// нужные KaTeX span/inline-style. Тот же контракт, что в Futurello.
|
|
15
|
+
|
|
16
|
+
// Плейсхолдеры — только заглавные буквы и цифры: marked/DOMPurify не трогают
|
|
17
|
+
// такие токены и не интерпретируют их как разметку.
|
|
18
|
+
const BLOCK_KEY = (i) => `XKATEXBLOCK${i}X`;
|
|
19
|
+
const INLINE_KEY = (i) => `XKATEXINLINE${i}X`;
|
|
20
|
+
|
|
21
|
+
// Делимитеры формул. Порядок важен: $$ и \[…\] извлекаются до инлайнового $…$,
|
|
22
|
+
// иначе инлайновый разбор «съест» первый $ блочной формулы.
|
|
23
|
+
const RE_BLOCK_DOLLARS = /\$\$([\s\S]+?)\$\$/g; // $$ ... $$
|
|
24
|
+
const RE_BLOCK_BRACKETS = /\\\[([\s\S]+?)\\\]/g; // \[ ... \]
|
|
25
|
+
const RE_INLINE_PARENS = /\\\(([\s\S]+?)\\\)/g; // \( ... \)
|
|
26
|
+
// Инлайн $...$: открывающий $ не перед пробелом/цифрой/$ (отсекает $5, $10);
|
|
27
|
+
// закрывающий $ не перед цифрой. (?<!\$) — не часть $$.
|
|
28
|
+
const RE_INLINE_DOLLARS = /(?<!\$)\$(?=[^\s\d$])([\s\S]+?[^\s$])\$(?!\d)/g;
|
|
29
|
+
|
|
30
|
+
// Блоки и инлайн-код. Их содержимое НЕ должно трактоваться как математика:
|
|
31
|
+
// например PowerShell `$dst`, `$env:APPDATA` — это переменные, а не формулы.
|
|
32
|
+
const RE_FENCED_CODE = /```[\s\S]*?```|~~~[\s\S]*?~~~/g; // ``` ... ``` и ~~~ ... ~~~
|
|
33
|
+
const RE_INLINE_CODE = /`[^`\n]+`/g; // `...`
|
|
34
|
+
|
|
35
|
+
// Убирает код из текста (для детекта) — заменяет на пробел, чтобы делимитеры
|
|
36
|
+
// внутри кода не считались формулами.
|
|
37
|
+
function stripCode(src) {
|
|
38
|
+
return src.replace(RE_FENCED_CODE, ' ').replace(RE_INLINE_CODE, ' ');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Быстрая проверка: есть ли в тексте хоть один делимитер формулы вне кода.
|
|
43
|
+
* Нужна, чтобы текст с одной формулой (без markdown-разметки) тоже
|
|
44
|
+
* рендерился богато, но код с `$`-переменными не считался формулой.
|
|
45
|
+
*/
|
|
46
|
+
export function hasMath(src) {
|
|
47
|
+
if (typeof src !== 'string' || !src) return false;
|
|
48
|
+
const t = stripCode(src);
|
|
49
|
+
return (
|
|
50
|
+
/\$\$[\s\S]+?\$\$/.test(t) ||
|
|
51
|
+
/\\\[[\s\S]+?\\\]/.test(t) ||
|
|
52
|
+
/\\\([\s\S]+?\\\)/.test(t) ||
|
|
53
|
+
/(?<!\$)\$(?=[^\s\d$])[\s\S]+?[^\s$]\$(?!\d)/.test(t)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractMath(src) {
|
|
58
|
+
const placeholders = [];
|
|
59
|
+
// Сначала защищаем код плейсхолдерами: внутри него $ и \ — не математика.
|
|
60
|
+
// Код возвращается в текст до markdown-парсинга, поэтому marked отрисует
|
|
61
|
+
// его как <pre><code> с экранированным содержимым.
|
|
62
|
+
const codeStore = [];
|
|
63
|
+
const maskCode = (re, prefix) => {
|
|
64
|
+
text = text.replace(re, (m) => {
|
|
65
|
+
const key = `XCODE${prefix}${codeStore.length}X`;
|
|
66
|
+
codeStore.push({ key, code: m });
|
|
67
|
+
return key;
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
let text = src;
|
|
72
|
+
maskCode(RE_FENCED_CODE, 'BLOCK');
|
|
73
|
+
maskCode(RE_INLINE_CODE, 'INLINE');
|
|
74
|
+
|
|
75
|
+
const replaceAll = (re, display, keyFn) => {
|
|
76
|
+
text = text.replace(re, (_m, tex) => {
|
|
77
|
+
const key = keyFn(placeholders.length);
|
|
78
|
+
placeholders.push({ key, tex: tex.trim(), display });
|
|
79
|
+
return key;
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
replaceAll(RE_BLOCK_DOLLARS, true, BLOCK_KEY);
|
|
84
|
+
replaceAll(RE_BLOCK_BRACKETS, true, BLOCK_KEY);
|
|
85
|
+
replaceAll(RE_INLINE_PARENS, false, INLINE_KEY);
|
|
86
|
+
replaceAll(RE_INLINE_DOLLARS, false, INLINE_KEY);
|
|
87
|
+
|
|
88
|
+
// Возвращаем код обратно — теперь его обработает marked.
|
|
89
|
+
for (const { key, code } of codeStore) {
|
|
90
|
+
text = text.split(key).join(code);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { text, placeholders };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function restoreMath(html, placeholders) {
|
|
97
|
+
let out = html;
|
|
98
|
+
for (const { key, tex, display } of placeholders) {
|
|
99
|
+
let rendered;
|
|
100
|
+
try {
|
|
101
|
+
rendered = katex.renderToString(tex, {
|
|
102
|
+
throwOnError: false,
|
|
103
|
+
output: 'html', // без MathML: дублирующее поддерево не нужно и тяжелее
|
|
104
|
+
displayMode: display,
|
|
105
|
+
});
|
|
106
|
+
} catch {
|
|
107
|
+
// На неожиданной ошибке оставляем сырой TeX видимым
|
|
108
|
+
rendered = display ? `$$${tex}$$` : `$${tex}$`;
|
|
109
|
+
}
|
|
110
|
+
out = out.split(key).join(rendered);
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Рендерит markdown с LaTeX-формулами в безопасный HTML.
|
|
117
|
+
* Результат вставляется через innerHTML.
|
|
118
|
+
*/
|
|
119
|
+
export function renderRichText(src) {
|
|
120
|
+
if (typeof src !== 'string' || !src) return '';
|
|
121
|
+
const { text, placeholders } = extractMath(src);
|
|
122
|
+
const html = renderMarkdown(text);
|
|
123
|
+
if (placeholders.length === 0) return html;
|
|
124
|
+
return restoreMath(html, placeholders);
|
|
125
|
+
}
|