kingkont 0.7.64 → 0.7.65
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/board.js +3 -0
- package/renderer/media.js +94 -11
- package/renderer/styles.css +4 -0
package/package.json
CHANGED
package/renderer/board.js
CHANGED
|
@@ -603,6 +603,9 @@ async function closeProject() {
|
|
|
603
603
|
if (state.currentBoard?.urls) {
|
|
604
604
|
for (const u of Object.values(state.currentBoard.urls)) URL.revokeObjectURL(u);
|
|
605
605
|
}
|
|
606
|
+
// Освобождаем blob URL'ы для миниатюр @-popup'а — они грузились лазиво
|
|
607
|
+
// при открытии mention-popup, кэш накапливался.
|
|
608
|
+
if (typeof revokeMentionThumbCache === 'function') revokeMentionThumbCache();
|
|
606
609
|
state.filmHandle = null;
|
|
607
610
|
state.currentBoard = null;
|
|
608
611
|
window.appProject?.notifyState(false);
|
package/renderer/media.js
CHANGED
|
@@ -1132,6 +1132,8 @@ function updateMentionPopup() {
|
|
|
1132
1132
|
type: 'image',
|
|
1133
1133
|
scope: 'char-image',
|
|
1134
1134
|
charName: c.name,
|
|
1135
|
+
file: img.file,
|
|
1136
|
+
boardHandle: c.handle,
|
|
1135
1137
|
});
|
|
1136
1138
|
}
|
|
1137
1139
|
}
|
|
@@ -1146,6 +1148,8 @@ function updateMentionPopup() {
|
|
|
1146
1148
|
type: 'image',
|
|
1147
1149
|
scope: 'loc-image',
|
|
1148
1150
|
locName: l.name,
|
|
1151
|
+
file: img.file,
|
|
1152
|
+
boardHandle: l.handle,
|
|
1149
1153
|
});
|
|
1150
1154
|
}
|
|
1151
1155
|
}
|
|
@@ -1162,6 +1166,9 @@ function updateMentionPopup() {
|
|
|
1162
1166
|
type: 'image',
|
|
1163
1167
|
scope: 'char',
|
|
1164
1168
|
hasImages, hasSheet: !!c.characterSheet,
|
|
1169
|
+
// Превью: characterSheet если есть, иначе первая imageNodes.
|
|
1170
|
+
file: c.characterSheet || c.imageNodes?.[0]?.file || null,
|
|
1171
|
+
boardHandle: c.handle,
|
|
1165
1172
|
});
|
|
1166
1173
|
}
|
|
1167
1174
|
items = items.filter(s => s.key.toLowerCase().includes(query)).slice(0, 16);
|
|
@@ -1174,7 +1181,11 @@ function updateMentionPopup() {
|
|
|
1174
1181
|
for (const n of state.currentBoard.metadata.nodes) {
|
|
1175
1182
|
if (!n.name || !n.id) continue;
|
|
1176
1183
|
if (!allowed.includes(n.type)) continue;
|
|
1177
|
-
items.push({
|
|
1184
|
+
items.push({
|
|
1185
|
+
key: n.name, label: n.name, type: n.type, scope: 'board',
|
|
1186
|
+
file: n.file || null,
|
|
1187
|
+
boardHandle: state.currentBoard.handle,
|
|
1188
|
+
});
|
|
1178
1189
|
}
|
|
1179
1190
|
if (state.genKind === 'image' || state.genKind === 'video') {
|
|
1180
1191
|
for (const c of state.charactersInfo) {
|
|
@@ -1187,6 +1198,8 @@ function updateMentionPopup() {
|
|
|
1187
1198
|
type: 'image',
|
|
1188
1199
|
scope: 'char',
|
|
1189
1200
|
hasImages, hasSheet: !!c.characterSheet,
|
|
1201
|
+
file: c.characterSheet || c.imageNodes?.[0]?.file || null,
|
|
1202
|
+
boardHandle: c.handle,
|
|
1190
1203
|
});
|
|
1191
1204
|
}
|
|
1192
1205
|
}
|
|
@@ -1200,6 +1213,8 @@ function updateMentionPopup() {
|
|
|
1200
1213
|
type: 'image',
|
|
1201
1214
|
scope: 'loc',
|
|
1202
1215
|
hasImages, hasSheet: !!l.sheet,
|
|
1216
|
+
file: l.sheet || l.imageNodes?.[0]?.file || null,
|
|
1217
|
+
boardHandle: l.handle,
|
|
1203
1218
|
});
|
|
1204
1219
|
}
|
|
1205
1220
|
}
|
|
@@ -1222,6 +1237,14 @@ function updateMentionPopup() {
|
|
|
1222
1237
|
const it = document.createElement('div');
|
|
1223
1238
|
it.className = 'mit' + (i === 0 ? ' selected' : '');
|
|
1224
1239
|
it.dataset.idx = i;
|
|
1240
|
+
// Превью миниатюра — только для image-items с file. Лазиво грузим
|
|
1241
|
+
// через blob URL (с кешем mentionThumbCache).
|
|
1242
|
+
if (s.type === 'image' && s.file && s.boardHandle) {
|
|
1243
|
+
const img = document.createElement('img');
|
|
1244
|
+
img.className = 'mit-thumb';
|
|
1245
|
+
loadMentionThumb(s.boardHandle, s.file).then(url => { if (url) img.src = url; }).catch(() => {});
|
|
1246
|
+
it.appendChild(img);
|
|
1247
|
+
}
|
|
1225
1248
|
const t = document.createElement('span');
|
|
1226
1249
|
t.className = `mtype ${s.type}`;
|
|
1227
1250
|
t.textContent = s.scope === 'char' ? 'персонаж'
|
|
@@ -1239,6 +1262,30 @@ function updateMentionPopup() {
|
|
|
1239
1262
|
popup.classList.remove('hidden');
|
|
1240
1263
|
}
|
|
1241
1264
|
|
|
1265
|
+
// Кеш blob URL'ов для миниатюр @-popup'а. Ключ = boardHandle.name + ':' + file.
|
|
1266
|
+
// blobURL'ы НЕ revoke'аются автоматически — копят память пока юзер открыт
|
|
1267
|
+
// в проекте; при закрытии проекта чистятся (revokeMentionThumbCache).
|
|
1268
|
+
// Это OK потому что миниатюры маленькие, и одна и та же картинка часто
|
|
1269
|
+
// показывается в нескольких popup'ах без релоада.
|
|
1270
|
+
const _mentionThumbCache = new Map();
|
|
1271
|
+
async function loadMentionThumb(boardHandle, file) {
|
|
1272
|
+
if (!boardHandle || !file) return null;
|
|
1273
|
+
const key = (boardHandle.name || '?') + ':' + file;
|
|
1274
|
+
if (_mentionThumbCache.has(key)) return _mentionThumbCache.get(key);
|
|
1275
|
+
try {
|
|
1276
|
+
const fh = await resolveBoardFile(boardHandle, file);
|
|
1277
|
+
const f = await fh.getFile();
|
|
1278
|
+
if (!f.type?.startsWith('image/') && !/\.(jpe?g|png|gif|webp|avif)$/i.test(file)) return null;
|
|
1279
|
+
const url = URL.createObjectURL(f);
|
|
1280
|
+
_mentionThumbCache.set(key, url);
|
|
1281
|
+
return url;
|
|
1282
|
+
} catch { return null; }
|
|
1283
|
+
}
|
|
1284
|
+
function revokeMentionThumbCache() {
|
|
1285
|
+
for (const url of _mentionThumbCache.values()) { try { URL.revokeObjectURL(url); } catch {} }
|
|
1286
|
+
_mentionThumbCache.clear();
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1242
1289
|
function closeMentionPopup() {
|
|
1243
1290
|
mentionState.open = false;
|
|
1244
1291
|
$('mentionPopup').classList.add('hidden');
|
|
@@ -1297,20 +1344,59 @@ $('genPrompt').addEventListener('keydown', e => {
|
|
|
1297
1344
|
});
|
|
1298
1345
|
|
|
1299
1346
|
// =================== Character settings ===================
|
|
1347
|
+
// Хелпер: пути к именованным image-нодам доски. Если эта доска сейчас
|
|
1348
|
+
// открыта в state.currentBoard — берём свежие in-memory нодии (юзер мог
|
|
1349
|
+
// только что добавить ноду, scheduleSave ещё не flush'нул на диск).
|
|
1350
|
+
// Иначе грузим с диска через loadBoardMetadata.
|
|
1351
|
+
// Включаем nameless-ноды по basename файла — без этого только что
|
|
1352
|
+
// созданные ноды не появляются в @-popup'е.
|
|
1353
|
+
async function _scanBoardForRefs(itemKind, item) {
|
|
1354
|
+
let nodes, character, location;
|
|
1355
|
+
const isActive = state.currentBoard?.kind === itemKind && state.currentBoard.name === item.name;
|
|
1356
|
+
if (isActive) {
|
|
1357
|
+
nodes = state.currentBoard.metadata.nodes || [];
|
|
1358
|
+
character = state.currentBoard.metadata.character;
|
|
1359
|
+
location = state.currentBoard.metadata.location;
|
|
1360
|
+
} else {
|
|
1361
|
+
const meta = await loadBoardMetadata(item.handle);
|
|
1362
|
+
nodes = meta.nodes || [];
|
|
1363
|
+
character = meta.character;
|
|
1364
|
+
location = meta.location;
|
|
1365
|
+
}
|
|
1366
|
+
// Image-ноды с file. Имя берём из n.name если есть, иначе basename файла
|
|
1367
|
+
// без расширения. Дедупим по итоговому labelKey.
|
|
1368
|
+
const seen = new Set();
|
|
1369
|
+
const imageNodes = [];
|
|
1370
|
+
for (const n of nodes) {
|
|
1371
|
+
if (n.type !== 'image' || !n.file) continue;
|
|
1372
|
+
let label = (n.name || '').trim();
|
|
1373
|
+
if (!label) {
|
|
1374
|
+
// basename без расширения
|
|
1375
|
+
const base = n.file.split('/').pop() || '';
|
|
1376
|
+
label = base.replace(/\.[^.]+$/, '') || base;
|
|
1377
|
+
}
|
|
1378
|
+
// Дедуп: если 2 ноды с одинаковым label → к второй припишем -<short id>
|
|
1379
|
+
let key = label;
|
|
1380
|
+
if (seen.has(key)) {
|
|
1381
|
+
key = `${label}-${(n.id || '').slice(0, 4)}`;
|
|
1382
|
+
}
|
|
1383
|
+
seen.add(key);
|
|
1384
|
+
imageNodes.push({ name: key, file: n.file, id: n.id });
|
|
1385
|
+
}
|
|
1386
|
+
return { imageNodes, character, location };
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1300
1389
|
async function loadAllCharactersInfo() {
|
|
1301
1390
|
if (!state.filmHandle) return;
|
|
1302
1391
|
const chars = await listCharacters(state.filmHandle);
|
|
1303
1392
|
const info = [];
|
|
1304
1393
|
for (const c of chars) {
|
|
1305
1394
|
try {
|
|
1306
|
-
const
|
|
1307
|
-
const imageNodes = meta.nodes
|
|
1308
|
-
.filter(n => n.type === 'image' && n.file && n.name)
|
|
1309
|
-
.map(n => ({ name: n.name, file: n.file, id: n.id }));
|
|
1395
|
+
const { imageNodes, character } = await _scanBoardForRefs('character', c);
|
|
1310
1396
|
info.push({
|
|
1311
1397
|
name: c.name,
|
|
1312
1398
|
handle: c.handle,
|
|
1313
|
-
...(
|
|
1399
|
+
...(character || {}),
|
|
1314
1400
|
imageNodes,
|
|
1315
1401
|
});
|
|
1316
1402
|
} catch {}
|
|
@@ -1354,14 +1440,11 @@ async function loadAllLocationsInfo() {
|
|
|
1354
1440
|
const info = [];
|
|
1355
1441
|
for (const l of locs) {
|
|
1356
1442
|
try {
|
|
1357
|
-
const
|
|
1358
|
-
const imageNodes = meta.nodes
|
|
1359
|
-
.filter(n => n.type === 'image' && n.file && n.name)
|
|
1360
|
-
.map(n => ({ name: n.name, file: n.file, id: n.id }));
|
|
1443
|
+
const { imageNodes, location } = await _scanBoardForRefs('location', l);
|
|
1361
1444
|
info.push({
|
|
1362
1445
|
name: l.name,
|
|
1363
1446
|
handle: l.handle,
|
|
1364
|
-
sheet:
|
|
1447
|
+
sheet: location?.sheet || null,
|
|
1365
1448
|
imageNodes,
|
|
1366
1449
|
});
|
|
1367
1450
|
} catch {}
|
package/renderer/styles.css
CHANGED
|
@@ -1036,6 +1036,10 @@
|
|
|
1036
1036
|
.mention-popup .mit .mtype.audio { background: #6a4a2a; color: #e6c8aa; }
|
|
1037
1037
|
.mention-popup .mit .mtype.text { background: #2a6a2a; color: #c8e6aa; }
|
|
1038
1038
|
.mention-popup .mit .mtype.image { background: #5a2a6a; color: #e6aac8; }
|
|
1039
|
+
.mention-popup .mit .mit-thumb {
|
|
1040
|
+
width: 28px; height: 28px; border-radius: 3px; object-fit: cover;
|
|
1041
|
+
flex-shrink: 0; background: #1a1a1a; border: 1px solid #333;
|
|
1042
|
+
}
|
|
1039
1043
|
.mention-popup .empty-msg { padding: 8px 12px; color: #666; font-size: 12px; }
|
|
1040
1044
|
|
|
1041
1045
|
.settings-row { display: flex; flex-direction: column; gap: 4px; }
|