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 +1 -1
- package/renderer/media.js +57 -0
- package/renderer/styles.css +1 -2
package/package.json
CHANGED
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;
|
package/renderer/styles.css
CHANGED
|
@@ -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;
|
|
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
|
}
|