kingkont 0.7.78 → 0.7.80

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.78",
3
+ "version": "0.7.80",
4
4
  "description": "KingKont · Chatium — нод-редактор сцен с AI-генерацией (картинки/видео/голос/SFX/музыка/текст)",
5
5
  "main": "main.js",
6
6
  "bin": {
package/renderer/media.js CHANGED
@@ -837,6 +837,63 @@ canvasWrap.addEventListener('drop', async e => {
837
837
  scheduleSave();
838
838
  });
839
839
 
840
+ // Cmd+V из системного буфера: вставляет картинку/файл в текущую сцену.
841
+ // Срабатывает только если фокус НЕ на input/textarea (там юзер пастит
842
+ // текст) и НЕ на наших нодах (там Cmd+V обрабатывается отдельно через
843
+ // keydown в settings.js — он preventDefault'ит, и paste event сюда не
844
+ // доходит). Поддерживает 2 источника:
845
+ // - Файлы (Finder Cmd+C) — clipboardData.files
846
+ // - Картинки прямо из браузера (Edit→Copy Image) — clipboardData.items
847
+ // с kind='file' и type='image/*'
848
+ document.addEventListener('paste', async e => {
849
+ if (!state.currentBoard) return;
850
+ // Если фокус на текстовом инпуте — паст обработает он (юзер пастит текст).
851
+ const ae = document.activeElement;
852
+ if (ae && ae.matches?.('input, textarea, [contenteditable=""], [contenteditable="true"]')) return;
853
+ const cd = e.clipboardData;
854
+ if (!cd) return;
855
+ // Собираем все источники файлов из буфера, дедупим по size+type.
856
+ const all = [...(cd.files || [])];
857
+ for (const it of (cd.items || [])) {
858
+ if (it.kind === 'file') {
859
+ const f = it.getAsFile();
860
+ if (f && !all.some(x => x.size === f.size && x.type === f.type && x.name === f.name)) {
861
+ all.push(f);
862
+ }
863
+ }
864
+ }
865
+ if (!all.length) return;
866
+ e.preventDefault();
867
+ const cx = canvasWrap.scrollLeft / state.zoom + 200;
868
+ const cy = canvasWrap.scrollTop / state.zoom + 100;
869
+ let i = 0;
870
+ for (const f of all) {
871
+ const type = getFileType(f);
872
+ if (!type) continue;
873
+ // Картинка из браузера обычно даёт безымянный File типа 'image/png'.
874
+ // Синтезируем читаемое имя со штампом времени.
875
+ let file = f;
876
+ if (!f.name || f.name === 'image.png' || f.name === 'unknown') {
877
+ const ext = (f.type.split('/')[1] || 'png').replace('jpeg', 'jpg');
878
+ file = new File([f], `pasted_${Date.now()}.${ext}`, { type: f.type });
879
+ }
880
+ try {
881
+ const filename = await importToBoard(state.currentBoard.handle, file, type);
882
+ const node = {
883
+ id: crypto.randomUUID(), type, file: filename,
884
+ x: cx + i*24, y: cy + i*24,
885
+ };
886
+ if (type === 'text') {
887
+ try { node.text = await file.text(); } catch { node.text = ''; }
888
+ }
889
+ state.currentBoard.metadata.nodes.push(node);
890
+ canvas.appendChild(await createNodeEl(node));
891
+ i++;
892
+ } catch (err) { console.error(err); alert(`Не удалось вставить: ${err.message}`); }
893
+ }
894
+ scheduleSave();
895
+ });
896
+
840
897
  // =================== Текстовая нода ===================
841
898
  $('addText').addEventListener('click', async () => {
842
899
  if (!state.currentBoard) return;
@@ -390,7 +390,6 @@
390
390
  .node-footer {
391
391
  display: flex; align-items: center; justify-content: center;
392
392
  gap: 2px; padding: 4px;
393
- border-top: 1px solid #383838;
394
393
  font-size: 11px; color: #888;
395
394
  }
396
395
  .node-footer button {
@@ -897,7 +896,7 @@
897
896
  .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
897
  .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
898
  .node-header {
900
- padding: 4px 8px; border-bottom: 1px solid #383838;
899
+ padding: 4px 8px;
901
900
  display: flex; justify-content: space-between; align-items: center; gap: 6px;
902
901
  min-height: 22px; cursor: move;
903
902
  }