kingkont 0.7.79 → 0.7.81

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kingkont",
3
- "version": "0.7.79",
3
+ "version": "0.7.81",
4
4
  "description": "KingKont · Chatium — нод-редактор сцен с AI-генерацией (картинки/видео/голос/SFX/музыка/текст)",
5
5
  "main": "main.js",
6
6
  "bin": {
@@ -52,6 +52,101 @@ $('settingsRegen').addEventListener('click', () => {
52
52
  regenerateNode(node); // там редактируются ВСЕ поля (промпт, модель, голос)
53
53
  });
54
54
 
55
+ // =================== Text-node body (с форматированием) ===================
56
+ // Шрифты — system-only, чтобы не тащить Google Fonts CDN (приложение
57
+ // должно работать офлайн). На macOS все четыре варианта выглядят отлично;
58
+ // на Windows/Linux fallback'и держат смысл (cursive/fantasy/serif/mono).
59
+ const TEXT_NODE_FONTS = [
60
+ { id: 'default', label: 'Обычный' },
61
+ { id: 'handwritten', label: 'Карандашом' },
62
+ { id: 'marker', label: 'Краской' },
63
+ { id: 'serif', label: 'С засечками' },
64
+ { id: 'mono', label: 'Моноширинный' },
65
+ ];
66
+ const TEXT_NODE_FONT_SIZES = [12, 14, 18, 24, 32, 48, 64];
67
+ const TEXT_NODE_DEFAULT_STYLE = { fontSize: 14, italic: false, fontFamily: 'default' };
68
+
69
+ function getTextNodeStyle(node) {
70
+ return { ...TEXT_NODE_DEFAULT_STYLE, ...(node.textStyle || {}) };
71
+ }
72
+
73
+ function applyTextNodeStyle(ta, style) {
74
+ ta.style.fontSize = (style.fontSize || 14) + 'px';
75
+ ta.style.fontStyle = style.italic ? 'italic' : 'normal';
76
+ ta.dataset.font = style.fontFamily || 'default';
77
+ }
78
+
79
+ function renderTextNodeBody(node, body) {
80
+ // Гарантируем что у ноды есть textStyle (без spread — реальный объект).
81
+ if (!node.textStyle) node.textStyle = { ...TEXT_NODE_DEFAULT_STYLE };
82
+
83
+ // Тулбар форматирования: шрифт, размер, italic.
84
+ const tb = document.createElement('div');
85
+ tb.className = 'text-toolbar';
86
+ // Не блокируем drag-mousedown по тулбару — но клики по контролам
87
+ // не должны таскать ноду. stopPropagation на mousedown решает это.
88
+ tb.addEventListener('mousedown', e => e.stopPropagation());
89
+
90
+ // Селект шрифта.
91
+ const fontSel = document.createElement('select');
92
+ fontSel.title = 'Шрифт';
93
+ for (const f of TEXT_NODE_FONTS) {
94
+ const o = document.createElement('option');
95
+ o.value = f.id;
96
+ o.textContent = f.label;
97
+ fontSel.appendChild(o);
98
+ }
99
+ fontSel.value = node.textStyle.fontFamily || 'default';
100
+ fontSel.addEventListener('change', () => {
101
+ node.textStyle.fontFamily = fontSel.value;
102
+ applyTextNodeStyle(ta, node.textStyle);
103
+ scheduleSave();
104
+ });
105
+ tb.appendChild(fontSel);
106
+
107
+ // Селект размера.
108
+ const sizeSel = document.createElement('select');
109
+ sizeSel.title = 'Размер шрифта';
110
+ for (const s of TEXT_NODE_FONT_SIZES) {
111
+ const o = document.createElement('option');
112
+ o.value = String(s);
113
+ o.textContent = s + 'px';
114
+ sizeSel.appendChild(o);
115
+ }
116
+ sizeSel.value = String(node.textStyle.fontSize || 14);
117
+ sizeSel.addEventListener('change', () => {
118
+ node.textStyle.fontSize = parseInt(sizeSel.value, 10) || 14;
119
+ applyTextNodeStyle(ta, node.textStyle);
120
+ scheduleSave();
121
+ });
122
+ tb.appendChild(sizeSel);
123
+
124
+ // Italic toggle.
125
+ const italicBtn = document.createElement('button');
126
+ italicBtn.className = 'tt-italic';
127
+ italicBtn.textContent = 'I';
128
+ italicBtn.title = 'Курсив';
129
+ if (node.textStyle.italic) italicBtn.classList.add('active');
130
+ italicBtn.addEventListener('click', e => {
131
+ e.stopPropagation();
132
+ node.textStyle.italic = !node.textStyle.italic;
133
+ italicBtn.classList.toggle('active', node.textStyle.italic);
134
+ applyTextNodeStyle(ta, node.textStyle);
135
+ scheduleSave();
136
+ });
137
+ tb.appendChild(italicBtn);
138
+
139
+ body.appendChild(tb);
140
+
141
+ const ta = document.createElement('textarea');
142
+ ta.value = node.text || '';
143
+ ta.placeholder = 'Текст ноды...';
144
+ applyTextNodeStyle(ta, node.textStyle);
145
+ ta.addEventListener('input', () => { node.text = ta.value; scheduleSave(); });
146
+ ta.addEventListener('mousedown', e => e.stopPropagation());
147
+ body.appendChild(ta);
148
+ }
149
+
55
150
  async function renderNodeBody(node, body) {
56
151
  body.innerHTML = '';
57
152
  if (node.status === 'generating') {
@@ -186,12 +281,7 @@ async function renderNodeBody(node, body) {
186
281
  }
187
282
 
188
283
  if (node.type === 'text') {
189
- const ta = document.createElement('textarea');
190
- ta.value = node.text || '';
191
- ta.placeholder = 'Текст ноды...';
192
- ta.addEventListener('input', () => { node.text = ta.value; scheduleSave(); });
193
- ta.addEventListener('mousedown', e => e.stopPropagation());
194
- body.appendChild(ta);
284
+ renderTextNodeBody(node, body);
195
285
  } else if (['video','audio','image'].includes(node.type) && node.file) {
196
286
  const nodeEl = body.closest('.node');
197
287
  nodeEl?.classList.toggle('image-node', node.type === 'image');
@@ -323,11 +323,56 @@
323
323
  box-shadow: 0 0 0 2px #5aa8ff, 0 8px 24px rgba(90,168,255,0.4);
324
324
  }
325
325
  .node-body { flex: 1; min-height: 0; }
326
- .node.text-node .node-body { padding: 0; display: flex; }
326
+ .node.text-node .node-body { padding: 0; display: flex; flex-direction: column; }
327
327
  .node.text-node .node-body textarea {
328
- width: 100%; height: 100%; resize: none; min-height: 80px;
328
+ width: 100%; resize: none; min-height: 80px;
329
329
  border: none; border-radius: 0; padding: 12px; flex: 1;
330
+ background: transparent; color: #eaeaea;
331
+ font-size: 14px; font-style: normal;
332
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
333
+ line-height: 1.4;
334
+ }
335
+ /* Декоративные шрифты для текстовых нод. */
336
+ .node.text-node .node-body textarea[data-font="handwritten"] {
337
+ /* «Карандашом» — рукописные шрифты, мягкие, наклонные. */
338
+ font-family: 'Caveat', 'Marker Felt', 'Bradley Hand', 'Snell Roundhand', cursive;
339
+ line-height: 1.2;
340
+ }
341
+ .node.text-node .node-body textarea[data-font="marker"] {
342
+ /* «Краской/маркером» — крупные мазки, plakat-стиль. */
343
+ font-family: 'Permanent Marker', 'Chalkduster', 'Marker Felt', 'Comic Sans MS', fantasy;
344
+ letter-spacing: 0.02em;
345
+ }
346
+ .node.text-node .node-body textarea[data-font="serif"] {
347
+ font-family: Georgia, 'Times New Roman', serif;
348
+ }
349
+ .node.text-node .node-body textarea[data-font="mono"] {
350
+ font-family: ui-monospace, 'SF Mono', Menlo, 'Courier New', monospace;
351
+ }
352
+
353
+ /* Тулбар форматирования — компактный, появляется на hover, чтобы не
354
+ отвлекать когда юзер просто читает. */
355
+ .node.text-node .text-toolbar {
356
+ display: flex; align-items: center; gap: 4px;
357
+ padding: 4px 6px; background: #181818; border-bottom: 1px solid #2a2a2a;
358
+ font-size: 11px; opacity: 0.35; transition: opacity 0.15s;
359
+ flex-shrink: 0;
330
360
  }
361
+ .node.text-node:hover .text-toolbar,
362
+ .node.text-node .text-toolbar:focus-within { opacity: 1; }
363
+ .node.text-node .text-toolbar select,
364
+ .node.text-node .text-toolbar button {
365
+ background: #1e1e1e; border: 1px solid #383838; color: #ccc;
366
+ border-radius: 3px; padding: 2px 6px; font-size: 11px;
367
+ cursor: pointer; line-height: 1.2;
368
+ }
369
+ .node.text-node .text-toolbar select:hover,
370
+ .node.text-node .text-toolbar button:hover { background: #2c2c2c; color: #fff; }
371
+ .node.text-node .text-toolbar button.active {
372
+ background: #3a5a8a; color: #fff; border-color: #4a6a9a;
373
+ }
374
+ .node.text-node .text-toolbar .tt-spacer { flex: 1; }
375
+ .node.text-node .text-toolbar .tt-italic { font-style: italic; min-width: 22px; }
331
376
  .node.video-node .node-body { padding: 0; overflow: hidden; border-radius: 0 0 8px 8px; }
332
377
  .node.video-node .node-body video { width: 100%; height: auto; max-height: none; background: transparent; border-radius: 0; display: block; }
333
378
  .audio-speed { display: flex; gap: 2px; margin-top: 6px; }
@@ -390,7 +435,6 @@
390
435
  .node-footer {
391
436
  display: flex; align-items: center; justify-content: center;
392
437
  gap: 2px; padding: 4px;
393
- border-top: 1px solid #383838;
394
438
  font-size: 11px; color: #888;
395
439
  }
396
440
  .node-footer button {
@@ -897,7 +941,7 @@
897
941
  .node.selected { border-color: #6a8aaa; box-shadow: 0 0 0 2px rgba(106,138,170,0.4), 0 4px 12px rgba(0,0,0,0.4); }
898
942
  .node.picked { border-color: #f0a040; box-shadow: 0 0 0 2px rgba(240,160,64,0.55), 0 4px 12px rgba(0,0,0,0.4); }
899
943
  .node-header {
900
- padding: 4px 8px; border-bottom: 1px solid #383838;
944
+ padding: 4px 8px;
901
945
  display: flex; justify-content: space-between; align-items: center; gap: 6px;
902
946
  min-height: 22px; cursor: move;
903
947
  }