codex-overleaf-link 1.9.13 → 2.1.0

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.
Files changed (122) hide show
  1. package/README.md +24 -23
  2. package/extension/bootstrap/manifest.template.json +2 -1
  3. package/extension/runtime-manifest.json +13 -6
  4. package/extension/src/background.js +7 -0
  5. package/extension/src/content/composerPanel.js +13 -3
  6. package/extension/src/content/contentRuntime.js +130 -65
  7. package/extension/src/content/contextTray.js +173 -9
  8. package/extension/src/content/markdownText.js +16 -1
  9. package/extension/src/content/mathText.js +233 -0
  10. package/extension/src/content/modelPicker.js +117 -106
  11. package/extension/src/content/modelPickerSupport.js +115 -0
  12. package/extension/src/content/nativeChannel.js +20 -1
  13. package/extension/src/content/panelMaintenance.js +2 -0
  14. package/extension/src/content/projectSettingsCoordinator.js +2 -0
  15. package/extension/src/content/providerSettingsCoordinator.js +413 -0
  16. package/extension/src/content/providerSettingsDialog.js +981 -0
  17. package/extension/src/content/runTimelineView.js +24 -5
  18. package/extension/src/content/settingsPanel.js +33 -2
  19. package/extension/src/content/writebackOrchestrator.js +3 -1
  20. package/extension/src/page/overleafProjectSnapshot.js +29 -33
  21. package/extension/src/page/snapshotRouter.js +75 -8
  22. package/extension/src/page/trackedChangesLifecycle.js +154 -26
  23. package/extension/src/page/writebackRouter.js +31 -60
  24. package/extension/src/pageBridge.js +1 -1
  25. package/extension/src/shared/agentTranscript.js +130 -0
  26. package/extension/src/shared/compatibility.js +10 -7
  27. package/extension/src/shared/failureReasons.js +75 -0
  28. package/extension/src/shared/i18n.js +22 -0
  29. package/extension/src/shared/models.js +1 -0
  30. package/extension/src/shared/providerProfiles.js +283 -0
  31. package/extension/src/shared/sessionState.js +12 -2
  32. package/extension/src/shared/storageDb.js +27 -19
  33. package/extension/src/shared/storageRunActions.js +99 -0
  34. package/extension/styles/panel.css +587 -1
  35. package/extension/vendor/katex/LICENSE +21 -0
  36. package/extension/vendor/katex/VERSION +2 -0
  37. package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
  38. package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
  39. package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  40. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  41. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  42. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  43. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  44. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  45. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  46. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  47. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  48. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  49. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  50. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  51. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  52. package/extension/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
  53. package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
  54. package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  55. package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  56. package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  57. package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  58. package/extension/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
  59. package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
  60. package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  61. package/extension/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
  62. package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
  63. package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  64. package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  65. package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  66. package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  67. package/extension/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
  68. package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
  69. package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  70. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  71. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  72. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  73. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  74. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  75. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  76. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  77. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  78. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  79. package/extension/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
  80. package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
  81. package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  82. package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
  83. package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
  84. package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  85. package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
  86. package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
  87. package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  88. package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
  89. package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
  90. package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  91. package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
  92. package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
  93. package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  94. package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  95. package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  96. package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  97. package/extension/vendor/katex/katex.min.css +1 -0
  98. package/extension/vendor/katex/katex.min.js +1 -0
  99. package/native-host/src/anthropicBridgeRequest.js +435 -0
  100. package/native-host/src/anthropicBridgeResponse.js +431 -0
  101. package/native-host/src/anthropicMessagesBridge.js +281 -0
  102. package/native-host/src/chatBridgeRequest.js +274 -0
  103. package/native-host/src/chatBridgeResponse.js +594 -0
  104. package/native-host/src/chatCompletionsBridge.js +485 -0
  105. package/native-host/src/codexCommand.js +25 -0
  106. package/native-host/src/codexPromptAssembly.js +9 -0
  107. package/native-host/src/codexProviderLaunch.js +213 -0
  108. package/native-host/src/codexProviderTest.js +196 -0
  109. package/native-host/src/codexSessionRunner.js +29 -25
  110. package/native-host/src/managedInstall.js +1 -0
  111. package/native-host/src/providerBridgeRoutes.js +11 -0
  112. package/native-host/src/providerModelCatalog.js +107 -0
  113. package/native-host/src/providerProfile.js +454 -0
  114. package/native-host/src/providerReasoning.js +235 -0
  115. package/native-host/src/providerRedaction.js +18 -0
  116. package/native-host/src/providerRuntime.js +365 -0
  117. package/native-host/src/providerStore.js +691 -0
  118. package/native-host/src/readProgressGuard.js +360 -0
  119. package/native-host/src/taskRunnerRuntime.js +40 -4
  120. package/native-host/src/updateArchive.js +1 -0
  121. package/native-host/src/updateTrust.js +2 -1
  122. package/package.json +2 -1
