kingkont 0.7.91 → 0.7.93

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.91",
3
+ "version": "0.7.93",
4
4
  "description": "KingKont · Chatium — нод-редактор сцен с AI-генерацией (картинки/видео/голос/SFX/музыка/текст)",
5
5
  "main": "main.js",
6
6
  "bin": {
@@ -210,7 +210,7 @@ async function addLabelAt(pos) {
210
210
  const node = {
211
211
  id: crypto.randomUUID(),
212
212
  type: 'label',
213
- text: '',
213
+ text: 'Подпись', // дефолтный текст — сразу видно ноду
214
214
  textStyle: { fontSize: 32, italic: false, fontFamily: 'pencil' },
215
215
  x, y,
216
216
  // width/height не задаём — label всегда auto-размер по тексту
@@ -220,19 +220,19 @@ async function addLabelAt(pos) {
220
220
  const el = await createNodeEl(node);
221
221
  canvas.appendChild(el);
222
222
  scheduleSave();
223
- // Авто-фокус в редактируемый текст, чтобы юзер сразу начал печатать.
223
+ // Сразу входим в режим редактирования и выделяем весь текст
224
+ // юзер начинает печатать, и «Подпись» заменяется на введённое.
224
225
  setTimeout(() => {
225
226
  const ed = el.querySelector('.label-text');
226
- if (ed) {
227
- ed.focus();
228
- // Курсор в конец (если контент пустой — это всё равно начало).
229
- const sel = window.getSelection();
230
- const r = document.createRange();
231
- r.selectNodeContents(ed);
232
- r.collapse(false);
233
- sel?.removeAllRanges();
234
- sel?.addRange(r);
235
- }
227
+ if (!ed) return;
228
+ ed.contentEditable = 'plaintext-only';
229
+ ed.focus();
230
+ // Select all — type-to-replace UX (как на iOS notes / новой подписи).
231
+ const sel = window.getSelection();
232
+ const r = document.createRange();
233
+ r.selectNodeContents(ed);
234
+ sel?.removeAllRanges();
235
+ sel?.addRange(r);
236
236
  }, 30);
237
237
  }
238
238
 
@@ -179,11 +179,21 @@ function renderLabelNodeBody(node, body) {
179
179
  ed.addEventListener('mousedown', e => {
180
180
  // В edit-режиме: не пускаем drag-обработчик (нужно поставить курсор/выделить текст).
181
181
  if (ed.isContentEditable) e.stopPropagation();
182
- // В read-режиме: пузырится — attachDrag-handler (повешен и на label-text)
183
- // обработает single-click как «выбрать ноду» и подхватит drag.
182
+ // В read-режиме: drag-handler (привязан ниже) обработает single-click
183
+ // как «выбрать ноду» и подхватит drag.
184
184
  });
185
185
 
186
186
  body.appendChild(ed);
187
+
188
+ // Привязываем drag-handler ИМЕННО ЗДЕСЬ — потому что refreshNodeDOM
189
+ // (вызывается при смене шрифта/размера/курсива через ПКМ) пересоздаёт
190
+ // .label-text. Если drag вешать в attachDrag (вызывается один раз в
191
+ // createNodeEl), то после refreshNodeDOM новый элемент окажется без
192
+ // обработчика — и ноду нельзя будет двигать.
193
+ const nodeEl = body.closest('.node');
194
+ if (nodeEl) {
195
+ ed.addEventListener('mousedown', makeDragHandler(nodeEl, node));
196
+ }
187
197
  }
188
198
 
189
199
  async function renderNodeBody(node, body) {
@@ -739,15 +749,18 @@ function attachResize(el, node, handle) {
739
749
 
740
750
  function attachDrag(el, node) {
741
751
  const header = el.querySelector('.node-header');
742
- // Для label-нод вешаем drag и на сам текст: header скрыт до hover, а
743
- // юзер хочет цеплять ноду с любого места видимого текста. В handler'е
744
- // пропускаем срабатывание, если label в edit-режиме (см. условие ниже).
745
- const sources = [header];
746
- if (node.type === 'label') {
747
- const lt = el.querySelector('.label-text');
748
- if (lt) sources.push(lt);
749
- }
750
- const handler = e => {
752
+ // Header-source: drag вешается один раз при создании ноды. .label-text
753
+ // отдельно привязывается из renderLabelNodeBody потому что при смене
754
+ // шрифта/размера через ПКМ refreshNodeDOM пересоздаёт DOM-элемент
755
+ // .label-text, и старый listener умирает вместе со старым элементом.
756
+ if (header) header.addEventListener('mousedown', makeDragHandler(el, node));
757
+ }
758
+
759
+ // Создаёт drag-handler для конкретной (el, node) пары. Вынесено отдельно
760
+ // чтобы можно было вешать на новый .label-text после refreshNodeDOM
761
+ // (renderLabelNodeBody делает это при каждом рендере).
762
+ function makeDragHandler(el, node) {
763
+ return e => {
751
764
  // Edit-режим label — не таскаем (контекст редактирования текста).
752
765
  if (e.currentTarget?.classList?.contains('label-text') && e.currentTarget.isContentEditable) return;
753
766
  if (e.target.closest('.delete')) return;
@@ -907,7 +920,6 @@ function attachDrag(el, node) {
907
920
  document.addEventListener('mousemove', onMove);
908
921
  document.addEventListener('mouseup', onUp);
909
922
  };
910
- for (const src of sources) if (src) src.addEventListener('mousedown', handler);
911
923
  }
912
924
 
913
925
  // Добавить ноду как клип в указанную дорожку с ripple-вставкой по времени
@@ -1084,7 +1096,10 @@ async function deleteSelectedNodes() {
1084
1096
  document.addEventListener('keydown', async e => {
1085
1097
  const mod = e.metaKey || e.ctrlKey;
1086
1098
  const tag = (e.target?.tagName || '').toLowerCase();
1087
- const inText = tag === 'textarea' || tag === 'input';
1099
+ // contentEditable label-text тоже текстовое поле: Backspace там должен
1100
+ // удалять символ, а не ноду. e.target.isContentEditable отлавливает любой
1101
+ // contenteditable-элемент (наша label-нода в edit-режиме).
1102
+ const inText = tag === 'textarea' || tag === 'input' || !!e.target?.isContentEditable;
1088
1103
 
1089
1104
  // Zoom shortcuts (работают даже в инпутах — это естественно)
1090
1105
  if (mod && (e.key === '=' || e.key === '+')) { e.preventDefault(); applyZoom(state.zoom * 1.25); return; }