codex-overleaf-link 1.8.6 → 1.9.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 +45 -21
- package/bin/codex-overleaf-link.mjs +58 -0
- package/extension/assets/icons/codex-overleaf-dialog-icon.png +0 -0
- package/extension/assets/icons/codex-overleaf-icon.png +0 -0
- package/extension/assets/icons/icon128.png +0 -0
- package/extension/assets/icons/icon16.png +0 -0
- package/extension/assets/icons/icon32.png +0 -0
- package/extension/assets/icons/icon48.png +0 -0
- package/extension/bootstrap/background.js +404 -0
- package/extension/bootstrap/manifest.template.json +51 -0
- package/extension/bootstrap/popup.css +26 -0
- package/extension/bootstrap/popup.html +38 -0
- package/extension/bootstrap/popup.js +44 -0
- package/extension/bootstrap/runtimeContext.js +2 -0
- package/extension/runtime-manifest.json +60 -0
- package/extension/src/background.js +532 -0
- package/extension/src/content/applyResultFormatters.js +387 -0
- package/extension/src/content/composerAttachments.js +522 -0
- package/extension/src/content/composerPanel.js +254 -0
- package/extension/src/content/contentRuntime.js +8895 -0
- package/extension/src/content/contextTray.js +686 -0
- package/extension/src/content/diagnosticsController.js +659 -0
- package/extension/src/content/diagnosticsPanel.js +342 -0
- package/extension/src/content/diffReviewPanel.js +797 -0
- package/extension/src/content/localSkillsPanel.js +280 -0
- package/extension/src/content/markdownText.js +606 -0
- package/extension/src/content/mirrorHealth.js +92 -0
- package/extension/src/content/modelPicker.js +489 -0
- package/extension/src/content/nativeChannel.js +62 -0
- package/extension/src/content/otWarmMirror.js +964 -0
- package/extension/src/content/otWarmMirrorController.js +174 -0
- package/extension/src/content/panelMaintenance.js +367 -0
- package/extension/src/content/panelRenderer.js +253 -0
- package/extension/src/content/recentProjects.js +1063 -0
- package/extension/src/content/reviewHunks.js +247 -0
- package/extension/src/content/runController.js +242 -0
- package/extension/src/content/runTimelineView.js +1126 -0
- package/extension/src/content/sessionManager.js +487 -0
- package/extension/src/content/sessionPanel.js +263 -0
- package/extension/src/content/settingsPanel.js +527 -0
- package/extension/src/content/themeController.js +91 -0
- package/extension/src/content/updateIdle.js +67 -0
- package/extension/src/content/writebackController.js +300 -0
- package/extension/src/content/writebackOrchestrator.js +850 -0
- package/extension/src/contentScript.js +8 -0
- package/extension/src/page/compileBridge.js +460 -0
- package/extension/src/page/overleafCapabilities.js +100 -0
- package/extension/src/page/overleafEditor.js +282 -0
- package/extension/src/page/overleafProjectSnapshot.js +234 -0
- package/extension/src/page/overleafRealtimeObserver.js +491 -0
- package/extension/src/page/pageBridgeCapability.js +71 -0
- package/extension/src/page/snapshotRouter.js +948 -0
- package/extension/src/page/trackedChangesLifecycle.js +1506 -0
- package/extension/src/page/treeOperations.js +1584 -0
- package/extension/src/page/writeGuard.js +142 -0
- package/extension/src/page/writebackRouter.js +1934 -0
- package/extension/src/pageBridge.js +2224 -0
- package/extension/src/popup.js +247 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/styles/panel.css +4048 -0
- package/native-host/src/index.js +9 -2
- package/native-host/src/managedInstall.js +393 -0
- package/native-host/src/managedLauncherRuntime.js +71 -0
- package/native-host/src/nativeHostPlatform.js +16 -0
- package/native-host/src/runtimeInstaller.js +6 -1
- package/native-host/src/taskRunnerRuntime.js +8 -0
- package/native-host/src/updateArchive.js +253 -0
- package/native-host/src/updateManager.js +548 -0
- package/native-host/src/updateTrust.js +156 -0
- package/package.json +11 -2
- package/scripts/install-managed.mjs +64 -0
- package/scripts/uninstall-managed.mjs +48 -0
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
(function initCodexOverleafMarkdownText() {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const LineReferences = window.CodexOverleafLineReferences;
|
|
5
|
+
|
|
6
|
+
// Markdown / assistant-text rendering — block + inline markdown, the
|
|
7
|
+
// assistant-visible sanitizers, and line-reference resolution/buttons carved
|
|
8
|
+
// out of contentRuntime.js (v1.4.5 structural-debt phase 1). The code below
|
|
9
|
+
// moved verbatim (original indentation kept); the four runtime collaborators
|
|
10
|
+
// are factory-injected so behavior is unchanged.
|
|
11
|
+
function create(deps = {}) {
|
|
12
|
+
const {
|
|
13
|
+
tx,
|
|
14
|
+
callPageBridge,
|
|
15
|
+
getCurrentProjectReferenceFiles,
|
|
16
|
+
showPluginToast
|
|
17
|
+
} = deps;
|
|
18
|
+
|
|
19
|
+
function renderMarkdownInlineText(target, value) {
|
|
20
|
+
target.replaceChildren(...buildMarkdownInlineNodes(value));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function sanitizeAssistantVisibleText(value) {
|
|
24
|
+
if (typeof value !== 'string' || !value) {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
if (LineReferences?.sanitizeLocalReferences) {
|
|
28
|
+
try {
|
|
29
|
+
return LineReferences.sanitizeLocalReferences(value, {
|
|
30
|
+
projectFiles: getCurrentProjectReferenceFiles(),
|
|
31
|
+
context: 'render'
|
|
32
|
+
});
|
|
33
|
+
} catch (_error) {
|
|
34
|
+
return fallbackSanitizeLocalReferences(value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return fallbackSanitizeLocalReferences(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function sanitizeAssistantVisibleValue(value, depth = 0) {
|
|
41
|
+
if (typeof value === 'string') {
|
|
42
|
+
return sanitizeAssistantVisibleText(value);
|
|
43
|
+
}
|
|
44
|
+
if (value === undefined || value === null || typeof value === 'number' || typeof value === 'boolean') {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
if (depth > 12) {
|
|
48
|
+
return '[redacted nested value]';
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(value)) {
|
|
51
|
+
return value.map(item => sanitizeAssistantVisibleValue(item, depth + 1));
|
|
52
|
+
}
|
|
53
|
+
if (typeof value === 'object') {
|
|
54
|
+
const result = {};
|
|
55
|
+
for (const key of Object.keys(value)) {
|
|
56
|
+
const safeKey = sanitizeAssistantVisibleText(key) || key;
|
|
57
|
+
result[safeKey] = sanitizeAssistantVisibleValue(value[key], depth + 1);
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildMarkdownInlineNodes(value) {
|
|
65
|
+
const source = String(value || '');
|
|
66
|
+
const nodes = [];
|
|
67
|
+
let index = 0;
|
|
68
|
+
|
|
69
|
+
while (index < source.length) {
|
|
70
|
+
const next = findNextInlineMarkdown(source, index);
|
|
71
|
+
if (!next) {
|
|
72
|
+
nodes.push(...buildLineReferenceInlineNodes(source.slice(index)));
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (next.start > index) {
|
|
77
|
+
nodes.push(...buildLineReferenceInlineNodes(source.slice(index, next.start)));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (next.type === 'strong') {
|
|
81
|
+
const strong = document.createElement('strong');
|
|
82
|
+
strong.append(...buildMarkdownInlineNodes(next.text));
|
|
83
|
+
nodes.push(strong);
|
|
84
|
+
} else if (next.type === 'code') {
|
|
85
|
+
nodes.push(...buildInlineCodeNodes(next.text));
|
|
86
|
+
} else if (next.type === 'link') {
|
|
87
|
+
nodes.push(...buildMarkdownLinkNodes(next.text, next.href));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
index = next.end;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return nodes;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function buildInlineCodeNodes(value) {
|
|
97
|
+
const source = String(value || '');
|
|
98
|
+
const trimmed = source.trim();
|
|
99
|
+
const refs = trimmed ? parseRuntimeLineReferences(trimmed, 'plain-text-token') : [];
|
|
100
|
+
const ref = refs.length === 1 && refs[0]?.rawText === trimmed ? refs[0] : null;
|
|
101
|
+
if (ref && !isRuntimeLocalPathLike(ref.rawPath)) {
|
|
102
|
+
const resolved = resolveRuntimeLineReference(ref);
|
|
103
|
+
if (resolved) {
|
|
104
|
+
return [createLineReferenceButton(resolved)];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const code = document.createElement('code');
|
|
108
|
+
code.textContent = sanitizeAssistantVisibleText(source);
|
|
109
|
+
return [code];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildLineReferenceInlineNodes(value) {
|
|
113
|
+
const source = String(value || '');
|
|
114
|
+
if (!source) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
const refs = parseRuntimeLineReferences(source, 'plain-text-token');
|
|
118
|
+
if (!refs.length) {
|
|
119
|
+
return [document.createTextNode(sanitizeAssistantVisibleText(source))];
|
|
120
|
+
}
|
|
121
|
+
const nodes = [];
|
|
122
|
+
let cursor = 0;
|
|
123
|
+
for (const ref of refs) {
|
|
124
|
+
const rawText = String(ref.rawText || '');
|
|
125
|
+
if (!rawText) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const start = source.indexOf(rawText, cursor);
|
|
129
|
+
if (start < cursor) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
appendSanitizedTextNode(nodes, source.slice(cursor, start));
|
|
133
|
+
const resolved = resolveRuntimeLineReference(ref);
|
|
134
|
+
if (resolved) {
|
|
135
|
+
nodes.push(createLineReferenceButton(resolved));
|
|
136
|
+
} else {
|
|
137
|
+
appendSanitizedTextNode(nodes, rawText);
|
|
138
|
+
}
|
|
139
|
+
cursor = start + rawText.length;
|
|
140
|
+
}
|
|
141
|
+
appendSanitizedTextNode(nodes, source.slice(cursor));
|
|
142
|
+
return nodes;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function buildMarkdownLinkNodes(text, href) {
|
|
146
|
+
const target = String(href || '').trim();
|
|
147
|
+
if (isHttpMarkdownHref(target)) {
|
|
148
|
+
const link = document.createElement('a');
|
|
149
|
+
link.href = formatMarkdownHref(target);
|
|
150
|
+
link.textContent = formatHttpMarkdownLinkLabel(text);
|
|
151
|
+
link.title = link.href;
|
|
152
|
+
link.target = '_blank';
|
|
153
|
+
link.rel = 'noreferrer';
|
|
154
|
+
return [link];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const resolved = resolveMarkdownLineReference(text, target);
|
|
158
|
+
if (resolved) {
|
|
159
|
+
return [createLineReferenceButton(resolved)];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return [document.createTextNode(sanitizeAssistantVisibleText(text))];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function formatHttpMarkdownLinkLabel(text) {
|
|
166
|
+
const label = sanitizeAssistantVisibleText(text).trim();
|
|
167
|
+
return label || 'link';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function appendSanitizedTextNode(nodes, value) {
|
|
171
|
+
const text = sanitizeAssistantVisibleText(value);
|
|
172
|
+
if (text) {
|
|
173
|
+
nodes.push(document.createTextNode(text));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function resolveMarkdownLineReference(text, href) {
|
|
178
|
+
const candidates = [
|
|
179
|
+
{ value: href, mode: 'markdown-link-target' },
|
|
180
|
+
{ value: text, mode: 'markdown-link-label' }
|
|
181
|
+
];
|
|
182
|
+
for (const candidate of candidates) {
|
|
183
|
+
for (const ref of parseRuntimeLineReferences(candidate.value, candidate.mode)) {
|
|
184
|
+
const resolved = resolveRuntimeLineReference(ref);
|
|
185
|
+
if (resolved) {
|
|
186
|
+
return resolved;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function isRuntimeLocalPathLike(value) {
|
|
194
|
+
if (LineReferences?.isLocalPathLike) {
|
|
195
|
+
try {
|
|
196
|
+
return LineReferences.isLocalPathLike(value);
|
|
197
|
+
} catch (_error) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return fallbackLooksLikeLocalPath(value);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parseRuntimeLineReferences(text, mode) {
|
|
205
|
+
if (!LineReferences?.parseLineReferencesFromText) {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
return LineReferences.parseLineReferencesFromText({ text: String(text || ''), mode }) || [];
|
|
210
|
+
} catch (_error) {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function resolveRuntimeLineReference(ref) {
|
|
216
|
+
if (!LineReferences?.resolveProjectReference) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
const line = Number(ref?.line);
|
|
220
|
+
const column = ref?.column === null || ref?.column === undefined || ref?.column === ''
|
|
221
|
+
? null
|
|
222
|
+
: Number(ref.column);
|
|
223
|
+
if (!Number.isSafeInteger(line) || line < 1 || (column !== null && (!Number.isSafeInteger(column) || column < 1))) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
let resolved;
|
|
227
|
+
try {
|
|
228
|
+
resolved = LineReferences.resolveProjectReference({
|
|
229
|
+
rawPath: ref.rawPath,
|
|
230
|
+
projectFiles: getCurrentProjectReferenceFiles()
|
|
231
|
+
});
|
|
232
|
+
} catch (_error) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
if (!resolved?.path || resolved.file?.kind === 'binary') {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
path: resolved.path,
|
|
240
|
+
line,
|
|
241
|
+
column
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function createLineReferenceButton(reference) {
|
|
246
|
+
const button = document.createElement('button');
|
|
247
|
+
button.className = 'codex-line-reference';
|
|
248
|
+
button.type = 'button';
|
|
249
|
+
button.textContent = formatLineReferenceText(reference);
|
|
250
|
+
button.dataset.path = reference.path;
|
|
251
|
+
button.dataset.line = String(reference.line);
|
|
252
|
+
if (reference.column) {
|
|
253
|
+
button.dataset.column = String(reference.column);
|
|
254
|
+
}
|
|
255
|
+
const ariaLabel = reference.column
|
|
256
|
+
? `Open ${reference.path} line ${reference.line} column ${reference.column}`
|
|
257
|
+
: `Open ${reference.path} line ${reference.line}`;
|
|
258
|
+
button.setAttribute('aria-label', ariaLabel);
|
|
259
|
+
button.title = ariaLabel;
|
|
260
|
+
button.addEventListener('click', async event => {
|
|
261
|
+
event?.preventDefault?.();
|
|
262
|
+
event?.stopPropagation?.();
|
|
263
|
+
const params = {
|
|
264
|
+
path: reference.path,
|
|
265
|
+
line: reference.line,
|
|
266
|
+
selectLine: !reference.column
|
|
267
|
+
};
|
|
268
|
+
if (reference.column) {
|
|
269
|
+
params.column = reference.column;
|
|
270
|
+
params.selectLine = false;
|
|
271
|
+
}
|
|
272
|
+
button.disabled = true;
|
|
273
|
+
button.dataset.status = 'pending';
|
|
274
|
+
try {
|
|
275
|
+
const result = await callPageBridge('jumpToPosition', params);
|
|
276
|
+
if (result?.ok === false) {
|
|
277
|
+
button.dataset.status = 'failed';
|
|
278
|
+
button.title = tx('Could not open that referenced line.', '无法打开引用的行。');
|
|
279
|
+
showPluginToast?.(tx('Could not open that referenced line.', '无法打开引用的行。'), {
|
|
280
|
+
status: 'warning'
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
delete button.dataset.status;
|
|
285
|
+
button.title = ariaLabel;
|
|
286
|
+
} catch (_error) {
|
|
287
|
+
button.dataset.status = 'failed';
|
|
288
|
+
button.title = tx('Could not open that referenced line.', '无法打开引用的行。');
|
|
289
|
+
showPluginToast?.(tx('Could not open that referenced line.', '无法打开引用的行。'), {
|
|
290
|
+
status: 'warning'
|
|
291
|
+
});
|
|
292
|
+
} finally {
|
|
293
|
+
button.disabled = false;
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
return button;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function formatLineReferenceText(reference) {
|
|
300
|
+
return `${reference.path}:${reference.line}${reference.column ? `:${reference.column}` : ''}`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function normalizeReferencePathForRuntime(value) {
|
|
304
|
+
if (LineReferences?.normalizeReferencePath) {
|
|
305
|
+
return LineReferences.normalizeReferencePath(value);
|
|
306
|
+
}
|
|
307
|
+
return String(value || '')
|
|
308
|
+
.replace(/\\/g, '/')
|
|
309
|
+
.replace(/\/+/g, '/')
|
|
310
|
+
.replace(/^\/+/, '')
|
|
311
|
+
.replace(/^\.\//, '')
|
|
312
|
+
.trim();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function hasUnsafeRuntimePathSegments(path) {
|
|
316
|
+
return normalizeReferencePathForRuntime(path)
|
|
317
|
+
.split('/')
|
|
318
|
+
.some(segment => segment === '.' || segment === '..');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function isHttpMarkdownHref(href) {
|
|
322
|
+
try {
|
|
323
|
+
const parsed = new URL(String(href || '').trim());
|
|
324
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
325
|
+
} catch (_error) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function containsLocalPathText(value) {
|
|
331
|
+
return /(?:file:\/\/\/?|[A-Za-z]:[\\/]|\/(?:Users|home|private|var|tmp)\/|[\\/]\.codex-overleaf[\\/]projects[\\/])/i.test(String(value || ''));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function fallbackSanitizeLocalReferences(value) {
|
|
335
|
+
return String(value || '')
|
|
336
|
+
.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_raw, label, target) => {
|
|
337
|
+
const safeLabel = fallbackSanitizeBareLocalPaths(label);
|
|
338
|
+
return /^https?:\/\//i.test(String(target || '').trim())
|
|
339
|
+
? `[${safeLabel}](${target})`
|
|
340
|
+
: `[${safeLabel}]`;
|
|
341
|
+
})
|
|
342
|
+
.replace(/(?:file:\/\/\/?[^\s)\]]+|[A-Za-z]:[\\/][^\s)\]]+|\/(?:Users|home|private|var|tmp)\/[^\s)\]]+|[^\s)\]]*[\\/]\.codex-overleaf[\\/]projects[\\/][^\s)\]]+)/gi, rawPath => {
|
|
343
|
+
const line = String(rawPath || '').match(/:(\d+)(?::\d+)?(?:[.,;!?])?$/)?.[1];
|
|
344
|
+
return line ? `[local path:${line}]` : '[local path]';
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function fallbackSanitizeBareLocalPaths(value) {
|
|
349
|
+
return String(value || '').replace(/(?:file:\/\/\/?[^\s)\]]+|[A-Za-z]:[\\/][^\s)\]]+|\/(?:Users|home|private|var|tmp)\/[^\s)\]]+|[^\s)\]]*[\\/]\.codex-overleaf[\\/]projects[\\/][^\s)\]]+)/gi, rawPath => {
|
|
350
|
+
const line = String(rawPath || '').match(/:(\d+)(?::\d+)?(?:[.,;!?])?$/)?.[1];
|
|
351
|
+
return line ? `[local path:${line}]` : '[local path]';
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function findNextInlineMarkdown(source, index) {
|
|
356
|
+
const candidates = [
|
|
357
|
+
findStrongMarkdown(source, index),
|
|
358
|
+
findCodeMarkdown(source, index),
|
|
359
|
+
findLinkMarkdown(source, index)
|
|
360
|
+
].filter(Boolean);
|
|
361
|
+
candidates.sort((left, right) => left.start - right.start);
|
|
362
|
+
return candidates[0] || null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function findStrongMarkdown(source, index) {
|
|
366
|
+
const start = source.indexOf('**', index);
|
|
367
|
+
if (start === -1) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
const end = source.indexOf('**', start + 2);
|
|
371
|
+
if (end === -1) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
type: 'strong',
|
|
376
|
+
start,
|
|
377
|
+
end: end + 2,
|
|
378
|
+
text: source.slice(start + 2, end)
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function findCodeMarkdown(source, index) {
|
|
383
|
+
const start = source.indexOf('`', index);
|
|
384
|
+
if (start === -1) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
const end = source.indexOf('`', start + 1);
|
|
388
|
+
if (end === -1) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
type: 'code',
|
|
393
|
+
start,
|
|
394
|
+
end: end + 1,
|
|
395
|
+
text: source.slice(start + 1, end)
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function findLinkMarkdown(source, index) {
|
|
400
|
+
const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
401
|
+
linkPattern.lastIndex = index;
|
|
402
|
+
const match = linkPattern.exec(source);
|
|
403
|
+
if (!match) {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
type: 'link',
|
|
408
|
+
start: match.index,
|
|
409
|
+
end: match.index + match[0].length,
|
|
410
|
+
text: match[1],
|
|
411
|
+
href: match[2]
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function formatMarkdownLinkLabel(text, href) {
|
|
416
|
+
const source = String(text || '');
|
|
417
|
+
const target = String(href || '');
|
|
418
|
+
const resolved = resolveMarkdownLineReference(source, target);
|
|
419
|
+
if (resolved) {
|
|
420
|
+
return formatLineReferenceText(resolved);
|
|
421
|
+
}
|
|
422
|
+
const workspaceMatch = target.match(/\/workspace\/([^:)]+)(?::(\d+))?/);
|
|
423
|
+
if (workspaceMatch) {
|
|
424
|
+
const fileLabel = sanitizeAssistantVisibleText(workspaceMatch[1]);
|
|
425
|
+
const line = workspaceMatch[2];
|
|
426
|
+
return line ? `workspace/${fileLabel}:${line}` : `workspace/${fileLabel}`;
|
|
427
|
+
}
|
|
428
|
+
return sanitizeAssistantVisibleText(source || target);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function formatMarkdownHref(href) {
|
|
432
|
+
const target = String(href || '').trim();
|
|
433
|
+
if (!/^[a-z][a-z0-9+.-]*:/i.test(target)) {
|
|
434
|
+
return '#';
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
const parsed = new URL(target);
|
|
438
|
+
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
|
|
439
|
+
if (containsLocalPathText(parsed.href)) {
|
|
440
|
+
parsed.search = '';
|
|
441
|
+
parsed.hash = '';
|
|
442
|
+
}
|
|
443
|
+
return parsed.href;
|
|
444
|
+
}
|
|
445
|
+
} catch (_error) {
|
|
446
|
+
// Fall through to inert link for malformed URLs.
|
|
447
|
+
}
|
|
448
|
+
return '#';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function renderMarkdownBlockText(target, value) {
|
|
452
|
+
const source = normalizeInlineOrderedLists(String(value || '').trim());
|
|
453
|
+
target.replaceChildren();
|
|
454
|
+
if (!source) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const lines = source.split(/\r?\n/);
|
|
459
|
+
let index = 0;
|
|
460
|
+
|
|
461
|
+
while (index < lines.length) {
|
|
462
|
+
const line = lines[index];
|
|
463
|
+
if (!line.trim()) {
|
|
464
|
+
index++;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (isMarkdownFenceLine(line)) {
|
|
469
|
+
const codeLines = [];
|
|
470
|
+
index++;
|
|
471
|
+
while (index < lines.length && !isMarkdownFenceLine(lines[index])) {
|
|
472
|
+
codeLines.push(lines[index]);
|
|
473
|
+
index++;
|
|
474
|
+
}
|
|
475
|
+
if (index < lines.length && isMarkdownFenceLine(lines[index])) {
|
|
476
|
+
index++;
|
|
477
|
+
}
|
|
478
|
+
const pre = document.createElement('pre');
|
|
479
|
+
pre.className = 'run-code-block';
|
|
480
|
+
const code = document.createElement('code');
|
|
481
|
+
code.textContent = sanitizeAssistantVisibleText(codeLines.join('\n'));
|
|
482
|
+
pre.append(code);
|
|
483
|
+
target.append(pre);
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (isMarkdownHeadingLine(line)) {
|
|
488
|
+
const heading = document.createElement('p');
|
|
489
|
+
heading.className = 'run-final-heading';
|
|
490
|
+
heading.append(...buildMarkdownInlineNodes(line.replace(/^\s*#{1,6}\s+/, '').trim()));
|
|
491
|
+
target.append(heading);
|
|
492
|
+
index++;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (isMarkdownListLine(line)) {
|
|
497
|
+
const ordered = isMarkdownOrderedListLine(line);
|
|
498
|
+
const list = ordered ? document.createElement('ol') : document.createElement('ul');
|
|
499
|
+
while (index < lines.length && isSameMarkdownListKind(lines[index], ordered)) {
|
|
500
|
+
const item = document.createElement('li');
|
|
501
|
+
item.append(...buildMarkdownInlineNodes(stripMarkdownListMarker(lines[index])));
|
|
502
|
+
list.append(item);
|
|
503
|
+
index++;
|
|
504
|
+
}
|
|
505
|
+
target.append(list);
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const paragraphLines = [];
|
|
510
|
+
while (
|
|
511
|
+
index < lines.length &&
|
|
512
|
+
lines[index].trim() &&
|
|
513
|
+
!isMarkdownListLine(lines[index]) &&
|
|
514
|
+
!isMarkdownHeadingLine(lines[index]) &&
|
|
515
|
+
!isMarkdownFenceLine(lines[index])
|
|
516
|
+
) {
|
|
517
|
+
paragraphLines.push(lines[index].trim());
|
|
518
|
+
index++;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const paragraph = document.createElement('p');
|
|
522
|
+
paragraph.append(...buildMarkdownInlineNodes(paragraphLines.join(' ')));
|
|
523
|
+
target.append(paragraph);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function isMarkdownFenceLine(line) {
|
|
528
|
+
return /^\s*```/.test(String(line || ''));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function isMarkdownListLine(line) {
|
|
532
|
+
return isMarkdownUnorderedListLine(line) || isMarkdownOrderedListLine(line);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function isMarkdownUnorderedListLine(line) {
|
|
536
|
+
return /^\s*-\s+/.test(line);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function isMarkdownOrderedListLine(line) {
|
|
540
|
+
return /^\s*\d+\.\s+/.test(line);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function isSameMarkdownListKind(line, ordered) {
|
|
544
|
+
return ordered ? isMarkdownOrderedListLine(line) : isMarkdownUnorderedListLine(line);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function stripMarkdownListMarker(line) {
|
|
548
|
+
return String(line || '').replace(/^\s*(?:-\s+|\d+\.\s+)/, '');
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function normalizeInlineOrderedLists(source) {
|
|
552
|
+
return String(source || '').split(/\r?\n/).map(line => {
|
|
553
|
+
if (isMarkdownListLine(line) || !/\s1\.\s+\S/.test(line) || !/\s2\.\s+\S/.test(line)) {
|
|
554
|
+
return line;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const markers = [];
|
|
558
|
+
const pattern = /(^|\s)(\d{1,2})\.\s+(?=\S)/g;
|
|
559
|
+
let match;
|
|
560
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
561
|
+
markers.push({
|
|
562
|
+
number: Number(match[2]),
|
|
563
|
+
start: match.index + match[1].length
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
if (markers.length < 2 || markers[0].number !== 1) {
|
|
567
|
+
return line;
|
|
568
|
+
}
|
|
569
|
+
for (let index = 1; index < markers.length; index++) {
|
|
570
|
+
if (markers[index].number !== markers[index - 1].number + 1) {
|
|
571
|
+
return line;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const parts = [];
|
|
576
|
+
const prefix = line.slice(0, markers[0].start).trim();
|
|
577
|
+
if (prefix) {
|
|
578
|
+
parts.push(prefix);
|
|
579
|
+
}
|
|
580
|
+
for (let index = 0; index < markers.length; index++) {
|
|
581
|
+
const start = markers[index].start;
|
|
582
|
+
const end = index + 1 < markers.length ? markers[index + 1].start : line.length;
|
|
583
|
+
parts.push(line.slice(start, end).trim());
|
|
584
|
+
}
|
|
585
|
+
return parts.join('\n');
|
|
586
|
+
}).join('\n');
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function isMarkdownHeadingLine(line) {
|
|
590
|
+
return /^\s*(#{1,6}\s+\S|\*\*[^*]+\*\*:?\s*)$/.test(line);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
return {
|
|
595
|
+
renderMarkdownInlineText,
|
|
596
|
+
renderMarkdownBlockText,
|
|
597
|
+
sanitizeAssistantVisibleText,
|
|
598
|
+
sanitizeAssistantVisibleValue,
|
|
599
|
+
buildMarkdownInlineNodes,
|
|
600
|
+
normalizeReferencePathForRuntime,
|
|
601
|
+
hasUnsafeRuntimePathSegments
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
window.CodexOverleafMarkdownText = { create };
|
|
606
|
+
})();
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
(function initCodexOverleafMirrorHealth(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafMirrorHealth = factory();
|
|
6
|
+
}
|
|
7
|
+
})(typeof globalThis !== 'undefined' ? globalThis : window, function mirrorHealthFactory() {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const MIRROR_REUSE_MAX_AGE_MS = 5 * 60 * 1000;
|
|
11
|
+
const MIRROR_FRESH_LABEL_MS = 60 * 1000;
|
|
12
|
+
const MIRROR_STALE_LABEL_MS = 5 * 60 * 1000;
|
|
13
|
+
const PREFETCH_DEBOUNCE_MS = 1200;
|
|
14
|
+
const PREFETCH_SUCCESS_COOLDOWN_MS = 30 * 1000;
|
|
15
|
+
const PREFETCH_ERROR_RETENTION_MS = 10 * 60 * 1000;
|
|
16
|
+
|
|
17
|
+
function classifyMirrorHealth(status = {}) {
|
|
18
|
+
if (status.exists !== true) {
|
|
19
|
+
return {
|
|
20
|
+
state: 'missing',
|
|
21
|
+
exists: false,
|
|
22
|
+
ageMs: null,
|
|
23
|
+
reusable: false
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ageMs = normalizeAgeMs(status.ageMs);
|
|
28
|
+
const reusable = ageMs < MIRROR_REUSE_MAX_AGE_MS;
|
|
29
|
+
if (ageMs <= MIRROR_FRESH_LABEL_MS) {
|
|
30
|
+
return {
|
|
31
|
+
state: 'fresh',
|
|
32
|
+
exists: true,
|
|
33
|
+
ageMs,
|
|
34
|
+
reusable
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
state: 'stale',
|
|
39
|
+
exists: true,
|
|
40
|
+
ageMs,
|
|
41
|
+
reusable
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function shouldStartPrefetch(state = {}) {
|
|
46
|
+
if (state.busy === true || state.inFlight) {
|
|
47
|
+
return { ok: false, reason: 'busy' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const now = normalizeTimestamp(state.now, 0);
|
|
51
|
+
if (isWithinWindow(now, state.lastAttemptAt, PREFETCH_DEBOUNCE_MS)) {
|
|
52
|
+
return { ok: false, reason: 'debounced' };
|
|
53
|
+
}
|
|
54
|
+
if (isWithinWindow(now, state.lastSuccessAt, PREFETCH_SUCCESS_COOLDOWN_MS)) {
|
|
55
|
+
return { ok: false, reason: 'success_cooldown' };
|
|
56
|
+
}
|
|
57
|
+
if (isWithinWindow(now, state.lastErrorAt, PREFETCH_ERROR_RETENTION_MS)) {
|
|
58
|
+
return { ok: false, reason: 'error_retained' };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { ok: true };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeAgeMs(value) {
|
|
65
|
+
const ageMs = Number(value);
|
|
66
|
+
return Number.isFinite(ageMs) && ageMs >= 0 ? ageMs : Infinity;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeTimestamp(value, fallback) {
|
|
70
|
+
const timestamp = Number(value);
|
|
71
|
+
return Number.isFinite(timestamp) ? timestamp : fallback;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isWithinWindow(now, startedAt, windowMs) {
|
|
75
|
+
const timestamp = Number(startedAt);
|
|
76
|
+
if (!Number.isFinite(timestamp) || timestamp <= 0) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return now - timestamp < windowMs;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
MIRROR_REUSE_MAX_AGE_MS,
|
|
84
|
+
MIRROR_FRESH_LABEL_MS,
|
|
85
|
+
MIRROR_STALE_LABEL_MS,
|
|
86
|
+
PREFETCH_DEBOUNCE_MS,
|
|
87
|
+
PREFETCH_SUCCESS_COOLDOWN_MS,
|
|
88
|
+
PREFETCH_ERROR_RETENTION_MS,
|
|
89
|
+
classifyMirrorHealth,
|
|
90
|
+
shouldStartPrefetch
|
|
91
|
+
};
|
|
92
|
+
});
|