@@ -47,11 +47,84 @@
47
47
  let contextProject = null;
48
48
  let contextLoadId = 0;
49
49
  let contextExpandedFolders = new Set();
50
+ let contextExactEnhancementAttempted = false;
51
+ let contextSyncState = 'idle';
52
+ let contextPrefetchTimer = null;
53
+ let contextPrefetchIdleHandle = null;
54
+
55
+ function cancelContextPrefetch() {
56
+ if (contextPrefetchTimer !== null) {
57
+ const clearTimer = typeof root.clearTimeout === 'function' ? root.clearTimeout.bind(root) : clearTimeout;
58
+ clearTimer(contextPrefetchTimer);
59
+ contextPrefetchTimer = null;
60
+ }
61
+ if (contextPrefetchIdleHandle !== null && typeof root.cancelIdleCallback === 'function') {
62
+ root.cancelIdleCallback(contextPrefetchIdleHandle);
63
+ contextPrefetchIdleHandle = null;
64
+ }
65
+ }
66
+
67
+ function scheduleContextPrefetch(options = {}) {
68
+ cancelContextPrefetch();
69
+ const enabled = Object.prototype.hasOwnProperty.call(options, 'enabled')
70
+ ? Boolean(options.enabled)
71
+ : getState()?.preloadProjectContext !== false;
72
+ const expectedProjectId = String(getCurrentProjectId() || '');
73
+ if (!enabled || !expectedProjectId) {
74
+ return;
75
+ }
76
+ const delayMs = Number.isFinite(Number(options.delayMs))
77
+ ? Math.max(0, Number(options.delayMs))
78
+ : 2500;
79
+ const setTimer = typeof root.setTimeout === 'function' ? root.setTimeout.bind(root) : setTimeout;
80
+ contextPrefetchTimer = setTimer(() => {
81
+ contextPrefetchTimer = null;
82
+ const currentProjectId = String(getCurrentProjectId() || '');
83
+ if (getState()?.preloadProjectContext === false || document?.visibilityState === 'hidden') {
84
+ return;
85
+ }
86
+ if (currentProjectId !== expectedProjectId) {
87
+ scheduleContextPrefetch({ delayMs: 500 });
88
+ return;
89
+ }
90
+ const run = () => {
91
+ contextPrefetchIdleHandle = null;
92
+ void loadContextFiles({ force: false, background: true });
93
+ };
94
+ if (typeof root.requestIdleCallback === 'function') {
95
+ contextPrefetchIdleHandle = root.requestIdleCallback(run, { timeout: 2000 });
96
+ } else {
97
+ run();
98
+ }
99
+ }, delayMs);
100
+ contextPrefetchTimer?.unref?.();
101
+ }
50
102
 
