@sequent-org/moodboard 1.4.55 → 1.4.57
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 +1 -1
- package/src/core/commands/PasteObjectCommand.js +7 -1
- package/src/core/commands/UpdateContentCommand.js +49 -0
- package/src/core/flows/ObjectLifecycleFlow.js +10 -1
- package/src/initNoBundler.js +29 -5
- package/src/tools/object-tools/TextTool.js +9 -3
- package/src/tools/object-tools/selection/SelectInputRouter.js +1 -1
- package/src/tools/object-tools/selection/TextEditorInteractionController.js +86 -11
- package/src/tools/object-tools/selection/TextInlineEditorController.js +2 -2
- package/src/ui/FramePropertiesPanel.js +7 -0
- package/src/ui/HtmlTextLayer.js +79 -6
- package/src/ui/ImagePropertiesPanel.js +7 -0
- package/src/ui/TextPropertiesPanel.js +163 -3
- package/src/ui/styles/panels.css +471 -31
- package/src/ui/styles/workspace.css +31 -7
- package/src/ui/text-properties/TextFormatControls.js +351 -41
- package/src/ui/text-properties/TextLinkControl.js +255 -0
- package/src/ui/text-properties/TextLockMoreControls.js +173 -0
- package/src/ui/text-properties/TextPropertiesPanelBindings.js +105 -2
- package/src/ui/text-properties/TextPropertiesPanelMapper.js +23 -0
- package/src/ui/text-properties/TextPropertiesPanelRenderer.js +348 -56
- package/src/ui/text-properties/TextPropertiesPanelState.js +6 -0
- package/src/utils/styleLoader.js +1 -1
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
bindTextPropertiesPanelControls,
|
|
4
4
|
unbindTextPropertiesPanelControls,
|
|
5
5
|
} from './text-properties/TextPropertiesPanelBindings.js';
|
|
6
|
+
import { updateLinkButtonState } from './text-properties/TextLinkControl.js';
|
|
6
7
|
import {
|
|
7
8
|
attachTextPropertiesPanelEventBridge,
|
|
8
9
|
detachTextPropertiesPanelEventBridge,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
import {
|
|
11
12
|
applyTextAppearanceToDom,
|
|
12
13
|
buildBackgroundColorUpdate,
|
|
14
|
+
buildHighlightColorUpdate,
|
|
13
15
|
buildFontFamilyUpdate,
|
|
14
16
|
buildFontSizeUpdate,
|
|
15
17
|
buildMarkdownUpdate,
|
|
@@ -26,10 +28,13 @@ import {
|
|
|
26
28
|
createTextPropertiesPanelRenderer,
|
|
27
29
|
hideBgColorDropdown,
|
|
28
30
|
hideColorDropdown,
|
|
31
|
+
hideHighlightDropdown,
|
|
29
32
|
toggleBgColorDropdown,
|
|
30
33
|
toggleColorDropdown,
|
|
34
|
+
toggleHighlightDropdown,
|
|
31
35
|
updateCurrentBgColorButton,
|
|
32
36
|
updateCurrentColorButton,
|
|
37
|
+
updateCurrentHighlightButton,
|
|
33
38
|
} from './text-properties/TextPropertiesPanelRenderer.js';
|
|
34
39
|
import {
|
|
35
40
|
clearTextPropertiesPanelState,
|
|
@@ -57,7 +62,7 @@ export class TextPropertiesPanel {
|
|
|
57
62
|
position: 'absolute',
|
|
58
63
|
inset: '0',
|
|
59
64
|
pointerEvents: 'none',
|
|
60
|
-
zIndex:
|
|
65
|
+
zIndex: 10000,
|
|
61
66
|
});
|
|
62
67
|
this.container.appendChild(this.layer);
|
|
63
68
|
|
|
@@ -107,8 +112,9 @@ export class TextPropertiesPanel {
|
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
this.panel.style.display = 'flex';
|
|
110
|
-
this.reposition();
|
|
111
115
|
this._updateControlsFromObject();
|
|
116
|
+
this._updateLockUI();
|
|
117
|
+
this.reposition();
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
hide() {
|
|
@@ -119,6 +125,7 @@ export class TextPropertiesPanel {
|
|
|
119
125
|
}
|
|
120
126
|
|
|
121
127
|
this._hideColorDropdown();
|
|
128
|
+
this._hideHighlightDropdown();
|
|
122
129
|
this._hideBgColorDropdown();
|
|
123
130
|
if (this._docMouseDownAttached) {
|
|
124
131
|
document.removeEventListener('mousedown', this._onDocMouseDown, true);
|
|
@@ -144,6 +151,24 @@ export class TextPropertiesPanel {
|
|
|
144
151
|
updateCurrentColorButton(this, color);
|
|
145
152
|
}
|
|
146
153
|
|
|
154
|
+
_toggleHighlightDropdown() {
|
|
155
|
+
toggleHighlightDropdown(this);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
_hideHighlightDropdown() {
|
|
159
|
+
hideHighlightDropdown(this);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
_selectHighlightColor(color) {
|
|
163
|
+
this._changeHighlightColor(color);
|
|
164
|
+
this._updateCurrentHighlightButton(color);
|
|
165
|
+
this._hideHighlightDropdown();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_updateCurrentHighlightButton(color) {
|
|
169
|
+
updateCurrentHighlightButton(this, color);
|
|
170
|
+
}
|
|
171
|
+
|
|
147
172
|
_toggleBgColorDropdown() {
|
|
148
173
|
toggleBgColorDropdown(this);
|
|
149
174
|
}
|
|
@@ -214,6 +239,19 @@ export class TextPropertiesPanel {
|
|
|
214
239
|
this._updateTextAppearance(this.currentId, { backgroundColor });
|
|
215
240
|
}
|
|
216
241
|
|
|
242
|
+
_changeHighlightColor(highlightColor) {
|
|
243
|
+
if (!this.currentId) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
248
|
+
objectId: this.currentId,
|
|
249
|
+
updates: buildHighlightColorUpdate(highlightColor),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
this._updateTextAppearance(this.currentId, { highlightColor });
|
|
253
|
+
}
|
|
254
|
+
|
|
217
255
|
_toggleFormat(prop) {
|
|
218
256
|
if (!this.currentId) {
|
|
219
257
|
return;
|
|
@@ -288,6 +326,40 @@ export class TextPropertiesPanel {
|
|
|
288
326
|
this._updateTextAppearance(this.currentId, { markdown });
|
|
289
327
|
}
|
|
290
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Добавляет web-ссылку к диапазону текста (plain-режим).
|
|
331
|
+
* При включённом MD-режиме ничего не делает (кнопка должна быть disabled).
|
|
332
|
+
* @param {string} url — нормализованный URL
|
|
333
|
+
* @param {number} start — индекс начала диапазона (включительно)
|
|
334
|
+
* @param {number} end — индекс конца диапазона (не включительно)
|
|
335
|
+
* @param {string} [objectId] — id объекта; если не задан, используется this.currentId.
|
|
336
|
+
* Нужен потому, что во время ввода URL фокус уходит в input и выделение объекта
|
|
337
|
+
* может сброситься (this.currentId → null).
|
|
338
|
+
*/
|
|
339
|
+
_addLink(url, start, end, objectId) {
|
|
340
|
+
const targetId = objectId || this.currentId;
|
|
341
|
+
if (!targetId || !url) return;
|
|
342
|
+
const props = getObjectProperties(this.eventBus, targetId);
|
|
343
|
+
if (props?.markdown === true) return;
|
|
344
|
+
|
|
345
|
+
const content = props?.content ?? '';
|
|
346
|
+
const safeEnd = Math.min(end, content.length);
|
|
347
|
+
const safeStart = Math.min(start, safeEnd);
|
|
348
|
+
if (safeStart >= safeEnd) return;
|
|
349
|
+
|
|
350
|
+
const oldLinks = Array.isArray(props?.links) ? props.links : [];
|
|
351
|
+
// Удаляем ссылки, пересекающиеся с новым диапазоном
|
|
352
|
+
const filtered = oldLinks.filter(l => l.end <= safeStart || l.start >= safeEnd);
|
|
353
|
+
const newLinks = [...filtered, { start: safeStart, end: safeEnd, url }]
|
|
354
|
+
.sort((a, b) => a.start - b.start);
|
|
355
|
+
|
|
356
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
357
|
+
objectId: targetId,
|
|
358
|
+
updates: { properties: { links: newLinks } },
|
|
359
|
+
});
|
|
360
|
+
this._updateTextAppearance(targetId, { links: newLinks });
|
|
361
|
+
}
|
|
362
|
+
|
|
291
363
|
_updateTextAppearance(objectId, properties) {
|
|
292
364
|
applyTextAppearanceToDom(objectId, properties);
|
|
293
365
|
syncPixiTextProperties(this.eventBus, objectId, properties);
|
|
@@ -310,9 +382,11 @@ export class TextPropertiesPanel {
|
|
|
310
382
|
this.fontSelect.value = values.fontFamily;
|
|
311
383
|
this.fontSizeSelect.value = values.fontSize;
|
|
312
384
|
this._updateCurrentColorButton(values.color);
|
|
385
|
+
this._updateCurrentHighlightButton(values.highlightColor);
|
|
313
386
|
this._updateCurrentBgColorButton(values.backgroundColor);
|
|
314
387
|
if (this.markdownToggle) {
|
|
315
388
|
this.markdownToggle.checked = values.markdown;
|
|
389
|
+
updateLinkButtonState(this, values.markdown);
|
|
316
390
|
}
|
|
317
391
|
|
|
318
392
|
if (this.boldBtn) this.boldBtn.classList.toggle('is-active', values.bold);
|
|
@@ -349,7 +423,11 @@ export class TextPropertiesPanel {
|
|
|
349
423
|
const panelY = screenY - this.panel.offsetHeight - 20;
|
|
350
424
|
|
|
351
425
|
const containerRect = this.container.getBoundingClientRect();
|
|
352
|
-
const
|
|
426
|
+
const toolbarEl = document.querySelector('.moodboard-toolbar');
|
|
427
|
+
const toolbarRight = toolbarEl
|
|
428
|
+
? toolbarEl.getBoundingClientRect().right - containerRect.left + 10
|
|
429
|
+
: 10;
|
|
430
|
+
const finalX = Math.max(toolbarRight, Math.min(panelX, containerRect.width - this.panel.offsetWidth - 10));
|
|
353
431
|
const finalY = Math.max(10, panelY);
|
|
354
432
|
|
|
355
433
|
this.panel.style.left = `${Math.round(finalX)}px`;
|
|
@@ -379,4 +457,86 @@ export class TextPropertiesPanel {
|
|
|
379
457
|
this.container.getBoundingClientRect();
|
|
380
458
|
this.hide();
|
|
381
459
|
}
|
|
460
|
+
|
|
461
|
+
_isLocked() {
|
|
462
|
+
if (!this.currentId) return false;
|
|
463
|
+
const objects = this.core?.state?.getObjects ? this.core.state.getObjects() : [];
|
|
464
|
+
const obj = objects.find((o) => o.id === this.currentId);
|
|
465
|
+
return !!(obj?.properties?.locked);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
_toggleLocked() {
|
|
469
|
+
if (!this.currentId) return;
|
|
470
|
+
const newLocked = !this._isLocked();
|
|
471
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
472
|
+
objectId: this.currentId,
|
|
473
|
+
updates: { properties: { locked: newLocked } },
|
|
474
|
+
});
|
|
475
|
+
this._updateLockUI();
|
|
476
|
+
this.reposition();
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
_updateLockUI() {
|
|
480
|
+
if (!this._tppBtnLock) return;
|
|
481
|
+
const locked = this._isLocked();
|
|
482
|
+
|
|
483
|
+
this._tppBtnLock.innerHTML = locked ? this._tppLockIcon : this._tppUnlockIcon;
|
|
484
|
+
this._tppBtnLock.title = locked ? 'Разблокировать' : 'Заблокировать';
|
|
485
|
+
|
|
486
|
+
if (Array.isArray(this._lockableEls)) {
|
|
487
|
+
this._lockableEls.forEach((el) => {
|
|
488
|
+
if (el) el.style.display = locked ? 'none' : '';
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (this._moreLockLabel) {
|
|
493
|
+
this._moreLockLabel.textContent = locked ? 'Разблокировать' : 'Заблокировать';
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (this.panel) {
|
|
497
|
+
this.panel.classList.toggle('is-locked', locked);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
_duplicateText() {
|
|
502
|
+
if (!this.currentId) return;
|
|
503
|
+
|
|
504
|
+
const posData = { objectId: this.currentId, position: null };
|
|
505
|
+
const sizeData = { objectId: this.currentId, size: null };
|
|
506
|
+
this.eventBus.emit(Events.Tool.GetObjectPosition, posData);
|
|
507
|
+
this.eventBus.emit(Events.Tool.GetObjectSize, sizeData);
|
|
508
|
+
|
|
509
|
+
if (!posData.position || !sizeData.size) return;
|
|
510
|
+
|
|
511
|
+
let w = sizeData.size.width;
|
|
512
|
+
if (typeof w !== 'number' || isNaN(w)) {
|
|
513
|
+
const pixiObj = this.core?.pixi?.objects?.get(this.currentId);
|
|
514
|
+
w = pixiObj ? pixiObj.width : 160;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const originalId = this.currentId;
|
|
518
|
+
const newPos = {
|
|
519
|
+
x: posData.position.x + (w || 160) + 14,
|
|
520
|
+
y: posData.position.y,
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
const onReady = (data) => {
|
|
524
|
+
if (!data || data.originalId !== originalId) return;
|
|
525
|
+
this.eventBus.off(Events.Tool.DuplicateReady, onReady);
|
|
526
|
+
this._selectObject(data.newId);
|
|
527
|
+
};
|
|
528
|
+
this.eventBus.on(Events.Tool.DuplicateReady, onReady);
|
|
529
|
+
|
|
530
|
+
this.eventBus.emit(Events.Tool.DuplicateRequest, { originalId, position: newPos });
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
_selectObject(objectId) {
|
|
534
|
+
if (!objectId) return;
|
|
535
|
+
const selectTool = this.core?.selectTool;
|
|
536
|
+
if (!selectTool || typeof selectTool.setSelection !== 'function') return;
|
|
537
|
+
selectTool.setSelection([objectId]);
|
|
538
|
+
if (typeof selectTool.updateResizeHandles === 'function') {
|
|
539
|
+
selectTool.updateResizeHandles();
|
|
540
|
+
}
|
|
541
|
+
}
|
|
382
542
|
}
|