codex-overleaf-link 1.9.11 → 1.9.12
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 +23 -23
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/runtime-manifest.json +5 -3
- package/extension/src/content/contentRuntime.js +60 -325
- package/extension/src/content/projectSettingsCoordinator.js +365 -0
- package/extension/src/content/sessionManager.js +20 -204
- package/extension/src/content/sessionMenuView.js +218 -0
- package/extension/src/page/saveState.js +292 -0
- package/extension/src/pageBridge.js +10 -274
- package/extension/src/shared/compatibility.js +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const PAGE_BRIDGE_INSTALL_VERSION = window.CodexOverleafCompatibility?.BUILD_TARGET_VERSION || 'dev';
|
|
5
|
-
const PAGE_BRIDGE_INSTALL_REVISION = '2026-05-21-editor-readiness-
|
|
5
|
+
const PAGE_BRIDGE_INSTALL_REVISION = '2026-05-21-editor-readiness-v11';
|
|
6
6
|
if (window.__codexOverleafPageBridgeInstalledVersion === PAGE_BRIDGE_INSTALL_VERSION
|
|
7
7
|
&& window.__codexOverleafPageBridgeInstalledRevision === PAGE_BRIDGE_INSTALL_REVISION) {
|
|
8
8
|
return;
|
|
@@ -21,6 +21,15 @@
|
|
|
21
21
|
let projectSnapshotBridge = null;
|
|
22
22
|
let writebackRouter = null;
|
|
23
23
|
let writeGuard = null;
|
|
24
|
+
const saveState = requirePageModule('CodexOverleafSaveState').create({
|
|
25
|
+
delay,
|
|
26
|
+
detectEditor,
|
|
27
|
+
document,
|
|
28
|
+
getActiveFilePath,
|
|
29
|
+
readActiveEditorText,
|
|
30
|
+
window
|
|
31
|
+
});
|
|
32
|
+
const { waitForSaveState } = saveState;
|
|
24
33
|
const compileBridge = window.CodexOverleafCompileBridge.create({
|
|
25
34
|
document,
|
|
26
35
|
getActiveFilePath,
|
|
@@ -1305,279 +1314,6 @@
|
|
|
1305
1314
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1306
1315
|
}
|
|
1307
1316
|
|
|
1308
|
-
async function waitForSaveState(params = {}) {
|
|
1309
|
-
const deadlineMs = Number.isFinite(Number(params.deadlineMs)) ? Number(params.deadlineMs) : 5000;
|
|
1310
|
-
const pollInterval = Number.isFinite(Number(params.pollIntervalMs)) ? Number(params.pollIntervalMs) : 100;
|
|
1311
|
-
const quietFallbackMs = Number.isFinite(Number(params.quietFallbackMs)) ? Number(params.quietFallbackMs) : 1000;
|
|
1312
|
-
const deadline = Date.now() + Math.max(0, deadlineMs);
|
|
1313
|
-
let lastState = { state: 'unknown', reason: 'Overleaf save state has not been checked yet.' };
|
|
1314
|
-
let quietSignature = '';
|
|
1315
|
-
let quietSince = 0;
|
|
1316
|
-
|
|
1317
|
-
do {
|
|
1318
|
-
let current;
|
|
1319
|
-
try {
|
|
1320
|
-
current = getOverleafSaveState();
|
|
1321
|
-
} catch (error) {
|
|
1322
|
-
return {
|
|
1323
|
-
ok: false,
|
|
1324
|
-
state: 'unavailable',
|
|
1325
|
-
reason: `Could not check Overleaf save state: ${error.message}`
|
|
1326
|
-
};
|
|
1327
|
-
}
|
|
1328
|
-
if (current.state === 'verified_saved') {
|
|
1329
|
-
return { ok: true, state: 'verified_saved' };
|
|
1330
|
-
}
|
|
1331
|
-
if (current.state === 'unavailable') {
|
|
1332
|
-
return {
|
|
1333
|
-
ok: false,
|
|
1334
|
-
state: 'unavailable',
|
|
1335
|
-
reason: current.reason || 'Overleaf save state is unavailable.'
|
|
1336
|
-
};
|
|
1337
|
-
}
|
|
1338
|
-
const quietEligible = params.allowQuietEditorFallback === true
|
|
1339
|
-
&& current.state === 'unknown'
|
|
1340
|
-
&& /save indicator was not found/i.test(current.reason || '')
|
|
1341
|
-
&& window.navigator?.onLine !== false
|
|
1342
|
-
&& detectEditor()?.ok === true;
|
|
1343
|
-
if (quietEligible) {
|
|
1344
|
-
const text = String(readActiveEditorText() || '');
|
|
1345
|
-
const signature = `${getActiveFilePath()}:${text.length}:${text.slice(0, 80)}:${text.slice(-80)}`;
|
|
1346
|
-
if (signature === quietSignature && Date.now() - quietSince >= Math.max(0, quietFallbackMs)) {
|
|
1347
|
-
return { ok: true, state: 'verified_quiet', evidence: 'stable_editor_snapshot_without_save_indicator' };
|
|
1348
|
-
}
|
|
1349
|
-
if (signature !== quietSignature) {
|
|
1350
|
-
quietSignature = signature;
|
|
1351
|
-
quietSince = Date.now();
|
|
1352
|
-
}
|
|
1353
|
-
} else {
|
|
1354
|
-
quietSignature = '';
|
|
1355
|
-
quietSince = 0;
|
|
1356
|
-
}
|
|
1357
|
-
lastState = current;
|
|
1358
|
-
if (Date.now() >= deadline) {
|
|
1359
|
-
break;
|
|
1360
|
-
}
|
|
1361
|
-
await delay(Math.min(Math.max(1, pollInterval), Math.max(1, deadline - Date.now())));
|
|
1362
|
-
} while (Date.now() < deadline);
|
|
1363
|
-
|
|
1364
|
-
return {
|
|
1365
|
-
ok: false,
|
|
1366
|
-
state: 'unknown_timeout',
|
|
1367
|
-
reason: buildSaveStateTimeoutReason(lastState)
|
|
1368
|
-
};
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
function getOverleafSaveState() {
|
|
1372
|
-
if (!document || typeof document.querySelectorAll !== 'function') {
|
|
1373
|
-
return {
|
|
1374
|
-
state: 'unavailable',
|
|
1375
|
-
reason: 'Overleaf document is unavailable for save-state detection.'
|
|
1376
|
-
};
|
|
1377
|
-
}
|
|
1378
|
-
const selectors = [
|
|
1379
|
-
'.toolbar-header [class*="save" i]',
|
|
1380
|
-
'[class*="saving-status" i]',
|
|
1381
|
-
'[class*="save-status" i]',
|
|
1382
|
-
'[data-testid*="save" i]',
|
|
1383
|
-
'[aria-label*="save" i]'
|
|
1384
|
-
];
|
|
1385
|
-
let sawSaveCandidate = false;
|
|
1386
|
-
let sawVerifiedSaved = false;
|
|
1387
|
-
let sawNegativeState = false;
|
|
1388
|
-
let sawSavingState = false;
|
|
1389
|
-
for (const selector of selectors) {
|
|
1390
|
-
const elements = document.querySelectorAll(selector);
|
|
1391
|
-
for (const el of elements) {
|
|
1392
|
-
if (isHiddenSaveIndicatorNode(el)) {
|
|
1393
|
-
continue;
|
|
1394
|
-
}
|
|
1395
|
-
const texts = readSaveIndicatorTexts(el);
|
|
1396
|
-
if (!texts.length) {
|
|
1397
|
-
continue;
|
|
1398
|
-
}
|
|
1399
|
-
const combinedText = texts.join(' ');
|
|
1400
|
-
if (isAnySaveIndicatorText(texts, combinedText, isNegativeSaveStateText)) {
|
|
1401
|
-
sawNegativeState = true;
|
|
1402
|
-
sawSaveCandidate = true;
|
|
1403
|
-
continue;
|
|
1404
|
-
}
|
|
1405
|
-
if (isAnySaveIndicatorText(texts, combinedText, isSavingStateText)) {
|
|
1406
|
-
sawSavingState = true;
|
|
1407
|
-
sawSaveCandidate = true;
|
|
1408
|
-
continue;
|
|
1409
|
-
}
|
|
1410
|
-
if (texts.some(isVerifiedSavedText)) {
|
|
1411
|
-
sawVerifiedSaved = true;
|
|
1412
|
-
continue;
|
|
1413
|
-
}
|
|
1414
|
-
if (isAnySaveIndicatorText(texts, combinedText, isSaveIndicatorCandidateText)) {
|
|
1415
|
-
sawSaveCandidate = true;
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
if (sawNegativeState) {
|
|
1420
|
-
return {
|
|
1421
|
-
state: 'unknown',
|
|
1422
|
-
reason: 'Overleaf save indicator reports changes are not saved.'
|
|
1423
|
-
};
|
|
1424
|
-
}
|
|
1425
|
-
if (sawSavingState) {
|
|
1426
|
-
return {
|
|
1427
|
-
state: 'saving',
|
|
1428
|
-
reason: 'Overleaf is still saving or syncing changes.'
|
|
1429
|
-
};
|
|
1430
|
-
}
|
|
1431
|
-
if (sawVerifiedSaved) {
|
|
1432
|
-
return { state: 'verified_saved' };
|
|
1433
|
-
}
|
|
1434
|
-
return {
|
|
1435
|
-
state: 'unknown',
|
|
1436
|
-
reason: sawSaveCandidate
|
|
1437
|
-
? 'Overleaf save indicator was present, but did not verify that all changes are saved.'
|
|
1438
|
-
: 'Overleaf save indicator was not found.'
|
|
1439
|
-
};
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
function isHiddenSaveIndicatorNode(node) {
|
|
1443
|
-
if (!node) {
|
|
1444
|
-
return true;
|
|
1445
|
-
}
|
|
1446
|
-
if (node.hidden === true || node.inert === true) {
|
|
1447
|
-
return true;
|
|
1448
|
-
}
|
|
1449
|
-
if (hasSaveIndicatorAttribute(node, 'hidden') || hasSaveIndicatorAttribute(node, 'inert')) {
|
|
1450
|
-
return true;
|
|
1451
|
-
}
|
|
1452
|
-
if (/^true$/i.test(String(node.getAttribute?.('aria-hidden') || ''))) {
|
|
1453
|
-
return true;
|
|
1454
|
-
}
|
|
1455
|
-
if (hasHiddenSaveIndicatorAncestor(node)) {
|
|
1456
|
-
return true;
|
|
1457
|
-
}
|
|
1458
|
-
if (isHiddenStyle(node.style)) {
|
|
1459
|
-
return true;
|
|
1460
|
-
}
|
|
1461
|
-
if (isHiddenStyle(getComputedStyleForSaveIndicator(node))) {
|
|
1462
|
-
return true;
|
|
1463
|
-
}
|
|
1464
|
-
return hasZeroSaveIndicatorLayout(node);
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
function hasHiddenSaveIndicatorAncestor(node) {
|
|
1468
|
-
if (typeof node.closest === 'function') {
|
|
1469
|
-
try {
|
|
1470
|
-
if (node.closest('[hidden],[inert],[aria-hidden="true"]')) {
|
|
1471
|
-
return true;
|
|
1472
|
-
}
|
|
1473
|
-
} catch (_error) {
|
|
1474
|
-
// Fall back to walking parentElement below.
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
let current = node.parentElement;
|
|
1478
|
-
while (current) {
|
|
1479
|
-
if (current.hidden === true || current.inert === true) {
|
|
1480
|
-
return true;
|
|
1481
|
-
}
|
|
1482
|
-
if (hasSaveIndicatorAttribute(current, 'hidden') || hasSaveIndicatorAttribute(current, 'inert')) {
|
|
1483
|
-
return true;
|
|
1484
|
-
}
|
|
1485
|
-
if (/^true$/i.test(String(current.getAttribute?.('aria-hidden') || ''))) {
|
|
1486
|
-
return true;
|
|
1487
|
-
}
|
|
1488
|
-
current = current.parentElement;
|
|
1489
|
-
}
|
|
1490
|
-
return false;
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
function hasSaveIndicatorAttribute(node, attribute) {
|
|
1494
|
-
if (typeof node.hasAttribute === 'function') {
|
|
1495
|
-
try {
|
|
1496
|
-
return node.hasAttribute(attribute);
|
|
1497
|
-
} catch (_error) {
|
|
1498
|
-
return false;
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
const value = node.getAttribute?.(attribute);
|
|
1502
|
-
return value != null && value !== '';
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
function isHiddenStyle(style) {
|
|
1506
|
-
if (!style) {
|
|
1507
|
-
return false;
|
|
1508
|
-
}
|
|
1509
|
-
return /^none$/i.test(String(style.display || ''))
|
|
1510
|
-
|| /^(hidden|collapse)$/i.test(String(style.visibility || ''));
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
function getComputedStyleForSaveIndicator(node) {
|
|
1514
|
-
try {
|
|
1515
|
-
const ownerWindow = node.ownerDocument?.defaultView || null;
|
|
1516
|
-
const fallbackWindow = typeof window !== 'undefined' ? window : null;
|
|
1517
|
-
const styleWindow = ownerWindow || fallbackWindow;
|
|
1518
|
-
return styleWindow?.getComputedStyle?.(node) || null;
|
|
1519
|
-
} catch (_error) {
|
|
1520
|
-
return null;
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
function hasZeroSaveIndicatorLayout(node) {
|
|
1525
|
-
if (typeof node.getClientRects !== 'function') {
|
|
1526
|
-
return false;
|
|
1527
|
-
}
|
|
1528
|
-
try {
|
|
1529
|
-
const rects = node.getClientRects();
|
|
1530
|
-
if (rects && rects.length > 0) {
|
|
1531
|
-
return false;
|
|
1532
|
-
}
|
|
1533
|
-
return node.offsetWidth === 0 && node.offsetHeight === 0;
|
|
1534
|
-
} catch (_error) {
|
|
1535
|
-
return false;
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
function readSaveIndicatorTexts(node) {
|
|
1540
|
-
const parts = [
|
|
1541
|
-
node.textContent,
|
|
1542
|
-
node.innerText,
|
|
1543
|
-
node.getAttribute?.('aria-label'),
|
|
1544
|
-
node.getAttribute?.('title'),
|
|
1545
|
-
node.getAttribute?.('value')
|
|
1546
|
-
].map(value => String(value || '').replace(/\s+/g, ' ').trim()).filter(Boolean);
|
|
1547
|
-
return Array.from(new Set(parts));
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
function isAnySaveIndicatorText(texts, combinedText, predicate) {
|
|
1551
|
-
return predicate(combinedText) || texts.some(predicate);
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
function isNegativeSaveStateText(text) {
|
|
1555
|
-
return /\bnot\b[\s\S]{0,40}\bsaved\b|\b(?:unsaved|save failed|failed to save|could not save|unable to save)\b|未保存|保存失败/i.test(text);
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
function isSavingStateText(text) {
|
|
1559
|
-
return /\b(saving|syncing|compiling)\b|保存中|正在保存|同步中/i.test(text);
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
function isVerifiedSavedText(text) {
|
|
1563
|
-
if (isNegativeSaveStateText(text)) {
|
|
1564
|
-
return false;
|
|
1565
|
-
}
|
|
1566
|
-
return /^\s*(?:all changes saved|changes saved|saved)[\s.!]*$/i.test(text)
|
|
1567
|
-
|| /已保存/i.test(text);
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
function isSaveIndicatorCandidateText(text) {
|
|
1571
|
-
return /\bsave\b|\bsaved\b|\bsaving\b|保存/i.test(text);
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
function buildSaveStateTimeoutReason(lastState = {}) {
|
|
1575
|
-
if (lastState.state === 'saving') {
|
|
1576
|
-
return `Timed out waiting for Overleaf to finish saving changes. ${lastState.reason || ''}`.trim();
|
|
1577
|
-
}
|
|
1578
|
-
return `Timed out waiting for a verified Overleaf save state. ${lastState.reason || ''}`.trim();
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
1317
|
function getRequestedSnapshotPaths(params = {}, activePath = '') {
|
|
1582
1318
|
return snapshotRouter.getRequestedSnapshotPaths(params, activePath);
|
|
1583
1319
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
const MIN_NATIVE_VERSION = '1.0.0';
|
|
13
13
|
const MIN_COMPATIBLE_NATIVE_VERSION = '1.0.0';
|
|
14
14
|
const MIN_COMPATIBLE_EXTENSION_VERSION = '1.0.0';
|
|
15
|
-
const BUILD_TARGET_VERSION = '1.9.
|
|
15
|
+
const BUILD_TARGET_VERSION = '1.9.12';
|
|
16
16
|
const DEFAULT_CHROME_EXTENSION_ID = 'illdpneeeopfffmiepaejglgmhpmdhdc';
|
|
17
17
|
const REQUIRED_CAPABILITIES = Object.freeze([
|
|
18
18
|
'bridgePing',
|