codex-overleaf-link 2.3.6 → 2.4.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/README.md +62 -34
- package/README.zh-CN.md +546 -0
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +3 -0
- package/extension/src/content/activeTurnControl.js +1 -0
- package/extension/src/content/assetTransferBroker.js +2 -0
- package/extension/src/content/contentRuntime.js +122 -118
- package/extension/src/content/contextTray.js +4 -0
- package/extension/src/content/diagnosticsController.js +4 -0
- package/extension/src/content/diagnosticsPanel.js +128 -4
- package/extension/src/content/generated/content.bundle.js +1233 -550
- package/extension/src/content/generated/content.bundle.meta.json +4 -3
- package/extension/src/content/legacyGlobalRegistry.js +1 -0
- package/extension/src/content/modelPicker.js +21 -9
- package/extension/src/content/modelPickerSupport.js +38 -1
- package/extension/src/content/nativeCompatibilityController.js +4 -1
- package/extension/src/content/otWarmMirror.js +34 -30
- package/extension/src/content/otWarmMirrorController.js +20 -0
- package/extension/src/content/pageBridgeClient.js +2 -0
- package/extension/src/content/panelMaintenance.js +8 -4
- package/extension/src/content/recentProjects.js +354 -355
- package/extension/src/content/runResultActions.js +16 -1
- package/extension/src/content/runTimelineView.js +9 -11
- package/extension/src/content/scopedPersistenceCoordinator.js +2 -1
- package/extension/src/content/scopedPersistencePanelState.js +45 -2
- package/extension/src/content/sessionForkController.js +20 -11
- package/extension/src/content/sessionPersistence.js +4 -1
- package/extension/src/content/settingsPanel.js +16 -2
- package/extension/src/content/trackedChangeCaptureController.js +153 -0
- package/extension/src/content/writebackOrchestrator.js +31 -0
- package/extension/src/page/binaryAssetUploader.js +101 -29
- package/extension/src/page/overleafRealtimeObserver.js +4 -1
- package/extension/src/page/saveState.js +44 -1
- package/extension/src/page/textFileCreator.js +493 -0
- package/extension/src/page/trackedChangeCapture.js +342 -0
- package/extension/src/page/trackedChangesLifecycle.js +149 -150
- package/extension/src/page/treeOperations.js +40 -29
- package/extension/src/page/writebackRouter.js +95 -49
- package/extension/src/pageBridge.js +65 -25
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/i18n.js +94 -40
- package/extension/src/shared/pageRpcContract.js +2 -1
- package/extension/src/shared/sessionState.js +6 -2
- package/extension/src/shared/storageDb.js +11 -12
- package/extension/src/shared/storageRunActions.js +73 -1
- package/extension/src/shared/writebackSettlement.js +19 -5
- package/extension/styles/panel.css +254 -405
- package/native-host/src/codexSessionRunner.js +41 -38
- package/native-host/src/codexSessionTiming.js +68 -0
- package/native-host/src/readProgressGuard.js +84 -17
- package/native-host/src/taskRunnerRuntime.js +25 -0
- package/package.json +5 -1
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
// (the row renderer is sync). The async loader populates this on panel
|
|
35
35
|
// mount and on each opportunistic enrichment call.
|
|
36
36
|
let projectNameCacheMirror = {};
|
|
37
|
+
let dashboardRenderGeneration = 0;
|
|
37
38
|
|
|
38
39
|
function loadProjectNameCacheFromStorage() {
|
|
39
40
|
return new Promise(function (resolve) {
|
|
@@ -140,7 +141,7 @@
|
|
|
140
141
|
// If the variant is currently visible, refresh row names in place.
|
|
141
142
|
var visibleList = getPanel() && getPanel().querySelector('[data-recent-projects-list]');
|
|
142
143
|
if (visibleList) {
|
|
143
|
-
var rows = visibleList.querySelectorAll('[data-
|
|
144
|
+
var rows = visibleList.querySelectorAll('[data-project-id]');
|
|
144
145
|
for (var k = 0; k < rows.length; k++) {
|
|
145
146
|
var row = rows[k];
|
|
146
147
|
var rowPid = row.getAttribute('data-project-id');
|
|
@@ -149,6 +150,7 @@
|
|
|
149
150
|
var cached = lookupProjectName(rowPid);
|
|
150
151
|
if (cached) {
|
|
151
152
|
nameEl.textContent = cached;
|
|
153
|
+
nameEl.title = cached;
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
}
|
|
@@ -229,64 +231,55 @@
|
|
|
229
231
|
var hr = Math.round(min / 60);
|
|
230
232
|
if (hr < 24) return tx(hr + ' hr ago', hr + ' 小时前');
|
|
231
233
|
var day = Math.round(hr / 24);
|
|
232
|
-
if (day < 30) return tx(day + ' day ago', day + ' 天前');
|
|
234
|
+
if (day < 30) return tx(day + (day === 1 ? ' day ago' : ' days ago'), day + ' 天前');
|
|
233
235
|
var month = Math.round(day / 30);
|
|
234
236
|
if (month < 12) return tx(month + ' mo ago', month + ' 个月前');
|
|
235
237
|
var year = Math.round(month / 12);
|
|
236
238
|
return tx(year + ' yr ago', year + ' 年前');
|
|
237
239
|
}
|
|
238
240
|
|
|
239
|
-
function
|
|
240
|
-
var el = document.createElement(
|
|
241
|
-
if (className)
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
el.textContent = text == null ? '' : String(text);
|
|
241
|
+
function createViewElement(tag, className, text) {
|
|
242
|
+
var el = document.createElement(tag);
|
|
243
|
+
if (className) el.className = className;
|
|
244
|
+
if (text !== undefined) el.textContent = text;
|
|
245
245
|
return el;
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
function
|
|
249
|
-
var
|
|
250
|
-
|
|
248
|
+
function createViewButton(label, onClick, attribute, className) {
|
|
249
|
+
var button = createViewElement('button', className, label);
|
|
250
|
+
button.type = 'button';
|
|
251
|
+
if (attribute) button.setAttribute(attribute, '');
|
|
252
|
+
if (onClick) button.addEventListener('click', onClick);
|
|
253
|
+
return button;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function textNode(text, className) {
|
|
257
|
+
return createViewElement('span', className, text == null ? '' : String(text));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function renderWelcomeHeader() {
|
|
261
|
+
var el = createViewElement('div', 'recent-projects-welcome');
|
|
251
262
|
el.setAttribute('data-recent-projects-welcome', '');
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
var subtitle = document.createElement('div');
|
|
256
|
-
subtitle.className = 'recent-projects-welcome-subtitle';
|
|
257
|
-
// v1.8.1: the most expensive spot on the first screen now carries a
|
|
258
|
-
// glanceable summary instead of boilerplate once the rows are known.
|
|
259
|
-
subtitle.textContent = stats && stats.projectCount
|
|
260
|
-
? tr('recentProjects_welcome_stats', { projects: stats.projectCount, sessions: stats.sessionTotal })
|
|
261
|
-
: tr('recentProjects_welcome_subtitle');
|
|
262
|
-
el.appendChild(title);
|
|
263
|
-
el.appendChild(subtitle);
|
|
263
|
+
el.appendChild(textNode('CODEX', 'recent-projects-eyebrow'));
|
|
264
|
+
el.appendChild(textNode(tr('recentProjects_welcome'), 'recent-projects-welcome-title'));
|
|
265
|
+
el.appendChild(textNode(tr('recentProjects_welcome_subtitle'), 'recent-projects-welcome-subtitle'));
|
|
264
266
|
return el;
|
|
265
267
|
}
|
|
266
268
|
|
|
267
269
|
function renderEmptyState() {
|
|
268
|
-
var el =
|
|
269
|
-
el.className = 'recent-projects-empty';
|
|
270
|
+
var el = createViewElement('div', 'recent-projects-empty', tr('recentProjects_empty'));
|
|
270
271
|
el.setAttribute('data-recent-projects-empty', '');
|
|
271
|
-
el.textContent = tr('recentProjects_empty');
|
|
272
272
|
return el;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
function renderDegradedState() {
|
|
276
|
-
var el =
|
|
277
|
-
el.className = 'recent-projects-degraded';
|
|
276
|
+
var el = createViewElement('div', 'recent-projects-degraded');
|
|
278
277
|
el.setAttribute('data-recent-projects-degraded', '');
|
|
279
278
|
// v1.8.1: the user IS signed in on /project — telling them to sign in
|
|
280
279
|
// was a dead end. Explain the real condition and offer a retry.
|
|
281
|
-
var hint =
|
|
282
|
-
hint.textContent = tr('recentProjects_degraded_hint');
|
|
280
|
+
var hint = createViewElement('div', '', tr('recentProjects_degraded_hint'));
|
|
283
281
|
el.appendChild(hint);
|
|
284
|
-
var retry =
|
|
285
|
-
retry.type = 'button';
|
|
286
|
-
retry.className = 'recent-projects-show-all';
|
|
287
|
-
retry.setAttribute('data-recent-projects-retry', '');
|
|
288
|
-
retry.textContent = tr('recentProjects_retryScope');
|
|
289
|
-
retry.addEventListener('click', function () {
|
|
282
|
+
var retry = createViewButton(tr('recentProjects_retryScope'), function () {
|
|
290
283
|
if (typeof refreshAccountScopeId === 'function') {
|
|
291
284
|
Promise.resolve(refreshAccountScopeId())
|
|
292
285
|
.then(function () { return renderRecentProjectsVariant(); })
|
|
@@ -294,7 +287,7 @@
|
|
|
294
287
|
} else {
|
|
295
288
|
renderRecentProjectsVariant();
|
|
296
289
|
}
|
|
297
|
-
});
|
|
290
|
+
}, 'data-recent-projects-retry', 'recent-projects-show-all');
|
|
298
291
|
el.appendChild(retry);
|
|
299
292
|
return el;
|
|
300
293
|
}
|
|
@@ -304,18 +297,11 @@
|
|
|
304
297
|
// skills tied to projects, custom instructions, project diagnostics).
|
|
305
298
|
function renderSettingsEntry(options) {
|
|
306
299
|
var scope = options && options.scope === 'account' ? 'account' : 'project';
|
|
307
|
-
var entry =
|
|
308
|
-
entry.type = 'button';
|
|
309
|
-
entry.className = 'recent-projects-settings-entry';
|
|
310
|
-
entry.setAttribute('data-recent-projects-settings-entry', '');
|
|
311
|
-
entry.setAttribute('data-settings-scope', scope);
|
|
312
|
-
var label = document.createElement('span');
|
|
313
|
-
label.className = 'recent-projects-settings-entry-label';
|
|
314
|
-
label.textContent = tr('recentProjects_settings_entry');
|
|
315
|
-
entry.appendChild(label);
|
|
316
|
-
entry.addEventListener('click', function () {
|
|
300
|
+
var entry = createViewButton(undefined, function () {
|
|
317
301
|
openSettingsInScope(scope);
|
|
318
|
-
});
|
|
302
|
+
}, 'data-recent-projects-settings-entry', 'recent-projects-settings-entry');
|
|
303
|
+
entry.setAttribute('data-settings-scope', scope);
|
|
304
|
+
entry.appendChild(textNode(tr('recentProjects_settings_entry'), 'recent-projects-settings-entry-label'));
|
|
319
305
|
return entry;
|
|
320
306
|
}
|
|
321
307
|
|
|
@@ -360,10 +346,8 @@
|
|
|
360
346
|
? status
|
|
361
347
|
: 'pending';
|
|
362
348
|
var cls = STATUS_BADGE_CLASS[safeStatus];
|
|
363
|
-
var el =
|
|
364
|
-
el.className = 'recent-projects-row-badge ' + cls;
|
|
349
|
+
var el = textNode(tr('recentProjects_badge_' + safeStatus), 'recent-projects-row-badge ' + cls);
|
|
365
350
|
el.setAttribute('data-status', safeStatus);
|
|
366
|
-
el.textContent = tr('recentProjects_badge_' + safeStatus);
|
|
367
351
|
return el;
|
|
368
352
|
}
|
|
369
353
|
|
|
@@ -378,125 +362,145 @@
|
|
|
378
362
|
window.location.assign('https://www.overleaf.com/project/' + encodeURIComponent(projectId));
|
|
379
363
|
}
|
|
380
364
|
|
|
381
|
-
function
|
|
382
|
-
var
|
|
383
|
-
var
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
el.appendChild(rowTime);
|
|
405
|
-
// v1.8.2: the session count lives in the row meta, matching its type
|
|
406
|
-
// scale, instead of inflating the expand toggle.
|
|
407
|
-
var sessionCount = Number(row && row.sessionCount) || 0;
|
|
408
|
-
if (sessionCount > 1) {
|
|
409
|
-
el.appendChild(textNode(tr('recentProjects_sessionsCount', { count: sessionCount }), 'recent-projects-row-sessions'));
|
|
410
|
-
}
|
|
411
|
-
el.appendChild(renderStatusBadge(settleDashboardRunStatus(row && row.primaryStatusBadge, row && row.lastActivityAt)));
|
|
412
|
-
if (valid) {
|
|
413
|
-
el.addEventListener('click', function () {
|
|
414
|
-
openProjectFromRow(projectId);
|
|
415
|
-
});
|
|
416
|
-
} else {
|
|
417
|
-
el.appendChild(textNode(tr('recentProjects_row_projectLinkUnavailable'), 'recent-projects-row-warning'));
|
|
418
|
-
}
|
|
419
|
-
if (!valid) {
|
|
420
|
-
var deadWrap = document.createElement('div');
|
|
421
|
-
deadWrap.className = 'recent-projects-row-wrap';
|
|
422
|
-
var deadHead = document.createElement('div');
|
|
423
|
-
deadHead.className = 'recent-projects-row-head';
|
|
424
|
-
var cleanup = document.createElement('button');
|
|
425
|
-
cleanup.type = 'button';
|
|
426
|
-
cleanup.className = 'recent-projects-row-cleanup';
|
|
427
|
-
cleanup.setAttribute('data-row-cleanup', '');
|
|
428
|
-
cleanup.title = tr('recentProjects_cleanup');
|
|
429
|
-
cleanup.setAttribute('aria-label', tr('recentProjects_cleanup'));
|
|
430
|
-
cleanup.textContent = '×';
|
|
431
|
-
cleanup.addEventListener('click', function () {
|
|
432
|
-
cleanupDeadProjectEntry(projectId).catch(function () { /* swallow */ });
|
|
433
|
-
});
|
|
434
|
-
deadHead.appendChild(el);
|
|
435
|
-
deadHead.appendChild(cleanup);
|
|
436
|
-
deadWrap.appendChild(deadHead);
|
|
437
|
-
return deadWrap;
|
|
438
|
-
}
|
|
439
|
-
var wrap = document.createElement('div');
|
|
440
|
-
wrap.className = 'recent-projects-row-wrap';
|
|
441
|
-
wrap.setAttribute('data-project-row-wrap', projectId);
|
|
442
|
-
var expand = document.createElement('button');
|
|
443
|
-
expand.type = 'button';
|
|
444
|
-
expand.className = 'recent-projects-row-expand';
|
|
445
|
-
expand.setAttribute('data-row-expand', '');
|
|
446
|
-
expand.setAttribute('aria-expanded', 'false');
|
|
447
|
-
// v1.8.1 a11y: tie the toggle to the container it reveals.
|
|
448
|
-
var sessionsElId = 'codex-project-sessions-' + String(projectId || '').slice(0, 24);
|
|
449
|
-
expand.setAttribute('aria-controls', sessionsElId);
|
|
450
|
-
expand.title = tr('recentProjects_sessions_toggle');
|
|
451
|
-
expand.setAttribute('aria-label', tr('recentProjects_sessions_toggle'));
|
|
452
|
-
expand.textContent = tr('recentProjects_sessions_action');
|
|
453
|
-
var head = document.createElement('div');
|
|
454
|
-
head.className = 'recent-projects-row-head';
|
|
455
|
-
var clear = document.createElement('button');
|
|
456
|
-
clear.type = 'button';
|
|
457
|
-
clear.className = 'recent-projects-row-clear';
|
|
458
|
-
clear.setAttribute('data-project-clear', '');
|
|
459
|
-
clear.title = tr('recentProjects_clearProject');
|
|
460
|
-
clear.setAttribute('aria-label', tr('recentProjects_clearProject'));
|
|
461
|
-
clear.textContent = '×';
|
|
462
|
-
clear.addEventListener('click', function (event) {
|
|
463
|
-
event.stopPropagation();
|
|
464
|
-
projectSessionCleanup.clearProjectSessions(projectId, name).catch(function () { /* surfaced by the action */ });
|
|
365
|
+
function renderActionsMenu(buttons) {
|
|
366
|
+
var menu = createViewElement('details', 'recent-projects-menu');
|
|
367
|
+
var trigger = createViewElement('summary', '', '\u22ef');
|
|
368
|
+
trigger.title = tr('recentProjects_more');
|
|
369
|
+
trigger.setAttribute('aria-label', tr('recentProjects_more'));
|
|
370
|
+
var actions = document.createElement('div');
|
|
371
|
+
actions.setAttribute('data-recent-menu-actions', '');
|
|
372
|
+
buttons.forEach(function (button) { actions.appendChild(button); });
|
|
373
|
+
actions.addEventListener('click', function (event) {
|
|
374
|
+
if (event.target.closest('button')) {
|
|
375
|
+
menu.open = false;
|
|
376
|
+
trigger.focus();
|
|
377
|
+
}
|
|
378
|
+
}, true);
|
|
379
|
+
menu.appendChild(trigger);
|
|
380
|
+
menu.appendChild(actions);
|
|
381
|
+
menu.addEventListener('keydown', function (event) {
|
|
382
|
+
if (event.key === 'Escape') {
|
|
383
|
+
event.preventDefault();
|
|
384
|
+
event.stopPropagation();
|
|
385
|
+
menu.open = false;
|
|
386
|
+
trigger.focus();
|
|
387
|
+
}
|
|
465
388
|
});
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
head.appendChild(clear);
|
|
469
|
-
var sessionsEl = document.createElement('div');
|
|
470
|
-
sessionsEl.className = 'recent-projects-sessions';
|
|
471
|
-
sessionsEl.setAttribute('data-project-sessions', '');
|
|
472
|
-
sessionsEl.id = sessionsElId;
|
|
473
|
-
sessionsEl.setAttribute('role', 'region');
|
|
474
|
-
sessionsEl.setAttribute('aria-label', tr('recentProjects_sessions_toggle'));
|
|
475
|
-
sessionsEl.hidden = true;
|
|
476
|
-
expand.addEventListener('click', function () {
|
|
477
|
-
toggleProjectSessions(wrap, projectId);
|
|
389
|
+
menu.addEventListener('focusout', function (event) {
|
|
390
|
+
if (event.relatedTarget && !menu.contains(event.relatedTarget)) menu.open = false;
|
|
478
391
|
});
|
|
479
|
-
|
|
480
|
-
wrap.appendChild(sessionsEl);
|
|
481
|
-
return wrap;
|
|
392
|
+
return menu;
|
|
482
393
|
}
|
|
483
394
|
|
|
484
|
-
function
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
395
|
+
function conversationActivity(record) {
|
|
396
|
+
return String(record && (record.lastActivityAt || record.updatedAt || record.createdAt) || '');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function conversationHasContent(record) {
|
|
400
|
+
return ['runs', 'history', 'pendingInputs'].some(function (key) {
|
|
401
|
+
return Array.isArray(record[key]) && record[key].length > 0;
|
|
402
|
+
}) || ['task', 'safeTaskSummary', 'codexThreadId'].some(function (key) {
|
|
403
|
+
return typeof record[key] === 'string' && Boolean(record[key].trim());
|
|
404
|
+
}) || (record.titleSource === 'manual' && typeof record.title === 'string' && Boolean(record.title.trim()));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function conversationStatus(record) {
|
|
408
|
+
if (!StorageDb || !Array.isArray(record.runs) || !record.runs.length) return '';
|
|
409
|
+
return settleDashboardRunStatus(StorageDb.derivePrimaryStatusBadge(record), conversationActivity(record));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function projectDisplayName(projectId) {
|
|
413
|
+
return lookupProjectName(projectId) || (isValidProjectId(projectId)
|
|
414
|
+
? tr('recentProjects_unnamed', { prefix: projectId.slice(0, 8) })
|
|
415
|
+
: tr('recentProjects_row_projectLinkUnavailable'));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async function loadDashboardConversations(scope) {
|
|
419
|
+
if (!scope || !StorageDb) throw new Error('Conversation storage is unavailable.');
|
|
420
|
+
var results = await Promise.all([StorageDb.getAllSessions(), SessionPersistence.loadTombstones()]);
|
|
421
|
+
var records = results[0];
|
|
422
|
+
var deletedByProject = results[1] || {};
|
|
423
|
+
if (!Array.isArray(records)) throw new Error('Conversation history could not be read.');
|
|
424
|
+
return records.filter(function (record) {
|
|
425
|
+
if (!record || typeof record.id !== 'string' || !record.id || record.accountScopeUnavailable === true) return false;
|
|
426
|
+
var deletedIds = Object.prototype.hasOwnProperty.call(deletedByProject, record.projectId)
|
|
427
|
+
? deletedByProject[record.projectId] : [];
|
|
428
|
+
return SessionPersistence.isVisibleRecord(record, Array.isArray(deletedIds) ? deletedIds : [], scope);
|
|
429
|
+
}).sort(function (a, b) {
|
|
430
|
+
var aTime = Date.parse(conversationActivity(a)) || 0;
|
|
431
|
+
var bTime = Date.parse(conversationActivity(b)) || 0;
|
|
432
|
+
return bTime - aTime || String(a.id).localeCompare(String(b.id));
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function dashboardViewOptions() {
|
|
437
|
+
var root = getPanel() && getPanel().querySelector('[data-recent-projects-root]');
|
|
438
|
+
var drafts = root && root.querySelector('[data-unstarted-conversations]');
|
|
439
|
+
return {
|
|
440
|
+
accountScopeId: root && root.dataset.accountScopeId || '',
|
|
441
|
+
projectFilter: root && root.dataset.projectFilter || '',
|
|
442
|
+
showAll: Boolean(root && root.dataset.showAll === 'true'),
|
|
443
|
+
draftsOpen: Boolean(drafts && drafts.open),
|
|
444
|
+
restoreScrollTop: root ? root.scrollTop : 0
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function refreshDashboardView() {
|
|
449
|
+
var panelEl = getPanel();
|
|
450
|
+
if (panelEl && panelEl.dataset.view === 'recent-projects') {
|
|
451
|
+
await renderRecentProjectsVariant(dashboardViewOptions());
|
|
489
452
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (
|
|
496
|
-
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function openDashboardConversation(projectId, record, row) {
|
|
456
|
+
var scope = record.accountScopeId;
|
|
457
|
+
var panelEl = getPanel();
|
|
458
|
+
if (!scope || scope !== getCachedAccountScopeId() || !isValidProjectId(projectId)
|
|
459
|
+
|| !panelEl || panelEl.dataset.view !== 'recent-projects' || row.getAttribute('aria-busy') === 'true') return;
|
|
460
|
+
row.setAttribute('aria-busy', 'true');
|
|
461
|
+
var resume = row.querySelector('[data-session-resume]');
|
|
462
|
+
if (resume) resume.disabled = true;
|
|
463
|
+
try {
|
|
464
|
+
var records = await loadProjectSessionRecords(projectId);
|
|
465
|
+
if (scope !== getCachedAccountScopeId() || panelEl !== getPanel()
|
|
466
|
+
|| panelEl.dataset.view !== 'recent-projects' || row.isConnected === false) return;
|
|
467
|
+
var selected = records.find(function (candidate) { return candidate.id === record.id; });
|
|
468
|
+
if (!selected) {
|
|
469
|
+
await refreshDashboardView();
|
|
470
|
+
showPluginToast(tr('recentProjects_conversationGone'), { status: 'warning' });
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
await activateSessionAndOpenProject(projectId, selected);
|
|
474
|
+
} catch (_error) {
|
|
475
|
+
if (scope === getCachedAccountScopeId() && row.isConnected !== false) {
|
|
476
|
+
showPluginToast(tr('recentProjects_continueFailed'), { status: 'warning' });
|
|
477
|
+
}
|
|
478
|
+
} finally {
|
|
479
|
+
row.removeAttribute('aria-busy');
|
|
480
|
+
if (resume) resume.disabled = false;
|
|
497
481
|
}
|
|
498
482
|
}
|
|
499
483
|
|
|
484
|
+
// Kept as a compatibility entry point and as the recovery path for invalid project links.
|
|
485
|
+
function renderRecentProjectRow(row) {
|
|
486
|
+
var projectId = row && row.projectId;
|
|
487
|
+
var valid = isValidProjectId(projectId);
|
|
488
|
+
var wrap = createViewElement('div', 'recent-projects-row-wrap recent-projects-unavailable-row');
|
|
489
|
+
wrap.setAttribute('data-project-id', projectId || '');
|
|
490
|
+
var link = createViewButton(undefined, null, 'data-recent-projects-row', 'recent-projects-row');
|
|
491
|
+
link.setAttribute('data-project-id', projectId || '');
|
|
492
|
+
link.disabled = !valid;
|
|
493
|
+
link.appendChild(textNode(projectDisplayName(projectId), 'recent-projects-row-name'));
|
|
494
|
+
if (valid) link.addEventListener('click', function () { openProjectFromRow(projectId); });
|
|
495
|
+
var cleanup = createViewButton(tr(valid ? 'recentProjects_clearProject' : 'recentProjects_cleanup'), function () {
|
|
496
|
+
if (valid) projectSessionCleanup.clearProjectSessions(projectId, projectDisplayName(projectId)).catch(function () {});
|
|
497
|
+
else cleanupDeadProjectEntry(projectId).catch(function () {});
|
|
498
|
+
}, valid ? 'data-project-clear' : 'data-row-cleanup');
|
|
499
|
+
wrap.appendChild(link);
|
|
500
|
+
wrap.appendChild(renderActionsMenu([cleanup]));
|
|
501
|
+
return wrap;
|
|
502
|
+
}
|
|
503
|
+
|
|
500
504
|
function sessionRecordDisplayTitle(record) {
|
|
501
505
|
if (record && typeof record.title === 'string' && record.title.trim()) {
|
|
502
506
|
return record.title.trim();
|
|
@@ -520,94 +524,86 @@
|
|
|
520
524
|
});
|
|
521
525
|
}
|
|
522
526
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
var sessionsLoading = document.createElement('div');
|
|
526
|
-
sessionsLoading.className = 'recent-projects-loading';
|
|
527
|
-
sessionsLoading.textContent = tx('Loading sessions…', '正在加载会话…');
|
|
528
|
-
sessionsEl.appendChild(sessionsLoading);
|
|
529
|
-
var records = [];
|
|
530
|
-
try {
|
|
531
|
-
records = await loadProjectSessionRecords(projectId);
|
|
532
|
-
} catch (_error) {
|
|
533
|
-
records = [];
|
|
534
|
-
}
|
|
535
|
-
sessionsLoading.remove();
|
|
536
|
-
if (!records.length) {
|
|
537
|
-
sessionsEl.appendChild(textNode(tr('recentProjects_sessions_empty'), 'recent-projects-sessions-empty'));
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
for (var i = 0; i < records.length; i++) {
|
|
541
|
-
sessionsEl.appendChild(renderProjectSessionRow(sessionsEl, projectId, records[i]));
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function renderProjectSessionRow(sessionsEl, projectId, record) {
|
|
527
|
+
function renderProjectSessionRow(sessionsEl, projectId, record, options) {
|
|
528
|
+
var featured = Boolean(options && options.featured);
|
|
546
529
|
var running = Boolean(StorageDb)
|
|
547
530
|
&& settleDashboardRunStatus(StorageDb.derivePrimaryStatusBadge(record), record.updatedAt || record.lastActivityAt) === 'running';
|
|
548
|
-
var row =
|
|
549
|
-
row.className = 'recent-projects-session-row';
|
|
531
|
+
var row = createViewElement('div', 'recent-projects-session-row' + (featured ? ' recent-projects-focus' : ''));
|
|
550
532
|
row.setAttribute('data-project-session-row', record.id);
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
533
|
+
row.setAttribute('data-project-id', projectId);
|
|
534
|
+
if (running) row.setAttribute('data-running', 'true');
|
|
554
535
|
var title = textNode(sessionRecordDisplayTitle(record), 'recent-projects-session-title');
|
|
555
|
-
title.title =
|
|
556
|
-
var
|
|
557
|
-
|
|
558
|
-
var
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
var
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
536
|
+
title.title = title.textContent;
|
|
537
|
+
var copy = createViewElement('div', 'recent-projects-session-copy');
|
|
538
|
+
var meta = createViewElement('div', 'recent-projects-session-meta');
|
|
539
|
+
var project = textNode(projectDisplayName(projectId), 'recent-projects-row-name');
|
|
540
|
+
project.title = project.textContent;
|
|
541
|
+
meta.appendChild(project);
|
|
542
|
+
var time = textNode(formatRelativeTime(conversationActivity(record)), 'recent-projects-session-time');
|
|
543
|
+
time.setAttribute('data-rel-time', conversationActivity(record));
|
|
544
|
+
time.title = conversationActivity(record);
|
|
545
|
+
var status = conversationStatus(record);
|
|
546
|
+
var footer = featured ? createViewElement('div', 'recent-projects-focus-footer') : null;
|
|
547
|
+
if (featured) {
|
|
548
|
+
meta.insertBefore(textNode(tr('recentProjects_focusEyebrow'), 'recent-projects-focus-eyebrow'), project);
|
|
549
|
+
copy.appendChild(meta);
|
|
550
|
+
copy.appendChild(title);
|
|
551
|
+
footer.appendChild(status ? renderStatusBadge(status)
|
|
552
|
+
: textNode(tr('recentProjects_savedDraft'), 'recent-projects-row-badge'));
|
|
553
|
+
} else {
|
|
554
|
+
copy.appendChild(title);
|
|
555
|
+
copy.appendChild(meta);
|
|
556
|
+
if (['failed', 'needs_review', 'interrupted', 'stale', 'needs_review_after_navigation', 'abandoned_after_navigation'].indexOf(status) !== -1) {
|
|
557
|
+
var attention = textNode(tr('recentProjects_attention'), 'recent-projects-session-attention');
|
|
558
|
+
attention.title = tr('recentProjects_badge_' + status);
|
|
559
|
+
meta.appendChild(attention);
|
|
560
|
+
}
|
|
579
561
|
}
|
|
580
|
-
|
|
562
|
+
meta.appendChild(time);
|
|
563
|
+
row.appendChild(copy);
|
|
564
|
+
|
|
565
|
+
var rename = createViewButton(tr('renameSession'), function () {
|
|
581
566
|
beginDashboardSessionRename(sessionsEl, projectId, record, row, title);
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
567
|
+
}, 'data-session-rename-dash');
|
|
568
|
+
rename.title = rename.textContent;
|
|
569
|
+
rename.setAttribute('aria-label', rename.textContent);
|
|
570
|
+
if (running) rename.disabled = true;
|
|
571
|
+
var del = createViewButton(tr('deleteSession'), function () {
|
|
572
|
+
deleteDashboardSession(sessionsEl, projectId, record).catch(function () {});
|
|
573
|
+
}, 'data-session-delete-dash');
|
|
574
|
+
del.title = running ? tr('recentProjects_zombieRunningNote') : del.textContent;
|
|
575
|
+
del.setAttribute('aria-label', del.textContent);
|
|
576
|
+
var projectConversations = createViewButton(tr('recentProjects_projectConversations'), function () {
|
|
577
|
+
renderRecentProjectsVariant({ projectFilter: projectId, showAll: true, restoreScrollTop: 0 }).catch(function () {});
|
|
578
|
+
}, 'data-project-conversations');
|
|
579
|
+
var openProject = createViewButton(tr('recentProjects_openProject'), function () { openProjectFromRow(projectId); });
|
|
580
|
+
var clear = createViewButton(tr('recentProjects_clearProject'), function () {
|
|
581
|
+
projectSessionCleanup.clearProjectSessions(projectId, projectDisplayName(projectId)).catch(function () {});
|
|
582
|
+
}, 'data-project-clear');
|
|
583
|
+
row.appendChild(renderActionsMenu([rename, del, projectConversations, openProject, clear]));
|
|
584
|
+
|
|
585
|
+
var activate = function () { openDashboardConversation(projectId, record, row).catch(function () {}); };
|
|
586
|
+
if (featured) {
|
|
587
|
+
row.setAttribute('role', 'group');
|
|
588
|
+
row.setAttribute('aria-label', tr('recentProjects_focusEyebrow'));
|
|
589
|
+
var resume = createViewButton(tr('recentProjects_continue'), activate, 'data-session-resume', 'recent-projects-continue');
|
|
590
|
+
resume.title = tr('recentProjects_continueHint');
|
|
591
|
+
footer.appendChild(resume);
|
|
592
|
+
row.appendChild(footer);
|
|
593
|
+
} else {
|
|
594
|
+
row.classList.add('recent-projects-session-row--linked');
|
|
595
|
+
row.setAttribute('role', 'button');
|
|
596
|
+
row.tabIndex = 0;
|
|
597
|
+
row.addEventListener('click', function (event) {
|
|
598
|
+
if (!event.target.closest('button, input, details')) activate();
|
|
599
|
+
});
|
|
600
|
+
row.addEventListener('keydown', function (event) {
|
|
601
|
+
if (event.target === row && (event.key === 'Enter' || event.key === ' ')) {
|
|
602
|
+
event.preventDefault();
|
|
603
|
+
activate();
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
611
607
|
return row;
|
|
612
608
|
}
|
|
613
609
|
|
|
@@ -757,16 +753,7 @@
|
|
|
757
753
|
} catch (error) {
|
|
758
754
|
showPluginToast(tr('deleteSessionHistoryFailedToast', { message: error.message }), { status: 'warning', sticky: true });
|
|
759
755
|
}
|
|
760
|
-
|
|
761
|
-
// deletion, then restore this project's expanded session list.
|
|
762
|
-
// v1.8.1: refresh the whole variant so the row's session count, badge,
|
|
763
|
-
// summary and the welcome stats all stay fresh — but restore the scroll
|
|
764
|
-
// position and this project's expanded list so the delete feels in-place.
|
|
765
|
-
var rootForScroll = getPanel() && getPanel().querySelector('[data-recent-projects-root]');
|
|
766
|
-
await renderRecentProjectsVariant({
|
|
767
|
-
expandProjectId: projectId,
|
|
768
|
-
restoreScrollTop: rootForScroll ? rootForScroll.scrollTop : undefined
|
|
769
|
-
});
|
|
756
|
+
await refreshDashboardView();
|
|
770
757
|
}
|
|
771
758
|
|
|
772
759
|
function beginDashboardSessionRename(sessionsEl, projectId, record, row, titleEl) {
|
|
@@ -780,7 +767,7 @@
|
|
|
780
767
|
input.className = 'recent-projects-session-rename-input';
|
|
781
768
|
input.value = seed;
|
|
782
769
|
titleEl.hidden = true;
|
|
783
|
-
|
|
770
|
+
titleEl.parentElement.insertBefore(input, titleEl);
|
|
784
771
|
input.focus();
|
|
785
772
|
input.select();
|
|
786
773
|
var settled = false;
|
|
@@ -839,7 +826,7 @@
|
|
|
839
826
|
})));
|
|
840
827
|
}
|
|
841
828
|
} catch (_error) { /* swallow */ }
|
|
842
|
-
await
|
|
829
|
+
await refreshDashboardView();
|
|
843
830
|
}
|
|
844
831
|
|
|
845
832
|
function ensureRecentProjectsRoot() {
|
|
@@ -850,9 +837,13 @@
|
|
|
850
837
|
if (existing) {
|
|
851
838
|
return existing;
|
|
852
839
|
}
|
|
853
|
-
var rootEl =
|
|
854
|
-
rootEl.className = 'recent-projects-root';
|
|
840
|
+
var rootEl = createViewElement('section', 'recent-projects-root');
|
|
855
841
|
rootEl.setAttribute('data-recent-projects-root', '');
|
|
842
|
+
rootEl.addEventListener('click', function (event) {
|
|
843
|
+
rootEl.querySelectorAll('.recent-projects-menu[open]').forEach(function (menu) {
|
|
844
|
+
if (!menu.contains(event.target)) menu.open = false;
|
|
845
|
+
});
|
|
846
|
+
});
|
|
856
847
|
// Insert as a sibling of the existing per-project main / composer slots
|
|
857
848
|
// so the variant lives inside the panel root (page-scoped) and the
|
|
858
849
|
// existing data-view CSS rules can hide it when the per-project view is
|
|
@@ -901,133 +892,141 @@
|
|
|
901
892
|
return;
|
|
902
893
|
}
|
|
903
894
|
rootEl.appendChild(renderWelcomeHeader());
|
|
904
|
-
var loading =
|
|
905
|
-
loading.className = 'recent-projects-loading';
|
|
906
|
-
loading.textContent = tx('Loading projects\u2026', '正在加载项目…');
|
|
895
|
+
var loading = createViewElement('div', 'recent-projects-loading', tx('Loading projects\u2026', '正在加载项目…'));
|
|
907
896
|
rootEl.appendChild(loading);
|
|
908
897
|
}
|
|
909
898
|
|
|
910
899
|
async function renderRecentProjectsVariant(options) {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
900
|
+
var panelEl = getPanel();
|
|
901
|
+
var editorMatch = String(window.location && window.location.pathname || '').match(/^\/project\/([a-f0-9]{24})(?:\/|$)/);
|
|
902
|
+
if (!panelEl || (editorMatch && isValidProjectId(editorMatch[1]))) return;
|
|
903
|
+
var previous = dashboardViewOptions();
|
|
904
|
+
options = Object.assign({}, previous, options || {});
|
|
905
|
+
var generation = ++dashboardRenderGeneration;
|
|
906
|
+
panelEl.dataset.view = 'recent-projects';
|
|
918
907
|
startRelativeTimeTicker();
|
|
919
908
|
var rootEl = ensureRecentProjectsRoot();
|
|
920
|
-
if (!rootEl)
|
|
921
|
-
|
|
909
|
+
if (!rootEl) return;
|
|
910
|
+
var accountScopeId = (window.codexOverleafDeriveAccountScopeId || function () { return null; })();
|
|
911
|
+
if (previous.accountScopeId !== accountScopeId) {
|
|
912
|
+
options.projectFilter = '';
|
|
913
|
+
options.showAll = options.draftsOpen = false;
|
|
922
914
|
}
|
|
915
|
+
var projectFilter = isValidProjectId(options.projectFilter) ? options.projectFilter : '';
|
|
916
|
+
rootEl.dataset.accountScopeId = accountScopeId || '';
|
|
917
|
+
rootEl.dataset.projectFilter = projectFilter;
|
|
918
|
+
rootEl.dataset.showAll = options.showAll ? 'true' : 'false';
|
|
923
919
|
rootEl.innerHTML = '';
|
|
924
920
|
rootEl.appendChild(renderWelcomeHeader());
|
|
925
|
-
|
|
926
|
-
var accountScopeId = (window.codexOverleafDeriveAccountScopeId || function () { return null; })();
|
|
927
921
|
if (!accountScopeId) {
|
|
928
922
|
rootEl.appendChild(renderDegradedState());
|
|
929
923
|
rootEl.appendChild(renderSettingsEntry({ scope: 'account' }));
|
|
930
|
-
opportunisticEnrichmentFromDom().catch(function () { /* swallow */ });
|
|
931
924
|
return;
|
|
932
925
|
}
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
926
|
+
if (projectFilter) {
|
|
927
|
+
var filterBar = createViewElement('div', 'recent-projects-filter');
|
|
928
|
+
var back = createViewButton(tr('recentProjects_allProjects'), function () {
|
|
929
|
+
renderRecentProjectsVariant({ projectFilter: '', showAll: false, restoreScrollTop: 0 }).catch(function () {});
|
|
930
|
+
});
|
|
931
|
+
filterBar.appendChild(back);
|
|
932
|
+
var filterName = textNode(projectDisplayName(projectFilter));
|
|
933
|
+
filterName.title = filterName.textContent;
|
|
934
|
+
filterBar.appendChild(filterName);
|
|
935
|
+
rootEl.appendChild(filterBar);
|
|
936
|
+
}
|
|
937
|
+
var listContainer = createViewElement('div', 'recent-projects-list');
|
|
936
938
|
listContainer.setAttribute('data-recent-projects-list', '');
|
|
937
939
|
rootEl.appendChild(listContainer);
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
// Pre-warm the project-name cache mirror so the synchronous
|
|
947
|
-
// `lookupProjectName` calls inside `renderRecentProjectRow` see the
|
|
948
|
-
// latest data the first time the variant renders.
|
|
940
|
+
var loading = textNode(tr('recentProjects_loadingHistory'), 'recent-projects-loading');
|
|
941
|
+
listContainer.appendChild(loading);
|
|
942
|
+
function stillCurrent() {
|
|
943
|
+
return generation === dashboardRenderGeneration && getPanel() === panelEl
|
|
944
|
+
&& panelEl.dataset.view === 'recent-projects' && rootEl.isConnected !== false
|
|
945
|
+
&& getCachedAccountScopeId() === accountScopeId;
|
|
946
|
+
}
|
|
947
|
+
var records;
|
|
949
948
|
try {
|
|
950
949
|
await loadProjectNameCacheFromStorage();
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
var rows = [];
|
|
954
|
-
var showAll = Boolean(options && options.showAll);
|
|
955
|
-
// Fetch one extra row beyond the fold so "show all" only renders when
|
|
956
|
-
// there really is more than one page of projects.
|
|
957
|
-
var pageLimit = 10;
|
|
958
|
-
try {
|
|
959
|
-
if (StorageDb) {
|
|
960
|
-
var deletedSessionIdsByProject = await SessionPersistence.loadTombstones();
|
|
961
|
-
rows = await StorageDb.listRecentProjectsAcrossAccount({
|
|
962
|
-
accountScopeId: accountScopeId,
|
|
963
|
-
limit: showAll ? 500 : pageLimit + 1,
|
|
964
|
-
deletedSessionIdsByProject: deletedSessionIdsByProject
|
|
965
|
-
});
|
|
966
|
-
}
|
|
950
|
+
records = await loadDashboardConversations(accountScopeId);
|
|
967
951
|
} catch (_error) {
|
|
968
|
-
|
|
952
|
+
if (!stillCurrent()) return;
|
|
953
|
+
loading.textContent = tr('recentProjects_loadFailed');
|
|
954
|
+
var retry = createViewButton(tr('recentProjects_retryScope'), function () {
|
|
955
|
+
renderRecentProjectsVariant(options).catch(function () {});
|
|
956
|
+
}, null, 'recent-projects-show-all');
|
|
957
|
+
listContainer.appendChild(retry);
|
|
958
|
+
rootEl.appendChild(renderSettingsEntry({ scope: 'account' }));
|
|
959
|
+
return;
|
|
969
960
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
961
|
+
if (!stillCurrent()) return;
|
|
962
|
+
loading.remove();
|
|
963
|
+
var validRecords = records.filter(function (record) {
|
|
964
|
+
return isValidProjectId(record.projectId) && (!projectFilter || record.projectId === projectFilter);
|
|
965
|
+
});
|
|
966
|
+
var started = validRecords.filter(conversationHasContent);
|
|
967
|
+
var unstarted = validRecords.filter(function (record) { return !conversationHasContent(record); });
|
|
968
|
+
if (started.length) {
|
|
969
|
+
listContainer.appendChild(renderProjectSessionRow(listContainer, started[0].projectId, started[0], { featured: true }));
|
|
970
|
+
if (started.length > 1) {
|
|
971
|
+
var heading = createViewElement('div', 'recent-projects-history-heading');
|
|
972
|
+
heading.appendChild(textNode(tr('recentProjects_historyHeading')));
|
|
973
|
+
heading.appendChild(textNode(tr('recentProjects_historyHint')));
|
|
974
|
+
listContainer.appendChild(heading);
|
|
975
|
+
var historyList = createViewElement('div', 'recent-projects-history-list');
|
|
976
|
+
var history = options.showAll ? started.slice(1) : started.slice(1, 10);
|
|
977
|
+
history.forEach(function (record) {
|
|
978
|
+
historyList.appendChild(renderProjectSessionRow(historyList, record.projectId, record));
|
|
979
|
+
});
|
|
980
|
+
listContainer.appendChild(historyList);
|
|
981
|
+
if (!options.showAll && started.length > 10) {
|
|
982
|
+
var more = createViewButton(tr('recentProjects_showAllConversations', { count: started.length }), function () {
|
|
983
|
+
renderRecentProjectsVariant({ showAll: true }).catch(function () {});
|
|
984
|
+
}, 'data-recent-projects-show-all', 'recent-projects-show-all');
|
|
985
|
+
listContainer.appendChild(more);
|
|
986
|
+
}
|
|
982
987
|
}
|
|
983
|
-
welcomeSubtitle.textContent = tr('recentProjects_welcome_stats', {
|
|
984
|
-
projects: rows.length,
|
|
985
|
-
sessions: sessionTotal
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
var hasMore = !showAll && rows && rows.length > pageLimit;
|
|
989
|
-
var visibleRows = hasMore ? rows.slice(0, pageLimit) : rows;
|
|
990
|
-
if (!visibleRows || !visibleRows.length) {
|
|
991
|
-
listContainer.appendChild(renderEmptyState());
|
|
992
988
|
} else {
|
|
993
|
-
|
|
994
|
-
|
|
989
|
+
var empty = renderEmptyState();
|
|
990
|
+
if (unstarted.length) empty.textContent = tr('recentProjects_noStarted');
|
|
991
|
+
listContainer.appendChild(empty);
|
|
992
|
+
}
|
|
993
|
+
if (unstarted.length) {
|
|
994
|
+
var drafts = createViewElement('details', 'recent-projects-drafts');
|
|
995
|
+
drafts.setAttribute('data-unstarted-conversations', '');
|
|
996
|
+
var summary = createViewElement('summary', '', tr('recentProjects_unstarted', { count: unstarted.length }));
|
|
997
|
+
drafts.appendChild(summary);
|
|
998
|
+
var draftsBuilt = false;
|
|
999
|
+
function populateDrafts() {
|
|
1000
|
+
if (draftsBuilt || !stillCurrent()) return;
|
|
1001
|
+
draftsBuilt = true;
|
|
1002
|
+
drafts.appendChild(textNode(tr('recentProjects_unstartedHint'), 'recent-projects-drafts-hint'));
|
|
1003
|
+
unstarted.forEach(function (record) {
|
|
1004
|
+
drafts.appendChild(renderProjectSessionRow(drafts, record.projectId, record));
|
|
1005
|
+
});
|
|
995
1006
|
}
|
|
1007
|
+
drafts.addEventListener('toggle', function () { if (drafts.open) populateDrafts(); });
|
|
1008
|
+
listContainer.appendChild(drafts);
|
|
1009
|
+
if (options.draftsOpen) { drafts.open = true; populateDrafts(); }
|
|
996
1010
|
}
|
|
997
|
-
if (
|
|
998
|
-
var
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1011
|
+
if (!projectFilter) {
|
|
1012
|
+
var invalid = records.filter(function (record) { return !isValidProjectId(record.projectId); });
|
|
1013
|
+
var invalidIds = new Set();
|
|
1014
|
+
if (invalid.length) listContainer.appendChild(textNode(tr('recentProjects_unavailableRecords'), 'recent-projects-history-heading'));
|
|
1015
|
+
invalid.forEach(function (record) {
|
|
1016
|
+
var id = String(record.projectId || '');
|
|
1017
|
+
if (!invalidIds.has(id)) {
|
|
1018
|
+
invalidIds.add(id);
|
|
1019
|
+
listContainer.appendChild(renderRecentProjectRow({ projectId: record.projectId }));
|
|
1020
|
+
}
|
|
1005
1021
|
});
|
|
1006
|
-
listContainer.appendChild(showAllButton);
|
|
1007
1022
|
}
|
|
1008
1023
|
rootEl.appendChild(renderSettingsEntry({ scope: 'account' }));
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
if (expandProjectId && toReopen.indexOf(expandProjectId) === -1) {
|
|
1012
|
-
toReopen.push(expandProjectId);
|
|
1013
|
-
}
|
|
1014
|
-
for (var reopenIndex = 0; reopenIndex < toReopen.length; reopenIndex++) {
|
|
1015
|
-
var wrap = listContainer.querySelector('[data-project-row-wrap="' + toReopen[reopenIndex] + '"]');
|
|
1016
|
-
if (wrap) {
|
|
1017
|
-
toggleProjectSessions(wrap, toReopen[reopenIndex]);
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
if (options && Number.isFinite(options.restoreScrollTop)) {
|
|
1021
|
-
rootEl.scrollTop = options.restoreScrollTop;
|
|
1022
|
-
var scrollerParent = rootEl.parentElement;
|
|
1023
|
-
if (scrollerParent && scrollerParent.scrollHeight > scrollerParent.clientHeight) {
|
|
1024
|
-
scrollerParent.scrollTop = options.restoreScrollTop;
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
opportunisticEnrichmentFromDom().catch(function () { /* swallow */ });
|
|
1024
|
+
if (Number.isFinite(options.restoreScrollTop)) rootEl.scrollTop = options.restoreScrollTop;
|
|
1025
|
+
opportunisticEnrichmentFromDom().catch(function () {});
|
|
1028
1026
|
}
|
|
1029
1027
|
|
|
1030
1028
|
function renderPerProjectVariant() {
|
|
1029
|
+
dashboardRenderGeneration++;
|
|
1031
1030
|
// Per-project mount: ensure the panel view attribute is back on the
|
|
1032
1031
|
// session view and the variant root is detached so the per-project DOM
|
|
1033
1032
|
// is the only thing visible. The existing applyStateToPanel path renders
|