kingkont 0.20.49 → 0.20.52
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 +1 -8
- package/renderer/cloudProjects.js +126 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kingkont",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.52",
|
|
4
4
|
"description": "KingKont \u00b7 Chatium \u2014 \u043d\u043e\u0434-\u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0441\u0446\u0435\u043d \u0441 AI-\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0435\u0439 (\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438/\u0432\u0438\u0434\u0435\u043e/\u0433\u043e\u043b\u043e\u0441/SFX/\u043c\u0443\u0437\u044b\u043a\u0430/\u0442\u0435\u043a\u0441\u0442)",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"bin": {
|
package/renderer/board.js
CHANGED
|
@@ -1671,14 +1671,7 @@ async function openShareModal(p) {
|
|
|
1671
1671
|
// там приложение живёт на kingkont.ru. В web используем origin.
|
|
1672
1672
|
const isLocal = /^https?:\/\/(localhost|127\.0\.0\.1)/.test(location.origin);
|
|
1673
1673
|
const base = isLocal ? 'https://kingkont.ru' : location.origin;
|
|
1674
|
-
|
|
1675
|
-
// НАВЕЧНО, и юзеры получали старую версию HTML со старыми хешами
|
|
1676
|
-
// скриптов. Старые cloudProjects.js не имеют R2 pub→worker rewrite
|
|
1677
|
-
// → картинки 503 → «Файл не найден».
|
|
1678
|
-
// Сейчас: /app/spaces/client/ — auth-aware stub, который JS'ом
|
|
1679
|
-
// редиректит на свежий /static/web-<sha>.html (новое имя файла =
|
|
1680
|
-
// свежий cache-entry на каждом deploy).
|
|
1681
|
-
return base + '/app/spaces/client/#template=' + proj.id;
|
|
1674
|
+
return base + '/static/web.html#template=' + proj.id;
|
|
1682
1675
|
}
|
|
1683
1676
|
function render() {
|
|
1684
1677
|
const isPub = !!proj.isPublic;
|
|
@@ -82,16 +82,23 @@
|
|
|
82
82
|
// openShareModal живёт в board.js (window.openShareModal). В Electron
|
|
83
83
|
// share-API'ы (/api/proj/setPublic|share|unshare|shares) проксируются
|
|
84
84
|
// через server.js → kingkont.ru/proj_*; в web — через web-shim.
|
|
85
|
+
// ↗ Перейти — рядом, только когда проект публичный (state.cloudIsPublic).
|
|
86
|
+
// Юзер: «на проекте если он публичный сделай кнопку перейти (с иконкой,
|
|
87
|
+
// без надписи) справа от кнопки Расшарить».
|
|
85
88
|
{
|
|
86
|
-
let
|
|
89
|
+
let row = $('shareProjectRow');
|
|
87
90
|
const wantShow = logged && state.cloudProjectId && state.cloudMine !== false;
|
|
88
|
-
if (wantShow && !
|
|
89
|
-
|
|
91
|
+
if (wantShow && !row && saveBtn?.parentNode) {
|
|
92
|
+
row = document.createElement('div');
|
|
93
|
+
row.id = 'shareProjectRow';
|
|
94
|
+
row.style.cssText = 'display:flex; gap:6px; margin-top:6px; align-items:stretch;';
|
|
95
|
+
|
|
96
|
+
const shareBtn = document.createElement('button');
|
|
90
97
|
shareBtn.id = 'shareProjectBtn';
|
|
91
98
|
shareBtn.className = 'kk-edit-only';
|
|
92
99
|
shareBtn.textContent = '🤝 Расшарить';
|
|
93
100
|
shareBtn.title = 'Расшарить проект другому юзеру или сделать публичным';
|
|
94
|
-
shareBtn.style.cssText = '
|
|
101
|
+
shareBtn.style.cssText = 'flex:1; font-size:12px;';
|
|
95
102
|
shareBtn.addEventListener('click', () => {
|
|
96
103
|
if (typeof window.openShareModal === 'function') {
|
|
97
104
|
window.openShareModal({
|
|
@@ -102,9 +109,31 @@
|
|
|
102
109
|
});
|
|
103
110
|
}
|
|
104
111
|
});
|
|
105
|
-
|
|
112
|
+
row.appendChild(shareBtn);
|
|
113
|
+
|
|
114
|
+
// ↗ Перейти — иконка, открывает share-URL в новой вкладке.
|
|
115
|
+
// Видна только если проект публичный (по private-проекту ссылка
|
|
116
|
+
// не работает у незалогиненных, и тут смысла нет).
|
|
117
|
+
const gotoBtn = document.createElement('button');
|
|
118
|
+
gotoBtn.id = 'gotoPublicBtn';
|
|
119
|
+
gotoBtn.className = 'kk-edit-only';
|
|
120
|
+
gotoBtn.textContent = '↗';
|
|
121
|
+
gotoBtn.title = 'Открыть публичную страницу проекта в новой вкладке';
|
|
122
|
+
gotoBtn.style.cssText = 'font-size:14px; padding:0 10px;';
|
|
123
|
+
gotoBtn.addEventListener('click', () => {
|
|
124
|
+
const isLocal = /^https?:\/\/(localhost|127\.0\.0\.1)/.test(location.origin);
|
|
125
|
+
const base = isLocal ? 'https://kingkont.ru' : location.origin;
|
|
126
|
+
const url = base + '/static/web.html#template=' + encodeURIComponent(state.cloudProjectId);
|
|
127
|
+
window.open(url, '_blank', 'noopener');
|
|
128
|
+
});
|
|
129
|
+
row.appendChild(gotoBtn);
|
|
130
|
+
|
|
131
|
+
saveBtn.parentNode.insertBefore(row, saveBtn.nextSibling);
|
|
106
132
|
}
|
|
107
|
-
if (
|
|
133
|
+
if (row) row.style.display = wantShow ? 'flex' : 'none';
|
|
134
|
+
// Видимость ↗ Перейти зависит от isPublic — обновляем каждый тик.
|
|
135
|
+
const gotoBtn = $('gotoPublicBtn');
|
|
136
|
+
if (gotoBtn) gotoBtn.style.display = (wantShow && state.cloudIsPublic) ? '' : 'none';
|
|
108
137
|
}
|
|
109
138
|
}).catch(() => {});
|
|
110
139
|
}
|
|
@@ -364,6 +393,66 @@
|
|
|
364
393
|
setCloudButtonsVisibility();
|
|
365
394
|
if (typeof window.renderTemplateOverlay === 'function') window.renderTemplateOverlay();
|
|
366
395
|
const bgBoards = Array.isArray(p.manifest?.boards) ? p.manifest.boards : [];
|
|
396
|
+
// Если в местной meta не было cdnByBoard (старая схема — meta
|
|
397
|
+
// сохранена до фичи), либо state.cloudCdnByBoard пустой — строим
|
|
398
|
+
// карту из свежего манифеста и применяем к текущей доске.
|
|
399
|
+
// Иначе картинки в web-view не находятся: state.currentBoard.urls
|
|
400
|
+
// остаётся {} → settings.js hydrate упирается в resolveBoardFile,
|
|
401
|
+
// который в web без локального файла бросает «Файл не найден».
|
|
402
|
+
// (a) Recovery cdnByBoard если в meta его не было.
|
|
403
|
+
const haveCdnMap = state.cloudCdnByBoard
|
|
404
|
+
&& Object.keys(state.cloudCdnByBoard).length > 0;
|
|
405
|
+
if (!haveCdnMap && bgBoards.length) {
|
|
406
|
+
state.cloudCdnByBoard = _buildCdnByBoard(bgBoards);
|
|
407
|
+
_applyCdnUrlsToCurrentBoard();
|
|
408
|
+
try {
|
|
409
|
+
const fresh = { ...meta, cdnByBoard: state.cloudCdnByBoard, syncedAt: Date.now() };
|
|
410
|
+
await writeCloudFile(projectId, '.kingkont-meta.json',
|
|
411
|
+
new TextEncoder().encode(JSON.stringify(fresh, null, 2)));
|
|
412
|
+
} catch {}
|
|
413
|
+
}
|
|
414
|
+
// (b) Recovery scene.json'ов независимо от (a). Старая сессия
|
|
415
|
+
// могла закешировать только часть досок в IDB (юзер не во все
|
|
416
|
+
// сцены кликал) — сайдбар тогда покажет 4 сцены вместо 5.
|
|
417
|
+
// Либо в IDB лежит «стартовый» scene.json на 0 нод. Если
|
|
418
|
+
// local-копия пустее серверной — перезаписываем.
|
|
419
|
+
let wroteAnyScene = false;
|
|
420
|
+
for (const board of bgBoards) {
|
|
421
|
+
if (!board.scene) continue;
|
|
422
|
+
const boardDir = boardKindToDir(board.kind, board.name);
|
|
423
|
+
const scenePath = joinPath(boardDir, 'scene.json');
|
|
424
|
+
let existing = null;
|
|
425
|
+
try {
|
|
426
|
+
existing = window.cloudFs
|
|
427
|
+
? await window.cloudFs.readText(projectId, scenePath).catch(() => null)
|
|
428
|
+
: await window.cloudFsShim?.readTextFile?.(projectId, scenePath).catch(() => null);
|
|
429
|
+
} catch {}
|
|
430
|
+
let needsWrite = !existing;
|
|
431
|
+
if (existing && !needsWrite) {
|
|
432
|
+
try {
|
|
433
|
+
const local = JSON.parse(existing);
|
|
434
|
+
const localN = Array.isArray(local?.nodes) ? local.nodes.length : 0;
|
|
435
|
+
const serverN = Array.isArray(board.scene?.nodes) ? board.scene.nodes.length : 0;
|
|
436
|
+
if (localN < serverN) needsWrite = true;
|
|
437
|
+
} catch { needsWrite = true; }
|
|
438
|
+
}
|
|
439
|
+
if (!needsWrite) continue;
|
|
440
|
+
try {
|
|
441
|
+
await writeCloudFile(projectId, scenePath,
|
|
442
|
+
new TextEncoder().encode(JSON.stringify(board.scene, null, 2)));
|
|
443
|
+
wroteAnyScene = true;
|
|
444
|
+
} catch {}
|
|
445
|
+
}
|
|
446
|
+
if (wroteAnyScene) {
|
|
447
|
+
try {
|
|
448
|
+
if (typeof refreshEpisodes === 'function') await refreshEpisodes();
|
|
449
|
+
if (typeof refreshLocations === 'function') await refreshLocations();
|
|
450
|
+
if (typeof refreshCharacters === 'function') await refreshCharacters();
|
|
451
|
+
} catch {}
|
|
452
|
+
}
|
|
453
|
+
if ((!haveCdnMap || wroteAnyScene) && state.currentBoard && typeof selectBoard === 'function') {
|
|
454
|
+
try { await selectBoard(state.currentBoard); } catch {}
|
|
455
|
+
}
|
|
367
456
|
await _downloadAllTexts(projectId, bgBoards).catch(() => {});
|
|
368
457
|
// Перечитываем тексты ТЕКУЩЕЙ доски (другие — при switchBoard).
|
|
369
458
|
await _refreshCurrentBoardTexts().catch(() => {});
|
|
@@ -422,28 +511,7 @@
|
|
|
422
511
|
// кешируется»). 1024px достаточно и для card-thumbnail и для
|
|
423
512
|
// полноэкранного просмотра. Маленькие thumbs (320x) — отдельной
|
|
424
513
|
// картой не требуются, scene-card сам ресайзит через CSS.
|
|
425
|
-
const cdnByBoard =
|
|
426
|
-
// R2 public bucket `pub-<id>.r2.dev` rate-limited Cloudflare'ом (503 при
|
|
427
|
-
// batch-загрузке из браузера, ~30 одновременных img-fetch'ей). Переписываем
|
|
428
|
-
// на Worker GET endpoint (Worker не лимитирован так агрессивно, +CORS).
|
|
429
|
-
// Старые manifests на чатиуме могут содержать pub-…r2.dev URL'ы — этот
|
|
430
|
-
// rewrite их чинит без серверной миграции.
|
|
431
|
-
const R2_PUB_PREFIX = 'https://pub-cd4114af9f7d44c9bf8c9442bc7dddc2.r2.dev/';
|
|
432
|
-
const R2_WORKER_GET = 'https://kingkont-r2-upload.timur-dd5.workers.dev/get/';
|
|
433
|
-
for (const board of boards) {
|
|
434
|
-
const map = {};
|
|
435
|
-
for (const [relPath, cdnUrlRaw] of Object.entries(board.files || {})) {
|
|
436
|
-
let cdnUrl = cdnUrlRaw;
|
|
437
|
-
if (cdnUrl && cdnUrl.startsWith(R2_PUB_PREFIX)) {
|
|
438
|
-
cdnUrl = R2_WORKER_GET + cdnUrl.slice(R2_PUB_PREFIX.length);
|
|
439
|
-
}
|
|
440
|
-
const thumbUrl = cdnUrl.includes('fs.chatium.ru/get/')
|
|
441
|
-
? cdnUrl.replace('/get/', '/thumbnail/') + '/1024x'
|
|
442
|
-
: cdnUrl;
|
|
443
|
-
map[relPath] = '/api/proxy?url=' + encodeURIComponent(thumbUrl);
|
|
444
|
-
}
|
|
445
|
-
cdnByBoard[board.name] = map;
|
|
446
|
-
}
|
|
514
|
+
const cdnByBoard = _buildCdnByBoard(boards);
|
|
447
515
|
// Skeleton meta + cdnByBoard (для fast cache-hit на reload).
|
|
448
516
|
await writeCloudFile(projectId, '.kingkont-meta.json',
|
|
449
517
|
new TextEncoder().encode(JSON.stringify({
|
|
@@ -605,6 +673,36 @@
|
|
|
605
673
|
if (changed) console.log('[cloudProjects] refreshed', changed, 'text-нод после фоновой загрузки');
|
|
606
674
|
}
|
|
607
675
|
|
|
676
|
+
// Построить CDN-карту {boardName: {relPath: '/api/proxy?url=...'}} из манифеста.
|
|
677
|
+
// Вынесено в helper — используется и в no-cache-ветке openCloudProject, и
|
|
678
|
+
// в фоновом fetch при cache-hit (когда местная meta была сохранена старой
|
|
679
|
+
// версией без cdnByBoard — иначе картинки в web-view не находятся).
|
|
680
|
+
// R2 public bucket `pub-<id>.r2.dev` rate-limited Cloudflare'ом (503 при
|
|
681
|
+
// batch-загрузке из браузера, ~30 одновременных img-fetch'ей). Переписываем
|
|
682
|
+
// на Worker GET endpoint (Worker не лимитирован так агрессивно, +CORS).
|
|
683
|
+
// Старые manifests на чатиуме могут содержать pub-…r2.dev URL'ы — этот
|
|
684
|
+
// rewrite их чинит без серверной миграции.
|
|
685
|
+
function _buildCdnByBoard(boards) {
|
|
686
|
+
const R2_PUB_PREFIX = 'https://pub-cd4114af9f7d44c9bf8c9442bc7dddc2.r2.dev/';
|
|
687
|
+
const R2_WORKER_GET = 'https://kingkont-r2-upload.timur-dd5.workers.dev/get/';
|
|
688
|
+
const cdnByBoard = {};
|
|
689
|
+
for (const board of boards || []) {
|
|
690
|
+
const map = {};
|
|
691
|
+
for (const [relPath, cdnUrlRaw] of Object.entries(board.files || {})) {
|
|
692
|
+
let cdnUrl = cdnUrlRaw;
|
|
693
|
+
if (cdnUrl && cdnUrl.startsWith(R2_PUB_PREFIX)) {
|
|
694
|
+
cdnUrl = R2_WORKER_GET + cdnUrl.slice(R2_PUB_PREFIX.length);
|
|
695
|
+
}
|
|
696
|
+
const thumbUrl = cdnUrl && cdnUrl.includes('fs.chatium.ru/get/')
|
|
697
|
+
? cdnUrl.replace('/get/', '/thumbnail/') + '/1024x'
|
|
698
|
+
: cdnUrl;
|
|
699
|
+
if (thumbUrl) map[relPath] = '/api/proxy?url=' + encodeURIComponent(thumbUrl);
|
|
700
|
+
}
|
|
701
|
+
cdnByBoard[board.name] = map;
|
|
702
|
+
}
|
|
703
|
+
return cdnByBoard;
|
|
704
|
+
}
|
|
705
|
+
|
|
608
706
|
// Подложить CDN-URL'ы под state.currentBoard.urls — рендеру медиа-нод
|
|
609
707
|
// в settings.js достаточно непустого `urls[node.file]`, чтобы не лезть
|
|
610
708
|
// в FS (см. settings.js:405).
|