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.
- package/README.md +24 -23
- package/extension/bootstrap/manifest.template.json +2 -1
- package/extension/runtime-manifest.json +13 -6
- package/extension/src/background.js +7 -0
- package/extension/src/content/composerPanel.js +13 -3
- package/extension/src/content/contentRuntime.js +130 -65
- package/extension/src/content/contextTray.js +173 -9
- package/extension/src/content/markdownText.js +16 -1
- package/extension/src/content/mathText.js +233 -0
- package/extension/src/content/modelPicker.js +117 -106
- package/extension/src/content/modelPickerSupport.js +115 -0
- package/extension/src/content/nativeChannel.js +20 -1
- package/extension/src/content/panelMaintenance.js +2 -0
- package/extension/src/content/projectSettingsCoordinator.js +2 -0
- package/extension/src/content/providerSettingsCoordinator.js +413 -0
- package/extension/src/content/providerSettingsDialog.js +981 -0
- package/extension/src/content/runTimelineView.js +24 -5
- package/extension/src/content/settingsPanel.js +33 -2
- package/extension/src/content/writebackOrchestrator.js +3 -1
- package/extension/src/page/overleafProjectSnapshot.js +29 -33
- package/extension/src/page/snapshotRouter.js +75 -8
- package/extension/src/page/trackedChangesLifecycle.js +154 -26
- package/extension/src/page/writebackRouter.js +31 -60
- package/extension/src/pageBridge.js +1 -1
- package/extension/src/shared/agentTranscript.js +130 -0
- package/extension/src/shared/compatibility.js +10 -7
- package/extension/src/shared/failureReasons.js +75 -0
- package/extension/src/shared/i18n.js +22 -0
- package/extension/src/shared/models.js +1 -0
- package/extension/src/shared/providerProfiles.js +283 -0
- package/extension/src/shared/sessionState.js +12 -2
- package/extension/src/shared/storageDb.js +27 -19
- package/extension/src/shared/storageRunActions.js +99 -0
- package/extension/styles/panel.css +587 -1
- package/extension/vendor/katex/LICENSE +21 -0
- package/extension/vendor/katex/VERSION +2 -0
- package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/extension/vendor/katex/katex.min.css +1 -0
- package/extension/vendor/katex/katex.min.js +1 -0
- package/native-host/src/anthropicBridgeRequest.js +435 -0
- package/native-host/src/anthropicBridgeResponse.js +431 -0
- package/native-host/src/anthropicMessagesBridge.js +281 -0
- package/native-host/src/chatBridgeRequest.js +274 -0
- package/native-host/src/chatBridgeResponse.js +594 -0
- package/native-host/src/chatCompletionsBridge.js +485 -0
- package/native-host/src/codexCommand.js +25 -0
- package/native-host/src/codexPromptAssembly.js +9 -0
- package/native-host/src/codexProviderLaunch.js +213 -0
- package/native-host/src/codexProviderTest.js +196 -0
- package/native-host/src/codexSessionRunner.js +29 -25
- package/native-host/src/managedInstall.js +1 -0
- package/native-host/src/providerBridgeRoutes.js +11 -0
- package/native-host/src/providerModelCatalog.js +107 -0
- package/native-host/src/providerProfile.js +454 -0
- package/native-host/src/providerReasoning.js +235 -0
- package/native-host/src/providerRedaction.js +18 -0
- package/native-host/src/providerRuntime.js +365 -0
- package/native-host/src/providerStore.js +691 -0
- package/native-host/src/readProgressGuard.js +360 -0
- package/native-host/src/taskRunnerRuntime.js +40 -4
- package/native-host/src/updateArchive.js +1 -0
- package/native-host/src/updateTrust.js +2 -1
- 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 (
|
|
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
|
|
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
|
-
|
|
261
|
+
return callPageBridge('getProjectFileList', {
|
|
158
262
|
force,
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
|
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);
|