kingkont 0.7.86 → 0.7.88
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 +40 -7
- package/renderer/settings.js +47 -6
- package/renderer/styles.css +11 -0
package/package.json
CHANGED
package/renderer/board.js
CHANGED
|
@@ -1111,25 +1111,35 @@ function askName(title, placeholder = '', initialValue = '', opts = {}) {
|
|
|
1111
1111
|
// Возвращает выбранную строку или null если юзер закрыл.
|
|
1112
1112
|
// window.prompt() в Electron renderer молча возвращает null — поэтому
|
|
1113
1113
|
// нельзя использовать его для select-подобных диалогов.
|
|
1114
|
-
|
|
1114
|
+
// askChoice(title, options, currentValue, opts?)
|
|
1115
|
+
// opts:
|
|
1116
|
+
// vertical: bool — стэкаем кнопки вертикально (по умолчанию — flex-wrap)
|
|
1117
|
+
// optionStyles: { [opt]: 'css-string' } — кастомный CSS на кнопку (для preview-эффектов,
|
|
1118
|
+
// напр. рендер опции «Карандашом» в самом шрифте Karandashom)
|
|
1119
|
+
function askChoice(title, options, currentValue, opts = {}) {
|
|
1115
1120
|
return new Promise(resolve => {
|
|
1116
1121
|
const overlay = document.createElement('div');
|
|
1117
1122
|
overlay.className = 'modal';
|
|
1118
1123
|
overlay.style.cssText = 'position:fixed; inset:0; background:rgba(0,0,0,0.55); display:flex; align-items:center; justify-content:center; z-index:9999;';
|
|
1119
1124
|
const box = document.createElement('div');
|
|
1120
|
-
box.style.cssText = 'background:#222; border:1px solid #444; border-radius:8px; padding:18px 20px; min-width:360px; box-shadow:0 8px 32px rgba(0,0,0,0.6);';
|
|
1125
|
+
box.style.cssText = 'background:#222; border:1px solid #444; border-radius:8px; padding:18px 20px; min-width:360px; max-height:80vh; overflow-y:auto; box-shadow:0 8px 32px rgba(0,0,0,0.6);';
|
|
1121
1126
|
const h = document.createElement('h3');
|
|
1122
1127
|
h.textContent = title;
|
|
1123
1128
|
h.style.cssText = 'margin:0 0 12px; font-size:14px; color:#e0e0e0;';
|
|
1124
1129
|
box.append(h);
|
|
1125
1130
|
const grid = document.createElement('div');
|
|
1126
|
-
grid.style.cssText =
|
|
1131
|
+
grid.style.cssText = opts.vertical
|
|
1132
|
+
? 'display:flex; flex-direction:column; gap:6px; margin-bottom:14px;'
|
|
1133
|
+
: 'display:flex; flex-wrap:wrap; gap:6px; margin-bottom:14px;';
|
|
1127
1134
|
const close = (val) => { overlay.remove(); resolve(val); };
|
|
1128
1135
|
for (const opt of options) {
|
|
1129
1136
|
const b = document.createElement('button');
|
|
1130
1137
|
b.textContent = opt;
|
|
1131
|
-
|
|
1132
|
-
|
|
1138
|
+
let css = 'padding:6px 12px; background:#1a1a1a; color:#e0e0e0; border:1px solid #444; border-radius:4px; font-size:13px; cursor:pointer;';
|
|
1139
|
+
if (opts.vertical) css += 'text-align:left;';
|
|
1140
|
+
if (opt === currentValue) css += 'border-color:#7c3aed; background:#2a1a3a;';
|
|
1141
|
+
if (opts.optionStyles?.[opt]) css += opts.optionStyles[opt];
|
|
1142
|
+
b.style.cssText = css;
|
|
1133
1143
|
b.addEventListener('click', () => close(opt));
|
|
1134
1144
|
grid.append(b);
|
|
1135
1145
|
}
|
|
@@ -1781,7 +1791,18 @@ function showNodeContextMenu(node, clientX, clientY) {
|
|
|
1781
1791
|
const ts = node.textStyle;
|
|
1782
1792
|
const curFont = LABEL_FONTS.find(f => f.id === ts.fontFamily) || LABEL_FONTS[0];
|
|
1783
1793
|
add(`🔤 Шрифт: ${curFont.label}`, async () => {
|
|
1784
|
-
|
|
1794
|
+
// Каждая опция в picker'е рендерится в собственно её шрифте — юзер
|
|
1795
|
+
// сразу видит как «Карандашом» / «Прописью» / «Кистью» выглядят.
|
|
1796
|
+
const fontStyles = {};
|
|
1797
|
+
for (const f of LABEL_FONTS) {
|
|
1798
|
+
fontStyles[f.label] = `font-family: ${getLabelFontFamily(f.id)}; font-size: 22px; padding: 8px 14px; line-height: 1.3;`;
|
|
1799
|
+
}
|
|
1800
|
+
const choice = await askChoice(
|
|
1801
|
+
'Шрифт label-ноды',
|
|
1802
|
+
LABEL_FONTS.map(f => f.label),
|
|
1803
|
+
curFont.label,
|
|
1804
|
+
{ vertical: true, optionStyles: fontStyles },
|
|
1805
|
+
);
|
|
1785
1806
|
if (!choice) return;
|
|
1786
1807
|
const picked = LABEL_FONTS.find(f => f.label === choice);
|
|
1787
1808
|
if (!picked) return;
|
|
@@ -1790,7 +1811,19 @@ function showNodeContextMenu(node, clientX, clientY) {
|
|
|
1790
1811
|
await refreshNodeDOM(node.id);
|
|
1791
1812
|
});
|
|
1792
1813
|
add(`📏 Размер: ${ts.fontSize || 32}px`, async () => {
|
|
1793
|
-
|
|
1814
|
+
// Каждый размер в picker'е рендерится в реальном размере (с лимитом),
|
|
1815
|
+
// чтобы юзер чувствовал «насколько крупно». Также вертикально.
|
|
1816
|
+
const sizeStyles = {};
|
|
1817
|
+
for (const s of LABEL_FONT_SIZES) {
|
|
1818
|
+
const previewSize = Math.min(s, 36); // не растим окно — лимит превью
|
|
1819
|
+
sizeStyles[s + 'px'] = `font-size: ${previewSize}px; line-height: 1.2; padding: 6px 14px;`;
|
|
1820
|
+
}
|
|
1821
|
+
const choice = await askChoice(
|
|
1822
|
+
'Размер шрифта',
|
|
1823
|
+
LABEL_FONT_SIZES.map(s => s + 'px'),
|
|
1824
|
+
(ts.fontSize || 32) + 'px',
|
|
1825
|
+
{ vertical: true, optionStyles: sizeStyles },
|
|
1826
|
+
);
|
|
1794
1827
|
if (!choice) return;
|
|
1795
1828
|
const n = parseInt(choice, 10);
|
|
1796
1829
|
if (Number.isFinite(n)) {
|
package/renderer/settings.js
CHANGED
|
@@ -85,6 +85,27 @@ function applyLabelStyle(ed, style) {
|
|
|
85
85
|
ed.dataset.font = style.fontFamily || 'default';
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// Соответствие label-font-id → CSS font-family stack. Используется для
|
|
89
|
+
// preview-кнопок в picker'e шрифта (askChoice показывает каждую опцию
|
|
90
|
+
// в собственно её шрифте, чтобы юзер видел как выглядит). Семейства
|
|
91
|
+
// должны совпадать с теми, что заданы в .label-text[data-font="..."]
|
|
92
|
+
// в renderer/styles.css.
|
|
93
|
+
function getLabelFontFamily(id) {
|
|
94
|
+
const map = {
|
|
95
|
+
default: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
96
|
+
pencil: "'KK Neucha', 'Marker Felt', 'Comic Sans MS', cursive",
|
|
97
|
+
handwritten: "'KK Caveat', 'Caveat', 'Marker Felt', cursive",
|
|
98
|
+
brush: "'KK BadScript', 'Bad Script', 'Snell Roundhand', cursive",
|
|
99
|
+
marker: "'KK Pacifico', 'Pacifico', 'Marker Felt', cursive",
|
|
100
|
+
display: "'KK Lobster', 'Lobster', Georgia, serif",
|
|
101
|
+
elegant: "'KK YesevaOne', 'Yeseva One', Georgia, serif",
|
|
102
|
+
rounded: "'KK Comfortaa', 'Comfortaa', -apple-system, sans-serif",
|
|
103
|
+
serif: "'KK PTSerif', 'PT Serif', Georgia, serif",
|
|
104
|
+
mono: "'KK PTMono', 'PT Mono', ui-monospace, Menlo, monospace",
|
|
105
|
+
};
|
|
106
|
+
return map[id] || map.default;
|
|
107
|
+
}
|
|
108
|
+
|
|
88
109
|
function renderLabelNodeBody(node, body) {
|
|
89
110
|
if (!node.textStyle) node.textStyle = { ...LABEL_DEFAULT_STYLE };
|
|
90
111
|
|
|
@@ -706,6 +727,12 @@ function attachDrag(el, node) {
|
|
|
706
727
|
// Edit-режим label — не таскаем (контекст редактирования текста).
|
|
707
728
|
if (e.currentTarget?.classList?.contains('label-text') && e.currentTarget.isContentEditable) return;
|
|
708
729
|
if (e.target.closest('.delete')) return;
|
|
730
|
+
// Источник mousedown'а: header или label-text. Для label-text НЕ
|
|
731
|
+
// вызываем preventDefault и canvas.appendChild на mousedown — они
|
|
732
|
+
// ломают браузерный счётчик кликов и dblclick на label не срабатывает.
|
|
733
|
+
// Z-promote и preventDefault выполнятся ниже, в onMove, когда drag
|
|
734
|
+
// реально начнётся (перешёл порог 4px).
|
|
735
|
+
const sourceIsLabelText = e.currentTarget?.classList?.contains('label-text');
|
|
709
736
|
// Multi-select c модификаторами — без drag
|
|
710
737
|
if (e.metaKey || e.ctrlKey || e.shiftKey) {
|
|
711
738
|
e.preventDefault();
|
|
@@ -720,16 +747,18 @@ function attachDrag(el, node) {
|
|
|
720
747
|
state.selectedNodeIds.add(node.id);
|
|
721
748
|
renderSelection();
|
|
722
749
|
}
|
|
723
|
-
e.preventDefault();
|
|
750
|
+
if (!sourceIsLabelText) e.preventDefault();
|
|
724
751
|
const startX = e.clientX, startY = e.clientY;
|
|
725
752
|
const origX = node.x, origY = node.y;
|
|
726
753
|
el.classList.add('selected');
|
|
727
|
-
canvas.appendChild(el);
|
|
728
754
|
const arr = state.currentBoard.metadata.nodes;
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
arr.
|
|
732
|
-
arr.
|
|
755
|
+
if (!sourceIsLabelText) {
|
|
756
|
+
canvas.appendChild(el);
|
|
757
|
+
const idx = arr.indexOf(node);
|
|
758
|
+
if (idx >= 0 && idx < arr.length - 1) {
|
|
759
|
+
arr.splice(idx, 1);
|
|
760
|
+
arr.push(node);
|
|
761
|
+
}
|
|
733
762
|
}
|
|
734
763
|
|
|
735
764
|
// Multi-drag: если выделено несколько нод и кликнули по одной из них —
|
|
@@ -759,6 +788,18 @@ function attachDrag(el, node) {
|
|
|
759
788
|
const onMove = ev => {
|
|
760
789
|
const dx = ev.clientX - startX, dy = ev.clientY - startY;
|
|
761
790
|
if (!dragInitialized && Math.hypot(dx, dy) < 4) return;
|
|
791
|
+
// Первый раз перешли порог — для label-text z-promote отложен сюда,
|
|
792
|
+
// чтобы не ломать dblclick. Также гасим случайно стартовавший
|
|
793
|
+
// text-selection в read-режиме.
|
|
794
|
+
if (!dragInitialized && sourceIsLabelText) {
|
|
795
|
+
canvas.appendChild(el);
|
|
796
|
+
const idx = arr.indexOf(node);
|
|
797
|
+
if (idx >= 0 && idx < arr.length - 1) {
|
|
798
|
+
arr.splice(idx, 1);
|
|
799
|
+
arr.push(node);
|
|
800
|
+
}
|
|
801
|
+
try { window.getSelection()?.removeAllRanges(); } catch {}
|
|
802
|
+
}
|
|
762
803
|
dragInitialized = true;
|
|
763
804
|
// Проверяем — курсор над таймлайном?
|
|
764
805
|
const tlTrackEl = document.elementsFromPoint(ev.clientX, ev.clientY)
|
package/renderer/styles.css
CHANGED
|
@@ -526,6 +526,17 @@
|
|
|
526
526
|
природу» ноды, в read-mode сигнал что dblclick → редактировать. */
|
|
527
527
|
.node.label-node .label-text { cursor: text; }
|
|
528
528
|
.node.label-node:hover .label-text:not([contenteditable]) { cursor: pointer; }
|
|
529
|
+
/* В read-режиме блокируем text-selection — чтобы single-click не выделял
|
|
530
|
+
слово, и mousedown без preventDefault (для работы dblclick) не давал
|
|
531
|
+
браузеру стартовать selection-drag. В edit-режиме выделение разрешено. */
|
|
532
|
+
.node.label-node .label-text:not([contenteditable]) {
|
|
533
|
+
user-select: none;
|
|
534
|
+
-webkit-user-select: none;
|
|
535
|
+
}
|
|
536
|
+
.node.label-node .label-text[contenteditable] {
|
|
537
|
+
user-select: text;
|
|
538
|
+
-webkit-user-select: text;
|
|
539
|
+
}
|
|
529
540
|
/* === Label-шрифты ===
|
|
530
541
|
Все варианты — bundled woff2 (см. assets/fonts/), кириллица поддержана. */
|
|
531
542
|
.node.label-node .label-text[data-font="pencil"] {
|