51
103
  function installContextDismiss() {
52
104
  if (!document) {
53
105
  return;
54
106
  }
107
+ document.addEventListener('change', event => {
108
+ const target = event?.target;
109
+ if (!target?.matches?.('[data-preload-project-context]')) {
110
+ return;
111
+ }
112
+ const enabled = Boolean(target.checked);
113
+ setState({ ...getState(), preloadProjectContext: enabled });
114
+ saveStateSoon();
115
+ if (enabled) {
116
+ scheduleContextPrefetch({ enabled: true, delayMs: 0 });
117
+ } else {
118
+ cancelContextPrefetch();
119
+ }
120
+ }, true);
121
+ document.addEventListener('visibilitychange', () => {
122
+ if (document.visibilityState !== 'hidden') {
123
+ scheduleContextPrefetch();
124
+ }
125
+ });
126
+ scheduleContextPrefetch();
127
+
55
128
  document.addEventListener('click', event => {
56
129
  if (isContextTrayClickTarget(event.target)) {
57
130
  return;
@@ -126,17 +199,24 @@
126
199
  if (!list) {
127
200
  return;
128
201
  }
129
- if (isExactContextFileListProject(contextProject) && !options.force) {
202
+ if (isContextFileListProject(contextProject) && !options.force) {
130
203
  renderContextFiles(contextProject);
204
+ if (isExactContextFileListProject(contextProject)) {
205
+ setContextSyncState('exact-ready');
206
+ } else if (contextSyncState !== 'partial') {
207
+ setContextSyncState('exact-loading');
208
+ void enhanceContextFilesFromExactSnapshot({ loadId: contextLoadId });
209
+ }
131
210
  return;
132
211
  }
133
212
 
134
213
  const loadId = ++contextLoadId;
214
+ setContextSyncState('tree-loading');
135
215
  setContextStatus(tr('contextLoadingFiles'));
136
216
  list.textContent = '';
137
217
 
138
218
  try {
139
- const project = await requestExactContextFiles({ force: Boolean(options.force) });
219
+ const project = await requestContextFiles({ force: Boolean(options.force) });
140
220
  if (!project?.ok) {
141
221
  throw new Error(project?.error || project?.reason || tr('unknownReason'));
142
222
  }
@@ -144,28 +224,82 @@
144
224
  return;
145
225
  }
146
226
  contextProject = project;
227
+ setContextSyncState(isExactContextFileListProject(project) ? 'exact-ready' : 'exact-loading');
147
228
  renderContextFiles(project);
229
+ if (!isExactContextFileListProject(project)) {
230
+ void enhanceContextFilesFromExactSnapshot({
231
+ force: Boolean(options.force),
232
+ loadId
233
+ });
234
+ }
148
235
  } catch (error) {
149
236
  if (loadId !== contextLoadId) {
150
237
  return;
151
238
  }
239
+ setContextSyncState('partial');
152
240
  setContextStatus(tr('contextReadFailed', { message: error.message }));
153
241
  }
154
242
  }
155
243
 
244
+ async function requestContextFiles({ force = false } = {}) {
245
+ const project = await callPageBridge('getProjectFileList', {
246
+ force,
247
+ preferExact: false
248
+ });
249
+ if (
250
+ project?.ok
251
+ && isContextFileListProject(project)
252
+ && Array.isArray(project.files)
253
+ && project.files.length
254
+ ) {
255
+ return project;
256
+ }
257
+ return requestExactContextFiles({ force });
258
+ }
259
+
156
260
  async function requestExactContextFiles({ force = false } = {}) {
157
- const project = await callPageBridge('getProjectSnapshot', {
261
+ return callPageBridge('getProjectFileList', {
158
262
  force,
159
- preferLightweight: true,
160
- allowZipFallback: false,
161
- allowEditorNavigation: false,
162
- requireFullProject: true,
163
- zipOnly: true,
263
+ preferExact: true,
264
+ exactOnly: true,
164
265
  includeBinaryFiles: true,
165
266
  includeContent: false,
166
267
  zipTimeoutMs: exactFileListZipTimeoutMs
167
268
  });
168
- return normalizeContextFileListFromZipSnapshot(project);
269
+ }
270
+
271
+ async function enhanceContextFilesFromExactSnapshot({ force = false, loadId = contextLoadId } = {}) {
272
+ if (isExactContextFileListProject(contextProject)) {
273
+ setContextSyncState('exact-ready');
274
+ return;
275
+ }
276
+ if (contextExactEnhancementAttempted && !force) {
277
+ return;
278
+ }
279
+ contextExactEnhancementAttempted = true;
280
+ setContextSyncState('exact-loading');
281
+ const expectedProjectId = String(contextProject?.id || getCurrentProjectId() || '');
282
+ try {
283
+ const project = await requestExactContextFiles({ force });
284
+ if (
285
+ loadId !== contextLoadId
286
+ || expectedProjectId && String(project.id || '') !== expectedProjectId
287
+ ) {
288
+ return;
289
+ }
290
+ if (!isExactContextFileListProject(project)) {
291
+ setContextSyncState('partial');
292
+ return;
293
+ }
294
+ contextProject = project;
295
+ setContextSyncState('exact-ready');
296
+ renderContextFiles(project);
297
+ } catch (_error) {
298
+ // The page file tree remains usable when the optional exact ZIP list is unavailable.
299
+ if (loadId === contextLoadId) {
300
+ setContextSyncState('partial');
301
+ }
302
+ }
169
303
  }
170
304
 
171
305
  function isExactContextFileListProject(project) {
@@ -601,6 +735,28 @@
601
735
  }
602
736
  }
603
737
 
738
+ function setContextSyncState(state) {
739
+ const indicator = getPanel()?.querySelector('[data-context-sync-status]');
740
+ const copy = indicator?.querySelector('[data-context-sync-copy]');
741
+ const stateKeys = {
742
+ 'tree-loading': 'contextSyncTreeLoading',
743
+ 'exact-loading': 'contextSyncExactLoading',
744
+ 'exact-ready': 'contextSyncExactReady',
745
+ partial: 'contextSyncPartial'
746
+ };
747
+ contextSyncState = Object.prototype.hasOwnProperty.call(stateKeys, state) ? state : 'idle';
748
+ if (!indicator) {
749
+ return;
750
+ }
751
+ indicator.dataset.state = contextSyncState;
752
+ const label = contextSyncState === 'idle' ? '' : tr(stateKeys[contextSyncState]);
753
+ indicator.title = label;
754
+ indicator.setAttribute('aria-label', label);
755
+ if (copy) {
756
+ copy.textContent = label;
757
+ }
758
+ }
759
+
604
760
  async function persistFocusFiles(focusFiles) {
605
761
  const nextState = updateActiveSession(getState(), {
606
762
  focusFiles
@@ -630,6 +786,9 @@
630
786
  function resetContextProject() {
631
787
  contextProject = null;
632
788
  contextLoadId += 1;
789
+ contextExactEnhancementAttempted = false;
790
+ setContextSyncState('idle');
791
+ scheduleContextPrefetch();
633
792
  }
634
793
 
635
794
  function getContextProject() {
@@ -638,11 +797,15 @@
638
797
 
639
798
  return {
640
799
  installContextDismiss,
800
+ scheduleContextPrefetch,
801
+ cancelContextPrefetch,
641
802
  isContextTrayClickTarget,
642
803
  toggleContextTray,
643
804
  closeContextTray,
644
805
  loadContextFiles,
806
+ requestContextFiles,
645
807
  requestExactContextFiles,
808
+ enhanceContextFilesFromExactSnapshot,
646
809
  isExactContextFileListProject,
647
810
  isContextFileListProject,
648
811
  normalizeContextFileListFromZipSnapshot,
@@ -662,6 +825,7 @@
662
825
  sortContextFiles,
663
826
  getContextFileRank,
664
827
  setContextStatus,
828
+ setContextSyncState,
665
829
  resetContextProject,
666
830
  getContextProject
667
831
  };
@@ -62,6 +62,21 @@
62
62
  }
63
63
 
64
64
  function buildMarkdownInlineNodes(value) {
65
+ const source = String(value || '');
66
+ const mathText = typeof window !== 'undefined'
67
+ ? window.CodexOverleafMathText
68
+ : null;
69
+ if (!mathText?.buildMathNodes) {
70
+ return buildMarkdownInlineNodesWithoutMath(source);
71
+ }
72
+ return mathText.buildMathNodes(source, {
73
+ document,
74
+ katex: window.katex,
75
+ renderText: buildMarkdownInlineNodesWithoutMath
76
+ });
77
+ }
78
+
79
+ function buildMarkdownInlineNodesWithoutMath(value) {
65
80
  const source = String(value || '');
66
81
  const nodes = [];
67
82
  let index = 0;
@@ -587,7 +602,7 @@
587
602
  }
588
603
 
589
604
  function isMarkdownHeadingLine(line) {
590
- return /^\s*(#{1,6}\s+\S|\*\*[^*]+\*\*:?\s*)$/.test(line);
605
+ return /^\s*(#{1,6}\s+\S.*|\*\*[^*]+\*\*:?\s*)$/.test(line);
591
606
  }
592
607
 
593
608
 
@@ -0,0 +1,233 @@
1
+ (function initCodexOverleafMathText(root) {
2
+ 'use strict';
3
+
4
+ const DELIMITERS = Object.freeze([
5
+ Object.freeze({ open: '$$', close: '$$', display: true, singleDollar: false }),
6
+ Object.freeze({ open: '\\[', close: '\\]', display: true, singleDollar: false }),
7
+ Object.freeze({ open: '\\(', close: '\\)', display: false, singleDollar: false }),
8
+ Object.freeze({ open: '$', close: '$', display: false, singleDollar: true })
9
+ ]);
10
+
11
+ function parseMathSegments(value) {
12
+ const source = String(value || '');
13
+ if (!source) {
14
+ return [];
15
+ }
16
+ const codeRanges = collectInlineCodeRanges(source);
17
+ const segments = [];
18
+ let cursor = 0;
19
+ while (cursor < source.length) {
20
+ const match = findNextMath(source, cursor, codeRanges);
21
+ if (!match) {
22
+ segments.push({ type: 'text', value: source.slice(cursor) });
23
+ break;
24
+ }
25
+ if (match.start > cursor) {
26
+ segments.push({ type: 'text', value: source.slice(cursor, match.start) });
27
+ }
28
+ segments.push({
29
+ type: 'math',
30
+ value: match.value,
31
+ raw: source.slice(match.start, match.end),
32
+ display: match.display
33
+ });
34
+ cursor = match.end;
35
+ }
36
+ return segments;
37
+ }
38
+
39
+ function findNextMath(source, fromIndex, codeRanges) {
40
+ let best = null;
41
+ for (const delimiter of DELIMITERS) {
42
+ const match = findNextDelimitedMath(source, fromIndex, delimiter, codeRanges);
43
+ if (!match) {
44
+ continue;
45
+ }
46
+ if (!best || match.start < best.start
47
+ || (match.start === best.start && delimiter.open.length > best.delimiter.open.length)) {
48
+ best = { ...match, delimiter };
49
+ }
50
+ }
51
+ return best;
52
+ }
53
+
54
+ function findNextDelimitedMath(source, fromIndex, delimiter, codeRanges) {
55
+ let searchFrom = fromIndex;
56
+ while (searchFrom < source.length) {
57
+ const start = source.indexOf(delimiter.open, searchFrom);
58
+ if (start < 0) {
59
+ return null;
60
+ }
61
+ if (!isValidOpening(source, start, delimiter, codeRanges)) {
62
+ searchFrom = start + delimiter.open.length;
63
+ continue;
64
+ }
65
+ const closeStart = findClosingDelimiter(
66
+ source,
67
+ start + delimiter.open.length,
68
+ delimiter,
69
+ codeRanges
70
+ );
71
+ if (closeStart < 0) {
72
+ searchFrom = start + delimiter.open.length;
73
+ continue;
74
+ }
75
+ const value = source.slice(start + delimiter.open.length, closeStart);
76
+ const end = closeStart + delimiter.close.length;
77
+ if (!isValidMathValue(value, delimiter) || overlapsCodeRange(start, end, codeRanges)) {
78
+ searchFrom = start + delimiter.open.length;
79
+ continue;
80
+ }
81
+ return { start, end, value, display: delimiter.display };
82
+ }
83
+ return null;
84
+ }
85
+
86
+ function isValidOpening(source, index, delimiter, codeRanges) {
87
+ if (isEscaped(source, index) || isInsideRange(index, codeRanges)) {
88
+ return false;
89
+ }
90
+ if (!delimiter.singleDollar) {
91
+ return true;
92
+ }
93
+ const previous = source[index - 1] || '';
94
+ const next = source[index + 1] || '';
95
+ return previous !== '$' && next !== '$' && next !== '' && !/\s/.test(next);
96
+ }
97
+
98
+ function findClosingDelimiter(source, fromIndex, delimiter, codeRanges) {
99
+ let searchFrom = fromIndex;
100
+ while (searchFrom < source.length) {
101
+ const index = source.indexOf(delimiter.close, searchFrom);
102
+ if (index < 0) {
103
+ return -1;
104
+ }
105
+ const previous = source[index - 1] || '';
106
+ const next = source[index + delimiter.close.length] || '';
107
+ const invalidSingleDollar = delimiter.singleDollar
108
+ && (previous === '$' || next === '$' || /\s/.test(previous));
109
+ if (!isEscaped(source, index) && !isInsideRange(index, codeRanges) && !invalidSingleDollar) {
110
+ return index;
111
+ }
112
+ searchFrom = index + delimiter.close.length;
113
+ }
114
+ return -1;
115
+ }
116
+
117
+ function isValidMathValue(value, delimiter) {
118
+ const source = String(value || '');
119
+ if (!source.trim()) {
120
+ return false;
121
+ }
122
+ return delimiter.display || !/[\r\n]/.test(source);
123
+ }
124
+
125
+ function collectInlineCodeRanges(source) {
126
+ const ranges = [];
127
+ let index = 0;
128
+ while (index < source.length) {
129
+ if (source[index] !== '`' || isEscaped(source, index)) {
130
+ index++;
131
+ continue;
132
+ }
133
+ let runLength = 1;
134
+ while (source[index + runLength] === '`') {
135
+ runLength++;
136
+ }
137
+ const marker = '`'.repeat(runLength);
138
+ const closeStart = source.indexOf(marker, index + runLength);
139
+ if (closeStart < 0) {
140
+ index += runLength;
141
+ continue;
142
+ }
143
+ ranges.push([index, closeStart + runLength]);
144
+ index = closeStart + runLength;
145
+ }
146
+ return ranges;
147
+ }
148
+
149
+ function isEscaped(source, index) {
150
+ let slashCount = 0;
151
+ for (let cursor = index - 1; cursor >= 0 && source[cursor] === '\\'; cursor--) {
152
+ slashCount++;
153
+ }
154
+ return slashCount % 2 === 1;
155
+ }
156
+
157
+ function isInsideRange(index, ranges) {
158
+ return ranges.some(([start, end]) => index >= start && index < end);
159
+ }
160
+
161
+ function overlapsCodeRange(start, end, ranges) {
162
+ return ranges.some(([rangeStart, rangeEnd]) => rangeStart < end && rangeEnd > start);
163
+ }
164
+
165
+ function buildMathNodes(value, options = {}) {
166
+ const documentRef = options.document || root.document;
167
+ const renderText = typeof options.renderText === 'function'
168
+ ? options.renderText
169
+ : text => [documentRef.createTextNode(text)];
170
+ const nodes = [];
171
+ for (const segment of parseMathSegments(value)) {
172
+ if (segment.type === 'text') {
173
+ if (segment.value) {
174
+ nodes.push(...toNodeArray(renderText(segment.value)));
175
+ }
176
+ continue;
177
+ }
178
+ nodes.push(createMathNode(segment, {
179
+ document: documentRef,
180
+ katex: options.katex || root.katex
181
+ }));
182
+ }
183
+ return nodes;
184
+ }
185
+
186
+ function createMathNode(segment, options = {}) {
187
+ const documentRef = options.document || root.document;
188
+ const node = documentRef.createElement('span');
189
+ node.className = `run-math ${segment.display ? 'run-math--display' : 'run-math--inline'}`;
190
+ node.dataset.mathDisplay = segment.display ? 'block' : 'inline';
191
+ try {
192
+ if (typeof options.katex?.render !== 'function') {
193
+ throw new Error('KaTeX is unavailable');
194
+ }
195
+ options.katex.render(segment.value, node, {
196
+ displayMode: segment.display,
197
+ throwOnError: true,
198
+ trust: false,
199
+ strict: 'warn',
200
+ maxExpand: 200,
201
+ maxSize: 20,
202
+ output: 'htmlAndMathml'
203
+ });
204
+ node.dataset.mathRendered = 'true';
205
+ return node;
206
+ } catch (_error) {
207
+ node.classList.add('run-math--fallback');
208
+ node.dataset.mathRendered = 'false';
209
+ node.textContent = segment.raw;
210
+ return node;
211
+ }
212
+ }
213
+
214
+ function toNodeArray(value) {
215
+ if (Array.isArray(value)) {
216
+ return value;
217
+ }
218
+ if (value === undefined || value === null) {
219
+ return [];
220
+ }
221
+ return [value];
222
+ }
223
+
224
+ const api = {
225
+ buildMathNodes,
226
+ createMathNode,
227
+ parseMathSegments
228
+ };
229
+ root.CodexOverleafMathText = api;
230
+ if (typeof module !== 'undefined' && module.exports) {
231
+ module.exports = api;
232
+ }
233
+ })(typeof window !== 'undefined' ? window : globalThis);