@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
|
@@ -60,7 +60,6 @@ const BOARD_IMAGE_REARRANGE_MS = 520;
|
|
|
60
60
|
const BOARD_IMAGE_PENDING_ENTER_FACTOR = 1.6;
|
|
61
61
|
// Каскад между блоками одного батча (мс): пользователь видит, что они приезжают друг за другом.
|
|
62
62
|
const BOARD_IMAGE_PENDING_STAGGER_MS = 90;
|
|
63
|
-
const REFERENCE_DRAG_PREVIEW_SIZE = 96;
|
|
64
63
|
|
|
65
64
|
const MODEL_OPTIONS = [
|
|
66
65
|
{
|
|
@@ -145,12 +144,12 @@ export class ChatWindow {
|
|
|
145
144
|
this._pendingOverlays = new Map();
|
|
146
145
|
this._pendingOverlayTimers = new Map();
|
|
147
146
|
this._boardImageShiftAnimations = new Map();
|
|
148
|
-
this._boardCursor = null;
|
|
149
|
-
this._draggedReferenceObject = null;
|
|
150
|
-
this._draggedReferenceStartPosition = null;
|
|
151
|
-
this._referenceDragPreview = null;
|
|
152
|
-
this._referenceDragHandlers = null;
|
|
153
147
|
this._clearSelectionOnSendClick = null;
|
|
148
|
+
this._selectionHandlers = null;
|
|
149
|
+
// Окно от BoxSelectStart до BoxSelectCommit: в это время SelectionAdd
|
|
150
|
+
// приходит на каждый mousemove и не должен пушить превью в чат —
|
|
151
|
+
// финальный набор картинок мы получим из BoxSelectCommit по strict-contains.
|
|
152
|
+
this._boxSelectActive = false;
|
|
154
153
|
}
|
|
155
154
|
|
|
156
155
|
attach() {
|
|
@@ -180,7 +179,7 @@ export class ChatWindow {
|
|
|
180
179
|
this._clearSelectionOnSendClick = () => this._clearBoardSelection();
|
|
181
180
|
this._refs.send.addEventListener('click', this._clearSelectionOnSendClick);
|
|
182
181
|
this._composer.attach();
|
|
183
|
-
this.
|
|
182
|
+
this._attachSelectionEvents();
|
|
184
183
|
|
|
185
184
|
this._extendedPromptModal = new ChatExtendedPromptModal(
|
|
186
185
|
this._container,
|
|
@@ -263,13 +262,12 @@ export class ChatWindow {
|
|
|
263
262
|
if (!this._attached) return;
|
|
264
263
|
this._clearPendingOverlays();
|
|
265
264
|
this._cancelBoardImageShiftAnimations();
|
|
266
|
-
this._clearReferenceDragState();
|
|
267
265
|
if (this._unsubscribe) { this._unsubscribe(); this._unsubscribe = null; }
|
|
268
266
|
if (this._clearSelectionOnSendClick && this._refs?.send) {
|
|
269
267
|
this._refs.send.removeEventListener('click', this._clearSelectionOnSendClick);
|
|
270
268
|
this._clearSelectionOnSendClick = null;
|
|
271
269
|
}
|
|
272
|
-
this.
|
|
270
|
+
this._detachSelectionEvents();
|
|
273
271
|
this._shiftedForImageBatchKeys.clear();
|
|
274
272
|
this._boardImageShiftHistory.clear();
|
|
275
273
|
this._composer?.destroy();
|
|
@@ -465,175 +463,94 @@ export class ChatWindow {
|
|
|
465
463
|
};
|
|
466
464
|
}
|
|
467
465
|
|
|
468
|
-
|
|
466
|
+
_attachSelectionEvents() {
|
|
469
467
|
const eventBus = this._boardCore?.eventBus;
|
|
470
|
-
if (!eventBus || typeof eventBus.on !== 'function' || this.
|
|
468
|
+
if (!eventBus || typeof eventBus.on !== 'function' || this._selectionHandlers) return;
|
|
471
469
|
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
this._boardCursor = { x, y };
|
|
475
|
-
this._updateReferenceDragPreview();
|
|
476
|
-
}
|
|
470
|
+
const onSelectionAdd = (data) => {
|
|
471
|
+
void this._handleSelectionAdd(data);
|
|
477
472
|
};
|
|
478
|
-
const
|
|
479
|
-
|
|
473
|
+
const onSelectionRemove = (data) => {
|
|
474
|
+
const objectId = data?.object;
|
|
475
|
+
if (objectId) this._composer?.removeAttachmentForObject?.(objectId);
|
|
480
476
|
};
|
|
481
|
-
const
|
|
482
|
-
|
|
477
|
+
const onSelectionClear = () => {
|
|
478
|
+
this._composer?.removeAllBoardAttachments?.();
|
|
483
479
|
};
|
|
484
|
-
const
|
|
485
|
-
|
|
480
|
+
const onBoxSelectStart = () => {
|
|
481
|
+
this._boxSelectActive = true;
|
|
482
|
+
};
|
|
483
|
+
const onBoxSelectCommit = (data) => {
|
|
484
|
+
void this._handleBoxSelectCommit(data);
|
|
486
485
|
};
|
|
487
486
|
|
|
488
|
-
this.
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
487
|
+
this._selectionHandlers = {
|
|
488
|
+
onSelectionAdd,
|
|
489
|
+
onSelectionRemove,
|
|
490
|
+
onSelectionClear,
|
|
491
|
+
onBoxSelectStart,
|
|
492
|
+
onBoxSelectCommit
|
|
493
|
+
};
|
|
492
494
|
eventBus.on(Events.Tool.SelectionAdd, onSelectionAdd);
|
|
495
|
+
eventBus.on(Events.Tool.SelectionRemove, onSelectionRemove);
|
|
496
|
+
eventBus.on(Events.Tool.SelectionClear, onSelectionClear);
|
|
497
|
+
eventBus.on(Events.Tool.BoxSelectStart, onBoxSelectStart);
|
|
498
|
+
eventBus.on(Events.Tool.BoxSelectCommit, onBoxSelectCommit);
|
|
493
499
|
}
|
|
494
500
|
|
|
495
|
-
|
|
501
|
+
_detachSelectionEvents() {
|
|
496
502
|
const eventBus = this._boardCore?.eventBus;
|
|
497
|
-
const handlers = this.
|
|
503
|
+
const handlers = this._selectionHandlers;
|
|
498
504
|
if (!eventBus || typeof eventBus.off !== 'function' || !handlers) return;
|
|
499
505
|
|
|
500
|
-
eventBus.off(Events.UI.CursorMove, handlers.onCursorMove);
|
|
501
|
-
eventBus.off(Events.Tool.DragStart, handlers.onDragStart);
|
|
502
|
-
eventBus.off(Events.Tool.DragEnd, handlers.onDragEnd);
|
|
503
506
|
eventBus.off(Events.Tool.SelectionAdd, handlers.onSelectionAdd);
|
|
504
|
-
|
|
507
|
+
eventBus.off(Events.Tool.SelectionRemove, handlers.onSelectionRemove);
|
|
508
|
+
eventBus.off(Events.Tool.SelectionClear, handlers.onSelectionClear);
|
|
509
|
+
eventBus.off(Events.Tool.BoxSelectStart, handlers.onBoxSelectStart);
|
|
510
|
+
eventBus.off(Events.Tool.BoxSelectCommit, handlers.onBoxSelectCommit);
|
|
511
|
+
this._selectionHandlers = null;
|
|
512
|
+
this._boxSelectActive = false;
|
|
505
513
|
}
|
|
506
514
|
|
|
507
515
|
async _handleSelectionAdd(data = {}) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
if (
|
|
512
|
-
|
|
513
|
-
await this._addImageObjectAsReference(object);
|
|
514
|
-
}
|
|
516
|
+
// Во время box-select каждый mousemove перевыставляет selection и снова
|
|
517
|
+
// эмитит SelectionAdd для тех же id. Финальный набор reference-картинок
|
|
518
|
+
// мы получим из BoxSelectCommit по strict-contains, поэтому здесь молчим.
|
|
519
|
+
if (this._boxSelectActive) return;
|
|
515
520
|
|
|
516
|
-
_handleReferenceDragStart(data = {}) {
|
|
517
521
|
const objectId = data?.object;
|
|
518
522
|
const object = this._boardCore?.state?.state?.objects?.find((item) => item?.id === objectId);
|
|
519
|
-
this._draggedReferenceObject = isReferenceImageObject(object) ? object : null;
|
|
520
|
-
this._draggedReferenceStartPosition = this._draggedReferenceObject?.position
|
|
521
|
-
? { ...this._draggedReferenceObject.position }
|
|
522
|
-
: null;
|
|
523
|
-
this._updateReferenceDragPreview();
|
|
524
|
-
}
|
|
525
523
|
|
|
526
|
-
|
|
527
|
-
const isDropTarget = this._isBoardCursorOverInput();
|
|
528
|
-
const objectId = data?.object;
|
|
529
|
-
const object = this._boardCore?.state?.state?.objects?.find((item) => item?.id === objectId);
|
|
530
|
-
const startPosition = this._draggedReferenceStartPosition;
|
|
531
|
-
this._clearReferenceDragState();
|
|
532
|
-
if (!isDropTarget || !isReferenceImageObject(object)) return null;
|
|
524
|
+
if (!isReferenceImageObject(object)) return;
|
|
533
525
|
|
|
534
|
-
this._restoreReferenceObjectPosition(objectId, startPosition);
|
|
535
526
|
await this._addImageObjectAsReference(object);
|
|
536
527
|
}
|
|
537
528
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if (typeof updatePosition === 'function') {
|
|
543
|
-
updatePosition.call(this._boardCore, objectId, position, { snap: false });
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
const object = this._boardCore?.state?.state?.objects?.find((item) => item?.id === objectId);
|
|
548
|
-
if (object?.position) {
|
|
549
|
-
object.position = { ...position };
|
|
550
|
-
}
|
|
551
|
-
}
|
|
529
|
+
async _handleBoxSelectCommit(data = {}) {
|
|
530
|
+
this._boxSelectActive = false;
|
|
531
|
+
const ids = Array.isArray(data?.selected) ? data.selected : (Array.isArray(data?.objects) ? data.objects : []);
|
|
532
|
+
if (!ids.length || !this._composer) return;
|
|
552
533
|
|
|
553
|
-
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
this._hideReferenceDragPreview();
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
const preview = this._ensureReferenceDragPreview(object, src);
|
|
567
|
-
const { clientX, clientY } = this._getBoardCursorClientPosition();
|
|
568
|
-
preview.style.left = `${Math.round(clientX)}px`;
|
|
569
|
-
preview.style.top = `${Math.round(clientY)}px`;
|
|
570
|
-
this._refs?.textarea?.closest?.('.moodboard-chat__input-row')?.classList.add('is-reference-drop-target');
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
_ensureReferenceDragPreview(object, src) {
|
|
574
|
-
if (!this._referenceDragPreview) {
|
|
575
|
-
const preview = document.createElement('img');
|
|
576
|
-
preview.className = 'moodboard-chat__reference-drag-preview';
|
|
577
|
-
preview.alt = getImageObjectFileName(object, src);
|
|
578
|
-
preview.width = REFERENCE_DRAG_PREVIEW_SIZE;
|
|
579
|
-
preview.height = REFERENCE_DRAG_PREVIEW_SIZE;
|
|
580
|
-
document.body.appendChild(preview);
|
|
581
|
-
this._referenceDragPreview = preview;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
if (this._referenceDragPreview.src !== src) {
|
|
585
|
-
this._referenceDragPreview.src = src;
|
|
534
|
+
const all = this._boardCore?.state?.state?.objects ?? [];
|
|
535
|
+
const byId = new Map(all.map((item) => [item?.id, item]));
|
|
536
|
+
const seen = new Set();
|
|
537
|
+
for (const id of ids) {
|
|
538
|
+
if (!id || seen.has(id)) continue;
|
|
539
|
+
seen.add(id);
|
|
540
|
+
const obj = byId.get(id);
|
|
541
|
+
if (!obj || !isReferenceImageObject(obj)) continue;
|
|
542
|
+
await this._addImageObjectAsReference(obj);
|
|
586
543
|
}
|
|
587
|
-
this._referenceDragPreview.alt = getImageObjectFileName(object, src);
|
|
588
|
-
|
|
589
|
-
return this._referenceDragPreview;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
_hideReferenceDragPreview() {
|
|
593
|
-
this._referenceDragPreview?.remove();
|
|
594
|
-
this._referenceDragPreview = null;
|
|
595
|
-
this._refs?.textarea?.closest?.('.moodboard-chat__input-row')?.classList.remove('is-reference-drop-target');
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
_clearReferenceDragState() {
|
|
599
|
-
this._draggedReferenceObject = null;
|
|
600
|
-
this._draggedReferenceStartPosition = null;
|
|
601
|
-
this._boardCursor = null;
|
|
602
|
-
this._hideReferenceDragPreview();
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
_isBoardCursorOverInput() {
|
|
606
|
-
const cursor = this._boardCursor;
|
|
607
|
-
const inputRow = this._refs?.textarea?.closest?.('.moodboard-chat__input-row');
|
|
608
|
-
if (!cursor || !inputRow) return false;
|
|
609
|
-
|
|
610
|
-
const containerRect = this._container.getBoundingClientRect?.();
|
|
611
|
-
const rect = inputRow.getBoundingClientRect();
|
|
612
|
-
const { clientX, clientY } = this._getBoardCursorClientPosition(containerRect);
|
|
613
|
-
|
|
614
|
-
return clientX >= rect.left
|
|
615
|
-
&& clientX <= rect.right
|
|
616
|
-
&& clientY >= rect.top
|
|
617
|
-
&& clientY <= rect.bottom;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
_getBoardCursorClientPosition(containerRect = null) {
|
|
621
|
-
const rect = containerRect || this._container.getBoundingClientRect?.();
|
|
622
|
-
const cursor = this._boardCursor || { x: 0, y: 0 };
|
|
623
|
-
|
|
624
|
-
return {
|
|
625
|
-
clientX: (rect?.left || 0) + cursor.x,
|
|
626
|
-
clientY: (rect?.top || 0) + cursor.y
|
|
627
|
-
};
|
|
628
544
|
}
|
|
629
545
|
|
|
630
546
|
async _addImageObjectAsReference(object) {
|
|
631
547
|
if (!object || !this._composer) return;
|
|
548
|
+
if (this._composer.hasAttachmentForObject?.(object.id)) return;
|
|
632
549
|
|
|
633
550
|
try {
|
|
634
551
|
const file = await createFileFromImageObject(object);
|
|
635
552
|
if (!file) return;
|
|
636
|
-
this._composer.addAttachment(file);
|
|
553
|
+
this._composer.addAttachment(file, { sourceObjectId: object.id });
|
|
637
554
|
this._composer.focus();
|
|
638
555
|
} catch (err) {
|
|
639
556
|
console.warn('[ChatWindow] cannot add selected image reference:', err);
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { Events } from '../../core/events/Events.js';
|
|
2
|
+
import { formatTime, pluralize, stripHtml } from './commentFormat.js';
|
|
3
|
+
|
|
4
|
+
const CLOSE_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
|
5
|
+
const CHECKMARK_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
|
|
6
|
+
const TRASH_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14"/></svg>`;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Боковая панель со списком всех тредов доски.
|
|
10
|
+
* Открывается/закрывается по Events.Comment.ListOpened (toggle).
|
|
11
|
+
*/
|
|
12
|
+
export class CommentListPanel {
|
|
13
|
+
constructor(container, eventBus, core, commentService) {
|
|
14
|
+
this.container = container;
|
|
15
|
+
this.eventBus = eventBus;
|
|
16
|
+
this.core = core;
|
|
17
|
+
this.commentService = commentService;
|
|
18
|
+
|
|
19
|
+
this._panel = null;
|
|
20
|
+
this._body = null;
|
|
21
|
+
this._isOpen = false;
|
|
22
|
+
|
|
23
|
+
this._onListOpened = () => this.toggle();
|
|
24
|
+
this._onUpdate = () => { if (this._isOpen) this._renderList(); };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
attach() {
|
|
28
|
+
this._panel = document.createElement('div');
|
|
29
|
+
this._panel.className = 'moodboard-comments-list';
|
|
30
|
+
this._panel.style.display = 'none';
|
|
31
|
+
|
|
32
|
+
const header = document.createElement('div');
|
|
33
|
+
header.className = 'moodboard-comments-list__header';
|
|
34
|
+
|
|
35
|
+
const closeBtn = document.createElement('button');
|
|
36
|
+
closeBtn.type = 'button';
|
|
37
|
+
closeBtn.className = 'moodboard-comments-list__close';
|
|
38
|
+
closeBtn.setAttribute('aria-label', 'Закрыть');
|
|
39
|
+
closeBtn.innerHTML = CLOSE_SVG;
|
|
40
|
+
closeBtn.addEventListener('click', () => this.hide());
|
|
41
|
+
|
|
42
|
+
const title = document.createElement('span');
|
|
43
|
+
title.className = 'moodboard-comments-list__title';
|
|
44
|
+
title.textContent = 'Комментарии';
|
|
45
|
+
|
|
46
|
+
header.appendChild(title);
|
|
47
|
+
header.appendChild(closeBtn);
|
|
48
|
+
|
|
49
|
+
this._body = document.createElement('div');
|
|
50
|
+
this._body.className = 'moodboard-comments-list__body';
|
|
51
|
+
|
|
52
|
+
this._panel.appendChild(header);
|
|
53
|
+
this._panel.appendChild(this._body);
|
|
54
|
+
this.container.appendChild(this._panel);
|
|
55
|
+
|
|
56
|
+
this.eventBus.on(Events.Comment.ListOpened, this._onListOpened);
|
|
57
|
+
this.eventBus.on(Events.Comment.RemoteUpdated, this._onUpdate);
|
|
58
|
+
this.eventBus.on(Events.Comment.MessageAdded, this._onUpdate);
|
|
59
|
+
this.eventBus.on(Events.Comment.Resolved, this._onUpdate);
|
|
60
|
+
this.eventBus.on(Events.Comment.ThreadDeleted, this._onUpdate);
|
|
61
|
+
this.eventBus.on(Events.Comment.PinCreated, this._onUpdate);
|
|
62
|
+
this.eventBus.on(Events.Comment.ColorChanged, this._onUpdate);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
destroy() {
|
|
66
|
+
this.eventBus.off(Events.Comment.ListOpened, this._onListOpened);
|
|
67
|
+
this.eventBus.off(Events.Comment.RemoteUpdated, this._onUpdate);
|
|
68
|
+
this.eventBus.off(Events.Comment.MessageAdded, this._onUpdate);
|
|
69
|
+
this.eventBus.off(Events.Comment.Resolved, this._onUpdate);
|
|
70
|
+
this.eventBus.off(Events.Comment.ThreadDeleted, this._onUpdate);
|
|
71
|
+
this.eventBus.off(Events.Comment.PinCreated, this._onUpdate);
|
|
72
|
+
this.eventBus.off(Events.Comment.ColorChanged, this._onUpdate);
|
|
73
|
+
if (this._panel) {
|
|
74
|
+
this._panel.remove();
|
|
75
|
+
this._panel = null;
|
|
76
|
+
}
|
|
77
|
+
this._body = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
toggle() {
|
|
81
|
+
if (this._isOpen) {
|
|
82
|
+
this.hide();
|
|
83
|
+
} else {
|
|
84
|
+
this.show();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
show() {
|
|
89
|
+
if (!this._panel) return;
|
|
90
|
+
this._isOpen = true;
|
|
91
|
+
this._panel.style.display = 'flex';
|
|
92
|
+
this._renderList();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
hide() {
|
|
96
|
+
if (!this._panel) return;
|
|
97
|
+
this._isOpen = false;
|
|
98
|
+
this._panel.style.display = 'none';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_renderList() {
|
|
102
|
+
if (!this._body) return;
|
|
103
|
+
const threads = this.commentService.getAllThreads();
|
|
104
|
+
this._body.replaceChildren();
|
|
105
|
+
|
|
106
|
+
if (!threads || threads.length === 0) {
|
|
107
|
+
const empty = document.createElement('div');
|
|
108
|
+
empty.className = 'moodboard-comments-list__empty';
|
|
109
|
+
empty.textContent = 'Пока нет комментариев';
|
|
110
|
+
this._body.appendChild(empty);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const fragment = document.createDocumentFragment();
|
|
115
|
+
for (const thread of threads) {
|
|
116
|
+
fragment.appendChild(this._buildCard(thread));
|
|
117
|
+
}
|
|
118
|
+
this._body.appendChild(fragment);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_buildCard(thread) {
|
|
122
|
+
const root = document.createElement('div');
|
|
123
|
+
root.className = 'moodboard-comments-list__card' +
|
|
124
|
+
(thread.resolved ? ' moodboard-comments-list__card--resolved' : '');
|
|
125
|
+
root.addEventListener('click', (e) => this._onCardClick(e, thread));
|
|
126
|
+
|
|
127
|
+
const firstMsg = thread.messages?.items?.[0];
|
|
128
|
+
const currentUser = this.commentService.currentUser;
|
|
129
|
+
const isSelf = currentUser?.id != null && thread.created_by != null &&
|
|
130
|
+
String(thread.created_by) === String(currentUser.id);
|
|
131
|
+
const authorName = firstMsg?.author_name || (isSelf ? 'Вы' : 'Участник');
|
|
132
|
+
const timeStr = firstMsg?.created_at ? formatTime(firstMsg.created_at) : '';
|
|
133
|
+
const content = stripHtml(firstMsg?.content || '');
|
|
134
|
+
|
|
135
|
+
const colorDot = document.createElement('span');
|
|
136
|
+
colorDot.className = 'moodboard-comments-list__card-dot';
|
|
137
|
+
if (thread.color) colorDot.style.background = thread.color;
|
|
138
|
+
|
|
139
|
+
const meta = document.createElement('div');
|
|
140
|
+
meta.className = 'moodboard-comments-list__card-meta';
|
|
141
|
+
|
|
142
|
+
const author = document.createElement('span');
|
|
143
|
+
author.className = 'moodboard-comments-list__card-author';
|
|
144
|
+
author.textContent = authorName;
|
|
145
|
+
|
|
146
|
+
const time = document.createElement('span');
|
|
147
|
+
time.className = 'moodboard-comments-list__card-time';
|
|
148
|
+
time.textContent = timeStr;
|
|
149
|
+
|
|
150
|
+
meta.appendChild(author);
|
|
151
|
+
meta.appendChild(time);
|
|
152
|
+
|
|
153
|
+
const top = document.createElement('div');
|
|
154
|
+
top.className = 'moodboard-comments-list__card-top';
|
|
155
|
+
top.appendChild(colorDot);
|
|
156
|
+
top.appendChild(meta);
|
|
157
|
+
|
|
158
|
+
const body = document.createElement('div');
|
|
159
|
+
body.className = 'moodboard-comments-list__card-body';
|
|
160
|
+
body.textContent = content;
|
|
161
|
+
|
|
162
|
+
const replyCount = (thread.messages?.items?.length || 0) - 1;
|
|
163
|
+
let repliesEl = null;
|
|
164
|
+
if (replyCount > 0) {
|
|
165
|
+
repliesEl = document.createElement('div');
|
|
166
|
+
repliesEl.className = 'moodboard-comments-list__card-replies';
|
|
167
|
+
repliesEl.textContent = `${replyCount} ${pluralize(replyCount, 'ответ', 'ответа', 'ответов')}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const actions = document.createElement('div');
|
|
171
|
+
actions.className = 'moodboard-comments-list__card-actions';
|
|
172
|
+
|
|
173
|
+
const resolveBtn = document.createElement('button');
|
|
174
|
+
resolveBtn.type = 'button';
|
|
175
|
+
resolveBtn.className = 'moodboard-comments-list__resolve-btn' +
|
|
176
|
+
(thread.resolved ? ' moodboard-comments-list__resolve-btn--resolved' : '');
|
|
177
|
+
resolveBtn.setAttribute('aria-label', thread.resolved ? 'Вернуть' : 'Решить');
|
|
178
|
+
resolveBtn.innerHTML = CHECKMARK_SVG + `<span>${thread.resolved ? 'Вернуть' : 'Решить'}</span>`;
|
|
179
|
+
resolveBtn.addEventListener('click', (e) => {
|
|
180
|
+
e.stopPropagation();
|
|
181
|
+
this.commentService.resolveThread(thread.id, !thread.resolved).catch(console.error);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const deleteBtn = document.createElement('button');
|
|
185
|
+
deleteBtn.type = 'button';
|
|
186
|
+
deleteBtn.className = 'moodboard-comments-list__delete-btn';
|
|
187
|
+
deleteBtn.setAttribute('aria-label', 'Удалить');
|
|
188
|
+
deleteBtn.innerHTML = TRASH_SVG;
|
|
189
|
+
deleteBtn.addEventListener('click', (e) => {
|
|
190
|
+
e.stopPropagation();
|
|
191
|
+
this.commentService.deleteThread(thread.id).catch(console.error);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
actions.appendChild(resolveBtn);
|
|
195
|
+
actions.appendChild(deleteBtn);
|
|
196
|
+
|
|
197
|
+
root.appendChild(top);
|
|
198
|
+
root.appendChild(body);
|
|
199
|
+
if (repliesEl) root.appendChild(repliesEl);
|
|
200
|
+
root.appendChild(actions);
|
|
201
|
+
|
|
202
|
+
return root;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
_onCardClick(e, thread) {
|
|
206
|
+
const pos = this.commentService.getThreadWorldPosition(thread, this.core);
|
|
207
|
+
if (pos) {
|
|
208
|
+
this.eventBus.emit(Events.UI.MinimapCenterOn, { worldX: pos.x, worldY: pos.y });
|
|
209
|
+
this.eventBus.emit(Events.Viewport.Changed);
|
|
210
|
+
}
|
|
211
|
+
this.commentService.openThread(thread.id);
|
|
212
|
+
}
|
|
213
|
+
}
|