kingkont 0.8.0 → 0.8.1
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/settings.js +66 -2
- package/renderer/styles.css +25 -4
package/package.json
CHANGED
package/renderer/settings.js
CHANGED
|
@@ -1285,7 +1285,7 @@ async function copySelectedNodes() {
|
|
|
1285
1285
|
const n = state.currentBoard.metadata.nodes.find(x => x.id === id);
|
|
1286
1286
|
if (!n) continue;
|
|
1287
1287
|
let blob = null, textContent = null;
|
|
1288
|
-
if (n.type === 'text') {
|
|
1288
|
+
if (n.type === 'text' || n.type === 'label') {
|
|
1289
1289
|
textContent = n.text || '';
|
|
1290
1290
|
} else if (n.file) {
|
|
1291
1291
|
try {
|
|
@@ -1296,13 +1296,16 @@ async function copySelectedNodes() {
|
|
|
1296
1296
|
state.clipboard.push({ node: { ...n }, blob, textContent });
|
|
1297
1297
|
}
|
|
1298
1298
|
console.log(`Скопировано в буфер: ${state.clipboard.length} нод`);
|
|
1299
|
+
// Дублируем в системный буфер обмена — чтобы Cmd+V в других приложениях
|
|
1300
|
+
// (браузер, Telegram, image-editor) тоже что-то получали.
|
|
1301
|
+
await writeNodesToSystemClipboard(state.clipboard);
|
|
1299
1302
|
}
|
|
1300
1303
|
|
|
1301
1304
|
// Скопировать одну ноду в буфер (для ПКМ → "Скопировать")
|
|
1302
1305
|
async function copyNodeToClipboard(node) {
|
|
1303
1306
|
if (!state.currentBoard) return;
|
|
1304
1307
|
let blob = null, textContent = null;
|
|
1305
|
-
if (node.type === 'text') {
|
|
1308
|
+
if (node.type === 'text' || node.type === 'label') {
|
|
1306
1309
|
textContent = node.text || '';
|
|
1307
1310
|
} else if (node.file) {
|
|
1308
1311
|
try {
|
|
@@ -1314,6 +1317,67 @@ async function copyNodeToClipboard(node) {
|
|
|
1314
1317
|
state.clipboardPasteCount = 0;
|
|
1315
1318
|
state.clipboardSourceBoardKey = state.currentBoard.key;
|
|
1316
1319
|
console.log(`В буфер: ${node.name || node.id}`);
|
|
1320
|
+
await writeNodesToSystemClipboard(state.clipboard);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// Кладёт ноды в системный буфер обмена (где это имеет смысл):
|
|
1324
|
+
// • image — image/png (конвертим из jpg/webp через canvas)
|
|
1325
|
+
// • text/label — text/plain (если несколько — соединяем через \n\n)
|
|
1326
|
+
// • video/audio — пропускаем (Clipboard API binary-MIME'ов не держит)
|
|
1327
|
+
// Ошибки глотаем — основной путь это внутренний state.clipboard, system
|
|
1328
|
+
// clipboard это бонус. Если permission denied / focus lost / unsupported
|
|
1329
|
+
// MIME — внутренний работает как раньше.
|
|
1330
|
+
async function writeNodesToSystemClipboard(items) {
|
|
1331
|
+
if (!items?.length || !navigator.clipboard) return;
|
|
1332
|
+
try {
|
|
1333
|
+
// Один image-ноду — пишем как картинку.
|
|
1334
|
+
if (items.length === 1 && items[0].blob && items[0].node?.type === 'image') {
|
|
1335
|
+
const blob = items[0].blob;
|
|
1336
|
+
const pngBlob = blob.type === 'image/png' ? blob : await blobToPng(blob);
|
|
1337
|
+
if (pngBlob && typeof ClipboardItem === 'function') {
|
|
1338
|
+
await navigator.clipboard.write([new ClipboardItem({ 'image/png': pngBlob })]);
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
// Текстовые ноды (text + label) — собираем тексты, пишем plain.
|
|
1343
|
+
const texts = items
|
|
1344
|
+
.filter(it => (it.node?.type === 'text' || it.node?.type === 'label') && typeof it.textContent === 'string')
|
|
1345
|
+
.map(it => it.textContent);
|
|
1346
|
+
if (texts.length) {
|
|
1347
|
+
await navigator.clipboard.writeText(texts.join('\n\n'));
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
// video/audio в одиночку — кладём имя файла как fallback (юзер хотя
|
|
1351
|
+
// бы pasted'ит куда-то осмысленный текст).
|
|
1352
|
+
const single = items[0]?.node;
|
|
1353
|
+
if (single?.file) {
|
|
1354
|
+
await navigator.clipboard.writeText(single.name || single.file.split('/').pop() || '');
|
|
1355
|
+
}
|
|
1356
|
+
} catch (e) {
|
|
1357
|
+
// Permission / async-context потерян — internal clipboard всё равно
|
|
1358
|
+
// отработал, юзер заметит при Cmd+V внутри редактора.
|
|
1359
|
+
console.warn('system clipboard write failed:', e?.message || e);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// Конвертирует image-blob (jpg/webp/heic/etc) в PNG через canvas —
|
|
1364
|
+
// system clipboard принимает только image/png универсально.
|
|
1365
|
+
async function blobToPng(blob) {
|
|
1366
|
+
const url = URL.createObjectURL(blob);
|
|
1367
|
+
try {
|
|
1368
|
+
const img = await new Promise((res, rej) => {
|
|
1369
|
+
const i = new Image();
|
|
1370
|
+
i.onload = () => res(i);
|
|
1371
|
+
i.onerror = rej;
|
|
1372
|
+
i.src = url;
|
|
1373
|
+
});
|
|
1374
|
+
const canvas = document.createElement('canvas');
|
|
1375
|
+
canvas.width = img.naturalWidth;
|
|
1376
|
+
canvas.height = img.naturalHeight;
|
|
1377
|
+
canvas.getContext('2d').drawImage(img, 0, 0);
|
|
1378
|
+
return await new Promise(res => canvas.toBlob(res, 'image/png'));
|
|
1379
|
+
} catch { return null; }
|
|
1380
|
+
finally { URL.revokeObjectURL(url); }
|
|
1317
1381
|
}
|
|
1318
1382
|
|
|
1319
1383
|
// Заменить контент текущей ноды содержимым из буфера (текущее уходит в history)
|
package/renderer/styles.css
CHANGED
|
@@ -639,8 +639,14 @@
|
|
|
639
639
|
transparent 0%, transparent 55%,
|
|
640
640
|
#777 55%, #777 65%, transparent 65%,
|
|
641
641
|
transparent 75%, #777 75%, #777 85%, transparent 85%);
|
|
642
|
-
|
|
642
|
+
/* Прячем по умолчанию — handle видно только на hover ноды. Раньше
|
|
643
|
+
был opacity:0.45, и на image-нодах после v0.7.97 (square corners)
|
|
644
|
+
полоски стали хорошо видны против картинки и выглядели как
|
|
645
|
+
посторонний UI-элемент. */
|
|
646
|
+
opacity: 0; transition: opacity 0.15s;
|
|
647
|
+
border-bottom-right-radius: 8px;
|
|
643
648
|
}
|
|
649
|
+
.node:hover .resize-handle { opacity: 0.55; }
|
|
644
650
|
.resize-handle:hover { opacity: 1; }
|
|
645
651
|
|
|
646
652
|
.node-footer {
|
|
@@ -1282,18 +1288,33 @@
|
|
|
1282
1288
|
.seg-control .seg:first-child { border-radius: 4px 0 0 4px; }
|
|
1283
1289
|
.seg-control .seg:last-child { border-radius: 0 4px 4px 0; border-left: none; }
|
|
1284
1290
|
.seg-control .seg.active { background: #3a5a8a; border-color: #4a6a9a; color: #fff; }
|
|
1291
|
+
/* Hover ТОЛЬКО на наведённую кнопку — синеватый оттенок, чтобы видно
|
|
1292
|
+
было «вот это сейчас выберется», а не серая «обычный hover» (которая
|
|
1293
|
+
раньше шла от глобального button:hover и выглядела одинаково с
|
|
1294
|
+
disabled-состоянием). */
|
|
1295
|
+
.seg-control .seg:hover:not(.active):not(:disabled) {
|
|
1296
|
+
background: #2a3854; border-color: #3a5a8a; color: #e8e8e8;
|
|
1297
|
+
}
|
|
1298
|
+
.seg-control .seg.active:hover:not(:disabled) {
|
|
1299
|
+
background: #4a6aa0;
|
|
1300
|
+
}
|
|
1285
1301
|
.status { font-size: 12px; color: #aaa; display: flex; align-items: center; gap: 8px; }
|
|
1286
1302
|
.status.error { color: #e07a6a; }
|
|
1287
1303
|
.spinner { width: 14px; height: 14px; border: 2px solid #444; border-top-color: #6a8aaa; border-radius: 50%; animation: spin 0.9s linear infinite; }
|
|
1288
1304
|
.spinner.lg { width: 32px; height: 32px; border-width: 3px; }
|
|
1289
1305
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
1290
1306
|
|
|
1291
|
-
/* mention popup
|
|
1307
|
+
/* mention popup — overlay, чтобы появление @-списка не двигало modal-actions
|
|
1308
|
+
вниз (модалка не менялась бы по высоте). Позиционируется absolute
|
|
1309
|
+
относительно <label>-родителя (см. правило ниже). */
|
|
1310
|
+
.modal-card label:has(> .mention-popup) { position: relative; }
|
|
1292
1311
|
.mention-popup {
|
|
1312
|
+
position: absolute; left: 0; right: 0; top: 100%;
|
|
1293
1313
|
background: #1e1e1e; border: 1px solid #444; border-radius: 6px;
|
|
1294
|
-
box-shadow: 0
|
|
1314
|
+
box-shadow: 0 8px 24px rgba(0,0,0,0.7);
|
|
1295
1315
|
max-height: 240px; overflow-y: auto;
|
|
1296
|
-
margin-top:
|
|
1316
|
+
margin-top: 4px;
|
|
1317
|
+
z-index: 100;
|
|
1297
1318
|
}
|
|
1298
1319
|
.mention-popup.hidden { display: none; }
|
|
1299
1320
|
.mention-popup .mit {
|