kingkont 0.7.85 → 0.7.86
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/renderer/board.js +32 -0
- package/renderer/settings.js +54 -60
- package/renderer/styles.css +16 -31
package/package.json
CHANGED
package/renderer/board.js
CHANGED
|
@@ -1773,6 +1773,38 @@ function showNodeContextMenu(node, clientX, clientY) {
|
|
|
1773
1773
|
menu.appendChild(b);
|
|
1774
1774
|
};
|
|
1775
1775
|
add(node.name ? `✏ Переименовать (${node.name})` : '✏ Переименовать', () => renameNode(node));
|
|
1776
|
+
// Label: пункты для смены стиля (шрифт / размер / курсив).
|
|
1777
|
+
// Сделано через ПКМ а не постоянный hover-тулбар, чтобы UI label-ноды
|
|
1778
|
+
// оставался «чистым текстом» без фурнитуры.
|
|
1779
|
+
if (node.type === 'label') {
|
|
1780
|
+
if (!node.textStyle) node.textStyle = { fontSize: 32, italic: false, fontFamily: 'pencil' };
|
|
1781
|
+
const ts = node.textStyle;
|
|
1782
|
+
const curFont = LABEL_FONTS.find(f => f.id === ts.fontFamily) || LABEL_FONTS[0];
|
|
1783
|
+
add(`🔤 Шрифт: ${curFont.label}`, async () => {
|
|
1784
|
+
const choice = await askChoice('Шрифт label-ноды', LABEL_FONTS.map(f => f.label), curFont.label);
|
|
1785
|
+
if (!choice) return;
|
|
1786
|
+
const picked = LABEL_FONTS.find(f => f.label === choice);
|
|
1787
|
+
if (!picked) return;
|
|
1788
|
+
ts.fontFamily = picked.id;
|
|
1789
|
+
scheduleSave();
|
|
1790
|
+
await refreshNodeDOM(node.id);
|
|
1791
|
+
});
|
|
1792
|
+
add(`📏 Размер: ${ts.fontSize || 32}px`, async () => {
|
|
1793
|
+
const choice = await askChoice('Размер шрифта', LABEL_FONT_SIZES.map(s => s + 'px'), (ts.fontSize || 32) + 'px');
|
|
1794
|
+
if (!choice) return;
|
|
1795
|
+
const n = parseInt(choice, 10);
|
|
1796
|
+
if (Number.isFinite(n)) {
|
|
1797
|
+
ts.fontSize = n;
|
|
1798
|
+
scheduleSave();
|
|
1799
|
+
await refreshNodeDOM(node.id);
|
|
1800
|
+
}
|
|
1801
|
+
});
|
|
1802
|
+
add(ts.italic ? '𝐼 Курсив: вкл' : '𝐼 Курсив: выкл', async () => {
|
|
1803
|
+
ts.italic = !ts.italic;
|
|
1804
|
+
scheduleSave();
|
|
1805
|
+
await refreshNodeDOM(node.id);
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1776
1808
|
add('📋 Логи', () => showNodeLogs(node));
|
|
1777
1809
|
if (node.generated) add('⚙ Параметры', () => showNodeSettings(node));
|
|
1778
1810
|
if (node.status === 'generating') {
|
package/renderer/settings.js
CHANGED
|
@@ -88,73 +88,56 @@ function applyLabelStyle(ed, style) {
|
|
|
88
88
|
function renderLabelNodeBody(node, body) {
|
|
89
89
|
if (!node.textStyle) node.textStyle = { ...LABEL_DEFAULT_STYLE };
|
|
90
90
|
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
for (const f of LABEL_FONTS) {
|
|
99
|
-
const o = document.createElement('option');
|
|
100
|
-
o.value = f.id; o.textContent = f.label;
|
|
101
|
-
fontSel.appendChild(o);
|
|
102
|
-
}
|
|
103
|
-
fontSel.value = node.textStyle.fontFamily || 'default';
|
|
104
|
-
fontSel.addEventListener('change', () => {
|
|
105
|
-
node.textStyle.fontFamily = fontSel.value;
|
|
106
|
-
applyLabelStyle(ed, node.textStyle);
|
|
107
|
-
scheduleSave();
|
|
108
|
-
});
|
|
109
|
-
tb.appendChild(fontSel);
|
|
110
|
-
|
|
111
|
-
const sizeSel = document.createElement('select');
|
|
112
|
-
sizeSel.title = 'Размер шрифта';
|
|
113
|
-
for (const s of LABEL_FONT_SIZES) {
|
|
114
|
-
const o = document.createElement('option');
|
|
115
|
-
o.value = String(s); o.textContent = s + 'px';
|
|
116
|
-
sizeSel.appendChild(o);
|
|
117
|
-
}
|
|
118
|
-
sizeSel.value = String(node.textStyle.fontSize || 32);
|
|
119
|
-
sizeSel.addEventListener('change', () => {
|
|
120
|
-
node.textStyle.fontSize = parseInt(sizeSel.value, 10) || 32;
|
|
121
|
-
applyLabelStyle(ed, node.textStyle);
|
|
122
|
-
scheduleSave();
|
|
123
|
-
});
|
|
124
|
-
tb.appendChild(sizeSel);
|
|
125
|
-
|
|
126
|
-
const italicBtn = document.createElement('button');
|
|
127
|
-
italicBtn.className = 'tt-italic';
|
|
128
|
-
italicBtn.textContent = 'I';
|
|
129
|
-
italicBtn.title = 'Курсив';
|
|
130
|
-
if (node.textStyle.italic) italicBtn.classList.add('active');
|
|
131
|
-
italicBtn.addEventListener('click', e => {
|
|
132
|
-
e.stopPropagation();
|
|
133
|
-
node.textStyle.italic = !node.textStyle.italic;
|
|
134
|
-
italicBtn.classList.toggle('active', node.textStyle.italic);
|
|
135
|
-
applyLabelStyle(ed, node.textStyle);
|
|
136
|
-
scheduleSave();
|
|
137
|
-
});
|
|
138
|
-
tb.appendChild(italicBtn);
|
|
139
|
-
|
|
140
|
-
body.appendChild(tb);
|
|
141
|
-
|
|
142
|
-
// Сам текст — contenteditable (без рамок, без скроллбаров, ровно как
|
|
143
|
-
// плавающая надпись на холсте). plaintext-only — режем форматирование
|
|
144
|
-
// из буфера обмена (текст копируется только как текст).
|
|
91
|
+
// Сам текст. По умолчанию НЕ contenteditable — обычный <div>. Это значит:
|
|
92
|
+
// • single-click = выделение ноды (drag handler из attachDrag)
|
|
93
|
+
// • ПКМ = node-context-menu (ниже добавлены пункты «Шрифт/Размер/Курсив»)
|
|
94
|
+
// • dblclick = вход в режим редактирования
|
|
95
|
+
// • blur = выход из редактирования
|
|
96
|
+
// Шрифт/размер/курсив меняются через ПКМ (а не через постоянный hover-тулбар),
|
|
97
|
+
// чтобы UI label-ноды был «чистый текст без фурнитуры».
|
|
145
98
|
const ed = document.createElement('div');
|
|
146
99
|
ed.className = 'label-text';
|
|
147
|
-
ed.contentEditable = 'plaintext-only';
|
|
148
100
|
ed.spellcheck = false;
|
|
149
|
-
ed.dataset.placeholder = '
|
|
101
|
+
ed.dataset.placeholder = 'Двойной клик — редактировать';
|
|
150
102
|
ed.textContent = node.text || '';
|
|
151
103
|
applyLabelStyle(ed, node.textStyle);
|
|
104
|
+
|
|
105
|
+
const enterEditMode = () => {
|
|
106
|
+
if (ed.isContentEditable) return;
|
|
107
|
+
ed.contentEditable = 'plaintext-only';
|
|
108
|
+
ed.focus();
|
|
109
|
+
// Курсор в конец (если контент пустой — это и есть начало).
|
|
110
|
+
const sel = window.getSelection();
|
|
111
|
+
const r = document.createRange();
|
|
112
|
+
r.selectNodeContents(ed);
|
|
113
|
+
r.collapse(false);
|
|
114
|
+
sel?.removeAllRanges();
|
|
115
|
+
sel?.addRange(r);
|
|
116
|
+
};
|
|
117
|
+
const exitEditMode = () => {
|
|
118
|
+
if (!ed.isContentEditable) return;
|
|
119
|
+
// removeAttribute полностью убирает contenteditable, чтобы
|
|
120
|
+
// closest('[contenteditable]') в обработчиках вне (dblclick/contextmenu)
|
|
121
|
+
// не матчился — и ПКМ снова открывал наш node-menu.
|
|
122
|
+
ed.removeAttribute('contenteditable');
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
ed.addEventListener('dblclick', e => {
|
|
126
|
+
e.stopPropagation();
|
|
127
|
+
enterEditMode();
|
|
128
|
+
});
|
|
129
|
+
ed.addEventListener('blur', exitEditMode);
|
|
152
130
|
ed.addEventListener('input', () => { node.text = ed.textContent; scheduleSave(); });
|
|
153
|
-
ed.addEventListener('mousedown', e => e.stopPropagation());
|
|
154
|
-
// Enter — перенос строки. Esc — снять фокус.
|
|
155
131
|
ed.addEventListener('keydown', e => {
|
|
156
132
|
if (e.key === 'Escape') { e.preventDefault(); ed.blur(); }
|
|
157
133
|
});
|
|
134
|
+
ed.addEventListener('mousedown', e => {
|
|
135
|
+
// В edit-режиме: не пускаем drag-обработчик (нужно поставить курсор/выделить текст).
|
|
136
|
+
if (ed.isContentEditable) e.stopPropagation();
|
|
137
|
+
// В read-режиме: пузырится — attachDrag-handler (повешен и на label-text)
|
|
138
|
+
// обработает single-click как «выбрать ноду» и подхватит drag.
|
|
139
|
+
});
|
|
140
|
+
|
|
158
141
|
body.appendChild(ed);
|
|
159
142
|
}
|
|
160
143
|
|
|
@@ -711,7 +694,17 @@ function attachResize(el, node, handle) {
|
|
|
711
694
|
|
|
712
695
|
function attachDrag(el, node) {
|
|
713
696
|
const header = el.querySelector('.node-header');
|
|
714
|
-
header
|
|
697
|
+
// Для label-нод вешаем drag и на сам текст: header скрыт до hover, а
|
|
698
|
+
// юзер хочет цеплять ноду с любого места видимого текста. В handler'е
|
|
699
|
+
// пропускаем срабатывание, если label в edit-режиме (см. условие ниже).
|
|
700
|
+
const sources = [header];
|
|
701
|
+
if (node.type === 'label') {
|
|
702
|
+
const lt = el.querySelector('.label-text');
|
|
703
|
+
if (lt) sources.push(lt);
|
|
704
|
+
}
|
|
705
|
+
const handler = e => {
|
|
706
|
+
// Edit-режим label — не таскаем (контекст редактирования текста).
|
|
707
|
+
if (e.currentTarget?.classList?.contains('label-text') && e.currentTarget.isContentEditable) return;
|
|
715
708
|
if (e.target.closest('.delete')) return;
|
|
716
709
|
// Multi-select c модификаторами — без drag
|
|
717
710
|
if (e.metaKey || e.ctrlKey || e.shiftKey) {
|
|
@@ -848,7 +841,8 @@ function attachDrag(el, node) {
|
|
|
848
841
|
};
|
|
849
842
|
document.addEventListener('mousemove', onMove);
|
|
850
843
|
document.addEventListener('mouseup', onUp);
|
|
851
|
-
}
|
|
844
|
+
};
|
|
845
|
+
for (const src of sources) if (src) src.addEventListener('mousedown', handler);
|
|
852
846
|
}
|
|
853
847
|
|
|
854
848
|
// Добавить ноду как клип в указанную дорожку с ripple-вставкой по времени
|
package/renderer/styles.css
CHANGED
|
@@ -485,39 +485,12 @@
|
|
|
485
485
|
position: relative;
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
-
/* Тулбар
|
|
489
|
-
|
|
490
|
-
position: absolute;
|
|
491
|
-
top: 100%; /* под label */
|
|
492
|
-
left: 0;
|
|
493
|
-
margin-top: 4px;
|
|
494
|
-
display: flex; align-items: center; gap: 4px;
|
|
495
|
-
padding: 3px 6px; background: rgba(20,20,20,0.92);
|
|
496
|
-
border-radius: 4px;
|
|
497
|
-
font-size: 11px; flex-shrink: 0;
|
|
498
|
-
opacity: 0; transition: opacity 0.15s;
|
|
499
|
-
pointer-events: none;
|
|
500
|
-
white-space: nowrap;
|
|
501
|
-
z-index: 10;
|
|
502
|
-
}
|
|
488
|
+
/* Тулбар (.label-toolbar) больше не используется — стиль/шрифт меняются
|
|
489
|
+
через ПКМ. Видимый header показываем только на hover. */
|
|
503
490
|
.node.label-node:hover .node-header,
|
|
504
|
-
.node.label-node:
|
|
505
|
-
.node.label-node:focus-within .node-header,
|
|
506
|
-
.node.label-node:focus-within .label-toolbar {
|
|
491
|
+
.node.label-node:focus-within .node-header {
|
|
507
492
|
opacity: 1; pointer-events: auto;
|
|
508
493
|
}
|
|
509
|
-
.node.label-node .label-toolbar select,
|
|
510
|
-
.node.label-node .label-toolbar button {
|
|
511
|
-
background: #1e1e1e; border: 1px solid #383838; color: #ccc;
|
|
512
|
-
border-radius: 3px; padding: 2px 6px; font-size: 11px;
|
|
513
|
-
cursor: pointer; line-height: 1.2;
|
|
514
|
-
}
|
|
515
|
-
.node.label-node .label-toolbar select:hover,
|
|
516
|
-
.node.label-node .label-toolbar button:hover { background: #2c2c2c; color: #fff; }
|
|
517
|
-
.node.label-node .label-toolbar button.active {
|
|
518
|
-
background: #3a5a8a; color: #fff; border-color: #4a6a9a;
|
|
519
|
-
}
|
|
520
|
-
.node.label-node .label-toolbar .tt-italic { font-style: italic; min-width: 22px; }
|
|
521
494
|
|
|
522
495
|
/* Сам текст — auto-ширина по контенту, до max-width.
|
|
523
496
|
line-height: 1.5 — чтобы descender'ы рукописных шрифтов (Caveat g/p/y,
|
|
@@ -539,8 +512,20 @@
|
|
|
539
512
|
}
|
|
540
513
|
.node.label-node .label-text:empty::before {
|
|
541
514
|
content: attr(data-placeholder);
|
|
542
|
-
color: rgba(200,200,200,0.
|
|
515
|
+
color: rgba(200,200,200,0.55);
|
|
516
|
+
}
|
|
517
|
+
/* Когда label пустая — рисуем подсказывающий dashed-контур, чтобы юзер
|
|
518
|
+
видел что нода существует (текст-плейсхолдер мог быть не замечен).
|
|
519
|
+
При наличии текста — никакой рамки, чисто подпись на холсте. */
|
|
520
|
+
.node.label-node:has(.label-text:empty) {
|
|
521
|
+
outline: 1px dashed rgba(200,200,200,0.25);
|
|
522
|
+
outline-offset: 1px;
|
|
523
|
+
border-radius: 3px;
|
|
543
524
|
}
|
|
525
|
+
/* Курсор «I-beam» над текстом — на hover показываем «текстуальную
|
|
526
|
+
природу» ноды, в read-mode сигнал что dblclick → редактировать. */
|
|
527
|
+
.node.label-node .label-text { cursor: text; }
|
|
528
|
+
.node.label-node:hover .label-text:not([contenteditable]) { cursor: pointer; }
|
|
544
529
|
/* === Label-шрифты ===
|
|
545
530
|
Все варианты — bundled woff2 (см. assets/fonts/), кириллица поддержана. */
|
|
546
531
|
.node.label-node .label-text[data-font="pencil"] {
|