kingkont 0.7.66 → 0.7.67
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 +85 -28
package/package.json
CHANGED
package/renderer/media.js
CHANGED
|
@@ -1119,40 +1119,81 @@ function updateMentionPopup() {
|
|
|
1119
1119
|
let items = [];
|
|
1120
1120
|
|
|
1121
1121
|
if (dotIdx >= 0) {
|
|
1122
|
-
// Уже выбран character
|
|
1122
|
+
// Уже выбран character / location / __scene (текущая доска) — drilldown.
|
|
1123
1123
|
const ownerName = query.slice(0, dotIdx);
|
|
1124
1124
|
const imgQuery = query.slice(dotIdx + 1);
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1125
|
+
if (ownerName === '__scene') {
|
|
1126
|
+
// Drilldown в ноды ТЕКУЩЕЙ доски (тип фильтруем по genKind).
|
|
1127
|
+
const allowed = state.genKind === 'image' ? ['image', 'text']
|
|
1128
|
+
: state.genKind === 'video' ? ['image', 'video', 'audio', 'text']
|
|
1129
|
+
: ['text', 'image', 'video', 'audio'];
|
|
1130
|
+
// Дедуп: если у двух нод одинаковый label (basename), второй
|
|
1131
|
+
// получает суффикс -<id4>. Это match'ит логику _scanBoardForRefs
|
|
1132
|
+
// для chars/locs.
|
|
1133
|
+
const seen = new Set();
|
|
1134
|
+
for (const n of state.currentBoard.metadata.nodes) {
|
|
1135
|
+
if (!n.id) continue;
|
|
1136
|
+
if (!allowed.includes(n.type)) continue;
|
|
1137
|
+
let label = (n.name || '').trim();
|
|
1138
|
+
if (!label) {
|
|
1139
|
+
label = n.file
|
|
1140
|
+
? (n.file.split('/').pop() || '').replace(/\.[^.]+$/, '')
|
|
1141
|
+
: n.id.slice(0, 8);
|
|
1138
1142
|
}
|
|
1143
|
+
if (!label) label = n.id.slice(0, 8);
|
|
1144
|
+
if (seen.has(label)) label = `${label}-${n.id.slice(0, 4)}`;
|
|
1145
|
+
seen.add(label);
|
|
1146
|
+
if (imgQuery && !label.toLowerCase().includes(imgQuery)) continue;
|
|
1147
|
+
items.push({
|
|
1148
|
+
// key = label: красивая ссылка типа [@kitchen] для безымянной
|
|
1149
|
+
// картинки kitchen.jpg. resolveMentions для board-нод сначала
|
|
1150
|
+
// ищет по name; если не нашёл — gatherMediaRefs упустит,
|
|
1151
|
+
// но мы переименуем после: для board-drill final key = label.
|
|
1152
|
+
// Чтобы он попал в gatherMediaRefs — добавим временный n.name
|
|
1153
|
+
// ниже. На самом деле проще: всегда insert = [@<label>], и в
|
|
1154
|
+
// gatherMediaRefs сравнение идёт по nodeRefKey = name||id.
|
|
1155
|
+
// Если label != name && != id — НЕ попадёт. Поэтому используем
|
|
1156
|
+
// n.name || n.id (id как fallback) — это работает в
|
|
1157
|
+
// resolveMentions.
|
|
1158
|
+
key: n.name || n.id,
|
|
1159
|
+
label, type: n.type, scope: 'board',
|
|
1160
|
+
file: n.file || null,
|
|
1161
|
+
boardHandle: state.currentBoard.handle,
|
|
1162
|
+
});
|
|
1139
1163
|
}
|
|
1140
1164
|
} else {
|
|
1141
|
-
const
|
|
1142
|
-
if (
|
|
1143
|
-
for (const img of (
|
|
1165
|
+
const c = state.charactersInfo.find(x => x.name.toLowerCase() === ownerName);
|
|
1166
|
+
if (c) {
|
|
1167
|
+
for (const img of (c.imageNodes || [])) {
|
|
1144
1168
|
if (!imgQuery || img.name.toLowerCase().includes(imgQuery)) {
|
|
1145
1169
|
items.push({
|
|
1146
|
-
key: `${
|
|
1170
|
+
key: `${c.name}.${img.name}`,
|
|
1147
1171
|
label: img.name,
|
|
1148
1172
|
type: 'image',
|
|
1149
|
-
scope: '
|
|
1150
|
-
|
|
1173
|
+
scope: 'char-image',
|
|
1174
|
+
charName: c.name,
|
|
1151
1175
|
file: img.file,
|
|
1152
|
-
boardHandle:
|
|
1176
|
+
boardHandle: c.handle,
|
|
1153
1177
|
});
|
|
1154
1178
|
}
|
|
1155
1179
|
}
|
|
1180
|
+
} else {
|
|
1181
|
+
const l = state.locationsInfo.find(x => x.name.toLowerCase() === ownerName);
|
|
1182
|
+
if (l) {
|
|
1183
|
+
for (const img of (l.imageNodes || [])) {
|
|
1184
|
+
if (!imgQuery || img.name.toLowerCase().includes(imgQuery)) {
|
|
1185
|
+
items.push({
|
|
1186
|
+
key: `${l.name}.${img.name}`,
|
|
1187
|
+
label: img.name,
|
|
1188
|
+
type: 'image',
|
|
1189
|
+
scope: 'loc-image',
|
|
1190
|
+
locName: l.name,
|
|
1191
|
+
file: img.file,
|
|
1192
|
+
boardHandle: l.handle,
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1156
1197
|
}
|
|
1157
1198
|
}
|
|
1158
1199
|
} else if (isCharOnly) {
|
|
@@ -1173,17 +1214,26 @@ function updateMentionPopup() {
|
|
|
1173
1214
|
}
|
|
1174
1215
|
items = items.filter(s => s.key.toLowerCase().includes(query)).slice(0, 16);
|
|
1175
1216
|
} else {
|
|
1176
|
-
// Верхний уровень:
|
|
1177
|
-
//
|
|
1217
|
+
// Верхний уровень: вместо плоского списка нод текущей доски —
|
|
1218
|
+
// одна запись «Эта сцена» (drilldown в её ноды). Это убирает
|
|
1219
|
+
// лишний шум когда в проекте много персонажей/локаций. Click →
|
|
1220
|
+
// selectMention вставит [@__scene. и реоткроет popup в drilldown.
|
|
1221
|
+
// Если у текущей доски НЕТ нод вообще — пункт не показываем.
|
|
1178
1222
|
const allowed = state.genKind === 'image' ? ['image', 'text']
|
|
1179
1223
|
: state.genKind === 'video' ? ['image', 'video', 'audio', 'text']
|
|
1180
1224
|
: ['text', 'image', 'video', 'audio'];
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1225
|
+
const boardHasNodes = (state.currentBoard?.metadata?.nodes || []).some(n =>
|
|
1226
|
+
n.id && allowed.includes(n.type)
|
|
1227
|
+
);
|
|
1228
|
+
if (boardHasNodes) {
|
|
1229
|
+
// Превью первой image-ноды для миниатюры (если есть).
|
|
1230
|
+
const firstImg = state.currentBoard.metadata.nodes.find(n => n.type === 'image' && n.file);
|
|
1184
1231
|
items.push({
|
|
1185
|
-
key:
|
|
1186
|
-
|
|
1232
|
+
key: '__scene__top', // не для вставки — drillFromSceneFolder заменит
|
|
1233
|
+
label: 'Эта сцена',
|
|
1234
|
+
type: 'image', // для type-чипа
|
|
1235
|
+
scope: 'scene-folder',
|
|
1236
|
+
file: firstImg?.file || null,
|
|
1187
1237
|
boardHandle: state.currentBoard.handle,
|
|
1188
1238
|
});
|
|
1189
1239
|
}
|
|
@@ -1251,6 +1301,7 @@ function updateMentionPopup() {
|
|
|
1251
1301
|
: s.scope === 'char-image' ? `персонаж·${s.charName}`
|
|
1252
1302
|
: s.scope === 'loc' ? 'локация'
|
|
1253
1303
|
: s.scope === 'loc-image' ? `локация·${s.locName}`
|
|
1304
|
+
: s.scope === 'scene-folder' ? 'сцена'
|
|
1254
1305
|
: s.type;
|
|
1255
1306
|
const nm = document.createElement('span');
|
|
1256
1307
|
nm.textContent = s.label;
|
|
@@ -1301,11 +1352,17 @@ function selectMention(idx = mentionState.selected) {
|
|
|
1301
1352
|
const after = ta.value.slice(cursor);
|
|
1302
1353
|
// Если выбрали персонажа/локацию и у них есть картинки — авто-drill:
|
|
1303
1354
|
// оставляем @name. для дальнейшего ввода имени картинки.
|
|
1355
|
+
// Если выбрали «Эта сцена» (scope='scene-folder') — то же самое со
|
|
1356
|
+
// спец-префиксом __scene. Финальный select board-ноды стирает префикс
|
|
1357
|
+
// и вставляет [@nodename] (см. ветку drillFromScene ниже).
|
|
1304
1358
|
let insert;
|
|
1305
1359
|
let reopen = false;
|
|
1306
1360
|
if ((item.scope === 'char' || item.scope === 'loc') && item.hasImages) {
|
|
1307
1361
|
insert = '[@' + item.key + '.';
|
|
1308
1362
|
reopen = true;
|
|
1363
|
+
} else if (item.scope === 'scene-folder') {
|
|
1364
|
+
insert = '[@__scene.';
|
|
1365
|
+
reopen = true;
|
|
1309
1366
|
} else {
|
|
1310
1367
|
insert = '[@' + (item.key || item.name) + '] ';
|
|
1311
1368
|
}
|