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.
@@ -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-v10';
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.11';
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',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-overleaf-link",
3
- "version": "1.9.11",
3
+ "version": "1.9.12",
4
4
  "description": "Cross-platform Chrome bridge that connects Codex to the active Overleaf project.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",